Forum guided project

Protected Demo Writes

Step 11 / 13 Beginner to intermediate SQLite to MySQL path

Protected Demo Writes

The local guided project writes to SQLite. The official documentation site is different: it is public, so visitor-written text must not be stored.

Goal

Keep the same forms, routes, validation, and permission checks while preventing public-site persistence when demo mode is enabled.

File Created

src/Modules/ForumDemo/Writes/PublicDemoWriteGuard.php

Step: Add A Demo Mode Flag

Use an environment flag for the hosted documentation site:

FORUM_DEMO_READ_ONLY=true

Local learners should leave this disabled so topic and reply creation writes to SQLite.

Step: Create The Guard

<?php
declare(strict_types=1);

namespace Modules\ForumDemo\Writes;

use Modules\ForumDemo\Permissions\DemoPermissionService;

final class PublicDemoWriteGuard
{
    public function __construct(private readonly DemoPermissionService $permissions = new DemoPermissionService())
    {
    }
}

This guard is not the normal persistence layer. It is a protection layer for the public documentation deployment.

Step: Check Permissions Before Demo Success

public function protect(?array $user, string $ability, string $action, array $payload = []): ?array
{
    if (!$this->isReadOnlyDemo()) {
        return null;
    }

    if (!$this->permissions->can($user, $ability)) {
        return [
            'ok' => false,
            'demo' => true,
            'status' => 403,
            'message' => 'Permission denied for this demo action.',
            'action' => $action,
        ];
    }

    if (!$this->hasEnoughInput($payload)) {
        return [
            'ok' => false,
            'demo' => true,
            'status' => 422,
            'message' => 'Add content before submitting the demo form.',
            'action' => $action,
        ];
    }

    return [
        'ok' => true,
        'demo' => true,
        'status' => 200,
        'message' => 'Demo mode: your ' . $action . ' passed validation but was not saved.',
        'action' => $action,
    ];
}

Returning null means the real write service should continue and persist the data.

Step: Implement The Helpers

private function isReadOnlyDemo(): bool
{
    return filter_var($_ENV['FORUM_DEMO_READ_ONLY'] ?? false, FILTER_VALIDATE_BOOL);
}

private function hasEnoughInput(array $payload): bool
{
    foreach ($payload as $value) {
        if (is_string($value) && trim($value) !== '') {
            return true;
        }
    }

    return false;
}

Step: Use It Before Real Writes

public function storeTopic(ServerRequestInterface $request): Response
{
    $payload = (array) $request->getParsedBody();
    $user = $this->auth->currentUser();

    $demoResult = $this->demoWriteGuard->protect($user, 'topic.create', 'create topic', $payload);
    $result = $demoResult ?? $this->writeService->createTopic($user, $payload);

    return $this->flashAndRedirect($result, '/forum-demo/topics');
}

The controller remains explicit: public demo protection is checked first, then real persistence handles normal local behavior. The response still redirects to a GET page so refresh and back navigation do not resubmit the form.

Checkpoint

With FORUM_DEMO_READ_ONLY=true, submit the topic form at /forum-demo/topics. You should get a clear demo success message and no database row should be created.

With the flag disabled locally, the same form should create a SQLite row.

Common Mistakes

  • Using protected demo writes as the main project architecture.
  • Returning demo success without checking permissions.
  • Letting the public documentation site store visitor-written content.

Next

Continue with API Endpoints.

Live version

Open the safe demo

Use the demo when a guide step asks you to verify behavior. Local projects write to SQLite; this public demo validates writes without persisting visitor content.

Open forum demo

Project files

Download project files

This download does not include the CorianderPHP framework. Start from a CorianderPHP project, then use the completed app files as a reference.

Download completed app