sylius/registry
Sylius Registry component provides a simple service registry to store, retrieve, and manage services by type and name. Useful for decoupling implementations, selecting handlers at runtime, and organizing extensible systems in Symfony/Laravel-style PHP apps.
Install the Package:
composer require sylius/registry
Ensure autoloading is configured (Laravel handles this automatically).
Define an Interface:
Create a shared interface for services you want to register (e.g., PaymentGatewayInterface):
namespace App\Contracts;
interface PaymentGatewayInterface
{
public function processPayment(float $amount): bool;
}
Instantiate the Registry:
use Sylius\Component\Registry\ServiceRegistry;
$registry = new ServiceRegistry(PaymentGatewayInterface::class);
Register a Service:
$registry->register('stripe', new StripePaymentGateway());
Retrieve a Service:
$stripeGateway = $registry->get('stripe');
$stripeGateway->processPayment(100.00);
Check Existence (Optional):
if ($registry->has('stripe')) {
// Service exists
}
Leverage in Laravel (Optional): Bind the registry to Laravel’s container in a service provider:
use Illuminate\Support\ServiceProvider;
use Sylius\Component\Registry\ServiceRegistry;
class RegistryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('payment.gateway.registry', function () {
return new ServiceRegistry(PaymentGatewayInterface::class);
});
}
}
Then access it via:
$registry = app('payment.gateway.registry');
Dynamic Service Loading:
$configGateways = config('payment.gateways');
foreach ($configGateways as $name => $class) {
$registry->register($name, new $class());
}
Dependency Injection Alternative:
class OrderService
{
public function __construct(
private ServiceRegistry $paymentGatewayRegistry
) {}
public function createOrder()
{
$gateway = $this->paymentGatewayRegistry->get('stripe');
// ...
}
}
Prioritized Registries:
Sylius\Component\Registry\PrioritizedServiceRegistry for ordered resolution (e.g., fallback payment methods):
$prioritizedRegistry = new PrioritizedServiceRegistry(PaymentGatewayInterface::class);
$prioritizedRegistry->register('stripe', new StripeGateway(), 10);
$prioritizedRegistry->register('paypal', new PaypalGateway(), 5);
$gateway = $prioritizedRegistry->get(); // Returns StripeGateway (highest priority)
Lazy Registration:
if (featureEnabled('new_shipping')) {
$registry->register('new_shipping', new NewShippingCalculator());
}
Integration with Laravel Events:
use Illuminate\Support\Facades\Event;
Event::listen('plugin.installed', function ($plugin) {
$registry = app('shipping.calculator.registry');
$registry->register($plugin->name, $plugin->getCalculator());
});
Facade for Cleaner Access: Create a facade to simplify registry access:
// app/Facades/PaymentGatewayRegistry.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class PaymentGatewayRegistry extends Facade
{
protected static function getFacadeAccessor()
{
return 'payment.gateway.registry';
}
}
Usage:
use App\Facades\PaymentGatewayRegistry;
$gateway = PaymentGatewayRegistry::get('stripe');
Macros for Extensibility: Extend the registry with custom methods:
$registry->macro('getByConfig', function ($configKey) {
$name = config($configKey);
return $this->get($name);
});
Usage:
$gateway = $registry->getByConfig('payment.default_gateway');
Service Provider Bootstrapping:
Initialize registries with default services in the boot() method:
public function boot()
{
$registry = $this->app['payment.gateway.registry'];
$registry->register('stripe', new StripeGateway());
$registry->register('paypal', new PaypalGateway());
}
Testing with Mock Registries: Replace registries in tests using Laravel’s container mocking:
$this->app->instance('payment.gateway.registry', $mockRegistry);
Interface Mismatches:
TypeError at runtime.is operator:
if ($service instanceof PaymentGatewayInterface) {
$registry->register('stripe', $service);
}
Duplicate Keys:
if ($registry->has('stripe')) {
throw new \RuntimeException('Gateway already registered');
}
Prioritized Registry Order:
PrioritizedServiceRegistry uses FIFO order for services with the same priority.Memory Leaks with all():
all() returns a new array copy, which can be expensive for large registries.$services = $registry->all(); // Cache this if possible
No Automatic Dependency Injection:
PHP 8+ Compatibility:
Thread Safety Assumptions:
Check Registered Services:
Use all() to inspect the registry contents:
dd($registry->all());
Enable Strict Typing:
Add strict_types=1 to your PHP files to catch type-related issues early.
Log Registry Operations: Wrap registry methods in a decorator for debugging:
$registry->macro('debugGet', function ($key) {
\Log::debug("Getting service: {$key}");
return $this->get($key);
});
Handle Missing Services Gracefully:
Use has() to avoid exceptions:
if ($registry->has('fallback_gateway')) {
$gateway = $registry->get('fallback_gateway');
} else {
throw new \RuntimeException('No fallback gateway configured');
}
Custom Registry Classes:
Extend ServiceRegistry to add domain-specific logic:
class PaymentGatewayRegistry extends ServiceRegistry
{
public function getDefault(): PaymentGatewayInterface
{
$default = config('payment.default_gateway');
return $this->get($default);
}
}
Dynamic Registration from Config: Load services from a config file or database:
$configServices = config('services');
foreach ($configServices as $name => $serviceConfig) {
$service = new $serviceConfig['class']($serviceConfig['params']);
$registry->register($name, $service);
}
Integration with Laravel’s Container: Bind the registry to Laravel’s container for seamless DI:
$this->app->bind(PaymentGatewayInterface::class, function ($app) {
return $app['payment.gateway.registry']->get('stripe');
});
Event-Driven Registration:
Trigger registration during Laravel events (e.g., Registered):
Event::listen('user.registered', function ($user) {
$registry = app('notification.channel.registry');
$registry->register("user
How can I help you explore Laravel packages today?