I'm browsing the Symfony 2 docs related to Dependency Injection, and can't find a reference to autowiring. I found a bundle that offers some of this functionality, but it's still in beta and seems to be tied to annotations (correct me if I'm wrong).
What I'm looking for is an object (such as the service container), that could inject dependencies in my services, via setter injection.
For example, I would define a Service:
class Service {
/**
* #var \PDO
*/
protected $pdo;
/**
* #param \PDO $pdo
* #Inject
*/
public function setPDO(\PDO $pdo) {
$this->pdo = $pdo;
}
}
And then, I could use this hypothetical service container to inject dependencies in the Service, even if this one has been created outside the container:
$service = new Service();
// ...
$container->inject($service);
Is there a DI container that could autowire dependencies this way?
Since Symfony 2.8, autowiring is natively supported: https://github.com/symfony/symfony/pull/15613
There is also autowiring bundle aviable at https://github.com/kutny/autowiring-bundle.
See the #InjectParams annotation from JMSDiExtraBundle.
Related
How to autowire nette services from the container, to be accessible to be used inside presenters ( controllers ) or models ,etc?
To autowire services in Nette to be easily retrievable, used, they must be registered in a config.neon config, or must be those preset by default by framework, in multiple ways:
specifying and passing by interface, class type in the constructor
use App\Model; // or use App\Model\ArticleRepository;
public function __construct(
Model\ArticlesRepository $articlesRepository,
...
)
{
$this->articlesRepository = $articlesRepository;
autowiring via a #inject tag attribute annotation
/** #var \Nette\Http\Request #inject */ // however , this one is just example, the request service is already available in the presenters via $this->request attribute
public $request;
the list of all available services including those configured in configs, also along those default predefined can be seen at the same place at the Nette Tracy Debugger bar -> DIC (dependency injection container) button
i have a factory for creating an object.
Abbruchprotokoll::class => factory(function(ContainerInterface $c){
return new Abbruchprotokoll($c->get(Request::class)->getRouterParam('stts-id'), $c->get(MySQL::class));
})
the factory is creating that object with a string and a dependency injection (MySQL Class).
In my Abbruchprotokoll::class i have an inject annotation:
/**
* #Inject
* #var \Smarty
*/
protected $smarty;
the problem is, that this inject annotation is not resolved. i think this is because i am on FactoryResolver and there is no injectMethodsAndProperties() like in ObjectCreator.
can i use injection annotations with factories in some other way?
You cannot use annotations with factories, you need to use autowire(Abbruchprotokoll::class) instead. autowire() asks for the class to be autowired, which resolves the annotations.
I am trying to implement a SimpleCache concrete instance in one of my service classes to also allow caching, however I am having some issues at wiring the dependencies.
config/services.yml
services:
Psr\SimpleCache\CacheInterface: '#Symfony\Component\Cache\Simple\FilesystemCache'
src/Services/Shouter/Sources/Twitter.php
<?php
namespace App\Services\Shouter\Sources;
use Psr\SimpleCache\CacheInterface;
class Twitter
{
/**
* Cache instance
* #var Psr\SimpleCache\CacheInterface
*/
protected $cache;
public function __construct(CacheInterface $cache)
{
$this->cache = $cache;
}
}
This is the error that I get:
You have requested a non-existent service "Symfony\Component\Cache\Simple\FilesystemCache".
Fixed by adding Symfony\Component\Cache\Simple\FilesystemCache: in the services.yaml.
I have a service class that has dependencies on multiple entity repositories, for example 4.
I could inject each repository and end up with many dependencies, or I could inject the entity manager as a single dependency; relying on EntityManager->getRepo('xyz').
Separate dependencies has the benefit of code hinting.
Single dependency means less verbose at construct.
Possibly easier mocking and less setup?
What is considered a better practice?
In this case EntityManager is something like Service Locator. When service depends on EntityManager, it also formally depends on all its API and all related objects (repositories, metatada, etc). Better inject only what you really need:
explicit injection of specific repositories makes your service easier to read and test.
Also, prefer interface over class if possible (ObjectRepository instead of EntityRepository, ObjectManager instead of EntityManager).
I assume, that you must use only one Doctrine Entity Manager in your service dependencies.
But if you want to have code hinting in your IDE, you can do it with phpdoc annotation like this
class SomeServiceWithDoctrineDependency
{
/** #var YourFirstObjectRepository */
protected $firstRepo;
/** #var YourSecondObjectRepository */
protected $secondRepo;
/** #var YourThirdObjectRepository */
protected $thirdRepo;
public function __construct(EntityManagerInterface $entityManager)
{
$this->firstRepo = $entityManager->getRepository('First:Repo');
$this->secondRepo = $entityManager->getRepository('Second:Repo');
$this->thirdRepo = $entityManager->getRepository('Third:Repo');
}
public function getCodeHint()
{
// You get hint here for find method
// $this->thirdRepo()->find()...
}
}
For example i have algorithmic function, which calculates specific hash-code. Function itself is 300+ lines of code. I need to use that functions many times in many different controllers in my bundle. Where can i store my calculate_hash() to use it in my bundle ? Can i access it from other bundles ?
Can i also write global calculate_hash() which have access to entity manager ?
Didn't find my answer here.
In the Symfony2 world, this is clearly belonging to a service. Services are in fact normal classes that are tied to the dependency injection container. You can inject them the dependencies you need. For example, say your class where the function calculate_hash is located is AlgorithmicHelper. The service holds "global" functions. You define your class something like this:
namespace Acme\AcmeBundle\Helper;
// Correct use statements here ...
class AlgorithmicHelper {
private $entityManager;
public function __construct(EntityManager $entityManager) {
$this->entityManager = $entityManager;
}
public function calculate_hash() {
// Do what you need, $this->entityManager holds a reference to your entity manager
}
}
This class then needs to be made aware to symfony dependecy container. For this, you define you service in the app/config/config.yml files by adding a service section like this:
services:
acme.helper.algorithmic:
class: Acme\AcmeBundle\Helper\AlgorithmicHelper
arguments:
entityManager: "#doctrine.orm.entity_manager"
Just below the service, is the service id. It is used to retrieve your service in the controllers for example. After, you specify the class of the service and then, the arguments to pass to the constructor of the class. The # notation means pass a reference to the service with id doctrine.orm.entity_manager.
Then, in your controller, you do something like this to retrieve the service and used it:
$helper = $this->get('acme.helper.algorithmic');
$helper-> calculate_hash();
Note that the result of the call to $this->get('acme.helper.algorithmic') will always return the same instance of the helper. This means that, by default, service are unique. It is like having a singleton class.
For further details, I invite you to read the Symfony2 book. Check those links also
The service container section from Symfony2 book.
An answer I gave on accesing service outside controllers, here.
Hope it helps.
Regards,
Matt
Braian in comment asked for Symfony 3 answer, so here is one Symfony 3.3 (released May 2017):
1. The original class remains the same
namespace Acme\AcmeBundle\Helper;
use Doctrine\ORM\EntityManager;
final class AlgorithmicHelper
{
/**
* #var EntityManager
*/
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function calculateHash()
{
// Do what you need, $this->entityManager holds a reference to your entity manager
}
}
2. Service registration is much simpler
# app/config/services.yml
services:
_defaults: autowire # this enabled constructor autowiring for all registered services
Acme\AcmeBundle\Helper\AlgorithmicHelper: ~
3. Use constructor injection to get the service
use Acme\AcmeBundle\Helper\AlgorithmicHelper;
class SomeController
{
/**
* #var AlgorithmicHelper
*/
private $algorithmicHelper;
public function __construct(AlgorithmicHelper $algorithmicHelper)
{
$this->algorithmicHelper = $algorithmicHelper;
}
public function someAction()
{
// some code
$hash = $this->algorithmicHelper->calculateHash();
// some code
}
}
You can read about Symfony 3.3 dependency injection (in this case registering services in config and using it in controller) news in these 2 posts:
https://www.tomasvotruba.cz/blog/2017/05/07/how-to-refactor-to-new-dependency-injection-features-in-symfony-3-3/
https://symfony.com/blog/the-new-symfony-3-3-service-configuration-changes-explained