Alpha ZealPHP is early-stage and under active development. APIs may change between minor versions until v1.0. Feedback and bug reports welcome on GitHub.

Parameter Injection

PHP is a permissive language. ZealPHP is a permissive language that also reads — specifically, the signature of every handler you write.

You will learn

  • The four magic parameter names ZealPHP injects for you
  • How URL params (like {id}) get pulled into your function signature
  • Why this is cached at registration — not done per request
  • What happens when you ask for something ZealPHP doesn't know about

You declare. The framework fetches.

Most PHP frameworks pass you a request object and make you fish out what you need. Express gives you (req, res, next). Laravel passes a Request instance you call methods on. Symfony hands you the kitchen sink and lets you sort it out.

ZealPHP does something quieter: it reads your function signature with reflection, figures out what each parameter wants, and passes the right object by name. You write a handler that looks like a plain function. The framework treats the parameter list as a shopping list.

$app->route('/users/{id}', function ($id, $request, $response) {
    return ['id' => $id, 'method' => $request->server['request_method']];
});

Three parameters, three different sources: $id from the URL pattern, $request from the framework, $response from the framework. You didn’t write a single line of plumbing.

The four magic names

ZealPHP recognizes four parameter-name patterns ($request and $response each have a short alias — $req and $res):

If your parameter is named…You get…
$request (or $req)The ZealPHP\HTTP\Request wrapper — headers, query, body, cookies, files
$response (or $res)The ZealPHP\HTTP\Response wrapper — json(), redirect(), stream(), sse(), cookie()
$appThe ResponseMiddleware instance handling this request — almost never useful in modern handlers; reach for the static \ZealPHP\App::* facade (render(), include(), after(), getServer()) instead
Anything matching a {name} in the routeThe captured URL segment as a string

$req / $res are accepted as short aliases for $request / $response — they receive the exact same wrappers. The reserved framework-object names (request / req / response / res / app) bind the injected object before any same-named URL segment (security fix #240) — so a handler typed function($req) always receives the wrapper, never an attacker-controllable path string. A URL segment that happens to use a reserved name is simply unbindable to that parameter; name it something else to read the segment.

Any other parameter with a default value gets its default. A parameter without a default that ZealPHP can’t resolve is passed null — if the parameter is typed non-nullable, PHP itself raises a TypeError; otherwise the handler receives null silently.

Examples by parameter name

1. Just the URL params

$app->route('/posts/{slug}', function ($slug) {
    return Post::findBySlug($slug);
});

Cleanest possible handler. ZealPHP returns the array as JSON.

2. URL params + request body

$app->route('/posts/{slug}/comment', ['methods' => ['POST']],
    function ($slug, $request) {
        $body = $request->post['comment'] ?? '';
        Comment::create($slug, $body);
        return ['ok' => true];
    }
);

3. URL params + response (for cookies / streaming / redirects)

$app->route('/login/{token}', function ($token, $response) {
    $response->cookie('session', $token, time() + 86400, '/', '', true, true);
    $response->redirect('/dashboard');
});

4. Mix everything

$app->route('/users/{id}/avatar', function ($id, $request, $response) {
    $size = (int)($request->get['size'] ?? 64);
    return $response->sendFile("storage/avatars/{$id}-{$size}.png");
});

Why this is fast

Reflection is famously slow in PHP. ZealPHP runs it once per route, at registration time, builds a parameter map (just an array of "this position gets the URL param named id, that one gets the request object"), and caches it on the route. At request time, the framework walks the cached map, not the reflection metadata.

The result: parameter injection costs you an array walk and a few function calls. The reflection cost is amortized across every request the worker handles — thousands of them — and in coroutine mode, the worker handles a lot.

Try it live

Every parameter-injection case is wired up in the demo app at /demo/view/inject/{case}. Visit a few to see what gets injected:

You register $app->route("/posts/{slug}", function ($request, $slug) {...}). Does the order of parameters matter?

Key Takeaways

  • Four magic names: $request, $response, $app, and any URL {placeholder}.
  • Parameter order in your handler signature doesn't matter — injection is by name.
  • Reflection runs once at route registration; the parameter map is cached — per-request injection is just an array walk.
  • Any parameter with a default value gets its default if not injected.
  • Parameters ZealPHP can't resolve are passed null — a non-nullable type hint turns that into a PHP TypeError.