Skip to content

Controllers

Controllers handle HTTP requests and return responses. In Lalaz, controllers are simple PHP classes with methods that receive request data and return responses.

Generating Controllers

Use the CLI to generate a new controller:

bash
php lalaz craft:controller User

This creates app/Controllers/UserController.php:

php
<?php declare(strict_types=1);

namespace App\Controllers;

use Lalaz\Web\Http\Response;
use Lalaz\Web\Routing\Attribute\Route;

final class UserController
{
    #[Route(path: '/example', method: 'GET')]
    public function handle(Response $response): void
    {
        $response->json(['message' => 'UserController works!']);
    }
}

Invokable Controllers

Generate a single-action controller with --invokable:

bash
php lalaz craft:controller CreateUser --invokable
php
<?php declare(strict_types=1);

namespace App\Controllers;

use Lalaz\Web\Http\Response;
use Lalaz\Web\Routing\Attribute\Route;

final class CreateUserController
{
    #[Route(path: '/example', method: 'GET')]
    public function __invoke(Response $response): void
    {
        $response->json(['message' => 'CreateUserController works!']);
    }
}

Controller Structure

A controller is a plain PHP class. Methods can receive dependencies via injection:

php
<?php declare(strict_types=1);

namespace App\Controllers;

use Lalaz\Web\Http\Request;
use Lalaz\Web\Http\Response;

class UserController
{
    public function index(): array
    {
        return ['users' => []];
    }

    public function show(Request $request): array
    {
        $id = $request->routeParam('id');
        return ['user' => ['id' => $id]];
    }

    public function store(Request $request, Response $response): void
    {
        $data = $request->json();
        $response->json(['created' => $data], 201);
    }
}

Routing to Controllers

String Syntax

php
$router->get('/users', UserController::class . '@index');
$router->get('/users/{id}', UserController::class . '@show');
$router->post('/users', UserController::class . '@store');

Array Syntax

php
$router->get('/users', [UserController::class, 'index']);

Attribute Routing

php
use Lalaz\Web\Routing\Attribute\Route;

class UserController
{
    #[Route('/users', method: 'GET')]
    public function index(): array
    {
        return ['users' => []];
    }
}

Dependency Injection

The container automatically injects dependencies into controller methods. Simply type-hint what you need:

Request Injection

php
use Lalaz\Web\Http\Request;

public function store(Request $request): array
{
    $data = $request->json();
    return ['data' => $data];
}

Response Injection

php
use Lalaz\Web\Http\Response;

public function index(Response $response): void
{
    $response->json(['users' => []]);
}

Multiple Dependencies

php
use Lalaz\Web\Http\Request;
use Lalaz\Web\Http\Response;
use App\Services\UserService;

public function store(
    Request $request,
    Response $response,
    UserService $userService
): void {
    $user = $userService->create($request->json());
    $response->json(['user' => $user], 201);
}

Constructor Injection

For dependencies used across multiple methods, inject them in the constructor:

php
<?php declare(strict_types=1);

namespace App\Controllers;

use App\Services\UserService;
use Lalaz\Web\Http\Request;

class UserController
{
    public function __construct(
        private UserService $userService
    ) {}

    public function index(): array
    {
        return ['users' => $this->userService->all()];
    }

    public function show(Request $request): array
    {
        $id = (int) $request->routeParam('id');
        return ['user' => $this->userService->find($id)];
    }

    public function store(Request $request): array
    {
        $user = $this->userService->create($request->json());
        return json(['user' => $user], 201);
    }
}

Return Types

Controllers can return data in different ways:

Array Return (JSON)

Return an array to automatically send a JSON response:

php
public function index(): array
{
    return ['users' => [], 'count' => 0];
}

Using Response Object

For more control, inject and use the Response object:

php
public function index(Response $response): void
{
    $response->json(['users' => []], 200);
}

public function download(Response $response): void
{
    $response->download('/path/to/file.pdf', 'report.pdf');
}

public function redirect(Response $response): void
{
    $response->redirect('/dashboard');
}

Using Helper Functions

Use the json() helper for JSON responses:

php
public function index(): mixed
{
    return json(['users' => []], 200);
}

public function store(Request $request): mixed
{
    // Create user...
    return json_success(['user' => $user], 'User created');
}

public function error(): mixed
{
    return json_error('Something went wrong', 500);
}

Controller Best Practices

Keep Controllers Thin

Controllers should coordinate between request handling and business logic. Move complex logic to service classes:

php
// ❌ Don't put business logic in controllers
public function store(Request $request): array
{
    $data = $request->json();
    
    // Validation
    if (empty($data['email'])) {
        return json_error('Email is required', 422);
    }
    
    // Database operations
    $user = new User();
    $user->email = $data['email'];
    $user->password = password_hash($data['password'], PASSWORD_DEFAULT);
    $user->save();
    
    // Send email
    mail($user->email, 'Welcome', '...');
    
    return ['user' => $user];
}

// ✅ Use services for business logic
public function store(Request $request, UserService $userService): array
{
    return $userService->create($request->json());
}

One Action Per Method

Each controller method should handle one action:

php
class UserController
{
    public function index(): array { /* List users */ }
    public function show(Request $request): array { /* Show one user */ }
    public function store(Request $request): array { /* Create user */ }
    public function update(Request $request): array { /* Update user */ }
    public function destroy(Request $request): array { /* Delete user */ }
}

Keep related actions in the same controller:

php
// UserController - User CRUD operations
// UserSettingsController - User settings
// UserNotificationsController - User notifications

Resource Controllers

For RESTful resources, implement standard actions:

php
<?php declare(strict_types=1);

namespace App\Controllers;

use Lalaz\Web\Http\Request;

class PostController
{
    public function index(): array
    {
        // GET /posts - List all posts
        return ['posts' => []];
    }

    public function store(Request $request): mixed
    {
        // POST /posts - Create a post
        return json(['post' => $request->json()], 201);
    }

    public function show(Request $request): array
    {
        // GET /posts/{postId} - Show a post
        $id = $request->routeParam('postId');
        return ['post' => ['id' => $id]];
    }

    public function update(Request $request): array
    {
        // PUT/PATCH /posts/{postId} - Update a post
        $id = $request->routeParam('postId');
        return ['post' => array_merge(['id' => $id], $request->json())];
    }

    public function destroy(Request $request): array
    {
        // DELETE /posts/{postId} - Delete a post
        $id = $request->routeParam('postId');
        return ['message' => "Post {$id} deleted"];
    }
}

Register with the resource() method:

php
$router->resource('posts', PostController::class);

Next Steps

Released under the MIT License.