Day 4: 4 Ways to Dependency Injection
Today is Day 4 of the Magento Advent Calendar, and I’ll show you three super-practical Dependency Injection examples you can start using immediately.
No theory overload — just real Magento code.
Example 1: Injecting a Service into a Block
We’ll inject a Logger into a block.
This is the simplest and most common DI pattern.
<?php
declare(strict_types=1);
namespace MageMastery\Module\Block;
use Magento\Framework\View\Element\Template;
use Psr\Log\LoggerInterface;
class Info extends Template
{
public function __construct(
Template\Context $context,
private readonly LoggerInterface $logger,
array $data = []
) {
parent::__construct($context, $data);
}
public function getMessage(): string
{
$this->logger->info('Get message from MageMastery');
return "Hello from MageMastery Advent Day 4!";
}
public function getCurrentTimestamp(): string
{
return date('Y-m-d H:i:s');
}
}
Magento’s Object Manager creates this block and gives it the logger automatically.
No manual instantiation. No new Logger().
Example 2: Injecting a Custom Model into a Controller
Here we inject our own service — a custom model — into a controller.
This is the clean way to work with business logic.
<?php
declare(strict_types=1);
namespace MageMastery\Module\Controller\Example;
use MageMastery\Module\Model\DataProvider;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\ResultInterface;
class Show implements HttpGetActionInterface
{
public function __construct(
private readonly JsonFactory $jsonFactory,
private readonly DataProvider $dataProvider,
) {
}
public function execute(): ResultInterface
{
$result = $this->jsonFactory->create();
$result->setData([
'value' => $this->dataProvider->getValue(),
]);
return $result;
}
}
And the DataProvider class:
<?php
declare(strict_types=1);
namespace MageMastery\Module\Model;
class DataProvider
{
public function getValue(): string
{
return 'Hello from DataProvider';
}
}
Example 3: Preference Replacement (Swapping Implementations)
And here’s a more advanced example.
We replace a core interface implementation with a custom class using DI preferences.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Catalog\Api\ProductRepositoryInterface"
type="MageMastery\Module\Model\ProductRepository" />
</config>
Custom ProductRepository class:
<?php
declare(strict_types=1);
namespace MageMastery\Module\Model;
use Magento\Catalog\Api\ProductRepositoryInterface;
class ProductRepository implements ProductRepositoryInterface
{
public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveOptions = false)
{
// TODO: Implement save() method.
}
public function get($sku, $editMode = false, $storeId = null, $forceReload = false)
{
// TODO: Implement get() method.
}
public function getById($productId, $editMode = false, $storeId = null, $forceReload = false)
{
// TODO: Implement getById() method.
}
public function delete(\Magento\Catalog\Api\Data\ProductInterface $product)
{
// TODO: Implement delete() method.
}
public function deleteById($sku)
{
// TODO: Implement deleteById() method.
}
public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
{
// TODO: Implement getList() method.
}
}
Now every time Magento needs ProductRepositoryInterface, it uses our custom class instead.
This is one of the most powerful features of DI in Magento.
Example 4: virtualType Instead of Full Preference (Real-World DI Example)
In many Magento projects, you don’t actually want to override an entire interface with a new class via preference.
That’s too heavy-handed and affects the whole system.
Instead, you want a custom version of a repository or model only for one specific class without impacting other functionality.
And that’s exactly what virtualType is made for.
We have a service class that depends on Magento\Catalog\Api\ProductRepositoryInterface.
But for this service only, we want a modified repository that logs extra debug information or loads products differently.
Instead of replacing the interface globally, we use a virtualType just for this one service.
Step 1: Create a virtualType of the existing repository
<virtualType name="MageMastery\Module\Model\ProductRepository"
type="Magento\Catalog\Model\ProductRepository">
<arguments>
<argument name="debugMode" xsi:type="boolean">true</argument>
</arguments>
</virtualType>
We created a customized “copy” of Magento’s internal ProductRepository class and added a DI argument (debugMode) — without touching the original class and without creating a preference. Magento will construct this repository only when specifically requested.
Step 2: Inject the virtualType into your service
Service using the modified repository:
<?php
declare(strict_types=1);
namespace MageMastery\Module\Model;
class ProductProcessor
{
private $repository;
public function __construct(
\Magento\Catalog\Api\ProductRepositoryInterface $repository
) {
$this->repository = $repository;
}
public function process(int $productId)
{
$product = $this->repository->getById($productId);
// processing logic...
return $product->getName();
}
}
Step 3: Bind this service to the virtualType (NOT globally)
We don’t want ProductRepositoryInterface everywhere to become our custom version only inside ProductProcessor.
So we override just one dependency:
<type name="MageMastery\Module\Model\ProductProcessor">
<arguments>
<argument name="repository" xsi:type="object">
MageMastery\Module\Model\DebugProductRepository
</argument>
</arguments>
</type>
Now the magic happens:
- Everywhere else in Magento, the repository works normally.
- But in
ProductProcessor, Magento will inject our virtualType, which is a customized clone ofProductRepository. - No preferences, no rewriting interfaces, no side effects.
This is a clean and safe way to customize behavior for a specific class without disturbing the rest of the system.
Virtual types allow you to create customized instances of existing classes for specific scenarios. Instead of overriding the entire interface globally with a preference — which can be risky — you create a lightweight virtual type and inject it only where needed.
This is one of the most powerful and safest Dependency Injection techniques in Magento 2.
These are four practical ways to use Dependency Injection in Magento 2:
Injecting services, injecting custom models, and replacing implementations.
Tomorrow is Day 5 — subscribe so you don’t miss it.
You may also want to watch a video I prepared as part of Day 4 of the Magento Advent Calendar dedicated to Dependency Injection.
