Skip to content

08: Registrar - #8

Open
nikolaystrikhar wants to merge 3 commits into
07-sub-pluginfrom
08-registrar
Open

08: Registrar#8
nikolaystrikhar wants to merge 3 commits into
07-sub-pluginfrom
08-registrar

Conversation

@nikolaystrikhar

Copy link
Copy Markdown
Contributor

What: the sub-plugin registry and its contract.

Stacked on #7.

Usage:

$registrar = new Registrar();
$registrar->register( $sub_plugin );
$registrar->all();   // [ "give-recurring" => Sub_Plugin ]

Why this way: keyed by slug so re-registering the same slug replaces rather than duplicates — a host that conditionally registers in two code paths gets one entry, not two loads. The interface ships in this PR rather than in a contracts-only PR so it arrives with an implementation and tests.

The review found no defects in the code, but did surface an undocumented guarantee. all() returns insertion order, and Loader::load_all() require_onces in that order — so if a host bundles an add-on whose main file extends a class defined by another sub-plugin, and registers the add-on first, they get a class-not-found fatal at plugins_loaded. Nothing said so anywhere a host would read it. The README now does.

Assigning by key (rather than appending) is also what keeps a re-registration in place instead of moving it to the end, which is what makes the order stable for conditional registration. Verified: $a["x"]=1; $a["y"]=2; $a["x"]=3 leaves x at index 0. Two tests pin this; the plan's version left load order entirely unasserted.

reset() sits on the interface deliberately, and now says why: a container-bound singleton registrar hands back the same populated instance after a Loader reset, so an implementation that no-ops reset() leaks registrations between boots. A third party implementing this contract had no way to know that.

Two plan fixes carried here: every remaining private function make(...) fixture helper is renamed (four of them, in Tasks 7, 8, 9 and 13 — each would have been a compile-time fatal against Codeception\Test\Unit::make()), and Loader::load_all() gains an instanceof Sub_Plugin skip. Registrar_Interface::all() can only declare array, so a host binding a registrar that returns anything else would fatal on the first predicate call inside plugins_loaded — the exact failure mode this library exists to prevent. PHPStan cannot catch that one: it trusts the interface's @return array<string,Sub_Plugin> and never analyses host code.

Verify: slic run unit — 85 tests, 120 assertions, green. composer test:analysis[OK] No errors, exit 0.

Registration order decides load order, and nothing said so where a host
would read it. A host registering an add-on before the plugin it extends
gets a class-not-found fatal at plugins_loaded.

Also explain on the contract why reset() is on it: a container-bound
singleton registrar survives a Loader reset, so an implementation that
no-ops reset() leaks registrations between boots.
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