Darstellung
Service Provider
Ein Service Provider bündelt alles, was ein Modul braucht: die Container-Bindings und die WordPress-Hooks. AD Form registriert dreizehn Kern-Provider; Add-ons hängen ihre eigenen über einen Filter an.
Der Vertrag
php
<?php
declare(strict_types=1);
namespace Dingfelder\AdForm\Core;
interface ServiceProviderInterface {
/**
* Bind services into the container.
*/
public function register( Container $container ): void;
/**
* Hook into WordPress after all services are registered.
*/
public function boot( Container $container ): void;
}Beide Methoden geben void zurück und bekommen denselben Container.
register() vs. boot()
Der Unterschied ist keine Stilfrage, sondern die Bedingung dafür, dass Provider sich gegenseitig sehen können. Plugin::boot() durchläuft erst alle register()-Methoden, danach alleboot()-Methoden.
register() | boot() | |
|---|---|---|
| Zweck | Bindings setzen | Auf WordPress reagieren |
| Erlaubt | singleton(), bind(), instance() | get(), add_action(), add_filter(), register_*() |
| Nicht erlaubt | get() auf fremde Bindings, add_action() | neue Bindings, die andere Provider erwarten |
| Zustand danach | Alle Kern-Bindings stehen | Plugin ist einsatzbereit |
Kein get() in register()
In register() ist nicht garantiert, dass ein Binding aus einem anderen Provider bereits existiert – die Reihenfolge innerhalb der Phase ist Implementierungsdetail. Abhängigkeiten werden deshalb innerhalb der Factory-Closure aufgelöst, die erst beim späteren get() läuft.
Richtig:
php
public function register( Container $container ): void {
$container->singleton(
CrmSync::class,
// Läuft erst beim get() – zu dem Zeitpunkt sind alle Bindings da.
static fn ( Container $container ): CrmSync => new CrmSync(
$container->get( LoggerInterface::class )
)
);
}Falsch:
php
public function register( Container $container ): void {
// Läuft sofort. LoggerInterface ist eventuell noch nicht gebunden.
$logger = $container->get( LoggerInterface::class );
$container->singleton( CrmSync::class, static fn (): CrmSync => new CrmSync( $logger ) );
}Beispiele aus dem Kern
Der kleinste Provider bindet genau einen Service und tut in boot() nichts:
php
final class ConditionsServiceProvider implements ServiceProviderInterface {
public function register( Container $container ): void {
$container->singleton(
ConditionEngineInterface::class,
static fn (): ConditionEngineInterface => new ConditionEngine()
);
}
public function boot( Container $container ): void {
unset( $container );
}
}unset( $container ); ist dabei kein Zufall: Es macht den unbenutzten Parameter explizit und hält die Coding-Standards-Prüfung ruhig.
Ein Provider mit Hook-Registrierung – hier wird der ActionRunner an das Ende der Submission gehängt:
php
public function boot( Container $container ): void {
$container->get( ActionRegistry::class )->boot();
$runner = $container->get( ActionRunner::class );
add_action(
Hooks::AFTER_SUBMISSION,
static function ( mixed $submission, mixed $form ) use ( $runner ): void {
if ( ! $submission instanceof Submission || ! $form instanceof Form ) {
return;
}
$runner->run( $form, $submission );
},
10,
2
);
}Ein Provider, der nur im Backend arbeitet, steigt früh aus:
php
public function boot( Container $container ): void {
if ( ! is_admin() ) {
return;
}
$container->get( FormsPage::class )->boot();
// …
}Eigenen Provider registrieren
Der Filter ad_form_service_providers bekommt die Liste der Kern-Provider und muss eine Liste zurückgeben. Plugin filtert anschließend alles heraus, was kein ServiceProviderInterface ist – ein Rückgabefehler führt also nicht zum Fatal Error, sondern zum stillen Ignorieren.
php
add_filter(
'ad_form_service_providers',
static function ( array $providers ): array {
$providers[] = new Acme\AdFormCrm\CrmServiceProvider();
return $providers;
}
);Timing
Der Filter läuft in Plugin::boot() auf plugins_loaded mit Priorität 5. Der add_filter()- Aufruf muss also beim Laden der Add-on-Datei passieren, nicht erst in einem späteren Hook.
Anhängen statt Ersetzen: $providers[] = … erhält die Kern-Reihenfolge. Ein eigener Provider läuft dadurch immer nach allen Kern-Providern – in register() wie in boot().
Vollständiges Beispiel
php
<?php
/**
* CRM add-on for AD Form.
*
* @package Acme\AdFormCrm
*/
declare(strict_types=1);
namespace Acme\AdFormCrm;
use Dingfelder\AdForm\Core\Container;
use Dingfelder\AdForm\Core\Hooks;
use Dingfelder\AdForm\Core\ServiceProviderInterface;
use Dingfelder\AdForm\Forms\Form;
use Dingfelder\AdForm\Logging\LoggerInterface;
use Dingfelder\AdForm\Submissions\Submission;
final class CrmServiceProvider implements ServiceProviderInterface {
public function register( Container $container ): void {
$container->singleton(
CrmClient::class,
static fn (): CrmClient => new CrmClient(
(string) get_option( 'acme_crm_api_key', '' )
)
);
$container->singleton(
CrmSync::class,
static fn ( Container $container ): CrmSync => new CrmSync(
$container->get( CrmClient::class ),
$container->get( LoggerInterface::class )
)
);
}
public function boot( Container $container ): void {
$sync = $container->get( CrmSync::class );
add_action(
Hooks::AFTER_SUBMISSION,
static function ( mixed $submission, mixed $form ) use ( $sync ): void {
if ( ! $submission instanceof Submission || ! $form instanceof Form ) {
return;
}
$sync->push( $form, $submission );
},
10,
2
);
}
}Und die Registrierung in der Haupt-Datei des Add-ons:
php
<?php
declare(strict_types=1);
add_filter(
'ad_form_service_providers',
static function ( array $providers ): array {
$providers[] = new Acme\AdFormCrm\CrmServiceProvider();
return $providers;
}
);Wann ein Provider – und wann nicht
Ein eigener Provider lohnt sich, sobald mehr als ein Service beteiligt ist oder Bindings und Hook-Registrierung zusammengehören.
Für einen einzelnen Service reicht ad_form_register_services:
php
add_action(
'ad_form_register_services',
static function ( Container $container ): void {
$container->singleton( MyService::class, static fn (): MyService => new MyService() );
}
);Für reines Verhalten ohne eigene Services genügen die normalen Hooks – siehe Hook-Referenz.
Provider-Liste auslesen
Zur Diagnose gibt Plugin::providers() die gefilterte Liste zurück:
php
add_action(
'ad_form_loaded',
static function ( \Dingfelder\AdForm\Plugin $plugin ): void {
foreach ( $plugin->providers() as $provider ) {
error_log( $provider::class );
}
}
);