Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 13 additions & 14 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ seams a host may rebind:

| Interface | Bound to | Responsibility |
|---|---|---|
| `Contracts\Registrar_Interface` | `Registrar` | holds registered `Sub_Plugin` objects |
| `Registry\Contracts\Registrar_Interface` | `Registry\Registrar` | holds registered `Sub_Plugin` objects |
| `Notices\Contracts\Writer_Interface` | `Notices\Writer` | what each notice says |
| `Conflict\Contracts\Resolver_Interface` | `Conflict\Resolver` | one method: which policy branch a conflict takes |
| `Contracts\Plugin_Deactivator_Interface` | `Plugin_Deactivator` | deactivates the standalone, network-aware |
| `Contracts\Plugin_Checker_Interface` | `Plugin_Checker` | answers whether a plugin is active |
| `Contracts\Activator_Interface` | `Activator` | run-once activation-callback tracking |

The rest — `Boot\Scheduler`, `Loader`, `Registry_Reader`, `Conflict\Detector`, `Conflict\Gatekeeper`,
The rest — `Boot\Scheduler`, `Loader`, `Registry\Reader`, `Conflict\Detector`, `Conflict\Gatekeeper`,
`Conflict\Redirector`, `Conflict\Rewriter`, `Notices\Store`, `Notices\Renderer`, `Notices\Presenter` —
are bound as concrete classes. A host that wants one of them different rebinds the class name; there
is no interface because nothing in the library dispatches on one. `Provider` also binds the container
Expand All @@ -95,8 +95,8 @@ unbound classes reflectively can still satisfy the collaborators that take one.

An interface belonging to a folder-scoped concern lives in that folder's `Contracts\`, not beside its
implementation and not in the top-level `src/Contracts/`. `src/Contracts/` is for the interfaces whose
implementations sit at the root — `Registrar`, `Plugin_Deactivator`, `Plugin_Checker`, `Activator` —
plus `Provider_Interface`.
implementations sit at the root — `Plugin_Deactivator`, `Plugin_Checker`, `Activator` — plus
`Provider_Interface`.

**`Notices\Writer` and `Notices\Presenter` split because they change for different reasons.** One
answers "what does this notice say", the other "who may see the pending set, and is it gone once they
Expand Down Expand Up @@ -145,7 +145,7 @@ Foundation's abstract does not extend.
**`Provider` never overwrites a binding.** It binds only what the container does not already have, so
a host that bound its own implementation wins, and the order in which the host calls
`set_container()` and `boot()` stops deciding which implementation it gets. Everything is a
singleton: each binding is either a registry whose contents are the point — a second `Registrar`
singleton: each binding is either a registry whose contents are the point — a second `Registry\Registrar`
would hold a second, emptier list — or a stateless worker with nothing to gain from a second copy.

The container is **never** used to wire hooks, and the reason is no longer that it is optional.
Expand All @@ -161,7 +161,7 @@ collaborator to be built at boot.
delegations to `$container->get()`, so what a host calls is unchanged; what changed is that a *collaborator* now
depends on the peer it was handed rather than on the facade.

**Nothing but `Absorber` names `Absorber`.** The registration buffer belongs to `Registry_Reader`,
**Nothing but `Absorber` names `Absorber`.** The registration buffer belongs to `Registry\Reader`,
which is also what reads it back out: `Absorber::register()` pushes a `Sub_Plugin` into it and
`Absorber::all()` delegates to it, while `Conflict\Detector`, `Conflict\Resolver` and `Loader` are
each handed one. The buffer is static because it must be — `register()` is a static call a host makes
Expand Down Expand Up @@ -195,13 +195,12 @@ that drives the whole of it against a real WordPress is `tests/unit/Scenario/`.
| `src/Sub_Plugin.php` | Value object; validates config and answers what it can without a container-bound collaborator. |
| `src/Conflict_Policy.php` | The three policy constants, `default()`, `is_valid()`. |
| `src/Plugin_Deactivator.php`, `src/Plugin_Checker.php` | The only files that touch WordPress plugin functions, through `Traits\Loads_Plugin_Functions`. |
| `src/Registrar.php` | Holds registered `Sub_Plugin` objects. |
| `src/Registry_Reader.php` | The registration buffer, drained into the registrar on the way past; the object every pass reads the registry through. |
| `src/Registry/` | `Registrar` (holds registered `Sub_Plugin` objects), `Reader` (the registration buffer, drained into the registrar on the way past; the object every pass reads the registry through), `Contracts\Registrar_Interface`. |
| `src/Activator.php` | Runs a sub-plugin's activation callback once ever, recorded in one option. |
| `src/Conflict/` | `Detector` (whether a standalone is in the way), `Resolver` (which policy branch to take), `Gatekeeper` (which requests, and which users, may have one resolved), `Redirector` (where the user lands afterwards), `Rewriter` (rewrites the activation-error screen for a registered standalone), `Contracts\Resolver_Interface`. |
| `src/Traits/` | `Loads_Plugin_Functions` (pulls in `wp-admin/includes/plugin.php`), `Guards_Hook_Prefix` (a missing prefix warns and stands down rather than throwing). |
| `src/Notices/` | `Writer` (what a notice says, stored under `slug:type`), `Presenter` (who may consume it, render-then-clear), `Store` (keeps it), `Renderer` (draws it), `Contracts\Writer_Interface`. |
| `src/Contracts/`, `src/Exceptions/` | `Provider_Interface`, `Registrar_Interface`, `Plugin_Deactivator_Interface`, `Plugin_Checker_Interface`, `Activator_Interface`, `Config_Exception`. |
| `src/Contracts/`, `src/Exceptions/` | `Provider_Interface`, `Plugin_Deactivator_Interface`, `Plugin_Checker_Interface`, `Activator_Interface`, `Config_Exception`. |

### Boot lifecycle

Expand Down Expand Up @@ -229,7 +228,7 @@ whenever the host's bootstrap happens to run it. This is also why `Absorber::reg
resolves nothing — registration at plugin-file scope is a shape a host is entitled to use, and it
would otherwise register into the throwaway.

**A duplicate slug is `Registrar::register()`'s exception, not `Absorber::register()`'s.** What
**A duplicate slug is `Registry\Registrar::register()`'s exception, not `Absorber::register()`'s.** What
`Absorber::register()` throws is config validation, from the `Sub_Plugin` constructor, in the call
the host can see in its own stack trace. The buffer reaches the registrar at the first read —
`plugins_loaded` priority 5 on a request that passes the gatekeeper, priority 6 otherwise — so the
Expand Down Expand Up @@ -275,7 +274,7 @@ carries the whole re-declaration guarantee, and it is the only gate meaning "thi
running" — warning that requirements are unmet for a plugin the admin can watch working would send
them after the wrong problem. `docs/filters.md` says the same.

`Registry_Reader::all()` narrows to `Sub_Plugin` instances itself, so no caller repeats that guard. A
`Registry\Reader::all()` narrows to `Sub_Plugin` instances itself, so no caller repeats that guard. A
host may bind a registrar returning anything, and PHP 7.4 cannot express `array<string,Sub_Plugin>` in
the interface signature — so it is filtered once where the untrusted value enters. All three readers —
the load pass, the conflict pass and `Conflict\Rewriter` — read through the reader they were
Expand Down Expand Up @@ -365,7 +364,7 @@ all this library gets to do about it is reword the sentence core's sandbox print
`conflict_notice_message`, the same message the merge notice carries, but wording it is as far as the
two share: nothing in `Rewriter` is stored, drawn or authored through the notice machinery — it reads
the request, checks the screen, verifies a nonce and edits markup core already wrote. Putting it on
`Writer` anyway would have needed a `Registry_Reader` argument for the one method that used it — a
`Writer` anyway would have needed a `Registry\Reader` argument for the one method that used it — a
collaborator only that method needs — and would have forced every host binding its own
`Notices\Contracts\Writer_Interface` to implement an error screen just to get its notices worded. It
sits in `Conflict\` rather than `Notices\` because what it is about is the standalone conflict — the
Expand Down Expand Up @@ -452,9 +451,9 @@ Tabs for PHP, 4 spaces for yml/yaml/json/md (see `.editorconfig`).
Production classes do not get a `reset()` for the suite's benefit — that becomes API the library
supports forever. Tests clear static state by reflection through a helper under `tests/_support/`:
`Tests\Support\Config_State::reset()` for `Config`, and `Tests\Support\Absorber_State::reset()` for
`Absorber` plus the registration buffer on `Registry_Reader`, which also unwires the hooks `boot()`
`Absorber` plus the registration buffer on `Registry\Reader`, which also unwires the hooks `boot()`
added. Any older sketch showing `Config::reset()` or `Absorber::reset()` means the support helper.
`Registrar` needs no such helper — its state is instance state behind a container binding, so a
`Registry\Registrar` needs no such helper — its state is instance state behind a container binding, so a
fresh container *is* the reset.

### Testing rules
Expand Down
6 changes: 3 additions & 3 deletions docs/extending.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ Bind any of these ids before or after `Absorber::boot()` — boot binds the defa
*interface* your container already answers for, so your binding wins either way:

```php
use Nexcess\PluginAbsorber\Contracts\Registrar_Interface;
use Nexcess\PluginAbsorber\Registry\Contracts\Registrar_Interface;

$container->singleton( Registrar_Interface::class, My_Registrar::class );
```

| Interface | Default | Responsibility |
|---|---|---|
| `Contracts\Registrar_Interface` | `Registrar` | Holds the registered sub-plugins. |
| `Registry\Contracts\Registrar_Interface` | `Registry\Registrar` | Holds the registered sub-plugins. |
| `Notices\Contracts\Writer_Interface` | `Notices\Writer` | Words the admin notices. |
| `Contracts\Plugin_Deactivator_Interface` | `Plugin_Deactivator` | Deactivates the standalone. |
| `Contracts\Plugin_Checker_Interface` | `Plugin_Checker` | Answers whether a plugin is active. |
Expand All @@ -36,7 +36,7 @@ want. See [the recipe](recipes.md#do-per-site-work-on-multisite).

Everything without an interface is bound by class name — `Notices\Store`, `Notices\Renderer`,
`Notices\Presenter`, `Conflict\Detector`, `Conflict\Gatekeeper`, `Conflict\Redirector`,
`Conflict\Rewriter`, `Loader`, `Registry_Reader`, `Boot\Scheduler`. Bind one of those **after**
`Conflict\Rewriter`, `Loader`, `Registry\Reader`, `Boot\Scheduler`. Bind one of those **after**
`Absorber::boot()`: di52 reports `has()` true for any class that exists, bound or not, so boot cannot
tell your binding from the container's own willingness to build the class, and replaces it.

Expand Down
9 changes: 5 additions & 4 deletions src/Absorber.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
use Nexcess\PluginAbsorber\Conflict\Contracts\Resolver_Interface;
use Nexcess\PluginAbsorber\Conflict\Rewriter;
use Nexcess\PluginAbsorber\Contracts\Provider_Interface;
use Nexcess\PluginAbsorber\Contracts\Registrar_Interface;
use Nexcess\PluginAbsorber\Exceptions\Config_Exception;
use Nexcess\PluginAbsorber\Notices\Contracts\Writer_Interface;
use Nexcess\PluginAbsorber\Notices\Presenter;
use Nexcess\PluginAbsorber\Registry\Contracts\Registrar_Interface;
use Nexcess\PluginAbsorber\Registry\Reader;
use Nexcess\PluginAbsorber\Traits\Guards_Hook_Prefix;
use Throwable;

Expand Down Expand Up @@ -78,7 +79,7 @@ public static function resolver(): Resolver_Interface {
* The sub-plugin is buffered rather than handed straight to the registrar, so that registering
* resolves nothing — not even the container. A host that registers before it calls
* Config::set_container() would otherwise fail on a call that has nothing to do with the
* container. The buffer belongs to `Registry_Reader`, which is where it is read back out: this
* container. The buffer belongs to `Registry\Reader`, which is where it is read back out: this
* class hands its collaborators no work and holds none of their state.
*
* The configuration is still validated here: building the Sub_Plugin is what rejects it, and
Expand All @@ -95,7 +96,7 @@ public static function resolver(): Resolver_Interface {
* @return void
*/
public static function register( array $config ): void {
Registry_Reader::buffer( new Sub_Plugin( $config ) );
Reader::buffer( new Sub_Plugin( $config ) );
}

/**
Expand All @@ -114,7 +115,7 @@ public static function register( array $config ): void {
* @return array<string,Sub_Plugin>
*/
public static function all(): array {
return self::collaborator( Registry_Reader::class )->all();
return self::collaborator( Reader::class )->all();
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/Conflict/Detector.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

use Nexcess\PluginAbsorber\Contracts\Plugin_Checker_Interface;
use Nexcess\PluginAbsorber\Exceptions\Config_Exception;
use Nexcess\PluginAbsorber\Registry_Reader;
use Nexcess\PluginAbsorber\Registry\Reader;
use Nexcess\PluginAbsorber\Sub_Plugin;

/**
Expand All @@ -34,7 +34,7 @@ class Detector {
/**
* @since 1.0.0
*
* @var Registry_Reader
* @var Reader
*/
private $registry;

Expand All @@ -48,10 +48,10 @@ class Detector {
/**
* @since 1.0.0
*
* @param Registry_Reader $registry Which sub-plugins are registered.
* @param Reader $registry Which sub-plugins are registered.
* @param Plugin_Checker_Interface $plugin_checker Whether the standalone is active.
*/
public function __construct( Registry_Reader $registry, Plugin_Checker_Interface $plugin_checker ) {
public function __construct( Reader $registry, Plugin_Checker_Interface $plugin_checker ) {
$this->registry = $registry;
$this->plugin_checker = $plugin_checker;
}
Expand Down
10 changes: 5 additions & 5 deletions src/Conflict/Resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
use Nexcess\PluginAbsorber\Contracts\Plugin_Deactivator_Interface;
use Nexcess\PluginAbsorber\Exceptions\Config_Exception;
use Nexcess\PluginAbsorber\Notices\Contracts\Writer_Interface;
use Nexcess\PluginAbsorber\Registry_Reader;
use Nexcess\PluginAbsorber\Registry\Reader;
use Nexcess\PluginAbsorber\Sub_Plugin;
use Nexcess\PluginAbsorber\Traits\Guards_Hook_Prefix;
use Throwable;
Expand Down Expand Up @@ -41,7 +41,7 @@ class Resolver implements Resolver_Interface {
/**
* @since 1.0.0
*
* @var Registry_Reader
* @var Reader
*/
private $registry;

Expand Down Expand Up @@ -76,14 +76,14 @@ class Resolver implements Resolver_Interface {
/**
* @since 1.0.0
*
* @param Registry_Reader $registry Which sub-plugins are registered.
* @param Reader $registry Which sub-plugins are registered.
* @param Detector $detector Whether a sub-plugin is in conflict.
* @param Plugin_Deactivator_Interface $plugin_deactivator Turns the standalone off.
* @param Writer_Interface $notices Where the user is told what happened.
* @param Writer_Interface $notices Where the user is told what happened.
* @param Redirector $redirector Where the user lands afterwards.
*/
public function __construct(
Registry_Reader $registry,
Reader $registry,
Detector $detector,
Plugin_Deactivator_Interface $plugin_deactivator,
Writer_Interface $notices,
Expand Down
8 changes: 4 additions & 4 deletions src/Conflict/Rewriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace Nexcess\PluginAbsorber\Conflict;

use Nexcess\PluginAbsorber\Exceptions\Config_Exception;
use Nexcess\PluginAbsorber\Registry_Reader;
use Nexcess\PluginAbsorber\Registry\Reader;
use Nexcess\PluginAbsorber\Sub_Plugin;

/**
Expand Down Expand Up @@ -38,16 +38,16 @@ class Rewriter {
/**
* @since 1.0.0
*
* @var Registry_Reader
* @var Reader
*/
private $registry;

/**
* @since 1.0.0
*
* @param Registry_Reader $registry Which sub-plugins are registered.
* @param Reader $registry Which sub-plugins are registered.
*/
public function __construct( Registry_Reader $registry ) {
public function __construct( Reader $registry ) {
$this->registry = $registry;
}

Expand Down
9 changes: 5 additions & 4 deletions src/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Nexcess\PluginAbsorber\Contracts\Activator_Interface;
use Nexcess\PluginAbsorber\Exceptions\Config_Exception;
use Nexcess\PluginAbsorber\Notices\Contracts\Writer_Interface;
use Nexcess\PluginAbsorber\Registry\Reader;
use Nexcess\PluginAbsorber\Traits\Guards_Hook_Prefix;
use Throwable;

Expand All @@ -26,7 +27,7 @@ class Loader {
/**
* @since 1.0.0
*
* @var Registry_Reader
* @var Reader
*/
private $registry;

Expand All @@ -47,12 +48,12 @@ class Loader {
/**
* @since 1.0.0
*
* @param Registry_Reader $registry Which sub-plugins are registered.
* @param Writer_Interface $notices Where a sub-plugin that could not load says so.
* @param Reader $registry Which sub-plugins are registered.
* @param Writer_Interface $notices Where a sub-plugin that could not load says so.
* @param Activator_Interface $activator Runs the activation callback of one that did.
*/
public function __construct(
Registry_Reader $registry,
Reader $registry,
Writer_Interface $notices,
Activator_Interface $activator
) {
Expand Down
18 changes: 10 additions & 8 deletions src/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
use Nexcess\PluginAbsorber\Contracts\Plugin_Checker_Interface;
use Nexcess\PluginAbsorber\Contracts\Plugin_Deactivator_Interface;
use Nexcess\PluginAbsorber\Contracts\Provider_Interface;
use Nexcess\PluginAbsorber\Contracts\Registrar_Interface;
use Nexcess\PluginAbsorber\Loader;
use Nexcess\PluginAbsorber\Notices\Contracts\Writer_Interface;
use Nexcess\PluginAbsorber\Notices\Presenter;
use Nexcess\PluginAbsorber\Notices\Renderer;
use Nexcess\PluginAbsorber\Notices\Store;
use Nexcess\PluginAbsorber\Notices\Writer;
use Nexcess\PluginAbsorber\Registry\Contracts\Registrar_Interface;
use Nexcess\PluginAbsorber\Registry\Reader;
use Nexcess\PluginAbsorber\Registry\Registrar;
use StellarWP\ContainerContract\ContainerInterface;

/**
Expand Down Expand Up @@ -94,24 +96,24 @@ static function () use ( $container ): Presenter {
);

$this->bind_once(
Registry_Reader::class,
static function () use ( $container ): Registry_Reader {
return new Registry_Reader( $container->get( Registrar_Interface::class ) );
Reader::class,
static function () use ( $container ): Reader {
return new Reader( $container->get( Registrar_Interface::class ) );
}
);

$this->bind_once(
Rewriter::class,
static function () use ( $container ): Rewriter {
return new Rewriter( $container->get( Registry_Reader::class ) );
return new Rewriter( $container->get( Reader::class ) );
}
);

$this->bind_once(
Detector::class,
static function () use ( $container ): Detector {
return new Detector(
$container->get( Registry_Reader::class ),
$container->get( Reader::class ),
$container->get( Plugin_Checker_Interface::class )
);
}
Expand All @@ -121,7 +123,7 @@ static function () use ( $container ): Detector {
Resolver_Interface::class,
static function () use ( $container ): Resolver {
return new Resolver(
$container->get( Registry_Reader::class ),
$container->get( Reader::class ),
$container->get( Detector::class ),
$container->get( Plugin_Deactivator_Interface::class ),
$container->get( Writer_Interface::class ),
Expand All @@ -134,7 +136,7 @@ static function () use ( $container ): Resolver {
Loader::class,
static function () use ( $container ): Loader {
return new Loader(
$container->get( Registry_Reader::class ),
$container->get( Reader::class ),
$container->get( Writer_Interface::class ),
$container->get( Activator_Interface::class )
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @package Nexcess\PluginAbsorber
*/

namespace Nexcess\PluginAbsorber\Contracts;
namespace Nexcess\PluginAbsorber\Registry\Contracts;

use Nexcess\PluginAbsorber\Exceptions\Config_Exception;
use Nexcess\PluginAbsorber\Sub_Plugin;
Expand Down
7 changes: 4 additions & 3 deletions src/Registry_Reader.php → src/Registry/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@

declare( strict_types=1 );

namespace Nexcess\PluginAbsorber;
namespace Nexcess\PluginAbsorber\Registry;

use Nexcess\PluginAbsorber\Contracts\Registrar_Interface;
use Nexcess\PluginAbsorber\Exceptions\Config_Exception;
use Nexcess\PluginAbsorber\Registry\Contracts\Registrar_Interface;
use Nexcess\PluginAbsorber\Sub_Plugin;

/**
* Every registered sub-plugin, as something a pass can be handed rather than reach for.
Expand All @@ -29,7 +30,7 @@
*
* @since 1.0.0
*/
class Registry_Reader {
class Reader {
/**
* Sub-plugins registered but not yet handed to the registrar.
*
Expand Down
Loading
Loading