Configuration¶
Two independent pieces: loading a .env file into the real process
environment, and typed access to whatever’s in it.
.env loading¶
APP_ENV=production
DB_HOST=db.internal
DB_PORT=3306
DEBUG=false
A missing .env is not an error — loading it does nothing and startup
continues. A real environment variable that is already set always wins
over the file, so a checked-in .env.example copied to .env locally
cannot override a production secret set through Docker, systemd, or a
secrets manager. See Appendix: Configuration Reference for exactly which
entry points load it, and when, relative to environment detection.
Both are read once, at boot: a persistent worker builds Config from
them when it starts and never rereads either afterward, so a changed
.env value or environment variable takes effect only once the worker
restarts — a new deploy, a supervisor restart, or equivalent.
Note
No AppEnvironment check gates this — .env loading runs in every
environment. Shared hosting and FPM-only deployments often give you file
access and no way to set a real process environment variable at all. For
that shape of deployment .env is the way to configure the application
in production, not a development-only convenience.
Typed config access¶
use Kinetis\Config\Config;
use Kinetis\Http\Attributes\Get;
final readonly class OrderController
{
public function __construct(
private Config $config,
) {}
#[Get('/orders')]
public function index(): array
{
return [
'host' => $this->config->string('DB_HOST', 'localhost'),
'debug' => $this->config->bool('DEBUG', false),
];
}
}
Kinetis\Config\Config is a snapshot of the environment taken once, not
live getenv() calls scattered through business logic. Environment
variables are worker-lifetime configuration, not per-request state.
Method |
Returns |
|---|---|
|
The raw value, or |
|
Same as |
|
The value as |
|
The value as |
|
The value as |
|
The value as |
|
The raw value, or throws |
The four parsing accessors — int(), intOrNull(), float(), bool()
— treat an explicitly empty value (DB_PORT=) the same as an unset one
and fall back to the default. That is the convention a named-connection
setting uses to turn itself off. Anything else that doesn’t parse, or
doesn’t fit, throws Kinetis\Config\Exception\InvalidConfigValueException
rather than taking whatever a lossy cast would produce.
get(), string() and required() do not parse, and so do not apply
that rule: they return an empty value as the empty string. DB_PASSWORD=
satisfies required('DB_PASSWORD') and yields ''. Only an absent key
throws.
int()/intOrNull() accept an optional sign followed by decimal digits
only — leading zeroes ("007") are fine, but a fraction, an exponent,
surrounding whitespace, an alternate base ("0x1A"), or a grouping
separator ("1_000") all throw, as does anything outside the platform’s
representable integer range (a huge digit string that would otherwise
saturate toward PHP_INT_MAX/PHP_INT_MIN).
float() accepts ordinary decimal notation ("5", "5.", ".5",
"5.5") with an optional scientific-notation exponent ("5e3",
"5.5e-3"); a leading +, leading zeroes, and both a leading and a
trailing dot are accepted. Beyond syntax, two cases are checked rather
than left to a plain (float) cast: an exponent large enough to overflow
to infinity throws instead of becoming INF, and a nonzero value whose
magnitude underflows to exactly 0.0 ("1e-400") throws too, since once
cast it is indistinguishable from a configured zero. A zero mantissa
("0e-400") returns 0.0.
bool() accepts "true"/"false", "1"/"0", "on"/"off", and
"yes"/"no" (case-insensitively) and rejects anything else, rather
than letting an unrecognized value like "purple" become false.
Config validates syntax and representable range. It has no idea whether
a key means a TCP port, a positive duration, or a ratio between 0 and 1.
That domain knowledge belongs to whichever factory or middleware reads
the key: kinetis/database-bridge’s ConnectionFactory rejects a
DB_PORT outside 1–65535,
FormLimits rejects a non-positive MAX_BODY_SIZE, TracerFactory
rejects an OTEL_TRACES_SAMPLER_ARG outside 0–1, and so on — each with
an InvalidArgumentException naming the key, rather than clamping into
range. Appendix: Configuration Reference records each key’s own bound.
intOrNull() exists for config that means something different when it is
absent than when it is zero — DB_CONNECT_TIMEOUT means “no timeout at
all” only when unset, not 0 standing in for it.
required() is for config with no sane default. A database password
absent from the environment fails at construction rather than proceeding
as an empty string and failing somewhere less obvious later:
$password = $this->config->required('DB_PASSWORD');
// throws Kinetis\Config\Exception\MissingConfigException if the key is absent
Named connections¶
Redis, a SQL database, and a few other storage technologies can each be
configured more than once under a name, alongside the usual unnamed
default connection — see Appendix: Configuration Reference’s “Named
connections” for the naming convention, every selector key it applies
to, and how to register one from bootstrap.php.
Resolving Config¶
Config is injected automatically: AppScope::boot() registers one
shared instance — Config::fromEnvironment() — unless you’ve already
registered your own, and it resolves anywhere via constructor injection,
including through RequestScope, which delegates to that same
AppScope-registered instance (see Appendix: Container Lifecycle) the same as any
other service you never explicitly registered on RequestScope itself.
See Appendix: Configuration Reference for overriding the default
registration and for why .env/Config sit outside the AOT cache, and
Bootstrapping for registering an application service more
generally.
See also¶
Bootstrapping — registering an application service or global middleware from
bootstrap.php, and the package/application boot order.Appendix: Container Lifecycle —
AppScope’s registration-lock discipline, and howRequestScopedelegates to aConfigit never explicitly registered itself.Appendix: Configuration Reference — every key Kinetis and its packages read, grouped by subsystem, the named-connection naming algorithm, and why
.envsits outside the AOT cache.Caching & AOT Compilation — the AOT cache Kinetis builds for production.
Appendix: System Layout — the
Kinetis\Confignamespace in the full system map.