Developer Guide
Developer Guide
Architecture Overview
MVC Pattern
Flatboard 5 follows the Model-View-Controller (MVC) pattern:
app/
âââ Controllers/ # Handle requests and logic
âââ Models/ # Data models and business logic
âââ Views/ # Template files
âââ Core/ # Core framework classes
âââ Helpers/ # Helper functions
âââ Middleware/ # Request middleware
âââ Services/ # Service classesDirectory Structure
Flatboard5/
âââ app/ # Application code
â âââ Controllers/ # Controllers
â âââ Models/ # Models
â âââ Views/ # Views
â âââ Core/ # Core classes
â âââ Helpers/ # Helpers
â âââ Middleware/ # Middleware
â âââ Services/ # Services
âââ public/ # Public files
â âââ index.php # Entry point
âââ stockage/ # Data storage
âââ uploads/ # User uploads
âââ plugins/ # Plugins
âââ themes/ # Themes
âââ vendor/ # DependenciesCore Components
Autoloader
PSR-4 autoloading:
use App\Core\Autoloader;
Autoloader::register(BASE_PATH);Router
Route registration (the Router requires Request and Response instances):
use App\Core\Router;
use App\Core\Request;
use App\Core\Response;
$router = new Router($request, $response);
$router->get('/path', 'Controller@method');
$router->post('/path', 'Controller@method');
// Named routes
$router->get('/forum', 'ForumController@index')->name('forum.index');
$url = $router->url('forum.index'); // Generate URL
$url = $router->url('discussion.show', ['id' => 42]); // With params
// Route groups (shared prefix + middleware)
$router->group(['prefix' => '/admin', 'middleware' => ['App\Middleware\AuthMiddleware']], function($router) {
$router->get('/dashboard', 'Admin\DashboardController@index');
});
// Parameter constraints
$router->get('/user/{id}', 'UserController@show')->where('id', '[0-9]+');
// RESTful resource routes (generates GET list, GET show, POST, PUT, DELETE)
$router->resource('/posts', 'PostController');
// Regex-based routes
$router->regex('GET', '#^/custom/(.+)$#', function($matches) { /* ... */ });
// Global before/after hooks (for monitoring, logging)
$router->beforeEach(function($request) { /* ... */ });
$router->afterEach(function($request, $response) { /* ... */ });
// Route cache for production (cached to stockage/cache/routes.php)
$router->enableCache();Controller
Base controller:
namespace App\Controllers;
use App\Core\Controller;
class MyController extends Controller
{
public function index()
{
return $this->view('template', ['data' => $data]);
}
}Model
Base model:
namespace App\Models;
class MyModel
{
public static function find($id)
{
// Load from storage
}
public static function create($data)
{
// Create new record
}
}Coding Standards
PSR Standards
Follow PSR standards:
- PSR-1 - Basic coding standard
- PSR-4 - Autoloading standard
- PSR-12 - Extended coding style
Code Style
<?php
namespace App\Controllers;
use App\Core\Controller;
use App\Models\User;
class UserController extends Controller
{
public function index()
{
$users = User::all();
return $this->view('users.index', ['users' => $users]);
}
public function show($id)
{
$user = User::find($id);
if (!$user) {
return $this->notFound();
}
return $this->view('users.show', ['user' => $user]);
}
}Naming Conventions
- Classes: PascalCase -
UserController - Methods: camelCase -
getUserData() - Variables: camelCase -
$userData - Constants: UPPER_SNAKE_CASE -
MAX_FILE_SIZE - Files: Match class name -
UserController.php
Plugin Development
Plugin Structure
plugins/my-plugin/
âââ plugin.json
âââ MyPluginPlugin.php
âââ assets/
âââ views/
âââ README.mdPlugin Class
<?php
namespace App\Plugins\MyPlugin;
use App\Core\Plugin;
class MyPluginPlugin
{
public function boot()
{
// Register plugin routes
Plugin::hook('router.plugins.register', [$this, 'registerRoutes']);
// Add CSS to page header
Plugin::hook('view.header.styles', [$this, 'addStyles']);
}
public function registerRoutes($router)
{
$router->get('/my-plugin', function() {
return 'Hello from plugin!';
});
}
public function addStyles(&$styles)
{
$styles[] = \App\Helpers\PluginAssetHelper::loadCss('my-plugin', 'css/style.css');
}
}Available Hooks
Below are the most commonly used hooks for plugin development:
| Hook | Use case |
|---|---|
router.plugins.register | Register plugin routes (preferred for plugins) |
app.routes.register | Register application-level routes |
view.header.styles | Inject CSS into <head> |
view.footer.scripts | Inject JS before </body> |
view.footer.content | Inject HTML into footer |
view.navbar.items | Add items to the main navigation bar |
view.admin.sidebar.items | Add items to the admin sidebar |
admin.dashboard.widgets | Add widgets to the admin dashboard |
discussion.created | After a discussion is saved |
post.created | After a reply is saved |
user.registered | After a user account is created |
search.results | Filter or augment search results |
notification.before.create | Intercept notifications before they are written |
markdown.editor.config | Modify the Markdown editor configuration |
visitor.page_info | Resolve page info for unknown URLs (presence) |
presence.users | Filter/enrich active users list |
Theme Development
Theme Structure
themes/my-theme/
âââ theme.json
âââ assets/
â âââ css/
â âââ js/
â âââ img/
âââ views/Template Overrides
Override default templates:
themes/my-theme/views/
âââ layouts/
â âââ main.php
âââ discussions/
âââ list.phpCSS Variables
Use CSS variables for customization:
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
--background-color: #ffffff;
--text-color: #212529;
}Plugin Settings API
Plugin settings live in the "plugin" section of plugin.json. Always use Plugin::getData/setData/saveData â never Config::get/set â for plugin-specific values:
use App\Core\Plugin;
// Read a setting (third arg is default value)
$apiKey = Plugin::getData('my-plugin', 'api_key', '');
// Dot-notation for nested keys
$host = Plugin::getData('my-plugin', 'smtp.host', 'localhost');
// Write a setting (in-memory only)
Plugin::setData('my-plugin', 'api_key', 'abc123');
// Persist all settings to plugin.json
Plugin::saveData('my-plugin', ['api_key' => 'abc123', 'enabled' => true]);
// Get plugin stats (for monitoring)
$stats = Plugin::getStats();
// Returns: ['total' => int, 'active' => int, 'inactive' => int, 'hooks' => int]Presence Service
App\Services\PresenceService provides a unified API for querying who is currently on the forum (all methods are static):
use App\Services\PresenceService;
// All presence data (anonymous visitors + bots + logged-in users)
$all = PresenceService::getAllPresence(minutes: 15, includeBots: true);
// Returns: ['visitors' => [...], 'bots' => [...], 'users' => [...], 'all' => [...], 'stats' => [...]]
// Presence on a specific page
$page = PresenceService::getPresenceByPage('/d/123', minutes: 15);
// Aggregate stats only
$stats = PresenceService::getPresenceStats(minutes: 15);
// Returns: ['total' => int, 'anonymous' => int, 'authenticated' => int, 'bots' => int]
// Filter helpers (work on any presence array)
$filtered = PresenceService::filterByPageType($all['all'], 'discussion');
$filtered = PresenceService::filterByCategory($all['all'], 'general');
$filtered = PresenceService::filterByUserGroup($all['users'], 'moderator');
// Sorting
$sorted = PresenceService::sortPresence($all['all'], sortBy: 'last_activity', order: 'desc');VisitorTrackingMiddleware runs automatically on every non-AJAX HTML request. It skips: authenticated users, paths under /api/, /presence/update, /favicon.ico, /robots.txt, static file extensions. It fires visitor.before_track before writing each record.
Translation System
Global helper
// Both are equivalent
$text = Translator::trans('key', ['var' => 'value'], 'domain');
$text = __('key', ['var' => 'value'], 'domain');Advanced methods
// Get current language code
$lang = Translator::getLanguage(); // e.g., 'fr', 'en'
// Change language for the current request
Translator::setLanguage('en');
// Reload all translations from disk
Translator::reload();
// Reload only theme translation overrides
Translator::reloadThemeTranslations();
// Get all keys for a domain (useful for debugging)
$all = Translator::getAll('main');
// Register plugin translations programmatically
Translator::addPluginTranslations('my-plugin', ['key' => 'value']);Storage Development
JSON Storage
Working with JSON storage:
use App\Core\AtomicFileHelper;
// Read (returns array or null if file absent)
$data = AtomicFileHelper::readAtomic('path/to/file.json');
// Write (returns bool)
AtomicFileHelper::writeAtomic('path/to/file.json', $data);
// Batch read multiple files in one pass
$results = AtomicFileHelper::readAtomicBatch([
'path/to/file1.json',
'path/to/file2.json',
]);SQLite Storage (Pro)
Working with SQLite:
use App\Storage\SqliteStorage;
$storage = new SqliteStorage();
$result = $storage->query('SELECT * FROM users WHERE id = ?', [$id]);Security Best Practices
Input Validation
Always validate input:
use App\Core\Validator;
$validator = new Validator();
$validator->required('email')->email('email');
if (!$validator->validate($data)) {
return $this->error($validator->errors());
}Output Sanitization
Sanitize all output:
use App\Core\Sanitizer;
$clean = Sanitizer::clean($userInput);
echo htmlspecialchars($clean, ENT_QUOTES, 'UTF-8');CSRF Protection
Use CSRF tokens:
use App\Core\Csrf;
// Generate token
$token = Csrf::token();
// Verify token
if (!Csrf::verify($token)) {
return $this->error('Invalid CSRF token');
}Testing
Unit Tests
Write unit tests:
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
public function testUserCreation()
{
$user = User::create([
'username' => 'testuser',
'email' => '[email protected]'
]);
$this->assertNotNull($user);
}
}Integration Tests
Test integrations:
public function testApiEndpoint()
{
$response = $this->get('/api/v1/discussions');
$this->assertEquals(200, $response->getStatusCode());
}Performance
Caching
Use caching:
use App\Core\Cache;
// Set cache
Cache::set('key', $data, 3600);
// Get cache
$data = Cache::get('key');
// Clear cache
Cache::clear('key');Database Optimization
Optimize queries:
// Use indexes
// Limit results
// Avoid N+1 queries
// Use transactionsContributing
Code Contribution
- Fork Repository - Fork on GitHub
- Create Branch - Create feature branch
- Write Code - Follow coding standards
- Test - Write and run tests
- Submit PR - Submit pull request
Documentation
- Code Comments - Add helpful comments
- PHPDoc - Document functions and classes
- README - Update README if needed
- Changelog - Update changelog
Version Compatibility
When developing plugins, themes, or customizations:
- Target Flatboard 5 - Code for Flatboard 5 architecture
- Not Compatible with v3/v4 - Code won't work on older versions
- Use Modern PHP - Take advantage of PHP 8 features
- Follow Architecture - Follow Flatboard 5 patterns
Resources
- Plugin Guide - Plugin development
- Theme Guide - Theme development
- API Documentation - API development
- GitHub Repository - Source code
Last updated: February 23, 2026