Skip to content
docsv0.34.0

AdminSettings

What the app declared about its admin panel, read once from the `admin` key of its config bag — and, per key, whether it declared it, the panel is running on a default, or the panel refused it. Every knob has a default that keeps a fresh app both safe and served: the panel lives at `/milpa/admin`, speaks English, answers only to loopback, and signs component state with a secret derived from this package unless the app declares one (`admin.secret`, else `live.secret`). Declaring is the whole interface — nothing here is read from the environment. The gate is the one knob the panel judges instead of copying. The rule: **only a literally empty list `[]` opens the panel**. Anything else that is not a list of strings naming a PSR-15 middleware class — a non-string entry, an associative map, a value that is not a list at all, an empty string, a class that does not exist, a class that exists but is not a middleware — is a misdeclaration, and the effective stack is the STRICT gate ({@see LoopbackOnlyMiddleware}) and nothing else: never «open», never the half that loads. The panel says so in Settings and in the topbar (greenhouse decisions/0204). Falling to strict keeps the panel safe; dying with a 500 would hide the cause.

AdminSettings::__construct()

public function __construct(string $route = self::DEFAULT_ROUTE, string $locale = self::DEFAULT_LOCALE, array $middleware = [], string $secret = '', string $title = self::DEFAULT_TITLE, array $sources = [], bool $declared = false, ?string $secretSource = null, array $rejected = []):

Parameters

Parameters of __construct()
NameTypeDescription
$routestringthe panel's mount point, an absolute local path
$localestringthe language of the panel's own copy — one the {@see Catalog} carries
$middlewarearray<mixed>what the app DECLARED under `admin.middleware` when it declared an array: every entry as written, non-strings included, keys included when it was a map; `[]` when it declared no array at all — {@see self::malformed()} tells that apart from an empty list. See {@see self::effectiveMiddleware()} for what the routes get
$secretstringthe HMAC secret that signs component state envelopes; empty derives one
$titlestringthe brand shown in the sidebar and the document title
$sourcesarray<string, string>per key, `config` when the app declared the value the panel is using; anything else, or a key left out, is `default`
$declaredboolwhether the `admin` key exists in the app's config at all
$secretSource(string | null)where the secret came from (`declared:admin.secret`, `declared:live.secret`); null reads it from `$secret` — a declared secret is `admin.secret`'s, an empty one is derived
$rejectedarray<string, string>per key, what the app declared and the panel refused, described (the value for a string, the type otherwise) — a key listed here is `rejected` whatever `$sources` says

AdminSettings::fromConfig()

public static function fromConfig(?Milpa\Runtime\Config $config): self

Reads the `admin.*` keys, falling back to the defaults for anything the app did not declare — and remembering, per key, which of three things happened: declared and used (`config`), left out (`default`), or declared and refused (`rejected` — the default runs, and the row says what was written). A missing config bag (the plugin booted without a kernel, as in unit tests) yields the defaults. A key set to null is not a declaration. A route that is a bare slash, a locale the catalog lacks, an empty title, a value of the wrong type — those the app DID write, so they are `rejected`, never painted `default`. The middleware is kept exactly as declared; judging it is {@see self::effectiveMiddleware()}'s.

Parameters

Parameters of fromConfig()
NameTypeDescription
$config?Milpa\Runtime\Config

AdminSettings::declared()

public function declared(): bool

True when the `admin` key exists in the app's config — false means the panel read nothing under it.

AdminSettings::sources()

public function sources(): array

Per key, `config` when the app declared the value the panel is using, `default` when it did not, `rejected` when it declared one the panel refused — the default runs, and {@see self::rejected()} says what was written.

Returns

`route`, `locale`, `middleware`, `secret`, `title` → `default` | `config` | `rejected`

AdminSettings::rejected()

public function rejected(): array

What the app declared and the panel refused, per key, described: the value for a string (`fr`, `/`, {@see self::EMPTY}), the type for anything else (`int`, `bool`, `array`).

Returns

only the rejected keys

AdminSettings::secretSource()

public function secretSource(): string

Where the signing secret came from — never the secret itself, not even a fragment.

Returns

`declared:admin.secret` | `declared:live.secret` | `derived`

AdminSettings::malformed()

public function malformed(): bool

True when `admin.middleware` was declared as something other than a list: a string, a bool, an int, an associative map. The declaration cannot be read entry by entry, so it is refused whole.

AdminSettings::unresolvedMiddleware()

public function unresolvedMiddleware(): array

Every reason the declared gate cannot be carried, for a human — empty when it can. One entry per declared entry that fails, described: `Acme\Nope (class does not exist)`, `stdClass (not a PSR-15 middleware)`, `int (not a class name)`, `(empty)`. A declaration that is not a list at all yields one entry naming what was received: `string (not a list)`.

AdminSettings::effectiveMiddleware()

public function effectiveMiddleware(): array

The middleware the panel's routes actually carry. The declared stack only when every entry is a string naming a class that exists and implements {@see MiddlewareInterface} — a literally empty list included: the app opened the panel on purpose. Anything else replaces the WHOLE stack with the strict gate: a gate with a hole in it is not a gate, and mixing the half that loads with a silent fallback would hide which half is running.

AdminSettings::gateKind()

public function gateKind(): string

What kind of gate the panel is behind. The topbar chip says {@see self::gateLabel()}, which is this except for the one custom stack it knows by name.

Returns

`loopback` (the strict default, declared or not) | `custom` (the app's own stack) | `open` (a literally empty list, on purpose) | `fallback` (a misdeclared stack fell to loopback-only)

AdminSettings::gateLabel()

public function gateLabel(): string

The gate as the panel names it — {@see self::gateKind()}, except that a stack that is exactly app-runtime's passkey gate (that one class, loadable, alone) is named `passkey`, not `custom`. A presentation rule over the kind, nothing more: the kind stays `custom` and the routes carry the class as declared; a passkey gate that cannot be loaded is a `fallback` like any other.

Returns

`loopback` | `custom` | `passkey` | `open` | `fallback`

AdminSettings::middlewareDefect()

public static function middlewareDefect(?mixed $entry): ?string

Why one declared middleware entry cannot be a panel gate, described for a human — or null when the runtime can load it: a non-empty string naming a class that exists and is a PSR-15 middleware.

Parameters

Parameters of middlewareDefect()
NameTypeDescription
$entry?mixed

AdminSettings::sectionUrl()

public function sectionUrl(string $id): string

The URL of one section of the panel.

Parameters

Parameters of sectionUrl()
NameTypeDescription
$idstring

AdminSettings::assetUrl()

public function assetUrl(string $file): string

The URL of one of the panel's own assets (CSS, client runtime).

Parameters

Parameters of assetUrl()
NameTypeDescription
$filestring

AdminSettings::liveUrl()

public function liveUrl(): string

The URL of the panel's live wire — `POST {route}/live`, the one endpoint every component of the page takes its actions through, host's and guests' alike (greenhouse decisions/0211). It carries the same middleware stack as every other panel route: a wire outside the gate would be a hole.

AdminSettings::composeUrl()

public function composeUrl(): string

The URL of the compose file the Stack section projects — every declared service, `text/yaml`.

AdminSettings::signingSecret()

public function signingSecret(): string

The signing secret, derived when the app declared none — stable per install, never empty.