pixielity / laravel-container
Dependency injection container utilities and extensions
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Forks: 0
pkg:composer/pixielity/laravel-container
Requires
- php: ^8.5
- illuminate/container: ^12.0
- pixielity/laravel-support: *
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^11.0
README
Automatic dependency injection and tagged class registration for Laravel applications
The Container package provides utilities for automatic class discovery and registration using PHP attributes, eliminating manual service provider configuration.
Table of Contents
- Overview
- Installation
- Features
- Quick Start
- Core Concepts
- Usage
- API Reference
- Advanced Usage
- Best Practices
- Performance
- Testing
- Troubleshooting
Overview
The Container package extends Laravel's service container with automatic class discovery and registration capabilities. It uses PHP 8 attributes to mark classes for automatic registration, eliminating boilerplate code in service providers.
Key Benefits
- ✅ Zero Configuration: Classes register themselves automatically
- ✅ Type Safe: Uses PHP attributes and reflection
- ✅ Performance: Leverages Composer's cached attribute data
- ✅ Monorepo Friendly: Discovers classes across packages
- ✅ Clean Code: Eliminates manual registration boilerplate
Installation
The Container package is part of the Framework meta-package and is automatically available:
composer require pixielity/laravel-framework
Features
🏷️ Tagged Class Registration
Automatically discover and register classes with custom tags using the #[Tagged] attribute.
🔍 Automatic Discovery
Uses the Discovery package to find tagged classes across your entire application.
📦 Bulk Registration
Efficiently registers multiple classes with a single container operation.
🎯 Service Provider Integration
Seamlessly integrates with Laravel's service provider system.
Quick Start
1. Mark Classes with Tags
use Pixielity\Container\Attributes\Tagged;
#[Tagged('payment.processors')]
class StripePaymentProcessor implements PaymentProcessor{
public function process(Payment $payment): Result
{
// Implementation
}
}
#[Tagged('payment.processors')]
class PayPalPaymentProcessor implements PaymentProcessor{
public function process(Payment $payment): Result
{
// Implementation
}
}
2. Enable Discovery in Service Provider
use Pixielity\Container\Concerns\HasDiscovery;
use Pixielity\Support\ServiceProvider;
class PaymentServiceProvider extends ServiceProvider
{
use HasDiscovery;
public function register(): void
{
parent::register();
// Automatically discovers and registers all tagged classes
$this->discoverTaggedClasses();
}
}
3. Retrieve Tagged Classes
// Get all payment processors
$processors = app()->tagged('payment.processors');
foreach ($processors as $processor) {
$processor->process($payment);
}
Core Concepts
Tagged Classes
Classes marked with the #[Tagged] attribute are automatically discovered and registered with the service container under the specified tag name.
Benefits:
- No manual registration required
- Self-documenting code
- Easy to add new implementations
- Supports multiple tags per class
Automatic Discovery
The package uses the Discovery facade to find all classes with the #[Tagged] attribute:
- Scan: Composer's attribute collector finds all tagged classes
- Filter: Validates that classes exist
- Group: Organizes classes by tag name
- Register: Bulk registers with Laravel's container
Usage
Basic Tagged Class
use Pixielity\Container\Attributes\Tagged;
#[Tagged('repositories')]
class UserRepository
{
public function find(int $id): ?User
{
return User::find($id);
}
}
Multiple Tags
A class can have multiple tags:
#[Tagged('repositories')]
#[Tagged('user.services')]
class UserRepository
{
// Implementation
}
Retrieving Tagged Classes
// Get all classes with a specific tag
$repositories = app()->tagged('repositories');
// Iterate through tagged classes
foreach ($repositories as $repository) {
// Use the repository
}
// Get as array
$repositoryArray = iterator_to_array(app()->tagged('repositories'));
Service Provider Integration
use Pixielity\Container\Concerns\HasDiscovery;
use Pixielity\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
use HasDiscovery;
public function register(): void
{
parent::register();
// Discover all tagged classes
$this->discoverTaggedClasses();
}
public function boot(): void
{
// Use tagged classes
$processors = app()->tagged('payment.processors');
// Register them with a manager
foreach ($processors as $processor) {
$this->app->make(PaymentManager::class)
->register($processor);
}
}
}
API Reference
Attributes
#[Tagged]
Marks a class for automatic registration with a tag.
Parameters:
tag(string): The tag name to register the class under
Example:
#[Tagged('event.listeners')]
class UserRegisteredListener
{
// Implementation
}
Concerns
HasDiscovery
Trait that provides automatic tagged class discovery and registration.
Methods:
discoverTaggedClasses(): void
Discovers and registers all classes with the #[Tagged] attribute.
Usage:
use Pixielity\Container\Concerns\HasDiscovery;
class MyServiceProvider extends ServiceProvider
{
use HasDiscovery;
public function register(): void
{
$this->discoverTaggedClasses();
}
}
Providers
ContainerServiceProvider
The main service provider for the Container package.
Features:
- Automatically discovers tagged classes
- Registers container utilities
- Provides dependency injection helpers
Advanced Usage
Custom Discovery
You can manually discover and process tagged classes:
use Pixielity\Container\Attributes\Tagged;
use Pixielity\Discovery\Facades\Discovery;
$taggedClasses = Discovery::attribute(Tagged::class)
->get()
->filter(fn($class) => class_exists($class));
foreach ($taggedClasses as $class) {
// Custom processing
}
Conditional Registration
Register classes based on conditions:
use Pixielity\Container\Attributes\Tagged;
#[Tagged('payment.processors')]
class StripePaymentProcessor
{
public static function shouldRegister(): bool
{
return config('payment.stripe.enabled', false);
}
}
// In service provider
$processors = app()->tagged('payment.processors');
foreach ($processors as $processor) {
if (method_exists($processor, 'shouldRegister')
&& !$processor::shouldRegister()) {
continue;
}
// Register the processor
}
Best Practices
1. Use Descriptive Tag Names
// ✅ Good - Clear and descriptive
#[Tagged('payment.processors')]
#[Tagged('notification.channels')]
#[Tagged('report.generators')]
// ❌ Bad - Vague or unclear
#[Tagged('processors')]
#[Tagged('handlers')]
2. Group Related Classes
// Group by domain
#[Tagged('user.repositories')]
#[Tagged('user.services')]
#[Tagged('user.validators')]
3. Document Tag Usage
/**
* Payment processor for Stripe integration.
*
* @tagged payment.processors
*/
#[Tagged('payment.processors')]
class StripePaymentProcessor
{
// Implementation
}
4. Use Interfaces
interface PaymentProcessor{
public function process(Payment $payment): Result;
}
#[Tagged('payment.processors')]
class StripePaymentProcessor implements PaymentProcessor{
// Implementation ensures contract compliance
}
Performance
Caching
The Discovery package uses Composer's attribute collector, which caches attribute data:
- First Run: Scans all files (done during
composer dump-autoload) - Subsequent Runs: Uses cached data (instant)
- Cache Location:
vendor/attributes.php
Optimization Tips
- Run in register(): Discover classes during registration phase
- Cache Results: Store discovered classes if needed multiple times
- Use Specific Tags: More specific tags = faster filtering
// Cache discovered classes
protected array $cachedProcessors;
public function register(): void
{
$this->discoverTaggedClasses();
// Cache for later use
$this->cachedProcessors = iterator_to_array(
app()->tagged('payment.processors')
);
}
Testing
Testing Tagged Classes
use Tests\TestCase;
class TaggedClassTest extends TestCase
{
public function test_class_is_tagged(): void
{
$processors = app()->tagged('payment.processors');
$processorClasses = iterator_to_array($processors);
$this->assertContains(
StripePaymentProcessor::class,
$processorClasses
);
}
public function test_all_processors_implement_interface(): void
{
$processors = app()->tagged('payment.processors');
foreach ($processors as $processor) {
$this->assertInstanceOf(
PaymentProcessorInterface::class,
$processor
);
}
}
}
Mocking Tagged Classes
public function test_with_mocked_processor(): void
{
$mock = $this->createMock(PaymentProcessorInterface::class);
// Override tagged classes for testing
app()->tag([$mock], 'payment.processors');
// Test code that uses tagged processors
}
Troubleshooting
Classes Not Being Discovered
Problem: Tagged classes aren't being registered.
Solutions:
- Run
composer dump-autoloadto rebuild attribute cache - Ensure the class is autoloadable
- Check that
discoverTaggedClasses()is called inregister() - Verify the attribute is imported correctly
# Rebuild attribute cache
composer dump-autoload
Tag Name Typos
Problem: Can't retrieve classes due to tag name mismatch.
Solution: Use constants for tag names:
class Tags
{
public const PAYMENT_PROCESSORS = 'payment.processors';
public const NOTIFICATION_CHANNELS = 'notification.channels';
}
#[Tagged(Tags::PAYMENT_PROCESSORS)]
class StripePaymentProcessor { }
// Retrieve
$processors = app()->tagged(Tags::PAYMENT_PROCESSORS);
Performance Issues
Problem: Discovery is slow.
Solutions:
- Ensure Composer's attribute cache is built
- Don't call
discoverTaggedClasses()multiple times - Cache results if used frequently
Related Packages
- Discovery: Provides the attribute discovery functionality
- ServiceProvider: Base service provider with discovery support
- Support: Reflection utilities used by Container
License
This package is part of the Pixielity Framework and is open-sourced software licensed under the MIT license.