15C: Scenario suite — the host's own wiring - #30
Open
nikolaystrikhar wants to merge 1 commit into
Open
Conversation
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.
Closed
nikolaystrikhar
force-pushed
the
15B-scenario-conflict
branch
from
August 13, 2026 11:16
0013b7e to
6a7022b
Compare
nikolaystrikhar
force-pushed
the
15C-scenario-host
branch
from
August 13, 2026 11:16
b6f2e36 to
248662e
Compare
nikolaystrikhar
force-pushed
the
15B-scenario-conflict
branch
from
August 13, 2026 11:17
6a7022b to
30b454d
Compare
nikolaystrikhar
force-pushed
the
15C-scenario-host
branch
from
August 13, 2026 11:17
248662e to
7ff3864
Compare
nikolaystrikhar
force-pushed
the
15B-scenario-conflict
branch
from
August 13, 2026 11:35
30b454d to
fe25fdd
Compare
nikolaystrikhar
force-pushed
the
15C-scenario-host
branch
from
August 13, 2026 11:35
7ff3864 to
bd5341d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What: three scenarios about the host rather than about a sub-plugin — that a
boot()arriving afterplugins_loadedpriority 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) — plusSpy_Gatekeeper.Usage: where a host may bind, and when.
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 answershas()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 afterboot()works becauseBoot\Schedulerwires closures that resolve when the hook fires rather than objects built at boot, so a host may rebind right up untilplugins_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_pluginsstill 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_Gatekeepercounts 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.