Understanding the service container, service providers, and application lifecycle
The Bifrost Music plugin uses a dependency injection container and service provider pattern to organize code and manage dependencies. This architecture is inspired by modern PHP frameworks like Laravel.
The container automatically resolves dependencies using PHP reflection:
private function resolveDependencies(array $params, array $providedParams): array{ $dependencies = []; foreach ($params as $param) { $name = $param->getName(); // Use provided parameter if available if (array_key_exists($name, $providedParams)) { $dependencies[] = $providedParams[$name]; continue; } $type = $param->getType(); // Handle anything that is not a named or built-in type. if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) { $dependencies[] = $this->resolveNonTyped($param); continue; } // Resolve typed dependency. $className = $type->getName(); // If class is registered with the container, resolve it. if ($this->has($className)) { $dependencies[] = $this->resolve($className); continue; } // If the class exists, resolve it. if (class_exists($className)) { $dependencies[] = $this->make($className); } } return $dependencies;}
From inc/Container/ServiceContainer.php:195
The container inspects constructor parameters and automatically injects registered services or creates new instances.
abstract class ServiceProvider{ public function __construct(protected readonly Container $container) {} public function register(): void { // Default empty implementation - override if needed. }}
final class ContentServiceProvider extends ServiceProvider implements Bootable{ public function register(): void { $this->container->singleton(Album::class); $this->container->singleton(Artist::class); $this->container->singleton(Genre::class); } public function boot(): void { $this->container->get(Album::class)->boot(); $this->container->get(Artist::class)->boot(); $this->container->get(Genre::class)->boot(); }}
Services are registered as singletons, meaning only one instance exists throughout the request lifecycle.
public function __construct(protected readonly Container $container){ // Register default bindings and service providers. $this->registerDefaultBindings(); $this->registerDefaultProviders(); // Allow third-party devs to register service providers. if (static::NAMESPACE !== '') { do_action(static::NAMESPACE . '/register', $this); }}
public static function boot(): void{ plugin()->boot();}
The boot method iterates through all registered providers:
public function boot(): void{ foreach ($this->serviceProviders as $provider) { if ($provider instanceof Bootable) { $provider->boot(); } } // Allow third-party devs access to hook in after booting. if (static::NAMESPACE !== '') { do_action(static::NAMESPACE . '/booted', $this); }}
From inc/Core/Application.php:113During this phase:
Location: inc/Contracts/Bootable.phpServices that need to perform actions during the boot phase implement the Bootable interface:
interface Bootable{ public function boot(): void;}
Example from the Artist class:
public function boot(): void{ // Register post types. add_action('init', $this->register(...)); // Filter the "enter title here" text. add_filter('enter_title_here', $this->enterTitleHere(...), 10, 2); // Filter the bulk and post updated messages. add_filter('bulk_post_updated_messages', $this->bulkPostUpdatedMessages(...), 5, 2); add_filter('post_updated_messages', $this->postUpdatedMessages(...), 5);}