alsciende/make-registry-bundle
Installation:
composer require --dev alsciende/make-registry-bundle
For non-Flex projects, also add the bundle to config/bundles.php:
Alsciende\Bundle\MakeRegistryBundle\AlsciendeMakeRegistryBundle::class => ['all' => true],
First Use Case:
Generate a registry for a new service type (e.g., PaymentGateway):
php bin/console make:registry PaymentGateway
This creates:
PaymentGatewayInterface.php (defines contract)SamplePaymentGateway.php (example implementation)PaymentGatewayRegistry.php (registry class with tagged service resolution)Where to Look First:
*Registry.php to understand the tagged service resolution logic.*Interface.php to define your service contract (e.g., required methods like getName()).Define Service Contract: Extend the generated interface to add domain-specific methods:
interface PaymentGatewayInterface {
public function getName(): string;
public function processPayment(float $amount): bool;
}
Implement Services:
Create concrete implementations tagged with app.payment_gateway:
#[AutoconfigureTag('app.payment_gateway')]
class StripeGateway implements PaymentGatewayInterface { ... }
Use the Registry:
Inject the registry (aliased as app.payment_gateway_registry) to access services:
public function __construct(
private PaymentGatewayRegistry $registry
) {}
public function executePayment(string $gatewayName, float $amount) {
$gateway = $this->registry->getPaymentGateway($gatewayName);
return $gateway->processPayment($amount);
}
Dynamic Registration:
Use the registry’s getNames() to list available services (e.g., for UI dropdowns).
$availableGateways = $this->registry->getNames();
Configuration Overrides: Customize the tag name in the registry constructor:
#[TaggedIterator('custom.payment_gateway_tag')]
public function __construct(iterable $services) { ... }
Validation: Extend the registry to add pre-registration validation (e.g., check for required methods):
if (!method_exists($service, 'processPayment')) {
throw new \RuntimeException('Service must implement processPayment()');
}
Testing: Mock the registry in tests to return specific implementations:
$this->mockBuilder->getMockBuilder(PaymentGatewayRegistry::class)
->disableOriginalConstructor()
->addMethods(['getPaymentGateway'])
->getMock();
Duplicate Service Names:
The registry throws a LogicException if two services return the same getName(). Ensure unique names in implementations:
// ❌ Duplicate
class StripeGateway implements PaymentGatewayInterface {
public function getName(): string { return 'stripe'; } // Conflict!
}
Tag Mismatch:
If services aren’t registered, verify the tag in #[AutoconfigureTag] matches the TaggedIterator in the registry (default: app.{registry_name}).
Circular Dependencies:
Avoid injecting the registry into services that are themselves tagged for the registry (e.g., PaymentGateway injecting PaymentGatewayRegistry).
Check Registered Services:
Temporarily add a dump($services) in the registry constructor to inspect tagged services:
public function __construct(iterable $services) {
dump($services); // Debug output
foreach ($services as $service) { ... }
}
TaggedIterator Issues: If services aren’t found, ensure:
symfony/dependency-injection package is loaded.#[AutoconfigureTag] or XML/YAML config).Custom Registry Logic:
Override the registry’s __construct() to add initialization logic:
public function __construct(iterable $services) {
parent::__construct($services);
$this->validateServices(); // Custom method
}
Dynamic Service Loading: Extend the registry to load services from a database or API:
public function loadExternalServices(): void {
$externalServices = $this->fetchFromApi();
foreach ($externalServices as $service) {
$this->services[$service['name']] = $service['instance'];
}
}
Registry Events: Dispatch events when services are added/removed:
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
public function __construct(
iterable $services,
private EventDispatcherInterface $dispatcher
) {
foreach ($services as $service) {
$this->dispatcher->dispatch(new ServiceRegisteredEvent($service));
$this->services[$service->getName()] = $service;
}
}
Performance Optimization:
Cache the registry’s getNames() result if the service list rarely changes:
private ?array $cachedNames = null;
public function getNames(): array {
return $this->cachedNames ?? ($this->cachedNames = array_keys($this->services));
}
How can I help you explore Laravel packages today?