Prejsť na obsah

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

Must: match the library

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