Repository navigation
API Routing
Caligrafy Quill 2.26.0. Generated from the source by
php qa/build-api-reference.php; the guides explain how to use these classes, this reference says exactly what each public method takes and returns. Back to the API reference.
Declaring requests and reading them: routes, the request, forms, downloads, webhooks, web-only actions, errors and flash messages.
| Class | What it is |
|---|---|
Caligrafy\Route |
Class that builds the routing logic of the framework |
Caligrafy\AuthRoutes |
Route::auth() (after Auth::authentication('User', 'users') in web.php) registers, with the views of the authentication module (application/views/Auth/login.pug and register.pug) |
Caligrafy\Request |
(no doc comment) |
Caligrafy\Web |
A capability declared #[Capability(..., webOnly: true)] and served by Route::action() may return one of these instead of plain data, to choose what the browser gets |
Caligrafy\Action |
Route::action('/connect', 'cloud_connect') answers a request with a capability declared webOnly: true, for what a page or a form cannot say: a sign-in between two sites, a session that decides where to go, a gate, a ... |
Caligrafy\Form |
Route::form('/books', 'create_book', ['success' => '/books/{id}', 'flash' => 'Book added']) answers the form's POST (or its PUT / DELETE through _method) with a capability, without a controller: 1. the CSRF token of the ... |
Caligrafy\Download |
Route::download('/books.csv', 'list_books', ['as' => 'csv', 'filename' => 'books.csv']) answers a GET with the result of a read-only capability as an attachment, without a controller: - inputs: the query string and the ... |
Caligrafy\Webhook |
Route::webhook('/stripe/events', 'record_payment', ['verify' => 'stripe']) answers the POSTs of another service with a capability, without a controller: - no CSRF token (the sender has no session): the signature of the ... |
Caligrafy\Uploads |
A capability declares a file input with the rule file, an optional allow-list and an optional maximum size |
Caligrafy\Throttle |
The counts are kept in the Cache (CACHE_STORE: the caligrafy_cache table or a private folder, shared by every session, so that a new cookie does not start again), or in the session when the cache is off. |
Caligrafy\Errors |
The Errors Class is the dictionary of all the error messages in the system |
Caligrafy\Flash |
Route::form() puts the field errors, the old input and a message here before it redirects; the page of the next request reads them with the view helpers errors($field), old($field) and flash($key), then they are gone. |
Caligrafy\Context |
Built by the channel adapters (web pages, REST, MCP, CLI) and read by the services: a service never looks at the request, the session or the headers itself. |
Class that builds the routing logic of the framework
Its 30 public methods have a page of their own: Caligrafy\Route.
Route::auth() (after Auth::authentication('User', 'users') in web.php) registers, with the views of the authentication module (application/views/Auth/login.pug and register.pug)
Its 15 public methods have a page of their own: Caligrafy\AuthRoutes.
namespace Caligrafy;
class Request extends \stdClassSource: framework/core/Request.php:6
No doc comment on this class.
-
public $uriParameters: array uriParameters defines the uri request parameters -
public $method: array method fetches the http request method -
public $uri: string uri defines the uri of the request without query parameters -
public $fullUri: string fulluri defines the full uri of the request includng the query parameters -
public $uriComponents: array uriComponents defines a list of all the elements of the uri -
public $host: string host reads the host information as taken from the headers of the request -
public $authorization: string host reads the host information as taken from the headers of the request -
public $cache: array cache has all the headers information of the request -
public $userAgent: array userAgent has all the headers information of the request -
public $accept: array accept has all the headers information of the request -
public $acceptEncoding: array acceptEncoding has all the headers information of the request -
public $acceptLanguage: array acceptLanguage has all the headers information of the request -
public $cookie: array cookie has all the headers information of the request -
public $parsedURI: array parsedURI has all the headers information of the request -
public $currentRoute: array currentRoute has all the headers information of the request -
public $currentBind: array currentBind has all the headers information of the request -
public $fetch: array fetch has all the headers information of the request -
public $parameters: array fetches all the request parameters -
public $token: array|null the personal access token of the request, once authorizeApiRequest() or Context::fromRequest() checked it
->__construct()->authorizeApiRequest()->authorizeSessionRequest()->apiRefusal()->bearer()->isLegacyApiKey()Request::legacyKeysDisabled()->wantsJson()->fetch()->forClient()->fullUri()->parameters()->files()
public function __construct()No doc comment.
Source: framework/core/Request.php:129
public function authorizeApiRequest($scope = '*')Checks if API requests are authorized
- a personal access token (Bearer cq_...) that grants $scope; the controller routes of web.php ask for '*', so they accept the tokens that grant everything, as the legacy key did
- the legacy API key (any key generated for this APP_KEY), unless LEGACY_API_KEY=false
Parameters
-
$scopestring, default'*': the scope the route needs
Source: framework/core/Request.php:162
public function authorizeSessionRequest()Whether a JSON call comes from a page of this application: it carries the session's CSRF token in an X-CSRF-Token header (or _token), which only pages of this origin can read. The controller then sees the signed-in user, or a visitor, exactly as for a form post
Source: framework/core/Request.php:184
public function apiRefusal()Why a JSON call to a controller route was refused, for the [ERROR 1004] message
Source: framework/core/Request.php:195
public function bearer()The bearer value of the Authorization header (null when there is none)
Source: framework/core/Request.php:216
public function isLegacyApiKey($key)Whether a value is a legacy API key of this project (encrypts APP_KEY), and legacy keys are allowed
Parameters
$key
Source: framework/core/Request.php:224
public static function legacyKeysDisabled()LEGACY_API_KEY=false in .env turns the legacy API key off (tokens only)
Source: framework/core/Request.php:236
public function wantsJson()Checks if request needs a Json as a way to recognize API calls
Source: framework/core/Request.php:245
public function fetch($argument)Fetches all the arguments and parameters of a request call
Parameters
-
$argument: defines the argument looked up
Source: framework/core/Request.php:264
public function forClient()What a client app page may see of the request (env.request of the Client/app view, 2.19.1): the address, the method, the route and its bound values, and the query string. Never the Cookie (the session id) nor the Authorization header, nor any other header that can carry a credential. A POST body is left out too: only the query string of the address is given
Returns array
Source: framework/core/Request.php:283
public function fullUri()Fetches the full uri a request
Source: framework/core/Request.php:306
public function parameters()Fetches the parameters of a request
Source: framework/core/Request.php:314
public function files($fileAttribute)Fetches the files uploaded with a request
Parameters
$fileAttribute
Source: framework/core/Request.php:324
namespace Caligrafy;
class WebSource: framework/core/Web.php:22
Class Web (2.21.0) A capability declared #[Capability(..., webOnly: true)] and served by Route::action() may return one of these instead of plain data, to choose what the browser gets:
return Web::view('Books/show', ['book' => $book]);
return Web::view('Books/missing', ['message' => 'No such book'], 404);
return Web::redirect('/books');
return Web::redirect('https://checkout.stripe.com/...', 303);
return Web::json(['enrolled' => true], 201);
return Web::text('ok')->header('Cache-Control', 'no-store');
public static function view($view, array $data = array(), $status = 200)A view, given who is signed in as Route::page gives it ('authorized', 'user'); $data wins
Parameters
$view-
$dataarray, defaultarray() -
$status, default200
Source: framework/core/Web.php:39
public static function redirect($to, $status = 302)A path of this site or a full http(s) address
Parameters
$to-
$status, default302
Source: framework/core/Web.php:45
public static function json($data, $status = 200)No doc comment.
Parameters
$data-
$status, default200
Source: framework/core/Web.php:50
public static function text($body = '', $status = 200)No doc comment.
Parameters
-
$body, default'' -
$status, default200
Source: framework/core/Web.php:55
public function header($name, $value)One more response header
Parameters
$name$value
Source: framework/core/Web.php:61
public function describe()What it answers, for tests: ['type', 'target', 'data', 'status', 'headers']
Source: framework/core/Web.php:68
public function send()Answers the request
Source: framework/core/Web.php:74
namespace Caligrafy;
class ActionSource: framework/core/Action.php:27
Class Action (2.21.0) Route::action('/connect', 'cloud_connect') answers a request with a capability declared webOnly: true, for what a page or a form cannot say: a sign-in between two sites, a session that decides where to go, a gate, a payment page.
- access: the 'signedIn', 'level', 'comeBack' and 'visitorsOnly' options of Route::page
- the capability is called with the query string or the form, and the route variables (which win), as the person who sent it, with its rules, permissions and audit log like any capability
- its answer goes to the browser: a Caligrafy\Web (a view, a redirect, JSON, text), or JSON of the plain data A JSON call needs the page's X-CSRF-Token or an API token, signed in or not ([ERROR 1004], 401)
- a refusal it does not answer itself is treated as in Route::form: invalid input or a conflict goes back with errors(), old() and flash('error'); a visitor goes to sign in; forbidden is 403, anything else 404. JSON requests get the REST error. Unlike every other capability, it may read the session and the request and may sign people in, and a GET may change data: so a webOnly capability is reachable through Route::action only, never through REST, MCP, the JS client, Route::page, Route::form or a controller.
public static function handle($capability, array $options = array(), $request = null)No doc comment.
Parameters
$capability-
$optionsarray, defaultarray() -
$request, defaultnull
Source: framework/core/Action.php:29
public static function answer($answer)Sends what the capability answered
Parameters
$answer
Source: framework/core/Action.php:64
namespace Caligrafy;
class FormSource: framework/core/Form.php:29
Class Form (2.19.0) Route::form('/books', 'create_book', ['success' => '/books/{id}', 'flash' => 'Book added']) answers the form's POST (or its PUT / DELETE through _method) with a capability, without a controller:
- the CSRF token of the form is checked by the request, as for every form (error 3000, 403)
- the capability is called with the form fields, the files of its
fileinputs (Uploads) and the route variables (which win), as the person who sent the form (Context::fromRequest(..., 'web')) - success: a redirect to 'success' (a path whose {placeholders} are filled from the result, then from the route variables, or a callable($result) returning the path; without it, back to the form), with the 'flash' message (also with placeholders) for flash('success')
- invalid input (or a conflict): back to the form ('back', else the Referer of this site, else /) with the field errors, the message and the old input for the next request: errors($field), old($field), flash('error')
- a visitor where the capability needs someone: to the sign-in page ('signIn', default /login) when Auth is active, else 403; forbidden: 403 (error 1006); not found: 404 (error 1005) A JSON request (Accept: application/json) gets the REST answer instead of redirects: {"data": ...} with 200 (201 for a POST that changes data), or {"error": ...} with 401, 403, 404, 409 or 422. Sent with the session cookie, it needs the X-CSRF-Token header, as REST.
-
SECRET = '/pass|secret|token|card|cvc|cvv|iban/i': fields never kept as old input
Form::handle()Form::environment()Form::outcome()Form::problems()Form::withServerInputs()Form::perform()Form::fill()Form::back()Form::sameSite()Form::oldInput()Form::refused()
public static function handle($capability, array $options = array(), $request = null)Answers the current request (the route's closure)
Parameters
$capability-
$optionsarray, defaultarray() -
$request, defaultnull
Source: framework/core/Form.php:37
public static function environment($request)What outcome() and refused() need to know about the request
Parameters
$request
Source: framework/core/Form.php:47
public static function outcome(
$capability,
array $options,
array $input,
Context $context,
$json,
array $env = array()
)What a form post leads to, without answering it (testable without a server). Flash data is written to the session.
Parameters
$capability-
$optionsarray -
$inputarray -
$contextContext $json-
$envarray, defaultarray(): referer, host, route (route variables), bearer (bool), csrf (bool: X-CSRF-Token is the session's), method, uploads (Uploads|null)
Returns array: ['redirect' => url] | ['status' => int, 'json' => array] | ['error' => 1005|1006]
Source: framework/core/Form.php:67
public static function problems(array $options, array $input)What the 'same' and 'accepted' options refuse (2.20.0): [field => message]
'same' => ['password_again' => ['password', 'The two passwords are not the same']]
'accepted' => ['terms' => 'Accept the terms of use to create an account']
Parameters
-
$optionsarray -
$inputarray
Source: framework/core/Form.php:121
public static function withServerInputs(array $options, array $input, array $route = array())The inputs the server sets itself (2.20.0, 'inputs': an array, or a callable($input) returning one): they replace what the form sent. The route variables still win. They are not kept for old().
Parameters
-
$optionsarray -
$inputarray -
$routearray, defaultarray()
Source: framework/core/Form.php:144
public static function perform(array $outcome)Answers an outcome
Parameters
-
$outcomearray
Source: framework/core/Form.php:174
public static function fill($template, array $values, $encode = true)Fills {name} placeholders: Form::fill('/books/{id}', ['id' => 3]) is /books/3
Parameters
-
$template -
$valuesarray -
$encodebool, defaulttrue: url-encode the values (paths) -
@throws \LogicException when a placeholder has no value
Source: framework/core/Form.php:199
public static function back(array $options, array $env)Where "back" is: the 'back' option (with the route variables), else the Referer when it is a page of this site, else the home page
Parameters
-
$optionsarray -
$envarray
Source: framework/core/Form.php:214
public static function sameSite($url, $host)Whether a URL is a page of this host (host and port): never redirect elsewhere from a Referer
Parameters
$url$host
Source: framework/core/Form.php:229
public static function oldInput(array $input)The input kept for old(): no secrets, no files, no framework fields
Parameters
-
$inputarray
Source: framework/core/Form.php:242
public static function refused(ServiceError $e, array $options, array $input, $json, array $env)No doc comment.
Parameters
-
$eServiceError -
$optionsarray -
$inputarray $json-
$envarray
Source: framework/core/Form.php:256
namespace Caligrafy;
class DownloadSource: framework/core/Download.php:32
Class Download (2.19.0) Route::download('/books.csv', 'list_books', ['as' => 'csv', 'filename' => 'books.csv']) answers a GET with the result of a read-only capability as an attachment, without a controller:
- inputs: the query string and the route variables (which win), as the person who opens the link (Context::fromRequest(..., 'web')); the capability must be readOnly (a link must not change data)
- 'as' => 'csv' (default): RFC 4180 (CRLF, fields with a comma, a quote or a line break in double quotes, quotes doubled), UTF-8, the header row from the first row's keys; 'bom' => true starts the file with a UTF-8 BOM (Excel); text cells that start with = + - @ or a tab are prefixed with ' so that spreadsheets do not run them as formulas ('formulas' => true keeps them as they are); arrays in a cell are written as JSON, booleans as true / false. 'as' => 'json': the result, pretty-printed
- 'as' => 'zip' (2.22.0): the files the capability lists, in one ZIP archive streamed from a temporary file. Each item is a path (the file keeps its name) or ['file' => path, 'name' => 'folder/name in the archive']; a page of paginate() gives its rows. Paths are relative to 'root', a folder of the project (default 'public/uploads'): a file outside it, or missing, is left out, so a link never reaches other files. Duplicate names get " (2)". Images, video, audio and archives are stored as they are, other files compressed. No file at all: 404 (error 1005). Needs the PHP zip extension (in Caligrafy's Docker images and on Caligrafy Cloud)
- 'filename': a name, or a callable($result, $input) returning one (default: the capability's name)
- refusals as Route::page: a visitor is sent to sign in when Auth is active, else 403 (error 1006); forbidden 403; anything else 404 (error 1005)
FORMATS = array('csv', 'json', 'zip')STORED = ...
Download::handle()Download::columns()Download::format()Download::root()Download::files()Download::entryName()Download::archive()Download::rows()Download::csvLines()Download::csv()Download::cell()Download::filename()Download::disposition()
public static function handle($capability, array $options = array(), $request = null)Answers the current request (the route's closure)
Parameters
$capability-
$optionsarray, defaultarray() -
$request, defaultnull
Source: framework/core/Download.php:42
public static function columns(array $options)The 'columns' option (2.19.5): a list of column names, or null for the keys of the first row
Parameters
-
$optionsarray
Source: framework/core/Download.php:99
public static function format(array $options)No doc comment.
Parameters
-
$optionsarray
Source: framework/core/Download.php:111
public static function root(array $options)The 'root' option of a ZIP (2.22.0): a folder of the project, 'public/uploads' by default, as an absolute path (null when it does not exist)
Parameters
-
$optionsarray
Source: framework/core/Download.php:127
public static function files($result, $root)The files of a result for a ZIP: [absolute path => name in the archive], in order. Paths outside the root or that are not files are left out; names are cleaned (no .., no leading /) and made unique
Parameters
$result$root
Source: framework/core/Download.php:142
public static function entryName($name)A name inside a ZIP: folders kept, without empty parts, . or .., control characters or a leading /
Parameters
$name
Source: framework/core/Download.php:173
public static function archive($result, array $options = array())The ZIP of a result (2.22.0): a temporary file the caller removes, or null when no file can go in it. Usable on its own, in a test: ['path' => ..., 'count' => ..., 'names' => [...]]
Parameters
$result-
$optionsarray, defaultarray()
Source: framework/core/Download.php:189
public static function rows($result)The rows of a result: a list of rows as it is, one row (an associative array) as a list of one, a list of values as rows of one 'value' column
Parameters
$result
Source: framework/core/Download.php:247
public static function csvLines(array $rows, $bom = false, $escapeFormulas = true, ?array $columns = null)The lines of a CSV file (each ends with CRLF): the header row from the first row's keys, then one line per row in that order (a missing column is empty, an extra one left out)
Parameters
-
$rowsarray -
$bom, defaultfalse -
$escapeFormulas, defaulttrue -
$columns?array, defaultnull
Source: framework/core/Download.php:270
public static function csv(array $rows, $bom = false, $escapeFormulas = true, ?array $columns = null)A whole CSV file
Parameters
-
$rowsarray -
$bom, defaultfalse -
$escapeFormulas, defaulttrue -
$columns?array, defaultnull
Source: framework/core/Download.php:293
public static function cell($value, $escapeFormulas = true)One field of a CSV line, quoted when it must be (RFC 4180)
Parameters
$value-
$escapeFormulas, defaulttrue
Source: framework/core/Download.php:301
public static function filename($option, $result, array $input, $default)The file name: the option, or what its callable returns, without a path, quotes or control characters
Parameters
$option$result-
$inputarray $default
Source: framework/core/Download.php:319
public static function disposition($filename)Content-Disposition: attachment, with an ASCII name and the UTF-8 one (RFC 6266)
Parameters
$filename
Source: framework/core/Download.php:330
namespace Caligrafy;
class WebhookSource: framework/core/Webhook.php:24
Class Webhook (2.19.0) Route::webhook('/stripe/events', 'record_payment', ['verify' => 'stripe']) answers the POSTs of another service with a capability, without a controller:
- no CSRF token (the sender has no session): the signature of the body proves who sent it. 'verify' is required: 'stripe' checks the Stripe-Signature header with \Stripe\Webhook::constructEvent and the secret in the variable 'secret_env' names (default STRIPE_WEBHOOK_SECRET), with 'tolerance' seconds (default 300); a callable function (string $rawBody, array $headers): bool checks anything else (headers with lower-case names)
- the raw body (php://input) must be JSON; the capability gets ['event' => the decoded body], as the system (Context::system('webhook', $path)): declare public $rules = ['event' => 'required']
- answers: 200 {"received": true}; 400 for a bad signature or a body that is not JSON; the status of a ServiceError; 500 for anything else (written to the PHP error log)
-
public static $logger = null: callable|null replaces error_log() (tests)
public static function handle($path, $capability, array $options)Answers the current request (the route's closure)
Parameters
$path$capability-
$optionsarray
Source: framework/core/Webhook.php:32
public static function process($path, $capability, array $options, $raw, array $headers)What a webhook post leads to, without answering it (testable without a server)
Parameters
$path$capability-
$optionsarray $raw-
$headersarray
Returns array: [status, body]
Source: framework/core/Webhook.php:47
public static function verify($raw, array $headers, array $options)Whether the body comes from the sender
Parameters
$raw-
$headersarray: lower-case names -
$optionsarray
Returns true|string: true, or why not
- @throws \LogicException when the webhook is not set up (no secret, unknown 'verify')
Source: framework/core/Webhook.php:84
namespace Caligrafy;
class UploadsSource: framework/core/Uploads.php:28
Class Uploads (2.19.0)
A capability declares a file input with the rule file, an optional allow-list and an optional maximum size:
public $rules = ['cover' => 'required|file|extension,png;jpg|max_size,2mb'];
- web pages (Route::form) and REST (Route::capability, Route::capabilities) take the field from $_FILES once the caller is allowed to call the capability, check it (one file, the allow-list - never an executable type, see isAllowedUpload() - and the size), save it in public/uploads under a new random name with uploadFile() (the name the client sent is never used) and give handle() the saved file's public URL, a string. A value sent in that field without a file is dropped. The saved file is deleted when the call fails afterwards.
- MCP and the command line cannot send files: the field is a URL or a path string; its extension is checked against the allow-list.
The file rules (file, extension, max_size, required_file) are never given to GUMP: Service::validate() strips them.
A field with required_file (or required) and no file is invalid: 'The <Field> field needs a file' (2.19.1).
Without extension the allow-list is DEFAULT_UPLOAD_EXTENSIONS.
RULES = array('file', 'extension', 'max_size', 'required_file')
->__construct()Uploads::fields()Uploads::parse()Uploads::required()Uploads::strip()Uploads::bytes()Uploads::allowed()Uploads::check()Uploads::checkString()->apply()->discard()->saved()
public function __construct(?array $files = null, ?callable $store = null, ?callable $delete = null)No doc comment.
Parameters
-
$files?array, defaultnull -
$store?callable, defaultnull -
$delete?callable, defaultnull
Source: framework/core/Uploads.php:44
public static function fields(array $rules)The file inputs of a set of rules
Parameters
-
$rulesarray
Returns array: field => ['extensions' => [..] or null, 'max' => bytes or null]
Source: framework/core/Uploads.php:55
public static function parse($rule)The file part of one rule: null when the rule has no file
Parameters
$rule
Source: framework/core/Uploads.php:70
public static function required($rule)Whether a file input must be sent: its rule has required_file, or required (2.19.1)
Parameters
$rule
Source: framework/core/Uploads.php:95
public static function strip(array $rules)Rules without their file parts, for GUMP
Parameters
-
$rulesarray
Source: framework/core/Uploads.php:111
public static function bytes($size)'2mb', '500kb', '1.5MB', '1g' or a number of bytes
Parameters
$size
Source: framework/core/Uploads.php:132
public static function allowed(array $spec)The allow-list of a file input (never an executable type: isAllowedUpload() refuses those anyway)
Parameters
-
$specarray
Source: framework/core/Uploads.php:144
public static function check($file, array $spec)What is wrong with an uploaded file (an entry of $_FILES), or null
Parameters
$file-
$specarray
Source: framework/core/Uploads.php:152
public static function checkString($value, array $spec)What is wrong with a file input sent as a string (MCP, the command line), or null
Parameters
$value-
$specarray
Source: framework/core/Uploads.php:177
public function apply(array $input, array $rules)Replaces the file inputs of $input with the saved files of the request (web and REST)
Parameters
-
$inputarray: the inputs -
$rulesarray: the capability's rules
Returns array: the inputs, with the public URL of each saved file
- @throws ServiceError invalid, with a message per file field
Source: framework/core/Uploads.php:196
public function discard()Deletes the files apply() saved (the call failed afterwards)
Source: framework/core/Uploads.php:231
public function saved()The URLs apply() saved
Source: framework/core/Uploads.php:242
namespace Caligrafy;
class ThrottleSource: framework/core/Throttle.php:19
Class Throttle (2.19.0)
$throttle = new Throttle(5, 900); // 5 failures within 15 minutes lock the key for the rest of them
if ($throttle->locked($key)) ...; $throttle->fail($key); $throttle->clear($key);
The counts are kept in the Cache (CACHE_STORE: the caligrafy_cache table or a private folder, shared by every session, so that a new cookie does not start again), or in the session when the cache is off. Each failure keeps the count for $seconds more.
-
public $max: int -
public $seconds: int
public function __construct($max = 5, $seconds = 900, ?array $store = null)No doc comment.
Parameters
-
$max, default5 -
$seconds, default900 -
$store?array, defaultnull
Source: framework/core/Throttle.php:30
public static function key($scope, $ip, $username)A key for a person at an address: the IP and the username (lower case), hashed
Parameters
$scope$ip$username
Source: framework/core/Throttle.php:40
public function limitedTo($max)The same counts and window with another limit (2.19.6: Route::auth's attemptsPerUser)
Parameters
$max
Source: framework/core/Throttle.php:48
public function locked($key)Whether the key failed $max times within the window
Parameters
$key
Source: framework/core/Throttle.php:58
public function failures($key)The failures of the key within the window
Parameters
$key
Source: framework/core/Throttle.php:66
public function fail($key)Counts one failure; returns the failures of the key
Parameters
$key
Source: framework/core/Throttle.php:75
public function clear($key)Forgets the failures of the key (a success)
Parameters
$key
Source: framework/core/Throttle.php:85
namespace Caligrafy;
class Errors extends \stdClassSource: framework/core/Errors.php:18
The Errors Class is the dictionary of all the error messages in the system
GENERIC_MESSAGE = '[ERROR 500] Internal Server Error'
public static function status($errorCode)Get the HTTP status code of an error code
Parameters
-
$errorCodeint: identifies the error code
Returns int: HTTP status code (500 when the error code is unknown)
Source: framework/core/Errors.php:78
public static function raise($errorCode, $detail = null)Creates the exception of a framework error and sets its HTTP status code right away, so that the status is correct even when the exception is caught by an application that only echoes the message
Parameters
-
$errorCodeint: identifies the error code -
$detailstring|null, defaultnull: why, added to the message: '[ERROR 1004] Access Denied: <detail>'
Returns \Exception: to be thrown
Source: framework/core/Errors.php:89
public static function handle(\Throwable $e, $hideUnknown = false)Sends an error response: HTTP status code and the error text (HTML requests) or {"error": {"code": ..., "message": ...}} (requests that want JSON) Framework errors keep their message. Other exceptions caught by the application keep theirs too unless $hideUnknown is true (uncaught exceptions), in which case the message is only shown when APP_DEBUG is true
Parameters
-
$e\Throwable: the error -
$hideUnknownbool, defaultfalse: hides the message of errors that are not framework errors unless APP_DEBUG is true
Source: framework/core/Errors.php:105
public static function shutdown()Shutdown function: turns PHP fatal errors into a 500 response (message only when APP_DEBUG is true)
Source: framework/core/Errors.php:121
public static function get($errorCode)Get error message based on error code
Parameters
-
$errorCodestring: identifies the error code
Returns string: identifying the error message
Source: framework/core/Errors.php:169
public static function set($errorMessage, $errorCode)No doc comment.
Parameters
$errorMessage$errorCode
Source: framework/core/Errors.php:173
namespace Caligrafy;
class FlashSource: framework/core/Flash.php:20
Class Flash (2.19.0) Route::form() puts the field errors, the old input and a message here before it redirects; the page of the next request reads them with the view helpers errors($field), old($field) and flash($key), then they are gone. Route::run() ages them when there are some: what the previous request put becomes this request's, and leaves the session (without Route::run, the first read does).
Kept in $_SESSION['__flash'] as ['errors' => [field => message], 'old' => [field => value], 'messages' => [key => text]].
KEY = '__flash'
public static function age()Moves what the previous request put into this request (Route::run calls it once)
Source: framework/core/Flash.php:30
public static function put($key, $value)Keeps a value for the next request
Parameters
-
$keystring: errors, old or messages $value
Source: framework/core/Flash.php:42
public static function message($key, $text)Keeps a message for the next request: Flash::message('success', 'Book added')
Parameters
$key$text
Source: framework/core/Flash.php:56
public static function get($key, $default = null)What the previous request put (errors, old or messages)
Parameters
$key-
$default, defaultnull
Source: framework/core/Flash.php:66
public static function reset()Forgets what this request read (tests)
Source: framework/core/Flash.php:77
namespace Caligrafy;
class ContextSource: framework/core/Context.php:23
Class Context Built by the channel adapters (web pages, REST, MCP, CLI) and read by the services: a service never looks at the request, the session or the headers itself.
The actor is one of:
- user: a signed-in user (session), or the owner of a token
- token: a personal access token (Tokens), with its scopes
- key: the legacy API key (every generated key is the same key: full access, as before 2.1)
- cli: a script or the command line (full access); a scheduled job is a system context on this channel
- guest: nobody: only public capabilities
CHANNELS = array('web', 'rest', 'mcp', 'cli')
-
public static $levelAttribute = 'permissions': Name of the users column that holds the permission level compared with a capability's level (Auth::guard) -
public $channel: string web, rest, mcp or cli -
public $viaAction = false: bool the call comes from Route::action() (2.21.0): the only way to a webOnly capability -
public $user: object|null the user model (signed in, or the owner of the token) -
public $token: array|null the token row (id, name, scopes, user_id...)
->__construct()Context::cli()Context::local()Context::system()Context::guest()Context::forToken()Context::fromRequest()->authenticated()->allows()->level()->userId()->actorType()->actorId()
public function __construct($channel = 'cli', $user = null, $token = null, $full = false)No doc comment.
Parameters
-
$channel, default'cli' -
$user, defaultnull -
$token, defaultnull -
$full, defaultfalse
Source: framework/core/Context.php:53
public static function cli()The context of the command line and of scripts: full access
Source: framework/core/Context.php:67
public static function local($channel)Full access through another channel for a local process (php caligrafer.php mcp without a token)
Parameters
$channel
Source: framework/core/Context.php:75
public static function system($type, $name)Full access for work the system does on its own, such as a scheduled job: the audit log records the actor type (schedule) and its name (the job's)
Parameters
-
$typestring: the actor type, e.g. schedule -
$namestring: the actor id, e.g. the job's name
Source: framework/core/Context.php:88
public static function guest($channel = 'rest')The context of nobody: only public capabilities
Parameters
-
$channel, default'rest'
Source: framework/core/Context.php:99
public static function forToken(array $token, $channel = 'rest')The context of a personal access token: its scopes, and its owner when it has one and authentication is on
Parameters
-
$tokenarray -
$channel, default'rest'
Source: framework/core/Context.php:107
public static function fromRequest(Request $request, $channel = 'rest')The context of the current HTTP request: a token (Authorization: Bearer cq_...), else the legacy API key, else the signed-in user, else a guest
Parameters
-
$requestRequest: the request -
$channelstring, default'rest': web, rest or mcp
Source: framework/core/Context.php:122
public function authenticated()Whether someone is acting (anything but a guest)
Source: framework/core/Context.php:150
public function allows($scope, $level = 0)Whether this context may use a capability that needs a scope and a minimum user level
- the CLI and the legacy key: always
- a token: it must grant the scope, and its owner (if any) must have the level
- a signed-in user: the level (users have every scope their level allows)
Parameters
-
$scopestring: e.g. books:write ('' = no scope needed) -
$levelint, default0: minimum value of the users permission column (0 = any signed-in user)
Source: framework/core/Context.php:163
public function level()The permission level of the user (0 when there is none)
Source: framework/core/Context.php:186
public function userId()The user id, when a user acts (signed in or owner of the token)
Source: framework/core/Context.php:195
public function actorType()user, token, key, cli or guest (as written to the audit log)
Source: framework/core/Context.php:203
public function actorId()The token id or the user id, as a string (null for the CLI, the legacy key and guests)
Source: framework/core/Context.php:220
Caligrafy: Describe it. Approve it. Ship it. Your idea turned into a complete app: every feature built once, for web pages, Vue apps, a REST API and AI agents, and online.
Why Caligrafy: the benefits, the measurements, and what it is not yet
-
Quickstart with Claude: describe it, approve it, ship it
or the Quickstart: the same three steps by hand, in 5 minutes
- How Caligrafy works: the ideas in 5 minutes
- Your first capability: build a feature, page included
-
Capabilities in depth
-
#[Capability]: every option - Scheduled jobs: capabilities on a timer
- Notifications: web push and email
- Background jobs: capabilities run later
- Migrations: database changes, in order
- The cache
-
Testing your app
- Dependency injection
- Validation rules
-
Web pages
- Views and page components
- Design styles: another look, one setting
- Declarative routes: every request, in one line
-
Route::page·Route::form·Route::auth·Route::webhook·Route::download·Route::redirect·Route::action·Route::snap· capability routes - How to export a list as CSV
- Controllers: moving one to a declared route · how to, step by step
- Forms and helpers
-
Vue apps
-
Mobile and tablet
-
REST API
-
AI agents
-
Snap Cards and the assistant
- Payments: Stripe and crypto
- Face detection and face login
- OpenAI: chat and images · Assistants
- Open models: Llama, Mistral...
- Chatbots with Watson
- Machine learning in the browser
- Metadata and social cards · search and analytics
-
Putting it online: the production image, the database at every start
-
API reference: every class and method
-
Routing ·
Route·AuthRoutes· pages ·Controller· capabilities -
Data ·
Model·Query· sign-in · jobs ·Notify· payments · AI -
Interfaces · Snap ·
Snap\Cards·Snap\Conversation· testing · tooling · helper functions
-
Routing ·
-
Caligrafer: every command
- Project · build · data · agents · jobs and tests · maintenance · deploy
- Installation, configuration and Caligrafer
- Routing · Request · Controllers · Views
- Vue: flow of information · routes · validations
- Security and upgrading
- Release notes: 2.26 · 2.25 · 2.24 · 2.23 · 2.22 · 2.21 · 2.20 · 2.19 · 2.18 · 2.17 · 2.16 · 2.15 · 2.14 · 2.13 · 2.12 · 2.11 · 2.10 · 2.9 · 2.8 · 2.7 · 2.6 · 2.5 · 2.4 · 2.3 · 2.1 · October 2026 · changelog
- Testing Caligrafy (contributors)