PluginManager
Process-global registry + lifecycle for PluginInterfaces, mirroring the static, worker-lifetime pattern of MiddlewareCatalog and Events: plugins are registered once and their contributions persist for the life of the process.
Lifecycle: - PluginManager::add() — programmatic registration (before bootstrap). - PluginManager::bootFromConfig() — called by Quiote::bootstrap() after settings load and before contexts are created (the one seam between those steps): reads the plugins config key, instantiates + adds them, then calls PluginInterface::register() on every plugin in deterministic order, de-duped by class. Idempotent. - PluginManager::configureContainer() — applies deferred DI-service contributions to a context’s container (register-if-absent). - PluginManager::configureHttpClients() — applies named-HTTP-client contributions to a container’s HttpClientFactory. - PluginManager::moduleDirectories() / PluginManager::contributedCommands() — read by the attribute route scanner / console application.
Synopsis
Section titled “Synopsis”final class PluginManager
| Source | Plugin/PluginManager.php |
Methods
Section titled “Methods”| Method | Description |
|---|---|
| [`add(PluginInterface | string $plugin): void`](#add) |
addCommand(string $fqcn): void | Records a console command class a plugin contributes to the CLI application. |
addContainerService(string $id, mixed $concrete, ?string $scope, list<string> $aliases): void | |
addHttpClientConfig(string $name, callable $configurator): void | Records a configurator for a named HTTP client, keyed by $name. |
addModuleDirectory(string $dir): void | Records a directory a plugin contributes as a module search root. |
addRequestEndClear(string $label, \Closure(): void $clear): void | Contribute a clear that runs when a request on any context ends. |
addStateReset(string $label, \Closure(): void $reset): void | Contribute a callback that clears a plugin-owned static registry, run as part of PluginManager::reset(). |
bootFromConfig(): void | Boot phase: pull plugins from the plugins config key, then invoke register() on every plugin once, in order. |
configureContainer(Container $container): void | Apply deferred DI-service contributions to a container, register-if-absent so app/core bindings (and the first contributing plugin) win. |
configureHttpClients(HttpClientFactory $factory): void | Apply named-HTTP-client contributions to a factory (does not overwrite an already-configured name). |
configureLifecycle(ContextLifecycle $lifecycle): void | Append plugin-contributed clears to a context’s lifecycle, after the framework’s own — the identity clears must not be displaced by a plugin. |
contributedCommands(): list<string> | |
isBooted(): bool | Reports whether plugin registration has already run in this process. |
moduleDirectories(): list<string> | |
registeredPlugins(): array<class-string, PluginInterface> | |
reset(): void | Test isolation: clears every plugin + contribution and the booted flag. |
public static function add(PluginInterface|string $plugin): void
Register a plugin (instance or class-string).
De-duped by class; declared order preserved.
| Parameter | Type | Description |
|---|---|---|
$plugin | PluginInterface` | “string` |
addCommand()
Section titled “addCommand()”public static function addCommand(string $fqcn): void
Records a console command class a plugin contributes to the CLI application.
De-duplicated on the class name, so registering the same command twice adds it once. The class is not loaded or instantiated here; it is handed over when the console application reads PluginManager::contributedCommands().
| Parameter | Type | Description |
|---|---|---|
$fqcn | string |
addContainerService()
Section titled “addContainerService()”public static function addContainerService(string $id, mixed $concrete, ?string $scope, list<string> $aliases): void
| Parameter | Type | Description |
|---|---|---|
$id | string | |
$concrete | mixed | |
$scope | ?``string | |
$aliases | list``<``string``> |
addHttpClientConfig()
Section titled “addHttpClientConfig()”public static function addHttpClientConfig(string $name, callable $configurator): void
Records a configurator for a named HTTP client, keyed by $name.
Registering the same name twice replaces the earlier configurator rather than stacking. The configurator is only invoked when PluginManager::configureHttpClients() applies it to a factory, and only for a name the factory has not already configured.
| Parameter | Type | Description |
|---|---|---|
$name | string | |
$configurator | callable |
addModuleDirectory()
Section titled “addModuleDirectory()”public static function addModuleDirectory(string $dir): void
Records a directory a plugin contributes as a module search root.
De-duplicated on the exact string, so re-registering the same directory is a no-op. The contribution is stored statically and applied later by whoever reads PluginManager::moduleDirectories(), not at the moment of the call.
| Parameter | Type | Description |
|---|---|---|
$dir | string |
addRequestEndClear()
Section titled “addRequestEndClear()”public static function addRequestEndClear(string $label, \Closure(): void $clear): void
Contribute a clear that runs when a request on any context ends.
| Parameter | Type | Description |
|---|---|---|
$label | string | |
$clear | \Closure(): void |
addStateReset()
Section titled “addStateReset()”public static function addStateReset(string $label, \Closure(): void $reset): void
Contribute a callback that clears a plugin-owned static registry, run as part of PluginManager::reset().
| Parameter | Type | Description |
|---|---|---|
$label | string | |
$reset | \Closure(): void |
bootFromConfig()
Section titled “bootFromConfig()”public static function bootFromConfig(): void
Boot phase: pull plugins from the plugins config key, then invoke register() on every plugin once, in order.
Called from Quiote::bootstrap() after settings load. Idempotent — safe if bootstrap runs more than once.
configureContainer()
Section titled “configureContainer()”public static function configureContainer(Container $container): void
Apply deferred DI-service contributions to a container, register-if-absent so app/core bindings (and the first contributing plugin) win.
Safe to call repeatedly for the same container (idempotent).
| Parameter | Type | Description |
|---|---|---|
$container | Container |
configureHttpClients()
Section titled “configureHttpClients()”public static function configureHttpClients(HttpClientFactory $factory): void
Apply named-HTTP-client contributions to a factory (does not overwrite an already-configured name).
| Parameter | Type | Description |
|---|---|---|
$factory | HttpClientFactory |
configureLifecycle()
Section titled “configureLifecycle()”public static function configureLifecycle(ContextLifecycle $lifecycle): void
Append plugin-contributed clears to a context’s lifecycle, after the framework’s own — the identity clears must not be displaced by a plugin.
| Parameter | Type | Description |
|---|---|---|
$lifecycle | ContextLifecycle |
contributedCommands()
Section titled “contributedCommands()”public static function contributedCommands(): list<string>
Returns list``<``string``>
isBooted()
Section titled “isBooted()”public static function isBooted(): bool
Reports whether plugin registration has already run in this process.
The flag guards registration against running twice per worker; PluginManager::reset() clears it along with the contributions.
Returns bool
moduleDirectories()
Section titled “moduleDirectories()”public static function moduleDirectories(): list<string>
Returns list``<``string``>
registeredPlugins()
Section titled “registeredPlugins()”public static function registeredPlugins(): array<class-string, PluginInterface>
Returns array``<``class-string``, PluginInterface>
reset()
Section titled “reset()”public static function reset(): void
Test isolation: clears every plugin + contribution and the booted flag.
Middleware contributions are cleared with the rest. They live in their own registries rather than in this class, and leaving them behind produced a half-registered plugin: the pipeline still advertised the plugin’s middleware while the container had lost the service that middleware’s factory resolves, so the next dispatch died on a missing service rather than simply running without the plugin.