architecture-MVC-libraries is a lightweight PHP MVC foundation designed for small to medium-sized APIs and server-rendered applications.
The project provides:
- a front controller in
public/ - a routing layer based on AltoRouter
- controller and model base classes
- request, response, and authentication services
- helper utilities for uploads, mail, and security
- a CLI generator named
automat
The current architecture favors:
- explicit controller dispatch
- centralized JSON responses
- lazy database access
- environment-driven configuration
- a clear separation between HTTP concerns and business logic
- PHP 8.0 or higher
- Composer
- A local or remote MySQL-compatible database
composer install
composer dump-autoload
cp .env.example .envUpdate .env with your runtime configuration:
DB_HOSTDB_NAMEDB_USERDB_PASSJWT_SECRETCORS_ALLOWED_ORIGINS- SMTP variables if mail delivery is required
Start the application locally:
php -S localhost:8000 -t publicconfig/ Environment and application configuration
controllers/ Base controller and application controllers
core/ Request, response, auth, config, and bootstrapping services
models/ Data access classes and domain models
public/ HTTP entry point
router/ AltoRouter wrapper and controller dispatcher
routes/ Route declarations grouped by HTTP verb
tests/ Lightweight test runner and architecture checks
utils/ Reusable helpers (security, uploads, mail)
views/ PHP templates for server-rendered pages
automat CLI generator for models and controllers
The runtime flow is intentionally simple and explicit:
public/index.phploads Composer, environment variables, and core services.Core\Coreconfigures request handling, CORS, authentication helpers, and route loading.routes/*.phpregister route definitions withRouter\Router.Router\Routerresolves the incoming request and dispatches the matched controller action.- Controllers read input through
Core\Request, access persistence through models, and send output throughCore\Response.
This keeps the HTTP pipeline understandable while remaining extensible for future middleware or service container work.
Routes are declared by HTTP verb in the routes/ directory and now use controller targets directly:
Router\Router::get('/api', [Controllers\ApiController::class, 'index']);
Router\Router::post('/api/articles', [Controllers\ArticleController::class, 'apiStore']);
Router\Router::put('/api/articles/[i:id]', [Controllers\ArticleController::class, 'apiUpdate']);
Router\Router::delete('/api/articles/[i:id]', [Controllers\ArticleController::class, 'apiDestroy']);This pattern is preferred over route closures for application endpoints because it is:
- easier to test
- more consistent
- easier to document
- better aligned with controller-based frameworks
Application controllers extend Controllers\Controller.
The base controller exposes:
render()for PHP viewsjsonResponse(),jsonSuccess(),jsonError()for API responsesredirection()for redirects- injected
DataModel,Response, andRequestdependencies
Example:
<?php
namespace Controllers;
class ArticleController extends Controller
{
public function apiShow(array $params = []): void
{
$id = $params['id'] ?? null;
if (!$id) {
$this->jsonError('ID non fourni', 400);
return;
}
$model = new \Models\ArticleModel($this->db);
$article = $model->findById((int) $id);
if (!$article) {
$this->jsonError('Article non trouvé', 404);
return;
}
$this->jsonResponse([
'success' => true,
'data' => $article,
]);
}
}The core/ layer centralizes HTTP behavior:
Core\Requestreads JSON payloads, headers, request method, URI, and uploaded filesCore\Responsestandardizes JSON output and status handlingCore\AuthServicevalidates bearer tokensCore\AppConfigreads runtime configuration from environment variables
This makes controllers thinner and avoids repeating low-level HTTP logic throughout the codebase.
Models\DataModel manages the PDO connection.
The connection is now lazy:
- the application no longer opens a database connection during bootstrap
- the connection is created only when
getPDO()is first called
This allows non-database endpoints to remain functional even if the database is temporarily unavailable.
JWT validation is handled by Core\AuthService.
The current implementation:
- validates the token structure
- enforces
HS256 - verifies the signature
- checks time-based claims such as
exp,nbf, andiat - refuses insecure fallback secrets
To require authentication in an endpoint, use:
$userId = $core->requireAuthenticatedUserId();or call:
$userId = $core->getArtisteIdFromToken();Utils\UploadHandler provides a basic but safer upload workflow:
- optional MIME and extension allowlists
- file size limitation
- random file naming
- validation with
is_uploaded_file() - public-facing relative path in the returned payload
Example:
use Utils\UploadHandler;
$uploader = new UploadHandler(
__DIR__ . '/../uploads',
5_000_000,
['image/jpeg', 'image/png'],
['jpg', 'jpeg', 'png']
);
$result = $uploader->handle('file');Utils\Security contains helper methods for:
- input sanitation
- HTML escaping
- password hashing and verification
- CSRF token generation and validation
- session-based rate limiting
Recommended usage:
- sanitize input when normalizing user data
- escape output when rendering HTML
- do not store HTML-escaped data in the database
CORS is environment-driven through CORS_ALLOWED_ORIGINS.
Example:
CORS_ALLOWED_ORIGINS=http://localhost:8000,http://localhost:3000In non-production environments, the application can fall back to permissive behavior when no explicit origin is configured. In production, define allowed origins explicitly.
automat is a CLI helper that scaffolds models and controllers:
php automat list
php automat create:model Article
php automat create:controller ArticleControllerGenerated controllers include:
- classic MVC actions such as
index,show,create,store,edit,update,destroy - API actions such as
apiIndex,apiShow,apiStore,apiUpdate,apiDestroy - JSON-aware request reading through the injected
Requestobject
After generating a controller, automat also prints route examples using the controller-target style introduced in this architecture.
A lightweight test runner is available:
php tests/run.phpThe current test suite covers:
- request JSON decoding
- JWT validation behavior
- controller dispatch through the router
- Do not commit real secrets to the repository.
- Keep
composer.jsonandcomposer.lockversioned together. - Do not edit
vendor/manually. - Restrict
CORS_ALLOWED_ORIGINSin production. - Use strong, non-default JWT secrets.
- Validate upload types and sizes server-side even if the client already does.
Natural next steps for this codebase are:
- introduce a dedicated dependency container
- add a richer automated test suite
- formalize error handling for HTML responses
- add middleware support for authentication and rate limiting
This project is licensed under the Apache License 2.0. See LICENSE.