Skip to main content

Overview

Controllers in ForkBB handle HTTP requests and orchestrate the application flow. They:
  • Receive route parameters from the Router
  • Interact with models to fetch/manipulate data
  • Return Page objects with view data
  • Handle forms and user input
  • Enforce permissions and access control

Controller Structure

Controllers are simple classes with action methods:
namespace ForkBB\Controllers;

use ForkBB\Core\Container;
use ForkBB\Models\Page;

class MyController
{
    public function __construct(protected Container $c)
    {
    }
    
    /**
     * Action method
     * 
     * @param array  $args   Route parameters
     * @param string $method HTTP method (GET, POST, etc.)
     * @return Page
     */
    public function actionName(array $args, string $method): Page
    {
        $page = $this->c->SomePage->create();
        
        // Handle request logic
        
        return $page;
    }
}
Controllers receive the Container in their constructor and must return a Page object from action methods.

The Routing Controller

The main routing logic is in app/Controllers/Routing.php:
app/Controllers/Routing.php
class Routing
{
    public function __construct(protected Container $c) {}
    
    public function routing(): Page
    {
        $user = $this->c->user;
        $r = $this->c->Router;
        
        // Define all routes (see routing.mdx for details)
        $r->add($r::GET, '/', 'Index:view', 'Index');
        $r->add($r::DUO, '/login', 'Auth:login', 'Login');
        // ... many more routes ...
        
        // Match current request to route
        $uri = FORK_URI;
        $route = $r->route(FORK_RMETHOD, $uri);
        
        switch ($route[0]) {
            case $r::OK:
                // Execute matched controller action
                list($page, $action) = \explode(':', $route[1], 2);
                $page = $this->c->$page->$action($route[2], FORK_RMETHOD);
                break;
                
            case $r::NOT_FOUND:
                $page = $this->c->Message->message('Not Found', true, 404);
                break;
                
            case $r::METHOD_NOT_ALLOWED:
                $page = $this->c->Message->message('Bad request', true, 405);
                break;
        }
        
        return $page;
    }
}

Example Controllers

Simple View Controller

namespace ForkBB\Controllers;

use ForkBB\Core\Container;
use ForkBB\Models\Page;

class Index
{
    public function __construct(protected Container $c) {}
    
    /**
     * Display forum index
     */
    public function view(array $args, string $method): Page
    {
        // Load forum tree
        $forums = $this->c->forums->loadTree();
        
        // Create page model
        $page = $this->c->IndexPage->create();
        $page->nameTpl = 'index';
        $page->onlinePos = 'index';
        $page->canonical = $this->c->Router->link('Index');
        $page->robots = 'index, follow';
        $page->forums = $forums;
        
        // Set breadcrumbs
        $page->crumbs = $this->c->Func->crumbs([]);
        
        return $page;
    }
}

Form Handling Controller

namespace ForkBB\Controllers;

use ForkBB\Core\Container;
use ForkBB\Models\Page;

class Auth
{
    public function __construct(protected Container $c) {}
    
    /**
     * Login page and handler
     */
    public function login(array $args, string $method): Page
    {
        // Guest only
        if (!$this->c->user->isGuest) {
            return $this->c->Redirect->page('Index');
        }
        
        $page = $this->c->LoginPage->create();
        $page->nameTpl = 'login';
        $page->onlinePos = 'login';
        $page->fIndex = 'login';
        
        // Build form
        $form = $this->c->FormLogin->create();
        
        // Handle POST request
        if ('POST' === $method) {
            $validator = $this->c->Validator;
            $data = $validator->validation($form);
            
            if ($data['submit']) {
                // Validate credentials
                $user = $this->c->users->authenticate(
                    $data['username'],
                    $data['password']
                );
                
                if ($user) {
                    // Set cookie and redirect
                    $this->c->Cookie->set('user', $user->id);
                    return $this->c->Redirect->page('Index');
                } else {
                    $page->fIswev = ['w', 'Invalid credentials'];
                }
            }
        }
        
        $page->form = $form;
        return $page;
    }
}

Controller with Permissions

namespace ForkBB\Controllers;

use ForkBB\Core\Container;
use ForkBB\Models\Page;

class Post
{
    public function __construct(protected Container $c) {}
    
    /**
     * Create new topic
     */
    public function newTopic(array $args, string $method): Page
    {
        $forum = $this->c->forums->get($args['id']);
        
        // Check permissions
        if (!$forum) {
            return $this->c->Message->message('Bad request', false, 404);
        }
        
        if (!$forum->canCreateTopic) {
            return $this->c->Message->message('No permission', false, 403);
        }
        
        $page = $this->c->PostPage->create();
        $page->nameTpl = 'post';
        $page->forum = $forum;
        
        // Handle POST
        if ('POST' === $method) {
            // Process form submission
            $topic = $this->c->topics->create([
                'forum_id' => $forum->id,
                'poster_id' => $this->c->user->id,
                'subject' => $data['subject'],
            ]);
            
            $this->c->topics->insert($topic);
            
            return $this->c->Redirect->page('Topic', [
                'id' => $topic->id
            ]);
        }
        
        return $page;
    }
}

The Page Base Class

All page models extend the Page class:
app/Models/Page.php
abstract class Page extends Model
{
    // Page identification
    public string|array $identifier;   // CSS class for <body>
    public string $fIndex;             // Active navigation item
    
    // HTTP response
    public int $httpStatus = 200;      // HTTP status code
    public array $httpHeaders = [];    // HTTP headers
    
    // Template
    public ?string $nameTpl = null;    // Template name
    
    // Meta data
    public array $titles = [];         // Page title parts
    public string $robots;             // robots meta tag
    public string $canonical;          // canonical URL
    
    // User data
    public User $user;                 // Current user
    public mixed $userRules;           // User permissions
    
    // Messages
    public array $fIswev = [];         // Info/success/warning/error/validation
    
    // Online tracking
    public ?string $onlinePos = null;  // Online position key
    public bool $onlineDetail = false; // Show online users detail
    public bool $onlineFilter = true;  // Filter by current page
    
    // Navigation
    public array $fNavigation = [];    // Main navigation
    public array $fNavigationUser = [];
    
    // Methods
    public function prepare(): void;                      // Prepare for rendering
    public function header(string $header, ?string $value): Page;
    public function pageHeader(string $name, string $type, int $weight, ?array $values): mixed;
    protected function crumbs(mixed ...$crumbs): array;   // Build breadcrumbs
}

Using the Page Class

$page = $this->c->MyPage->create();
$page->nameTpl = 'my_template';     // Template to render
$page->identifier = 'my-page';      // CSS identifier
$page->fIndex = 'index';            // Active nav item
$page->onlinePos = 'my-page';       // For online tracking

// Set title (builds breadcrumbs)
$page->titles = ['My Page'];

// Set meta tags
$page->canonical = $this->c->Router->link('MyPage');
$page->robots = 'index, follow';

return $page;

Controller Patterns

Redirects

// Redirect to named route
return $this->c->Redirect->page('Index');

// Redirect with parameters
return $this->c->Redirect->page('Forum', ['id' => 123]);

// Redirect to URL
return $this->c->Redirect->url($url);

Error Pages

// 404 Not Found
return $this->c->Message->message('Not Found', true, 404);

// 403 Forbidden
return $this->c->Message->message('No permission', false, 403);

// 400 Bad Request
return $this->c->Message->message('Bad request', false, 400);

Permission Checks

public function myAction(array $args, string $method): Page
{
    $user = $this->c->user;
    
    // Guest only
    if (!$user->isGuest) {
        return $this->c->Redirect->page('Index');
    }
    
    // Authenticated only
    if ($user->isGuest) {
        return $this->c->Message->message('Login required', false, 403);
    }
    
    // Admin only
    if (!$user->isAdmin) {
        return $this->c->Message->message('No permission', false, 403);
    }
    
    // Permission-based
    if (1 !== $user->g_post_topics) {
        return $this->c->Message->message('No permission', false, 403);
    }
    
    // Continue...
}

Form Validation

public function formAction(array $args, string $method): Page
{
    $page = $this->c->MyPage->create();
    $form = $this->c->FormBuilder->create();
    
    if ('POST' === $method) {
        $validator = $this->c->Validator;
        $data = $validator->validation($form);
        
        if ($data['submit']) {
            // Process valid form
            // ...
            
            return $this->c->Redirect->page('Success');
        } else {
            // Show validation errors
            $page->fIswev = ['e', 'Please fix errors'];
        }
    }
    
    $page->form = $form;
    return $page;
}

Best Practices

Controllers should orchestrate, not contain business logic.
// Good - logic in model
if ($topic->canReply) {
    $reply = $topic->createReply($this->c->user, $data);
}

// Bad - logic in controller
if ($topic->closed == 0 && $user->g_post_replies == 1) {
    $reply = new Post(...);
    // ... lots of code
}
Every controller action must return a Page object.
public function action(array $args, string $method): Page
{
    // Always return Page
    return $page;
}
Validate permissions before doing expensive operations.
// Check permissions first
if (!$user->isAdmin) {
    return $this->c->Message->message('No permission', false, 403);
}

// Then do expensive work
$data = $this->loadExpensiveData();
Always type hint parameters and return types.
public function action(array $args, string $method): Page

Controller Registration

Controllers are registered in the container configuration:
config/main.dist.php
'shared' => [
    // Controllers
    'Primary' => \ForkBB\Controllers\Primary::class,
    'Routing' => \ForkBB\Controllers\Routing::class,
    'Index' => \ForkBB\Controllers\Index::class,
    'Auth' => \ForkBB\Controllers\Auth::class,
    // ... more controllers
]

Next Steps

Views

Render controller data in templates

Models

Work with data in controllers

Routing

Define routes for controllers