I often find @inject
being overused in projects I review while mentoring. They often bring less writing, but in exchange they break SOLID principles.
Today I will show you solution that will keep your code both small and clean - Decorator feature in Nette.
As Derek Simons says says...
Why am I writing this article? I try to improve knowledge interoperability between frameworks so it is easier to understand and use each other. The goal is to discourage Nette- (or any framework-) specific things in favor of those that may be common.
Today, I will try to agree on setter injection with you.
@Inject
Overuse is SpreadingThis code is common to 80 % Nette applications I came across in last year:
// app/Presenter/ProductPresenter.php
namespace App\Presenter;
final class ProductPresenter extends AbstractBasePresenter
{
/**
* @inject
* @var ProductRepository
*/
public $productRepository;
}
Using @inject
annotations over constructor injection is fast, short and it just works.
Ok, why not use it everywhere:
// app/Repository/ProductRepository.php
namespace App\Repository;
class ProductRepository
{
/**
* @inject
* @var Doctrine
*/
public $entityManager;
}
and
# app/config/config.neon
services:
-
class: App\Repository\ProductRepository
inject: on
Why? Because "what you see is what you write". New programmer joins the teams, sees this handy @inject
feature and uses when possible and handy.
Some of you, who already talked about @inject
method usage already there are some and only few specific places to use it.
@inject
?To prevent constructor hell. If you meet this term first time, go read this short explanation by David Grudl.
The best use case is AbstractBasePresenter
.
Let's say I need Translator
service in all of my presenters.
// app/Presenter/AbstractBasePresenter.php
namespace App\Presenter;
abstract class AbstractBasePresenter extends Nette\Application\UI\Presenter
{
/**
* @inject
* @var Translator
*/
public $translator;
}
And I can use it in ProductPresenter
along with constructor injection
// app/Presenter/ProductPresenter.php
namespace App\Presenter;
final class ProductPresenter extends AbstractBasePresenter
{
/**
* @var ProductRepository
*/
private $productRepository;
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}
}
This is quite clean and easy to use, because presenters have injects enabled by default.
But what if we have other objects that:
2 common case pop to my mind:
AbstractBaseRepository
for all our repositoriesAbstractBaseControl
for all our componentsLet's take the first one:
// app/Repository/AbstractBaseRepository.php
namespace App\Repository;
use Doctrine\ORM\EntityManagerInterface;
abstract class AbstractBaseRepository
{
/**
* @var EntityManagerInterface
*/
protected $entityManager;
public function setEntityManager(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
}
And specific repository with some dependency:
// app/Repository/ProductRepository.php
namespace App\Repository;
use App\Model\Product\ProductSorter;
final ProductRepository extends AbstractBaseRepository
{
/**
* @var ProductSorter
*/
private $productSorter;
public function __construct(ProductSorter $productSorter)
{
$this->productSorter = $productSorter;
}
}
So our config would look like:
# app/config/config.neon
services:
-
class: App\Repository\ProductRepository
setup:
- setEntityManager
# and for other repositories
-
class: App\Repository\UserRepository
setup:
- setEntityManager
-
class: App\Repository\CategoryRepository
setup:
- setEntityManager
It is cleaner, but with so much writing? Thanks, but no, thanks. Let's go back to @inject
...
Wait! Before any premature conclusion, let's set the goal first.
# app/config.config.neon
services:
- App\Repository\ProductRepository
- App\Repository\UserRepository
- App\Repository\CategoryRepository
That would be great, right? Is that possible in Nette while keeping the code clean?
This feature is in Nette since 2014 (<= the best documentation for it so far).
How does it work?
# app/config/config.neon
decorator: # keyword used by Nette
App\Repository\AbstractBaseRepository: # 1. find every service this type
setup: # same setup as we use in service configuration
- setEntityManager # 2. call this setter injection on it
# or do you need to call "setTranslator" on every component?
App\Component\AbstractBaseControl:
setup:
- setTranslator
That's it!
@inject
in places where it doesn't solve any problem@inject
/inject<method>
method were born to solve is called dependency hell
In next article, we will look at other practical use cases for Decorator Extension.
How do you use @inject
, constructor injection or Decorator Extension? Let me know in the comments, I'm curious.
Happy coding!
Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors.
I'd really appreciate it!