DI Review Overview
DI Review Areas
Dependency Injection Review:
├── Constructor Injection
│ ├── All dependencies injected
│ ├── No object manager usage
│ ├── Proper type hints
│ └── Required vs optional dependencies
├── Interface Binding
│ ├── Interfaces for all services
│ ├── Proper interface binding
│ ├── No concrete class dependencies
│ └── Interface segregation
├── Virtual Types
│ ├── Proper configuration
│ ├── No overuse
│ └── Clear purpose
└── Shared Objects
├── Proper sharing configuration
├── Stateless services
├── Thread safety
└── Memory implications
Review Checklist
$diChecklist = [
'constructor' => [
'All dependencies injected via constructor',
'No ObjectManager usage',
'Proper type hints',
'Required dependencies first'
],
'interfaces' => [
'Services have interfaces',
'Dependencies on interfaces',
'No concrete class dependencies',
'Interface segregation principle'
],
'virtual_types' => [
'Clear purpose for virtual type',
'Not overused',
'Properly configured'
],
'shared' => [
'Stateless services shared',
'Stateful services not shared',
'Memory implications considered'
]
];
Constructor Injection Review
Proper Constructor Injection
// Bad: ObjectManager usage
class ProductService
{
public function getProduct($id)
{
$product = $this->_objectManager->create('Magento\Catalog\Model\Product');
$product->load($id);
return $product;
}
}
// Good: Constructor injection
class ProductService
{
private $productRepository;
public function __construct(
ProductRepositoryInterface $productRepository
) {
$this->productRepository = $productRepository;
}
public function getProduct($id)
{
return $this->productRepository->getById($id);
}
}
Required vs Optional Dependencies
// Good: Required dependencies first
class OrderService
{
public function __construct(
OrderRepositoryInterface $orderRepository, // Required
PaymentInterface $payment, // Required
LoggerInterface $logger = null // Optional
) {
$this->orderRepository = $orderRepository;
$this->payment = $payment;
$this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class);
}
}
// Bad: Optional before required
class OrderService
{
public function __construct(
LoggerInterface $logger = null, // Optional
OrderRepositoryInterface $orderRepository // Required
) {
// This causes issues with DI
}
}
Type Hints
// Good: Interface type hints
class ProductService
{
public function __construct(
ProductRepositoryInterface $repository, // Interface
LoggerInterface $logger // Interface
) {}
}
// Bad: Concrete class type hints
class ProductService
{
public function __construct(
MysqlProductRepository $repository, // Concrete
FileLogger $logger // Concrete
) {}
}
// Check for type hint violations
function checkTypeHints($class)
{
$reflection = new \ReflectionClass($class);
$constructor = $reflection->getConstructor();
if (!$constructor) {
return [];
}
$violations = [];
foreach ($constructor->getParameters() as $param) {
if ($param->getTypeHint() && !interface_exists($param->getTypeHint())) {
$violations[] = $param->getName();
}
}
return $violations;
}
Interface Binding Review
Check Interface Binding
<!-- di.xml -->
<config>
<!-- Good: Interface binding -->
<type name="Magento\Framework\Logger\Monolog\Logger">
<plugin name="my_plugin" type="Vendor\Module\Plugin\LoggerPlugin"/>
</type>
<!-- Bad: Concrete class binding -->
<type name="Magento\Framework\Logger\Monolog\Logger">
<plugin name="my_plugin" type="Vendor\Module\Plugin\LoggerPlugin"/>
</type>
</config>
Interface Segregation
// Bad: Fat interface
class ProductRepositoryInterface
{
public function get($id);
public function save($product);
public function delete($product);
public function search($query);
public function export($format);
}
// Good: Segregated interfaces
class ProductReadInterface
{
public function get($id);
public function search($query);
}
class ProductWriteInterface
{
public function save($product);
public function delete($product);
}
class ProductExportInterface
{
public function export($format);
}
No Concrete Dependencies
// Bad: Depends on concrete class
class OrderService
{
private $mysqlOrderRepository;
public function __construct(
MysqlOrderRepository $repository // Concrete
) {
$this->mysqlOrderRepository = $repository;
}
}
// Good: Depends on interface
class OrderService
{
private $orderRepository;
public function __construct(
OrderRepositoryInterface $repository // Interface
) {
$this->orderRepository = $repository;
}
}
// Configuration
<type name="Vendor\Module\Service\OrderService">
<arguments>
<argument name="repository" xsi:type="object">MysqlOrderRepository</argument>
</arguments>
</type>
Virtual Type Review
Virtual Type Usage
<!-- Good: Virtual type for specific configuration -->
<config>
<virtualType name="CustomLogger" type="Magento\Framework\Logger\Monolog\Logger">
<arguments>
<argument name="name" xsi:type="string">custom_channel</argument>
<argument name="handlers" xsi:type="array">
<item name="stream" xsi:type="object">CustomStreamHandler</item>
</argument>
</arguments>
</virtualType>
<!-- Use virtual type -->
<type name="Vendor\Module\Service\MyService">
<arguments>
<argument name="logger" xsi:type="object">CustomLogger</argument>
</arguments>
</type>
</config>
<!-- Bad: Overuse of virtual types -->
<!-- Multiple virtual types for same purpose -->
<!-- Unclear purpose -->
Virtual Type Best Practices
// When to use virtual types
$useCases = [
'configuration' => 'Different configuration for same class',
'testing' => 'Mock objects for testing',
'multi-tenant' => 'Different implementations per tenant'
];
// When NOT to use virtual types
$avoidCases = [
'simple_dependency' => 'Just use interface binding',
'overuse' => 'Too many virtual types confuse DI',
'unclear_purpose' => 'If you can\'t explain why, don\'t use'
];
// Review virtual types
function reviewVirtualTypes($config)
{
$virtualTypes = $config->getVirtualTypes();
$issues = [];
foreach ($virtualTypes as $name => $type) {
// Check for overuse
if (count($virtualTypes) > 10) {
$issues[] = 'Too many virtual types';
}
// Check for clear purpose
if (!$this->hasClearPurpose($name, $type)) {
$issues[] = "Virtual type $name lacks clear purpose";
}
}
return $issues;
}
Practice Problems
Review module for DI best practices: constructor injection, interface usage, shared objects.
Solution
// Findings:
// 1. ObjectManager usage → Constructor injection
// 2. Concrete class dependency → Interface
// 3. Stateful service shared → Non-shared
// 4. Virtual type overuse → Simplify
// Results: Proper DI implementation Quiz
1. What is constructor injection?
2. Why use interfaces for dependencies?
3. When should objects be non-shared?
4. What is a virtual type?
Flashcards
Question
Constructor injection?
Click to reveal answer
Answer
Dependencies as constructor parameters, no ObjectManager
Question
Why interfaces?
Click to reveal answer
Answer
Loose coupling, easy testing, swappable implementations
Question
Shared vs non-shared?
Click to reveal answer
Answer
Shared: Stateless services; Non-shared: Stateful/request-specific
Question
Virtual type purpose?
Click to reveal answer
Answer
Type alias with custom configuration for DI
Question
DI review checklist?
Click to reveal answer
Answer
Constructor injection, interfaces, shared objects, virtual types
Revision Notes
Key Takeaways
- 1. Constructor injection: Dependencies as parameters, no ObjectManager
- 2. Interfaces: Loose coupling, easy testing, swappable
- 3. Shared: Stateless services; Non-shared: Stateful/request-specific
- 4. Virtual types: Type alias with custom configuration
- 5. Review: Constructor, interfaces, shared objects, virtual types
Interview Tips
- • Explain constructor injection benefits
- • Discuss interface vs concrete dependencies
- • Know when to use shared vs non-shared
- • Understand virtual type purpose
Cheat Sheet
DI Review
- Constructor: Dependencies as params, no ObjectManager
- Interfaces: Loose coupling, testing
- Shared: Stateless; Non-shared: Stateful
- Virtual: Type alias with config
- Review: Constructor, interfaces, shared, virtual