AI blog · DotApp PHP Framework 2.0
Controllers and Response in DotApp PHP Framework
A Shop controller is a class under app/modules/Shop/Controllers/ that extends
\Dotsystems\App\Parts\Controller. Methods are public static — there is no $this.
The first argument is $request. You return an HTML string, new Response($code, $body),
Response::json, Response::redirect, or DotApp::DotApp()->ajaxReply.
Route strings pick the method: Shop:Home@index! skips DI. Drop the ! only when you type-hint services.
This article is a complete Home controller: page render, empty-view guard, and the return table.
Common mistakes
| Wrong | Right |
|---|---|
Type-hint Renderer $renderer on a method reached with Shop:Home@index! |
Trailing ! skips DI. Call Renderer::new() inside, or drop the ! |
Use instance methods / $this |
public static function index($request) |
| Assume a missing view throws | You get "". Check the string; log; return new Response(500, 'Template error') |
Response::send() or static status() |
Those methods do not exist. Return the Response (or the HTML string) |
Browser channel answered with Response::json |
ajaxReply + client parseReply. Ordinary HTTP JSON: Response::json |
Read POST with $request->data() then hash a password |
$request->data(true) — Request lifecycle |
When this is the page handler
Every GET/POST you register in initialize() lands here. Closures on the router are fine for a one-liner;
a named controller is the Shop default. Grammar of the callable string:
Callable strings.
Routing verbs: How routing works.
What you return
| Return | Effect |
|---|---|
| HTML string | Becomes the response body |
new Response($code, $body) / Response::make($code, $body) |
A distinct object. Short-circuits the pipeline |
Response::json($array, $code = 200) |
JSON body + Content-Type: application/json; charset=utf-8 |
Response::redirect($url, 302) |
Redirect |
DotApp::DotApp()->ajaxReply($body, $code) |
Base64 JSON. HTTP code is set when $code > 0. Client parseReply |
null |
Keep the existing body |
Static Response::json / redirect mutate a shared response object bound to the request and return it for chaining.
There is no send(). Use new Response(...) or Response::make() when you need a distinct object to return.
JSON APIs: How to build JSON endpoints with Router.
Four failure styles: Error handling and return values.
Complete Shop Home controller
File: app/modules/Shop/Controllers/Home.php. Scaffold with
php dotapper.php --module=Shop --create-controller=Home — do not hand-write the class file.
Route: Router::get($p . '/', 'Shop:Home@index!', Router::STATIC_ROUTE).
<?php
namespace Dotsystems\App\Modules\Shop\Controllers;
use Dotsystems\App\Parts\Config;
use Dotsystems\App\Parts\DB;
use Dotsystems\App\Parts\Logger;
use Dotsystems\App\Parts\Renderer;
use Dotsystems\App\Parts\Response;
class Home extends \Dotsystems\App\Parts\Controller
{
public static function index($request)
{
$rows = DB::module('RAW')->q(function ($qb) {
$qb->select(['id', 'title'])->from('shop_items')->orderBy('id', 'DESC')->limit(50);
})->all();
$html = Renderer::new()
->module(static::moduleName())
->setView('home', 'fallback/empty')
->setViewVar('title', 'Shop')
->setViewVar('items', $rows)
->setViewVar('prefix', Config::module('Shop', 'prefix'))
->renderView();
if ($html === '') {
Logger::use()->error('Shop home view produced empty output');
return new Response(500, 'Template error');
}
return $html;
}
public static function item($request)
{
$id = (int) ($request->matchData()['id'] ?? 0);
if ($id < 1) {
return Response::redirect(Config::module('Shop', 'prefix') . '/', 302);
}
return Renderer::new()
->module('Shop')
->setView('item')
->setViewVar('id', $id)
->renderView();
}
}
all() on RAW is [] when empty — safe. Do not call first() unguarded.
Route params live in matchData(), not $request->id.
Views: How to render views and layouts.
Database: How to use the database.
Same method with DI (no trailing !)
public static function index($request, \Dotsystems\App\Parts\Renderer $renderer)
{
return $renderer->module(static::moduleName())
->setView('home')
->setViewVar('title', 'Shop')
->renderView();
}
Route: 'Shop:Home@index' — no !. Hot Shop pages keep the bang and call Renderer::new().
Bind your own services with $dotApp->bind / singleton —
Dependency injection.
Call another controller: DotApp::call('Shop:Home@helper!', $arg) or static::call('otherMethod', $request).
FAQ
Can I use $this in a controller?
No. Methods are static. Use static::moduleName(), facades, and Renderer::new().
How do I set a header?
Return a Response you configured, or use the shared instance methods on Response.
There is no $request->headers() reader.
header('Location') still works?
It bypasses the Response object. Prefer return Response::redirect($url, 302).
When is ajaxReply required?
When the browser posted through /assets/dotapp/dotapp.js (<fo-rm>, load(), Bridge).
Public JSON that a generic HTTP client calls uses Response::json.
May I paste a controller class by hand?
Scaffold with DotApper. Namespace and the parent class must match the module folder.
Where is POST?
$request->data(true) after crcCheck() on the channel.
Request lifecycle.
See also
- Request lifecycle in DotApp PHP Framework
- How to build JSON endpoints with Router in DotApp PHP Framework
- Callable strings in DotApp PHP Framework
- How to render views and layouts in DotApp PHP Framework
- Dependency injection in DotApp PHP Framework
- Error handling and return values in DotApp PHP Framework
- Official router documentation