Configuration
On this page
Introduction
Configuration has two layers. Values that differ per machine (a database password, the debug switch) live in the environment or in .env. Everything else lives in PHP files under config/, which read the environment where they need to. Your code reads only config(), so there is one place to look for any setting.
Environment variables
.env holds this machine's values, one per line:
APP_NAME=Orders
APP_DEBUG=true
DB_DRIVER=mysql
DB_DATABASE=orders
DB_USERNAME=root
DB_PASSWORD=
Read one with env(), giving the value to use when it is not set:
// config/app.php
return [
'name' => env('APP_NAME', 'Crab'),
'debug' => env('APP_DEBUG', false),
];
- The real environment wins over
.env, so a container or a deploy tool can set values without a file. - The words
true,falseandnullbecome those values; everything else is text. - The file may be absent, as it is in production when the environment is set another way.
[!WARNING] Call
env()only inside configuration files. Everywhere else readconfig(). Two sources for one setting is how a value ends up different in two places.
Configuration files
Every PHP file in config/ returns an array, and is keyed by its file name:
// config/mail.php
return [
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => (int) env('MAIL_PORT', 25),
'from' => ['address' => '[email protected]', 'name' => 'Orders'],
];
To add a setting of your own, add a key to a file, or add a file. There is nothing to register.
A framework section works without a file, from its defaults. To see every setting of one and change it, write its complete file, each setting at its default with what it does:
php cua config:publish # lists the sections
php cua config:publish server queue
# Created config/server.php
# Created config/queue.php
An existing file is left alone; --force replaces it, losing what it set.
Reading configuration
| Setting | Default | Use |
|---|---|---|
async, on a database connection |
false |
Use the nonblocking MySQL driver inside workers; console commands and SQLite use PDO. See Database. |
config() reads a value by its file name and key, with dots between the levels:
config('mail.host'); // "127.0.0.1"
config('mail.from.address'); // "[email protected]"
config('mail.timeout', 10); // 10, because the key is not set
It works anywhere after the application is created. In a class, you may prefer to take the values in the constructor and read config() in the plugin that makes it, which keeps the class usable without the framework:
// app/Setup.php
$container->singleton(Mailer::class, static fn (): Mailer => new SmtpMailer(
config('mail.host'),
config('mail.port'),
));
The core and plugins declare their defaults before your application runs, so config() exposes the same values their consumers use. Values you supply, including explicit null and false, take precedence.
Configuration is fixed once the application is defined. Do not change it at run time: every worker has its own copy, and a change in one is seen by no other.
Checked at startup
Every framework setting says what it takes, and the application checks all of them before it serves or runs workers. A typo or a wrong value stops php cua start with every problem at once, each naming the setting and what it takes, instead of failing on the first request that reads it:
$ php cua start
3 settings do not fit:
log.level is "verbose"; it takes "debug", "info", "notice", "warning", "error", "critical", "alert" or "emergency".
database.connections.main.port is "70000"; it takes a whole number from 1 to 65535 or null.
cache.stroe is not a setting; cache takes store, directory, table, database, prefix and ttl. Did you mean cache.store?
The same check runs for reload and restart before they touch the running server, so a deploy with a broken setting leaves the old server serving. queue:work and schedule:work run it too, and a component checks its own section when its service is first made, so a console command or a test meets the same message. php cua describe lists the problems under the application's name, with any service that cannot be made.
What the check takes:
- A string of digits counts as a whole number and a numeric string as a number, since that is what
env()gives:SERVER_WORKERS=4is fine.true,false,0and1count as a switch. - A database connection's keys may be
null, whichenv()gives for a variable that is not set, and means the same as leaving the key out. - A framework section takes no keys other than its own, so a misspelt key is reported rather than ignored.
appis the exception: put the application's own settings there, or in a file of their own, which the check leaves alone. - A value named like a secret (
password,secret,token,key) is never repeated in a message, nor a password inside a URL.
A plugin of your own declares what its settings take the same way; see Settings with defaults.
Describing the application
app.php describes the application as one chain and returns it. php cua runs it and your tests load it.
// app.php
use App\Setup;
use Crab\App;
use Crab\Http\Cors;
return App::configure(__DIR__)
->withSetup(Setup::class)
->withMiddleware(prepend: [new Cors(['https://app.example.com'])])
->withRouting(
routes: 'routes/web.php',
commands: 'routes/console.php',
health: '/up',
)
->create();
Every step is optional and they may come in any order. Nothing happens until create(), which loads the environment, then the configuration, makes the application, registers the plugins and the middleware, and then includes the route and command files.
App::configure(__DIR__) begins the chain with two steps on your behalf, withEnv('.env') and withConfig('config', required: false). The core framework plugins are also registered by default; see Core and optional plugins. Call either file step yourself to name another file or folder:
use Crab\App;
return App::configure(__DIR__)
->withEnv('.env.testing')
->withConfig('settings')
->create();
Plugins
App::configure() starts with the framework's plugins, App::PLUGINS, in this order: DatabasePlugin, AuthPlugin, CachePlugin, EventsPlugin, QueuePlugin, SchedulePlugin, HttpClientPlugin, BroadcastPlugin. Add your own and your packages' plugins with withPlugins(), as one plugin or a list, by class name or as objects. They come after the framework's, in the order given, so a plugin that listens to events or adds scheduled tasks finds them already bound.
To leave out a core plugin or replace the core list, use one of these two forms:
// app.php — choose one
use Crab\Cache\CachePlugin;
use Crab\Database\DatabasePlugin;
->withPlugins(remove: [CachePlugin::class]) // without one of the core plugins
->withPlugins(only: [DatabasePlugin::class]) // these in place of the core plugins
remove: accepts only classes in App::PLUGINS and names the list when you give another. only: [] leaves out all core plugins. only: and remove: cannot be combined, even across calls. Plugins given as the first argument are still added after the replacement list. Calling withPlugins() again adds plugins; another only: replaces the previous replacement.
A framework plugin named again, by class or as an object, is registered once at its first place. TokenAuthPlugin and every package, including crabphp/redis and crabphp/clockwerk, stay opt-in. Nothing is found by convention. new App() used directly registers no plugins.
See Services and plugins.
Middleware
withMiddleware() sets what runs around every request. See Middleware.
Routes and commands
routes: and commands: each take one file or a list of files. A route file declares with Route::, a command file with Command::add():
->withRouting(
routes: ['routes/web.php', 'routes/admin.php'],
commands: 'routes/console.php',
)
A health check
health: '/up' makes GET /up answer 200 and {"status":"up"}, for a load balancer or a deploy script that needs to know the server takes requests.
It says that this worker answers, and asks nothing of the database. That is deliberate: a slow database should not take every server out of rotation at once. To check more, leave health: out and route the path to an action of your own.
The application's own settings
| Setting | Default | |
|---|---|---|
app.name |
Crab |
Shown above the command list and when the server starts |
app.debug |
false |
Show a failure's details to the client. Never in production. |
app.path |
the folder given to App::configure() |
Where the application is; read it, do not set it here |
app.storage |
storage in the application's folder |
Where the application writes, as withStorage() or CRAB_STORAGE put it; read it, do not set it here. See Storage elsewhere |
app.url |
none | Where the application is reached, such as https://api.example.com; the start of every URL Urls writes. See Named routes |
auth.ticket_ttl |
30 |
Positive whole seconds a single-use browser socket ticket lives; see Authentication |
log.path |
php://stderr |
Where the log is written; see Logging |
log.level |
debug |
The least severe level written |
server.address |
tcp://127.0.0.1:8080 |
Where the server listens |
server.workers |
1 |
Worker processes |
server.watch |
app, app.php, config, routes, .env |
What dev watches |
server.max_connections |
512 |
Total HTTP and upgraded connections per worker; at most 900 on the select backend |
server.loop |
select |
select, ev, uv or event; a native backend needs its PHP extension |
server.max_body |
1048576 |
The largest request body taken, in bytes |
server.body_timeout |
30 |
Seconds a client has to send a body |
server.drain |
5.0 |
Seconds an HTTP worker has to finish in-flight requests on stop, reload or restart; unfinished requests at the limit are cut and counted in one warning per worker. Idle keep-alive connections do not count. Must be a finite number greater than zero |
server.pid |
storage/crab.pid |
The process id of a server in the background |
server.log |
storage/logs/server.log |
What a server in the background prints |
Each component lists its own settings on its page. Broadcasting lists broadcast.carrier and channel limits; choose Redis when multiple processes send updates. WebSockets lists the socket limits; when socket routes are declared, websocket.max_connections must not exceed server.max_connections.