Dependency Injection Without Pain: Symfony, Laravel, and Drupal Explained
Introduction
Dependency Injection (DI) is a cornerstone of modern PHP frameworks, but many developers—especially those coming from Drupal—find it confusing. Why inject services instead of calling them directly? How do DI containers differ between Drupal, Symfony, and Laravel?
In this article, we’ll break it down with practical examples, showing how to use DI without headaches across these three systems.
1. What is Dependency Injection?
Simply put:
Dependency Injection is a way to give a class the objects it needs rather than letting the class create them itself.
Benefits:
- Loose coupling – classes don’t need to know implementation details
- Testability – dependencies can be mocked
- Flexibility – change services without modifying the consumer
2. Drupal DI: Services and Container
2.1 Define a Service
my_module.services.yml
services:
my_module.logger:
class: Drupal\my_module\Service\UserLogger
arguments: ['@logger.channel.user']
2.2 Inject Service in a Controller
namespace Drupal\my_module\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\my_module\Service\UserLogger;
class MyController extends ControllerBase {
public function __construct(private UserLogger $logger) {}
public function logUser(): string {
$this->logger->log('Test message');
return 'Logged!';
}
}
✅ Key Points
- Drupal uses Symfony DI container
- Services can be injected in controllers, forms, or other services
- Reduces use of global functions (
\Drupal::service())
3. Symfony DI: Explicit and Powerful
3.1 Define Service in YAML
services:
App\Service\UserLogger:
arguments: ['@logger']
3.2 Inject in Controller
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Service\UserLogger;
class UserController extends AbstractController {
public function index(UserLogger $logger) {
$logger->log('User accessed page');
return $this->json(['status' => 'ok']);
}
}
✅ Key Points
- DI is core philosophy
- All services are typed and resolved automatically
- Works well with autowiring and autoconfiguration
4. Laravel DI: Service Providers and Constructor Injection
4.1 Bind Service in ServiceProvider
$this->app->bind(UserLogger::class, function($app) {
return new UserLogger($app->make('log'));
});
4.2 Inject in Controller
namespace App\Http\Controllers;
use App\Services\UserLogger;
class UserController extends Controller {
public function index(UserLogger $logger) {
$logger->log('User accessed page');
return response()->json(['status' => 'ok']);
}
}
✅ Key Points
- Laravel auto-resolves services by type
- Service Providers are entry points for registering dependencies
- Less strict than Symfony, more flexible than Drupal
5. Comparison Table
| Aspect | Drupal | Symfony | Laravel |
|---|---|---|---|
| DI container | Symfony-based | Symfony-native | Laravel-native |
| Autowiring | Yes | Yes | Yes |
| Manual binding | Optional | Optional | Common |
| Injection in controllers | ✅ | ✅ | ✅ |
| Injection in forms/plugins | ✅ | Limited | ❌ |
6. Practical Tips to Avoid DI Pain
- Always type-hint services in constructors
- Avoid calling global service functions (
\Drupal::service()) - Keep services single-responsibility
- Use service configuration files consistently
- Prefer autowiring over manual injection when possible
7. Conclusion
Dependency Injection may seem tricky at first, but:
- Drupal = DI + CMS features + Plugin API
- Symfony = strict, typed, architectural DI
- Laravel = developer-friendly, auto-resolving DI
Mastering DI across these three frameworks will make your code:
- cleaner
- testable
- reusable
Comments