Skip to main content

Overview

ForkBB’s routing system maps URLs to controller actions and generates URLs from route names. It supports:
  • Static routes (exact URL matches)
  • Dynamic routes (with parameters)
  • HTTP method constraints (GET, POST, both)
  • CSRF token integration
  • SEO-friendly URLs

Router Class

The Router class is located at app/Core/Router.php and provides routing and link generation:
app/Core/Router.php
class Router
{
    const OK = 200;
    const NOT_FOUND = 404;
    const METHOD_NOT_ALLOWED = 405;
    const NOT_IMPLEMENTED = 501;

    const DUO = ['GET', 'POST'];
    const GET = 'GET';
    const PST = 'POST';
    
    public function __construct(
        protected string $baseUrl, 
        protected Csrf $csrf
    ) {
        $this->host = \parse_url($baseUrl, \PHP_URL_HOST);
        $this->prefix = \parse_url($baseUrl, \PHP_URL_PATH) ?? '';
    }
    
    // Add a route
    public function add(
        array|string $method, 
        string $route, 
        string $handler, 
        ?string $marker = null
    ): void
    
    // Match a URL to a route
    public function route(string $method, string $uri): array
    
    // Generate a link from a marker
    public function link(?string $marker = null, array $args = []): string
}
The Router automatically integrates with the CSRF protection system for secure form handling.

Defining Routes

Routes are defined in app/Controllers/Routing.php:

Static Routes

Simple URLs with no parameters:
app/Controllers/Routing.php
// Homepage
$r->add(
    $r::GET,           // HTTP method
    '/',               // URL pattern
    'Index:view',      // Controller:action
    'Index'            // Route marker (for link generation)
);

// Login page
$r->add(
    $r::DUO,           // Both GET and POST
    '/login',
    'Auth:login',
    'Login'
);

Dynamic Routes with Parameters

URLs with variable segments:
// View a forum with pagination
$r->add(
    $r::GET,
    '/forum/{id|i:[1-9]\d*}/{name}[/{page|i:[1-9]\d*}]',
    'Forum:view',
    'Forum'
);

// Explanation:
// {id|i:[1-9]\d*}  - Required integer parameter (regex: [1-9]\d*)
// {name}           - Required string parameter
// [{page|i:...}]   - Optional integer parameter (in square brackets)
// View user profile
$r->add(
    $r::GET,
    '/user/{id|i:[1-9]\d*}/{name}',
    'ProfileView:view',
    'User'
);

Parameter Syntax

{name}
string
Required string parameter (matches [^/\x00-\x1f]+)
{name:pattern}
custom
Required parameter with custom regex pattern
{name|i}
integer
Required integer parameter (type hint i)
{name|i:pattern}
integer
Required integer with custom pattern
[/optional]
optional
Optional URL segment (in square brackets)

Examples

PatternMatchesParameters
/forum/{id|i:[1-9]\d*}/forum/123['id' => 123]
/user/{id|i}/{name}/user/5/john['id' => 5, 'name' => 'john']
/topic/{id}[/{page|i}]/topic/42 or /topic/42/2['id' => '42', 'page' => 1 or 2]

Routing Process

When a request comes in:
1

Parse URI

Extract URI from request, remove query string and base path prefix
2

Check Static Routes

Try to match URI against static routes (fastest)
3

Check Dynamic Routes

If no static match, try dynamic routes with regex patterns
4

Extract Parameters

Parse parameters from URL and convert types (integers, strings)
5

Return Route

Return status code, handler, parameters, and marker
Route matching example
$route = $r->route('GET', '/forum/123/my-forum/2');

// Returns:
[
    Router::OK,              // Status: 200
    'Forum:view',            // Handler
    [                        // Parameters
        'id' => 123,
        'name' => 'my-forum',
        'page' => 2
    ],
    'Forum'                  // Marker
]
Generate URLs from route markers and parameters:
// Generate forum link
$url = $c->Router->link('Forum', [
    'id' => 123,
    'name' => 'my-forum'
]);
// Result: https://example.com/forum/123/my-forum

// With pagination
$url = $c->Router->link('Forum', [
    'id' => 123,
    'name' => 'my-forum',
    'page' => 2
]);
// Result: https://example.com/forum/123/my-forum/2
$url = $c->Router->link('Topic', [
    'id' => 456,
    'name' => 'discussion',
    '#' => 'p1234'  // Anchor to post
]);
// Result: https://example.com/topic/456/discussion#p1234

Automatic CSRF Tokens

The router automatically generates CSRF tokens when needed:
// Route with {token} parameter
$r->add(
    $r::GET,
    '/logout/{token}',
    'Auth:logout',
    'Logout'
);

// Token automatically generated
$url = $c->Router->link('Logout');
// Result: https://example.com/logout/abc123...token
Links with {hash} or {token} parameters automatically generate CSRF protection values. Never hardcode these!

Conditional Routing

Routes can be conditionally registered based on user permissions:
app/Controllers/Routing.php
$user = $this->c->user;
$config = $this->c->config;

// Guest-only routes
if ($user->isGuest) {
    $r->add($r::DUO, '/login', 'Auth:login', 'Login');
    
    if (1 === $config->b_regs_allow) {
        $r->add($r::GET, '/registration', 'Rules:confirmation', 'Register');
    }
}

// Authenticated user routes
if (!$user->isGuest) {
    $r->add($r::GET, '/logout/{token}', 'Auth:logout', 'Logout');
    $r->add($r::DUO, '/post/{id|i:[1-9]\d*}/edit', 'Edit:edit', 'EditPost');
}

// Admin-only routes
if ($user->isAdmin) {
    $r->add($r::GET, '/admin/', 'AdminIndex:index', 'Admin');
    $r->add($r::DUO, '/admin/options', 'AdminOptions:edit', 'AdminOptions');
}

// Permission-based routes
if (1 === $user->g_search) {
    $r->add($r::GET, '/search', 'Search:view', 'Search');
}

Route Validation

Validate and normalize URLs:
// Validate that a URL belongs to the forum
$validUrl = $c->Router->validate(
    $url,              // URL to validate
    'Index',           // Default marker if invalid
    []                 // Default args if invalid
);

// Returns canonical URL or default if invalid

URL Patterns

Forums

/                              - Homepage (forum index)
/forum/{id}/{name}[/{page}]    - View forum
/forum/{id}/new/topic          - Create new topic
/forum/{id}/markread/{token}   - Mark forum as read

Topics

/topic/{id}/{name}[/{page}]    - View topic
/topic/{id}/view/new           - Jump to first new post
/topic/{id}/view/last          - Jump to last post
/topic/{id}/new/reply          - Reply to topic

Posts

/post/{id}#p{id}               - View specific post
/post/{id}/edit                - Edit post
/post/{id}/delete              - Delete post
/post/{id}/report              - Report post

Users

/userlist                      - List users
/user/{id}/{name}              - View profile
/user/{id}/edit/profile        - Edit profile
/search                        - Search form
/search/simple/{keywords}[/{page}]          - Simple search
/search/advanced[/{params}][/{page}]        - Advanced search
/search[/user/{uid}]/{action}[/{page}]      - Search actions

Best Practices

Always provide route markers for link generation. Never hardcode URLs.
// Good
$link = $c->Router->link('Forum', ['id' => $forum->id]);

// Bad
$link = "/forum/{$forum->id}";
Use type hints in route patterns to validate parameters.
// Ensures id is a positive integer
'/forum/{id|i:[1-9]\d*}/{name}'
Optional parameters should have sensible defaults.
$page = $args['page'] ?? 1;
Include human-readable slugs in URLs.
$c->Router->link('Forum', [
    'id' => $forum->id,
    'name' => $c->Func->friendly($forum->forum_name)
]);

Next Steps

Controllers

Learn how controllers handle routes

Models

Work with data in your controllers