Dependency Injection
Glueful uses a service container for dependency injection, making your code testable and decoupled.
Quick Start
Resolving from Container
// Get services from the container
$db = app('database'); // Glueful\\Database\\Connection
$cache = app(\\Glueful\\Cache\\CacheStore::class);
// Convenience alias
$logger = app(\\Psr\\Log\\LoggerInterface::class); // aliased to 'logger'
Constructor Injection
class UserController
{
public function __construct(
private UserRepository $users,
private CacheInterface $cache
) {}
public function index()
{
return $this->cache->remember('users:all', function () {
return $this->users->all();
});
}
}
Method Injection
class TaskController
{
public function store(Request $request, TaskRepository $tasks)
{
$validated = $request->validate([
'title' => 'required|string',
]);
$task = $tasks->create($validated);
return Response::success($task, 201);
}
}
Container Bindings
In Glueful, you register services via Service Providers (extending the framework’s BaseServiceProvider). Define your services in the defs()
method using autowire/alias/factory definitions.
namespace App\Providers;
use Glueful\Container\Providers\BaseServiceProvider;
use Glueful\Container\Definition\FactoryDefinition;
use Glueful\Container\Definition\AliasDefinition;
final class AppServiceProvider extends BaseServiceProvider
{
public function defs(): array
{
return [
// Autowire concrete classes (shared by default)
App\Services\OrderService::class => $this->autowire(App\Services\OrderService::class),
// Factory-built services
'report.generator' => new FactoryDefinition(
'report.generator',
fn() => new App\Services\ReportGenerator(config('app'))
),
// Aliases (map type-hints to existing ids)
\Psr\Log\LoggerInterface::class =>
new AliasDefinition(\Psr\Log\LoggerInterface::class, 'logger'),
];
}
}
Register your provider in config/serviceproviders.php
under 'enabled'
to load it. Extensions/providers can also be managed in config/extensions.php
.
Auto-Resolution
The container resolves type-hinted dependencies for services it knows about (registered or autowireable):
class OrderService
{
public function __construct(
private Database $db,
private EmailService $email,
private LoggerInterface $logger
) {}
}
// Container resolves dependencies
$orderService = app(OrderService::class);
Type-Hinted Parameters
public function processOrder(
Request $request,
OrderService $orders,
PaymentGateway $gateway
) {
// All parameters auto-injected
}
Service Providers
Organize container services with providers (defs + autowire/factory/alias):
use Glueful\\Container\\Providers\\BaseServiceProvider;
use Glueful\\Container\\Definition\\FactoryDefinition;
use Glueful\\Container\\Definition\\AliasDefinition;
final class CacheServiceProvider extends BaseServiceProvider
{
public function defs(): array
{
return [
// Cache store (factory)
'cache.store' => new FactoryDefinition(
'cache.store',
fn() => \\Glueful\\Cache\\CacheFactory::create()
),
// Alias for type-hints
\\Glueful\\Cache\\CacheStore::class =>
new AliasDefinition(\\Glueful\\Cache\\CacheStore::class, 'cache.store'),
];
}
}
Registering Providers
Register application providers via config/serviceproviders.php
(order preserved). Extensions/providers can also be managed in config/extensions.php
.
Contextual Binding
Note: The Laravel-style contextual binding API (when()->needs()->give()
) is not supported in Glueful’s container. Prefer explicit factories, adapters, or separate service ids registered via providers.
Practical Examples
Repository Pattern
interface UserRepositoryInterface
{
public function find($id);
public function all();
}
class UserRepository implements UserRepositoryInterface
{
public function __construct(private Database $db) {}
public function find($id)
{
return $this->db->table('users')->find($id);
}
public function all()
{
return $this->db->table('users')->get();
}
}
// Bind interface to implementation
$container->bind(
UserRepositoryInterface::class,
UserRepository::class
);
// Use in controller
class UserController
{
public function __construct(
private UserRepositoryInterface $users
) {}
public function index()
{
return Response::success($this->users->all());
}
}
Service Layer
class OrderService
{
public function __construct(
private Database $db,
private PaymentGateway $payment,
private EmailService $email,
private LoggerInterface $logger
) {}
public function createOrder(array $data): Order
{
$this->db->beginTransaction();
try {
// Create order
$order = $this->db->table('orders')->create($data);
// Process payment
$this->payment->charge($order->total, $data['payment_method']);
// Send confirmation
$this->email->send($order->email, 'order-confirmation', [
'order' => $order,
]);
$this->db->commit();
$this->logger->info('Order created', ['order_id' => $order->id]);
return $order;
} catch (\Exception $e) {
$this->db->rollback();
$this->logger->error('Order failed', ['error' => $e->getMessage()]);
throw $e;
}
}
}
// Register service via provider autowire (see AppServiceProvider above)
// Use in controller
class OrderController
{
public function store(Request $request, OrderService $orders)
{
$order = $orders->createOrder($request->all());
return Response::success($order, 201);
}
}
Factory Pattern
interface NotificationChannelInterface
{
public function send($recipient, $message);
}
class NotificationFactory
{
public function __construct(private Container $container) {}
public function make(string $channel): NotificationChannelInterface
{
return match ($channel) {
'email' => $this->container->make(EmailChannel::class),
'sms' => $this->container->make(SmsChannel::class),
'push' => $this->container->make(PushChannel::class),
default => throw new \InvalidArgumentException("Unknown channel: {$channel}"),
};
}
}
// Register factory
$container->singleton(NotificationFactory::class);
// Use factory
class NotificationService
{
public function __construct(private NotificationFactory $factory) {}
public function notify($user, $message, $channels)
{
foreach ($channels as $channelName) {
$channel = $this->factory->make($channelName);
$channel->send($user, $message);
}
}
}
Testing with DI
Dependency injection makes testing easy:
use PHPUnit\Framework\TestCase;
class OrderServiceTest extends TestCase
{
public function test_creates_order()
{
// Mock dependencies
$db = $this->createMock(Database::class);
$payment = $this->createMock(PaymentGateway::class);
$email = $this->createMock(EmailService::class);
$logger = $this->createMock(LoggerInterface::class);
// Set expectations
$payment->expects($this->once())
->method('charge')
->with(100.00, 'card_123');
// Create service with mocks
$service = new OrderService($db, $payment, $email, $logger);
// Test
$order = $service->createOrder([
'total' => 100.00,
'payment_method' => 'card_123',
]);
$this->assertNotNull($order);
}
}
Swap Bindings in Tests
class UserControllerTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
// Replace real repository with mock
app()->bind(UserRepositoryInterface::class, function () {
return new FakeUserRepository();
});
}
public function test_lists_users()
{
$response = $this->get('/api/users');
$response->assertStatus(200);
}
}
Best Practices
1. Depend on Interfaces
// ✅ Good - depend on interface
class UserService
{
public function __construct(
private CacheInterface $cache,
private LoggerInterface $logger
) {}
}
// ❌ Bad - depend on concrete class
class UserService
{
public function __construct(
private RedisCache $cache,
private FileLogger $logger
) {}
}
2. Use Constructor Injection
// ✅ Good - dependencies clear and testable
class TaskService
{
public function __construct(
private Database $db,
private EventDispatcher $events
) {}
public function create(array $data)
{
$task = $this->db->table('tasks')->create($data);
$this->events->dispatch(new TaskCreated($task));
return $task;
}
}
// ❌ Bad - hidden dependencies
class TaskService
{
public function create(array $data)
{
$task = db()->table('tasks')->create($data);
event(new TaskCreated($task));
return $task;
}
}
3. Keep Constructors Simple
// ✅ Good - only store dependencies
public function __construct(
private Database $db,
private CacheInterface $cache
) {}
// ❌ Bad - logic in constructor
public function __construct(Database $db)
{
$this->db = $db;
$this->users = $db->table('users')->get(); // Don't do this
}
4. Use Singletons Wisely
// ✅ Good - stateless singletons
$container->singleton(Database::class);
$container->singleton(CacheInterface::class);
// ⚠️ Careful - stateful singletons
$container->singleton(ShoppingCart::class); // May cause issues
Container Methods
Binding
// Bind service
$container->bind('service', callable $factory);
// Singleton (shared instance)
$container->singleton('service', callable $factory);
// Bind instance
$container->instance('service', $instance);
// Alias
$container->alias('name', 'actual-name');
Resolving
// Resolve service
$service = $container->make('service');
$service = $container->get('service');
$service = $container['service'];
// Check if bound
if ($container->bound('service')) {
//...
}
// Check if singleton
if ($container->isShared('service')) {
//...
}
Helper Functions
// Get container instance
$container = app();
// Resolve service by id or class
$db = app('database');
$cache = app(\Glueful\Cache\CacheStore::class);
// Convenience helper (same as app())
$queue = service(\Glueful\Queue\QueueManager::class);
Built-in Aliases
Common container ids and class aliases registered by core providers:
- 'logger' → \Psr\Log\LoggerInterface
- Resolve with: app('logger') or app(\Psr\Log\LoggerInterface::class)
- 'database' → \Glueful\Database\Connection
- Resolve with: app('database')
- Also available: \Glueful\Database\QueryBuilder::class, \Glueful\Database\Schema\Interfaces\SchemaBuilderInterface::class
- 'cache.store' → \Glueful\Cache\CacheStore
- Resolve with: app('cache.store') or app(\Glueful\Cache\CacheStore::class)
- 'request' → \Symfony\Component\HttpFoundation\Request (from globals)
- Queue manager (class-based): \Glueful\Queue\QueueManager via service()/app()
Note: Exact registrations can vary with environment and extensions. Check config/serviceproviders.php, config/extensions.php, and the provider classes for full lists.
More Core Services and Aliases
These are commonly available from CoreProvider and related providers:
- Logging
- 'logger' (Factory) and alias to \Psr\Log\LoggerInterface
- Database
- 'database' → \Glueful\Database\Connection
- \Glueful\Database\QueryBuilder::class (factory via connection)
- \Glueful\Database\Schema\Interfaces\SchemaBuilderInterface::class (factory via connection)
- Cache
- 'cache.store' (Factory) and alias to \Glueful\Cache\CacheStore
- HTTP/Request
- 'request' → \Symfony\Component\HttpFoundation\Request
- Auth (selected examples)
- \Glueful\Auth\AuthenticationManager (autowire)
- \Glueful\Auth\AuthenticationGuard (factory using AuthenticationService)
- \Glueful\Auth\TokenManager (factory + initializer)
- Permissions
- \Glueful\Permissions\Gate (factory configured from config/permissions.php)
- Queue & Scheduling
- \Glueful\Queue\QueueManager (autowire)
- \Glueful\Queue\Failed\FailedJobProvider (autowire)
- \Glueful\Scheduler\JobScheduler (autowire)
This list focuses on stable, commonly used services. Explore \Glueful\Container\Providers\CoreProvider, QueueProvider, SecurityProvider, SerializerProvider, HttpClientProvider, TasksProvider, etc., for the definitive set.
Custom AppServiceProvider (Quick Start)
- Create a provider (e.g.,
app/Providers/AppServiceProvider.php
):
namespace App\Providers;
use Glueful\Container\Providers\BaseServiceProvider;
use Glueful\Container\Definition\FactoryDefinition;
use Glueful\Container\Definition\AliasDefinition;
final class AppServiceProvider extends BaseServiceProvider
{
public function defs(): array
{
return [
// Autowired service
App\Services\ReportService::class => $this->autowire(App\Services\ReportService::class),
// Factory-built service with config
'reports.exporter' => new FactoryDefinition(
'reports.exporter',
fn() => new App\Services\Exporter(config('app.urls'))
),
// Alias a type-hint to an id
App\Contracts\ExporterInterface::class =>
new AliasDefinition(App\Contracts\ExporterInterface::class, 'reports.exporter'),
];
}
}
- Enable it in
config/serviceproviders.php
:
return [
'enabled' => [
App\Providers\AppServiceProvider::class,
],
];
- Resolve anywhere:
$reports = app(App\Services\ReportService::class);
$exporter = app(App\Contracts\ExporterInterface::class); // resolves to 'reports.exporter'
Troubleshooting
Class not found?
- Check namespace is correct
- Ensure class is autoloaded
- Verify binding is registered
Circular dependency?
- Review constructor dependencies
- Use setter injection if needed
- Refactor to remove circular reference
Cannot instantiate interface?
- Bind interface to implementation
- Check binding is registered before use
Wrong instance injected?
- Check binding order (last binding wins)
- Verify contextual binding if used
- Clear container cache if applicable
Next Steps
- Service Providers - Organize bindings
- Repositories - Repository pattern
- Testing - Test with DI
- Middleware - DI in middleware