queue:work, Horizon). Every request starts from the state the application has right after its boot, the same as a new PHP-FPM request.
Installation
php artisan optimize and reload the workers after every deploy, the deploy script does both:
localizedRoutes() work on Octane and with route:cache, the routes are registered once for every language.
What CrudAdmin resets between requests
Under PHP-FPM every request is a new process. An Octane worker boots the application once and serves thousands of requests, a queue worker does the same with jobs. Before every request, task, tick and queue job (except jobs of thesync connection) CrudAdmin returns its state to the state after the boot:
Admin::cache(),Admin::get(),Admin::set(),Admin::push()and thecache()of classes using theDataStoretrait keep values for one request or job. Only the registry of admin models, derived from the code and the config, is kept for the worker.- Base fields and relations trees of the models are kept for the worker, for every language, the same entries
php artisan admin:cachecompiles: built without the administrator, without options of the database. A model is built once per worker and language instead of once per request. - Laravel properties the model boots from its fields (
fillable,casts,dates,hidden) are kept for the worker as well, outside the administration, for every language and for the website and the console separately. - Runtime mutations, options of fields, fields of models reading the logged administrator, fields of a given row, the properties of models booted in the administration or on the website, and the admin scopes added to model classes are built again for every request.
- Static properties marked with
#[RequestScoped]get their default values back. Static flags changed during a request are returned:AdminFile::$isApi,AdminModel::$localizedResponseArray,setGlobalPublishable(),setGlobalModelPublishable(), the fields context of the administrator. - The gettext locale and text domain return to the values after the boot, then the localization of the request sets them again.
- php.ini options changed with
RequestState::iniSet()get their previous values back. - Queue workers forget resolved facades before every job, as Octane does before every request.
Rules for project code
Admin::cache()is for one request. Do not use a static property, astatic $varor a singleton for data of a user, a request, a language or the database, it would be kept for the next requests of the worker.- A service provider boots once for the whole worker, with a fake console request. Code in
boot()which reads the request (request(), headers, the URL,Admin::isAdmin(),Admin::isFrontend()) runs in a middleware, in aRouteMatchedlistener or throughRequestState::bootRequestUsing():
app/Providers/AppServiceProvider.php
- Config files are loaded once for the worker. A config file must not read the request (
request(),$_SERVER), set such values per request withconfig()->set(); the config is copied for every request. fields(),mutateFields()andmutateBootedFields()of models and modules and the field mutations build the same fields for every request in the same language. They read only the code, the config after the boot and the translations. Everything which depends on the logged administrator, the request, the row or the database belongs into the processing which runs for every request: a runtime mutation for the administrator,getFields($row)for a row, options loaded withwithOptions(). A config value changed during a request does not reach fields kept by the worker.- A callback registered with
cachableFieldsProperties()which reads the request returns a differentgetCachablePropertiesVariant()for each case, CrudAdmin models returnadmin,weborconsole. A moduleboot()runs once per worker and variant, not for every request. - Mark a static property which lives for one request with
#[RequestScoped]. Before every request and job it gets back the default value of its declaration, reading it stays as fast as any static:
- A static property configured by the service providers (a builder, global options) which a request may still change is marked with
#[RequestScoped(boot: true)]. It gets back its value after the boot, a change made during a request lives for that request only. - Mark a static property which is the same for every request with
#[RequestShared]: a registry filled inboot(), a cache derived only from the code and the config. A value nobody changes is a class constant, not a static property. Every static property carries one of the two attributes,php artisan admin:upgrade:staticsofcrudadmin/upgradelists the ones without them inapp/and in the crudadmin packages. Astatic $varinside a function can not carry an attribute, move it into a marked static property. - State which the default value can not describe (an object, a setting of PHP or of an extension) is reset by a callback registered with
RequestState::flushUsing():
- Facades of services bound with
bind()are resolved again for every request. Settings set on such a facade inboot()are kept only by services which store them statically, for examplePaymentService::setPaymentUrl(),Discounts::addDiscount()orOrderService::setMutators()of the CrudAdmin packages. - Change php.ini options of a request with
RequestState::iniSet('memory_limit', '1G')instead ofini_set(). - Do not call
exit,dieordd()in code reachable by a request, they end the worker.autoAjax()->throw()throws an exception which Laravel renders. - Read the request through Laravel (
request()->getContent(),request()->header()), not through$_SERVER,$_GET,$_POSTorphp://input. RoadRunner and Swoole do not fill them. - Do not add CrudAdmin services to
octane.warm:admin,localization,localization.admin,admin.payments.service,store,cartand bootstrap request classes hold the state of one request. - Enable
CollectGarbage::classamong theOperationTerminatedlisteners ofconfig/octane.phpand setgarbageto about 20 MB above the memory of an idle worker (80 for most CrudAdmin projects). Every request clones the application, the clones are reference cycles which PHP frees only when its garbage collector runs. Without the listener they pile up over many requests and a worker with the defaultmemory_limitof 128 MB fails on an ordinary request. A collection takes under 2 ms. - Long imports and exports belong to the queue. Octane stops a request after
max_execution_timeofconfig/octane.php,ini_set()does not change it.
Checking a project
Start one worker, so every request goes through the same process, and use the application as two different users or in two languages:max-requests points to data kept in a static property or a singleton. Memory which goes up and down by a few MB is garbage waiting for the collector. --once lists the workers only once.