AI blog · DotApp PHP Framework 2.0
Error handling and return values in DotApp PHP Framework
DotApp PHP Framework does not have one failure style. Guessing the wrong one is the usual source of blank pages and uncaught exceptions.
Database execute() uses a callback pair. Cache misses are null. Validator::validate returns true or an array (the array is truthy).
resolve(), some Auth calls, and QueryBuilder build errors throw.
A missing view is "" plus a log warning — not an exception.
This article is the map plus a complete Shop save that guards every shape.
Four failure styles
Do not wrap everything in try/catch and assume that is enough.
Do not test if ($result) when failure is a non-empty array or when success is 0.
| Style | Where | How to handle |
|---|---|---|
A. Callback pair ($ok, $err) |
execute(), Entity::save() / delete(), DB::schema() |
Always pass both callbacks |
| B. Boolean / null / false | Cache::load, crcCheck, Crypto::decrypt, missing views |
Check === false / === null / === '' |
| C. Result envelope | HttpHelper::request, FastSearch |
Read ['success'] before ['data'] |
| D. Exceptions | $dotApp->resolve, QueryBuilder build errors, some Auth calls |
try/catch |
Fifth trap: some methods return void and only report through callbacks (Entity::save()).
Database details: How to use the database.
Request form() shapes: Request lifecycle.
Common mistakes
| Wrong | Right |
|---|---|
execute() with only the success callback |
On failure it throws. Pass the error callback; then failure returns false |
if ($validate) after Validator::validate |
if ($result === true). A failure array is truthy |
$request->form(...) without the error callback |
Throws. Guard null / false / missing ['body'] |
crcCheck() in middleware and again in save() |
One call per request. The first success burns the token; the second is false. Request lifecycle |
first() when the row might be missing |
all() then $rows[0] ?? null. ORM empty first() is fatal |
| Assume a missing template throws | "" + log. Check before you return HTML |
Cache::load() === false |
A miss is null |
execute() return table
| Situation | execute() returns |
|---|---|
| Success | Result (rows / Collection / driver result) |
Error with $onError |
false |
Error without $onError |
Throws \Exception |
| No DB connection | Throws \Exception |
On a cache hit, $execution_data is an empty array — use ?? null.
Keys when present: affected_rows, insert_id, num_rows, result, query, bindings.
Complete Shop save with every guard
File: app/modules/Shop/Controllers/Item.php — POST half. Channel form + named RAW + empty view already covered in other articles;
this is the failure-shape checklist in one method.
crcCheck() appears here once — not also in middleware.
<?php
namespace Dotsystems\App\Modules\Shop\Controllers;
use Dotsystems\App\DotApp;
use Dotsystems\App\Parts\DB;
use Dotsystems\App\Parts\Logger;
use Dotsystems\App\Parts\Validator;
class Item extends \Dotsystems\App\Parts\Controller
{
public static function save($request)
{
try {
if (!$request->crcCheck()) {
return DotApp::DotApp()->ajaxReply(['status' => 0, 'message' => 'Bad request'], 400);
}
$answer = $request->form(
['POST'],
'saveItem',
function ($request) {
$payload = $request->data(true)['data'] ?? [];
$title = trim((string) ($payload['title'] ?? ''));
$check = Validator::validate(['title' => $title], ['title' => 'required|min:1']);
if ($check !== true) {
return ['code' => 200, 'body' => ['status' => 0, 'message' => 'Title required']];
}
$newId = null;
DB::module('RAW')->q(function ($qb) use ($title) {
$qb->raw(
'INSERT INTO shop_items (title, created_at) VALUES (:title, :created_at)',
['title' => $title, 'created_at' => date('Y-m-d H:i:s')]
);
})->execute(
function ($result, $db, $execution_data) use (&$newId) {
$newId = $execution_data['insert_id'] ?? $db->inserted_id();
},
function ($error) {
Logger::use()->error('item save failed', $error);
}
);
if ($newId === null) {
return ['code' => 200, 'body' => ['status' => 0, 'message' => 'Save failed']];
}
DotApp::dotApp()->trigger('shop.item.saved', true, (int) $newId);
return ['code' => 200, 'body' => ['status' => 1, 'id' => $newId]];
},
function () {
return ['code' => 403, 'body' => ['status' => 0, 'message' => 'Invalid signature']];
}
);
if (!is_array($answer) || !isset($answer['body'])) {
return DotApp::DotApp()->ajaxReply(['status' => 0, 'message' => 'Rejected'], 400);
}
return DotApp::DotApp()->ajaxReply($answer['body'], $answer['code']);
} catch (\Throwable $e) {
Logger::use()->error('Shop save failed', ['msg' => $e->getMessage()]);
return DotApp::DotApp()->ajaxReply(['status' => 0, 'message' => 'Server error'], 500);
}
}
}
Application errors stay HTTP 200 with status 0 so .after() can show them.
Envelope failures stay 400/403 and need .onError() in JS.
Events: Events and listeners.
JSON HTTP (no channel): JSON endpoints.
Quick lookup
| Call | Failure |
|---|---|
Cache::load |
null |
Crypto::decrypt |
false |
Auth::login |
array, or false on malformed input — check false first |
Config::module('X','k') |
null if unset — use ?? fallback in initialize() |
Middleware::use('missing') |
Throws |
$dotApp->resolve('missing') |
Throws |
Renderer missing file |
"" |
FAQ
Why is if ($result) wrong for Validator?
Failure is an array of messages. Arrays are truthy in PHP. Compare with === true.
Why is first() dangerous?
RAW zero rows: unusable value / notice. ORM zero rows: fatal on null->getItem(0).
Prefer all() and an index check, or exists() first.
What does Entity::save() return?
void. Success and failure go through the callbacks you pass. Do not if (Entity::save()).
Should validation be HTTP 500?
No. Validation is HTTP 200 + status 0 on the channel, or 422 on public Response::json.
500 is for unexpected exceptions you logged.
insert_id missing after execute?
Cache hit empties $execution_data. Use ??. Do not cache writes.
Auth::login returned false
Malformed input (missing password, both email and username, wrong stage). Do not read $login['error'] until you know it is an array.
Authentication and 2FA.
See also
- Request lifecycle in DotApp PHP Framework
- Controllers and Response in DotApp PHP Framework
- How to use the database in DotApp PHP Framework
- How to build JSON endpoints with Router in DotApp PHP Framework
- Events and listeners in DotApp PHP Framework
- How authentication and 2FA work in DotApp PHP Framework
- Official documentation