Auto-gegenereerd via Reflection — blijft altijd in sync met src/.
Container
Framework\App\ContainerMinimale DI-container, PSR-11 compatible.
- `bind()` voor transient services (factory wordt elke get() opnieuw aangeroepen)
- `singleton()` voor services die maar één keer geconstrueerd worden
- `instance()` voor reeds gemaakte objecten
Geen auto-wiring: explicit is better than implicit voor een klein framework.
Gooit:
- {@see NotFoundException} (PSR-11 NotFoundExceptionInterface) als id onbekend is
- {@see ContainerException} (PSR-11 ContainerExceptionInterface) als factory faalt
6 public methods
bind(string $id, Closure|string $factory): voidget(string $id): ?mixedhas(string $id): boolinstance(string $id, ?mixed $instance): voidmake(string $id): objectTyped resolver — return-type matcht de meegegeven class-string.
Gooit ContainerException als de service van het verkeerde type is.
singleton(string $id, Closure|string $factory): voidContainerException
Framework\App\ContainerExceptionGenerieke container-fout (factory crash, dubbele binding, etc.).
Voor "service niet gevonden" → {@see NotFoundException}.
__construct(string $message = '', int $code = 0, ?Throwable $previous = NULL)EnvLoader
Framework\App\EnvLoaderMinimale .env-parser — geen Composer-dependency nodig.
Ondersteund formaat:
DB_HOST=127.0.0.1
DB_NAME="mijn_db" # aanhalingstekens worden gestript
DB_PASS='geheim'
# dit is een commentaarregel
LEGE_WAARDE=
Laadt waarden in $_ENV en $_SERVER, en via putenv().
Bestaande waarden (bijv. gezet door de webserver/Docker) worden
NIET overschreven — de omgeving wint altijd.
Gebruik:
EnvLoader::load('/pad/naar/.env');
$host = EnvLoader::get('DB_HOST', '127.0.0.1');
3 public methods
static get(string $key, ?string $default = NULL): ?stringLees een omgevingsvariabele — zoekt in $_ENV, $_SERVER en getenv().
Geeft $default terug als de variabele niet bestaat.
static isLoaded(): boolGeeft true als EnvLoader::load() al is aangeroepen.
static load(string $path): voidLaad een .env-bestand. Idempotent: tweede aanroep met hetzelfde
bestand heeft geen effect.
Kernel
Framework\App\KernelKernel — framework-kernel, CMS-vrij.
Eén instance per request. Kern-services hangen als readonly virtual properties
(PHP 8.4 property hooks) zodat ze kort opvraagbaar zijn maar nog steeds via
de container resolven — testen kan via Kernel::swap() of $kernel->container.
$kernel->router->get('/path', $handler);
$kernel->router->post('/path', $handler);
$kernel->layout->wrap(...)
$kernel->get(SomeService::class)
8 public methods
bind(string $id, Closure|string $factory, bool $singleton = true): voidstatic boot(string $appRoot, bool $debug = false, bool $registerErrorHandler = true): selfBoot een Kernel met de standaard kern-services geregistreerd.
CMS-bootstrap (Website, etc.) gebeurt daarna door de aanroeper.
static current(): selfget(string $id): ?mixedhandle(\ServerRequest $request): \ResponseVerwerk het verzoek en geef de gerenderde {@see Response} terug. Pure
functie (geen `header()`/`echo`-side-effects) — testbaar via PHPUnit.
Trailing-slash redirects, dispatch, 404 en 405 worden hier afgehandeld.
instance(string $id, ?mixed $value): voidrun(): voidVerwerk het huidige HTTP-verzoek en stuur de response (status, headers,
body) naar de client. Wrappert {@see handle()} met de IO-laag.
static swap(?self $kernel): voidSwap voor tests. Geef null om te resetten.
Layout
Framework\App\LayoutPaginawrapper met sidebar-layout.
Sidebar-data staat in `config/sidebar.php` zodat de structuur op één plek
beheerd wordt en de live-search dezelfde lijst kan gebruiken.
__construct(string $appRoot)2 public methods
notFound(string $requestedPath = ''): stringRender een 404-pagina, gewikkeld in de standaard layout.
wrap(string $title, string $content): stringWikkel content in de volledige HTML-pagina.
NotFoundException
Framework\App\NotFoundExceptionGegooid door {@see Container::get()} als de gevraagde id niet geregistreerd is.
__construct(string $message = '', int $code = 0, ?Throwable $previous = NULL)Route
Framework\App\RouteEén geregistreerde route. Bevat:
- methods : welke HTTP-methods matchen (lege list = alle)
- pattern : regex-pattern voor `$request->url->path`
- action : callable(ServerRequest): string|\Stringable
- middleware: per-route stack (na de globale Router-middleware)
`$route->middleware()` is chainable — lift-en-shift maakt 'm mutable maar
de constructor-properties blijven readonly.
__construct(array $methods, string $pattern, ?mixed $action)3 public methods
getMiddleware(): arraymatchesMethod(string $method): boolIs `$method` toegestaan voor deze route? Lege methods-list = altijd ja.
middleware(callable ...$middleware = ?): selfVoeg één of meer middleware-callables toe. Een middleware krijgt
`(ServerRequest $req, callable $next)` en moet `$next($req)` aanroepen
(of een eigen response teruggeven om de chain te short-circuiten).
RouteResult
Framework\App\RouteResultResultaat van `Router::dispatch()`.
Drie outcomes:
- **Matched** → URL én HTTP-method matchen; `response` draagt status,
headers en body
- **Method not allowed** → URL matched, method niet; `allowedMethods` lijst
gaat in de `Allow:`-header van een 405-response
- **Not found** → geen URL match; consumer rendert een 404
Pure value-object; geen state, alleen tagging.
6 public methods
isMatched(): boolisMethodNotAllowed(): boolisNotFound(): boolstatic matched(\Response $response): selfstatic methodNotAllowed(array $allowed): selfstatic notFound(): selfRouter
Framework\App\RouterRouter — regex-based, method-aware, met middleware-pipe.
Method-shortcuts:
$router->get ('/users', $handler);
$router->post ('/users', $handler);
$router->put ('/users/(\d+)',$handler);
$router->patch ('/users/(\d+)',$handler);
$router->delete('/users/(\d+)',$handler);
$router->any ('/healthz', $handler); // alle methods
$router->match (['GET','POST'],'/contact', $handler); // custom set
Capture-groups in het pattern komen door als positionele route-params:
$router->get('/articles/([a-z0-9-]+)',
fn(ServerRequest $req) => 'Slug: ' . $req->param(0));
Middleware (PSR-15-stijl, maar simpler):
$router->use(fn($req, $next) => $next($req)); // globaal
$router->get('/admin', $h)->middleware($authMw); // per-route
Een middleware krijgt `(ServerRequest, callable $next)` en MOET `$next($req)`
aanroepen om door te gaan, of een eigen response (`Response`/string/\Stringable)
returnen om te short-circuiten (bv. een redirect of 401).
`dispatch()` returnt een {@see RouteResult} met drie outcomes: matched,
methodNotAllowed (405), notFound (404).
9 public methods
any(string $pattern, callable $action): \RouteMatch alle HTTP-methods op dit pattern.
delete(string $pattern, callable $action): \Routedispatch(\ServerRequest $request): \RouteResultget(string $pattern, callable $action): \Routematch(array $methods, string $pattern, callable $action): \RouteMatch een expliciete set methods.
patch(string $pattern, callable $action): \Routepost(string $pattern, callable $action): \Routeput(string $pattern, callable $action): \Routeuse(callable $middleware): selfRegistreer globale middleware (loopt vóór de per-route middleware).