Returning a Response
Most frameworks make you construct a response object. ZealPHP infers it from what you return — like a thoughtful waiter who doesn't need you to spell out medium-rare, no onions.
You will learn
- The six return-value conventions ZealPHP recognizes
- When to
return $datavs reach for the$responseobject - How streaming (Generators) fits into the same conventions
- What happens to
echooutput (and when it matters)
The six conventions
Whatever your handler returns, ZealPHP’s ResponseMiddleware looks at the
type and translates it into the right HTTP response. Six types, six behaviors:
| Return type | What happens | Example |
|---|---|---|
int | Status code, empty body | return 404; |
string | HTML body, 200 OK | return '<h1>Hello</h1>'; |
array / object | JSON-encoded, Content-Type: application/json | return ['ok' => true]; |
\Generator | Streaming — each yield sent immediately | yield "<li>{$row->name}</li>"; |
void + echo | Captured output buffer becomes the body | echo App::render(...); |
ResponseInterface | PSR-7 passthrough — sent verbatim | return $response->redirect('/'); |
The first three cover 90% of handlers. The other three exist for the cases where 90% isn’t enough.
The default path: return data
JSON APIs are the cleanest case. Return an array. Done.
$app->route('/api/users/{id}', function ($id) {
$user = User::find($id);
return $user ? $user->toArray() : 404;
});
Note the : 404 branch — an int return-value short-circuits to a status code.
No $response->status(404)->end(); no http_response_code(404); exit;.
The framework reads "404" and does the right thing.
When to reach for $response
The $response object is for cases that don’t fit a single return value:
setting cookies, redirects, custom headers, streaming, sending a file. You
mutate the response object, then either return the result or just return:
$app->route('/logout', function ($response) {
$response->cookie('session', '', time() - 3600, '/');
return $response->redirect('/');
});
Same handler, the redirect() call returns a PSR-7 response which is then returned
from the closure. ResponseMiddleware sees the ResponseInterface and ships it.
Streaming via Generator
Yield from your handler and you’re streaming. The framework flushes each yielded chunk immediately — no buffer, no waiting for the function to return.
$app->route('/feed', function () {
return (function () {
yield "<html><body><ul>";
foreach (Post::recent() as $p) {
yield "<li>{$p->title}</li>";
}
yield "</ul></body></html>";
})();
});
Lesson 11 (Streaming Done Right) covers the four patterns in detail. For now: any Generator return triggers streaming mode. The browser starts rendering before your handler finishes.
echo + void: the legacy escape hatch
You can still write PHP the old way — echo to stdout, let the framework capture it:
$app->route('/about', function () {
App::render('about', ['user' => auth()]);
// no return — rendered output is captured via ob_get_clean()
});
App::render() echoes the rendered template. The handler returns void.
ResponseMiddleware grabs the output buffer and sends it. This is how every WordPress page
handler works, more or less.
Prefer returning a value when you can. Returning is cheaper than buffering, easier to test, and works inside coroutines without surprises. But if you’re porting Apache code, echo-and-void is the bridge.
Try it live
Every response convention has a demo at /demo/response/{method}:
- Return an array → JSON
- 301 permanent redirect —
$response->redirect($url, 301) - 302 redirect —
$response->redirect($url) - Custom headers —
$response->header() - Set a cookie —
$response->cookie()
Your handler is function () { return "<p>hi</p>"; }. What Content-Type does the browser see?
Key Takeaways
- Return an
intfor status-only responses (return 404). - Return a
stringfor HTML, anarrayfor JSON — the framework picks the Content-Type. - Return a
Generatorto stream chunks as they're yielded. - Reach for the
$responseobject when you need cookies, headers, or redirects alongside the body. echo+ void works for legacy code — the framework captures stdout, but returning is cleaner.