marwa-framework

Controllers

Marwa\Framework\Controllers\Controller is the base class for HTTP controllers in the framework. It gives you a compact set of helpers for the most common controller jobs: reading request data, validating input, returning HTML or JSON responses, redirecting, flashing session data, and stopping execution with authorization-style guards.

Use it when you want controller code to stay focused on application behavior instead of response plumbing.

When To Extend Controller

Extend the base controller when your class handles HTTP requests and you want access to framework helpers such as:

If a class does not need HTTP concerns, keep it as a regular service instead of making it a controller.

Basic Example

<?php

declare(strict_types=1);

namespace App\Controllers;

use Marwa\Framework\Controllers\Controller;
use Psr\Http\Message\ResponseInterface;

final class PostController extends Controller
{
    public function show(int $id): ResponseInterface
    {
        return $this->view('posts/show', [
            'postId' => $id,
        ]);
    }
}

The controller method returns a PSR-7 ResponseInterface. In most cases, that response will come from one of the inherited helper methods.

Reading Request Data

Use request() when you need the full PSR-7 request object. Use input() when you only need submitted input values.

public function store(): ResponseInterface
{
    $request = $this->request();
    $title = $this->input('title');
    $status = $this->input('status', 'draft');

    return $this->json([
        'method' => $request->getMethod(),
        'title' => $title,
        'status' => $status,
    ]);
}

Behavior summary:

Returning Views And HTML

Use view() when you want to render a Twig template and immediately return an HTML response. Use render() when you only need the rendered string, for example when building a larger custom response.

public function index(): ResponseInterface
{
    return $this->view('posts/index', [
        'posts' => [
            ['title' => 'First post'],
            ['title' => 'Second post'],
        ],
    ]);
}

You can also control status codes and headers:

return $this->view('posts/index', ['posts' => $posts], 200, [
    'X-Controller' => 'PostController',
]);

If you need plain HTML without a template:

return $this->response('<h1>Service unavailable</h1>', 503);

Returning JSON

Use json() for API endpoints, AJAX handlers, or endpoints that should return machine-readable output.

public function apiShow(int $id): ResponseInterface
{
    return $this->json([
        'data' => [
            'id' => $id,
            'title' => 'Example post',
        ],
    ]);
}

You can pass a custom status code and headers:

return $this->json(['message' => 'Created'], 201, [
    'X-Resource' => 'posts',
]);

Validating Input

The validate() helper is the fastest way to validate incoming request data inside a controller.

public function store(): ResponseInterface
{
    $data = $this->validate([
        'title' => 'required|string|min:3',
        'body' => 'required|string',
        'published' => 'nullable|boolean',
    ]);

    return $this->json([
        'message' => 'Post created',
        'data' => $data,
    ], 201);
}

validate() returns only the validated payload as an array. It also accepts custom messages, custom attribute labels, and an optional request instance when you need to validate a different request object.

If your project uses a dedicated form request object, use validated():

use App\Http\Requests\StorePostRequest;

public function store(StorePostRequest $request): ResponseInterface
{
    $data = $this->validated($request);

    return $this->json(['data' => $data], 201);
}

Redirects And Post-Redirect-Get

Use redirect() when you know the target URI and back() when you want to send the user to the previous page.

public function destroy(int $id): ResponseInterface
{
    // Delete the record...

    return $this->redirect('/posts');
}

back() uses the request Referer header when it is available, but only when the target is safe. Relative paths are allowed, and absolute URLs are only used when they match the current request origin. If the header is missing or unsafe, it falls back to the current request URI, and finally to /.

Flashing Form State

The base controller includes session flash helpers for common form flows. This is especially useful when validation fails and you want to redirect back with the previous input and the error bag.

public function store(): ResponseInterface
{
    if (trim((string) $this->input('title', '')) === '') {
        $this->withInput();
        $this->withErrors([
            'title' => ['The title field is required.'],
        ]);

        return $this->back();
    }

    return $this->redirect('/posts');
}

Available helpers:

Example of reading old input in a later request:

$title = $this->old('title', '');

Authorization And Guard Clauses

The base controller provides lightweight guard helpers for common response flow and includes Marwa\Framework\Controllers\Concerns\AuthorizesRequests for policy-based authorization.

Use authorize() when you need policy-based access control:

public function edit(Post $post): ResponseInterface
{
    $this->authorize('update', $post);

    return $this->view('posts/edit', ['post' => $post]);
}

Use abortUnless() when a condition must be true:

public function edit(int $id): ResponseInterface
{
    if ($response = $this->abortUnless(user()?->isAdmin() === true)) {
        return $response;
    }

    return $this->view('posts/edit', ['id' => $id]);
}

Use abortIf() or abortUnless() when you want the same pattern with explicit intent:

if ($response = $this->abortUnless($post->isEditable(), 'Post is locked', 403)) {
    return $response;
}

These helpers return null when execution may continue, or a ready-made ResponseInterface when the request should stop immediately.

Practical CRUD Example

This example combines the most common helpers in one controller method:

public function update(Post $post): ResponseInterface
{
    $this->authorize('update', $post);

    $data = $this->validate([
        'title' => 'required|string|min:3',
        'body' => 'required|string',
    ]);

    // Update the post...

    $this->flash('success', 'Post updated successfully.');

    return $this->redirect('/posts/' . $post->id);
}

This style keeps controller methods short and readable:

Good Practices