Skip to content

15C: Scenario suite — the host's own wiring - #30

Open
nikolaystrikhar wants to merge 1 commit into
15B-scenario-conflictfrom
15C-scenario-host
Open

15C: Scenario suite — the host's own wiring#30
nikolaystrikhar wants to merge 1 commit into
15B-scenario-conflictfrom
15C-scenario-host

Conversation

@nikolaystrikhar

Copy link
Copy Markdown
Contributor

What: three scenarios about the host rather than about a sub-plugin — that a boot() arriving after plugins_loaded priority 5 still loads, and that a host's own implementations are the objects the request reaches, across seven seams (registrar, plugin checker, plugin deactivator, notice writer, activator, gatekeeper and resolver) — plus Spy_Gatekeeper.

Usage: where a host may bind, and when.

$container->singleton( Resolver_Interface::class, static fn() => $resolver );

$this->boot( $container );

// A concrete class id has to wait until after boot().
$container->singleton( Gatekeeper::class, static fn() => $gatekeeper );

$this->run_request();

Why this way:

An interface binds before boot, a concrete class after it. Provider::bind_once() skips only when ! class_exists( $id ) && $container->has( $id ) — and di52 answers has() true for any class that exists whether or not anything was bound, so the provider cannot see a host's binding for a class and replaces it. Binding after boot() works because Boot\Scheduler wires closures that resolve when the hook fires rather than objects built at boot, so a host may rebind right up until plugins_loaded. ProviderTest::host_bindings() states the same rule from the provider's side; these scenarios show both halves of it in a request.

Each binding scenario asserts the defaults did not run. active_plugins still holds the standalone, and both options are unwritten. On their own the positive assertions would hold just as well if the library had also resolved a second, default copy behind the host's back — which is the failure they exist to catch.

Spy_Gatekeeper counts the two gates separately. They are asked at different moments: the request gate turns away everything that is not an interactive admin GET, and the capability gate is asked only once a conflict is known to exist. One counter would read a request that never passed the first gate as one that passed both.

The binding scenario runs two requests. A DEACTIVATE resolution ends in a redirect and exit, so the load pass belongs to the request after it — one request would be asserting the load happened where production never reached it.

Three scenarios about the host rather than about a sub-plugin: that a boot
arriving too late still loads, and that a host's own implementations are the
objects the request really reaches.

The two binding scenarios show both halves of a rule that is easy to get wrong.
An interface id is bound before boot() and the provider leaves it alone, because
nothing can build an interface unprompted and has() is therefore true only where
a binding exists. A concrete class id has to be bound after boot(), because di52
answers has() true for any class that exists whether or not anything was bound,
so the provider cannot see the host's binding and replaces it. Binding after
boot works because Boot\Scheduler wires closures that resolve when the hook
fires rather than objects built at boot, which is the property that makes the
late binding reach the request at all. ProviderTest states the same rule from
the provider's side.

Each binding scenario also asserts the defaults did *not* run: active_plugins
still holds the standalone, and both options are unwritten. Without that, a
library that resolved a second, default copy behind the host's back would
satisfy every positive assertion in these tests.

Spy_Gatekeeper counts the two gates separately, because they are asked at
different moments -- the request gate turns away everything that is not an
interactive admin GET, and the capability gate is asked only once a conflict is
known to exist. One counter would read a request that never passed the first
gate as one that passed both.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant