These files are templates, not live config — copy the ones you need into your
App config root (the directory you pass to
(new BuildDomainKernelFromEnv())($configPath)) and strip the .example
suffix. BuildDomainKernelFromEnv loads that directory via
JardisSupport\DotEnv\DotEnv::loadPrivate() — the same load() / load?()
cascade every other Jardis config file understands (required includes throw
EnvFileNotFoundException if missing; load?() includes are silently
skipped).
Content revised from the Builder's former SharedRuntime/ ENV generat
(tools/builder tests/Builder/Generated/Domain/SharedRuntime, read-only
source for this revision) — that generat used stale, unread ENV keys
(DB_WRITER_*, CACHE_REDIS_ENABLED, …, and a Messaging section this
kernel does not build). The keys below are the ones the eleven
Bootstrap/Handler/* closures actually read:
ls src/Bootstrap/Handler/*.php → 11 files (ten before
BuildConnectionPoolConfigFromEnv joined for the DB_POOL_* keys). That
this now numerically matches the packed DomainKernel's accessor count
(grep -cE "public function [a-z]" src/DomainKernel.php → 11) is
coincidence — a different layer (the Koffer's read side) from the
Bootstrap-Packer's build side documented here. Don't conflate the two
counts.
| Template | Feeds | Required to activate |
|---|---|---|
.env.example |
cascade root — load()s the others |
copy first, adjust load?() list to what you use |
.env.database.example |
BuildConnectionFromEnv (incl. the DB_POOL_* keys its BuildConnectionPoolConfigFromEnv sub-closure reads) |
DB_HOST (or DB_DRIVER=sqlite) |
.env.redis.example |
BuildRedisFromEnv — shared by cache + logger (fan-out, D4) |
REDIS_HOST |
.env.cache.example |
BuildCacheFromEnv (requires jardisadapter/cache) |
CACHE_LAYERS |
.env.logger.example |
BuildLoggerFromEnv (requires jardisadapter/logger) |
LOG_HANDLERS |
.env.http.example |
BuildHttpClientFromEnv (requires jardisadapter/http) |
none — works with zero config once the adapter is installed |
.env.mail.example |
BuildMailerFromEnv (requires jardisadapter/mailer) |
MAIL_HOST |
Four of the eleven Bootstrap handlers need no ENV at all and have no template
here: BuildFilesystemFromEnv (stateless factory) and
BuildEventListenerProviderFromEnv / BuildEventDispatcherFromProvider (pure
in-memory pair, D3) activate purely by the corresponding adapter package
being installed (jardisadapter/filesystem, jardisadapter/eventdispatcher);
ExtractPdoFromConnection is the fourth — see "No ENV at all" below for why
it's a different case (not adapter-gated, a pure data-transform closure).
Every value here is optional — an unconfigured service resolves to null on
the packed DomainKernel (see "Two states, not three" below). See
README.md (package root) section "DomainKernel — the Koffer" for the packed
accessor list.
Every key any Bootstrap/Handler/* closure reads, grouped by handler. This is
the doc list AC A6 diffs against the source — see "Verifying this list" below.
| Key | Default | Notes |
|---|---|---|
DB_DRIVER |
mysql |
mysql | pgsql | sqlite |
DB_HOST |
— | required for mysql/pgsql; unset ⇒ null (no connection) unless DB_DRIVER=sqlite |
DB_PORT |
3306 (5432 for pgsql) |
|
DB_USER |
root |
|
DB_PASSWORD |
'' |
|
DB_DATABASE |
'' |
|
DB_CHARSET |
utf8mb4 (utf8 for pgsql) |
|
DB_PATH |
:memory: |
read only when DB_DRIVER=sqlite — overrides the host/port/user block above for that driver |
DB_READER{N}_HOST |
— | numbered read replica (DB_READER1_HOST, DB_READER2_HOST, …); presence of _HOST is what registers reader N |
DB_READER{N}_PORT |
writer's DB_PORT |
falls back to the writer value if unset |
DB_READER{N}_USER |
writer's DB_USER |
falls back to the writer value if unset |
DB_READER{N}_PASSWORD |
writer's DB_PASSWORD |
falls back to the writer value if unset |
DB_READER{N}_DATABASE |
writer's DB_DATABASE |
falls back to the writer value if unset |
DB_POOL_VALIDATE_CONNECTIONS |
adapter default (true) |
pool only; string comparison — only the literal true is true |
DB_POOL_HEALTH_CHECK_CACHE_TTL |
adapter default (30) |
pool only; seconds |
DB_POOL_HEALTH_CHECK_NEGATIVE_CACHE_TTL |
adapter default (0) |
pool only; seconds, 0 = no negative caching |
DB_POOL_LOAD_BALANCING_STRATEGY |
adapter default (round-robin) |
pool only; round-robin | random |
DB_POOL_STICKY_WRITER |
adapter default (false) |
pool only; stickyWriterDuringTransaction — requires jardisadapter/dbconnection >= 1.1.0; on older versions the pool build fails into the plain-PDO fallback (error_log notice) |
At least one DB_READER{N}_HOST plus jardisadapter/dbconnection installed
(class_exists(ConnectionPool::class)) builds a ConnectionPool; otherwise a
plain PDO on DB_HOST is returned. The five DB_POOL_* keys are read by
BuildConnectionPoolConfigFromEnv and only take effect on the pool branch:
setting none of them keeps the two-argument ConnectionPool construction
(adapter ConnectionPoolConfig defaults, behaviour unchanged); setting any
builds an explicit config in which only the set keys deviate from those
defaults. Code: src/Bootstrap/Handler/BuildConnectionFromEnv.php (reader
family in findReaders, DB_PATH in buildSqlite) and
src/Bootstrap/Handler/BuildConnectionPoolConfigFromEnv.php.
| Key | Default | Notes |
|---|---|---|
REDIS_HOST |
— | unset ⇒ null (no Redis connection at all) |
REDIS_PORT |
6379 |
|
REDIS_PASSWORD |
— | AUTH only sent if set and non-empty |
REDIS_DATABASE |
— | SELECT only sent if set |
One connection feeds both the cache redis layer and the logger redis
handler (fan-out, D4) — there is no separate per-consumer Redis config. Code:
src/Bootstrap/Handler/BuildRedisFromEnv.php:23-50.
| Key | Default | Notes |
|---|---|---|
CACHE_NAMESPACE |
null |
|
CACHE_LAYERS |
— (empty list) | comma-separated, tried in order; unknown names are skipped, not an error — see CacheLayer below |
CACHE_DB_TABLE |
cache |
only read for the db layer |
Code: src/Bootstrap/Handler/BuildCacheFromEnv.php:43-65.
| Key | Default | Notes |
|---|---|---|
LOG_HANDLERS |
— | unset ⇒ null (no logger at all); comma-separated handler[:LEVEL] list — see LogHandler below |
LOG_CONTEXT |
app |
|
LOG_LEVEL |
INFO |
fallback level when an entry in LOG_HANDLERS omits :LEVEL |
LOG_FILE_PATH |
/var/log/app.log |
only read for the file handler |
LOG_SLACK_URL |
— | only read for the slack handler; unset/empty ⇒ handler skipped, no error |
LOG_TEAMS_URL |
— | same pattern, teams handler |
LOG_LOKI_URL |
— | same pattern, loki handler |
LOG_WEBHOOK_URL |
— | same pattern, webhook handler |
Code: src/Bootstrap/Handler/BuildLoggerFromEnv.php:30-103.
| Key | Default | Notes |
|---|---|---|
HTTP_TIMEOUT |
30 |
|
HTTP_CONNECT_TIMEOUT |
10 |
|
HTTP_BASE_URL |
null |
|
HTTP_VERIFY_SSL |
true |
string comparison — anything other than the literal true is treated as false |
HTTP_BEARER_TOKEN |
null |
|
HTTP_BASIC_USER |
null |
|
HTTP_BASIC_PASSWORD |
null |
|
HTTP_MAX_RETRIES |
0 |
not 3 — see §3.7 of the project PRD; 0 is the correct, unchanged default ported from foundation |
HTTP_RETRY_DELAY_MS |
100 |
No key here is required — the client builds with zero config once
jardisadapter/http is installed. Code:
src/Bootstrap/Handler/BuildHttpClientFromEnv.php:24-58.
| Key | Default | Notes |
|---|---|---|
MAIL_HOST |
— | unset ⇒ null (no mailer at all) |
MAIL_PORT |
587 |
|
MAIL_ENCRYPTION |
tls |
|
MAIL_USERNAME |
null |
|
MAIL_PASSWORD |
null |
|
MAIL_TIMEOUT |
30 |
|
MAIL_FROM_ADDRESS |
null |
|
MAIL_FROM_NAME |
null |
Code: src/Bootstrap/Handler/BuildMailerFromEnv.php:24-51.
Four of the eleven Bootstrap/Handler/* closures read no ENV key at all.
BuildFilesystemFromEnv, BuildEventListenerProviderFromEnv and
BuildEventDispatcherFromProvider activate purely on the matching adapter
package being installed (class_exists(...)). ExtractPdoFromConnection
(src/Bootstrap/Handler/ExtractPdoFromConnection.php:20-27) is the fourth,
and a different kind of case: it isn't an ENV-to-service builder at all, but
a pure data-transform closure — it extracts a plain PDO handle from the
connection BuildConnectionFromEnv already built, feeding BuildCacheFromEnv's
db layer regardless of whether the domain runs on a bare connection or a
ConnectionPool. No ENV key belongs to it because there is nothing for it to
read; the ENV it depends on was already consumed upstream by
BuildConnectionFromEnv.
grep -rhoE "env\('[a-z0-9_]+'\)" src/Bootstrap/Handler/*.php | sed -E "s/.*'([a-z0-9_]+)'.*/\1/"
grep -rhoE 'env\("db_reader\{\$i\}_[a-z]+"\)' src/Bootstrap/Handler/*.php | sed -E 's/.*"(.*)".*/\1/' | sed 's/{\$i}/{N}/'
grep -rhoE "prefix \. '[a-z0-9_]+'" src/Bootstrap/Handler/*.php | sed -E "s/.*'([a-z0-9_]+)'.*/\1/" | sed 's/^/redis_/'
grep -rhoE "'log_[a-z_]+_url'" src/Bootstrap/Handler/*.php | tr -d "'"Run from core/kernel (each line piped through tr 'a-z' 'A-Z' | sort -u,
50 total, and compared against the tables above). Deliberately drop the
leading $ from every pattern above (env( / prefix ., not $env( /
$prefix .) — inside a double-quoted bash string, \$ is consumed by
bash's own escaping before grep ever sees it (a single backslash yields a
bare, unescaped $ — which GNU grep's ERE then fails to match literally
outside the pattern's true end), and this file's code fences are exactly the
double-quoted strings a copy-pasting reader would run. env(/prefix . are
distinctive enough substrings on their own; no $, no escaping puzzle, no
silent zero-result. Separately: use grep -r, never find | xargs grep -c
— the latter drops the filename prefix whenever exactly one file matches,
which an awk -F:-style summation then silently reads as zero.
CACHE_LAYERS and LOG_HANDLERS entries are validated against two enums
under src/Bootstrap/Data/ — neither is referenced by the "Files" table
above, and neither appeared anywhere in this doc before this revision:
CacheLayer(src/Bootstrap/Data/CacheLayer.php:13-19) —memory|apcu|redis|db. Read byBuildCacheFromEnv(src/Bootstrap/Handler/BuildCacheFromEnv.php:54); an unrecognized name is skipped, not an error.LogHandler(src/Bootstrap/Data/LogHandler.php:13-26) —file|console|errorlog|syslog|browserconsole|redis|slack|teams|loki|webhook|null. Read byBuildLoggerFromEnv(src/Bootstrap/Handler/BuildLoggerFromEnv.php:43); same skip-on-unknown behaviour.
jardiscore/foundation's JardisApp accessors returned
Interface|false|null — three states, e.g.
protected function cache(): CacheInterface|false|null (archived,
src/JardisApp.php:48, same pattern on lines 38/61/66/71/76/81 for the other
six services). The base hook these overrode documented false explicitly as
"explicitly disable [this service] for this DomainApp, ignore [the]
shared [fallback]" (archived vendored copy,
vendor/jardiscore/kernel/src/DomainApp.php:204-330).
That third state is removed, not deprecated. Every
Bootstrap/Handler/* closure in this package and every DomainKernel
accessor returns ?Interface — two states only, an instance or null.
Belege: DomainKernel::eventDispatcher(): ?EventDispatcherInterface
(src/DomainKernel.php:81-84) and identically-shaped signatures for
cache() (:71-74), logger() (:76-79), eventListenerRegistry()
(:86-89), httpClient() (:91-94), mailer() (:101-104), filesystem()
(:106-109); dbConnection() (:96-99) is
ConnectionPoolInterface|PDO|null — same two-state shape, PDO/pool
replacing the single interface type. BuildEventDispatcherFromProvider
(src/Bootstrap/Handler/BuildEventDispatcherFromProvider.php:24-31) is
representative of every handler: it returns ?EventDispatcherInterface,
never false.
There is no ENV key or mechanism in core/kernel to force a service "off"
while a shared fallback stays reachable elsewhere — the only way a service
resolves to null is that it was never configured (no matching ENV key, or
the adapter package not installed). Any doc that still describes a
three-state Instance|false|null contract for a pre-decoupling kernel accessor describes
removed foundation-era behaviour, not this package.
The only key in this Bootstrap-Packer's ENV landscape with App-, not
Kernel-semantics. core/kernel neither reads nor validates it — like any
other key, it only becomes visible through
DomainKernel::env('app_debug') (src/DomainKernel.php:60-64), the same
private-ENV-then-$_ENV lookup every other key here goes through. There is
no Bootstrap/Handler/* closure for it, and it feeds nothing on the packed
DomainKernel. Its meaning — gating the generic-vs-detailed error response —
is defined and documented by jardiscore/app (core/app/docs/getting-started.md,
AppConfig's debug flag); not repeated here.
DB_DRIVER=sqlite
DB_PATH=:memory:
LOG_HANDLERS=console
LOG_CONTEXT=app
LOG_LEVEL=DEBUG