Symfony - Inject and access DIC service in Doctrine EntityRepository - symfony

This is relevant to legacy project written in Symfony 2.2.1, doctrine 2.3.3.
I need to access DIC #service in one of EntityRepository classes.
Am i able to inject this service, or container into it via some event listeners or somehow else?
I dont want to inject it into Entity, but particular EntityRepository.

You can try to declare your Repo as service and add calls to inject your other service
#services.yml
entity.repo :
class: 'YourRepoNamespace'
factory-service: 'doctrine.orm.default_entity_manager'
factory-method: 'getRepository'
arguments: ['YourEntityNamespace']
calls:
- ['setOtherService', ['#other_service']]

Ok, this is really not clean solution, but it works even if you want to keep getting the repo by $entityManaget->getRepository().
Since the project will be deprecated soon, its good enough...
Just add this DIC getter to your EntityRepository class and you are able to get any DIC service in any Repository in project...
protected function getDICService($serviceName)
{
/** #var \AppKernel $kernel */
$kernel = $GLOBALS['kernel'];
$container = $kernel->getContainer();
return $container->get($serviceName);
}

Related

How to use Symfony autowiring with multiple entity managers

I would like to use the autowiring in a service that use 2 different entity manager. How to achieve something like that ?
use Doctrine\ORM\EntityManager;
class TestService
{
public function __construct(EntityManager $emA, EntityManager $emB)
{
}
}
My service.yml file use to be configured like that :
app.testservice:
class: App\Services\TestService
arguments:
- "#doctrine.orm.default_entity_manager"
- "#doctrine.orm.secondary_entity_manager"
There are already two good answers posted but I'd like to add a third as well as some context to help chose which approach to use in a given situation.
emix's answer is very simple but a bit fragile in that it relies on the name of the argument for injecting the correct service. Which is fine but you won't get any help from your IDE and sometimes might be a bit awkward. The answer should probably use EntityManagerInterface but that is a minor point.
DynlanKas's answer requires a bit of code in each service to locate the desired manager. It's okay but can be a bit repetitive. On the other hand, the answer is perfect when you don't know in advance exactly which manager is needed. It allows you to select a manager based on some dynamic information.
This third answer is largely based on Ron's Answer but refined just a bit.
Make a new class for each entity manager:
namespace App\EntityManager;
use Doctrine\ORM\Decorator\EntityManagerDecorator;
class AEntityManager extends EntityManagerDecorator {}
class BEntityManager extends EntityManagerDecorator {}
Don't be alarmed that you are extending a decorator class. The class has the same interface and the same functionality as a 'real' entity manager. You just need to inject the desired manager:
# config/services.yaml
App\EntityManager\AEntityManager:
decorates: doctrine.orm.a_entity_manager
App\EntityManager\BEntityManager:
decorates: doctrine.orm.b_entity_manager
This approach requires making a new class for each entity manager as well as a couple of lines of configuration, but allows you to simply typehint against the desired class:
public function __construct(AEntityManager $emA, BEntityManager $emB)
{
}
It is, arguably, the most robust and standard way to approach the original question.
Dylan's answer violates the Demeter's Law principle. It's very easy and elegant since Symfony 3.4, meet Local service binding:
services:
_defaults:
bind:
$emA: "#doctrine.orm.default_entity_manager"
$emB: "#doctrine.orm.secondary_entity_manager"
Then in your service the autoloading will do the hard work for you:
class TestService
{
public function __construct(EntityManager $emA, EntityManager $emB)
{
…
}
}
The easy way would be to autowire ManagerRegistry in your constructor and use it to get the managers you want by using the names of the entity manger you have set in your configuration file (doctrine.yaml) :
use Doctrine\Common\Persistence\ManagerRegistry;
class TestService
{
private $emA;
private $emB;
public function __construct(ManagerRegistry $doctrine)
{
$this->emA = $doctrine->getManager('emA');
$this->emB = $doctrine->getManager('emB');
}
}
And you should be able to use them as you want.
Another way would be to follow this answer by Ron Mikluscak
Simply use EntityManagerInterface $secondaryEntityManager
If you're using Symfony's framework bundle (which I'm pretty sure you are), then Symfony >= 4.4 automatically generates camelcased autowiring aliases for every Entitymanger you define.
You can simply get a list of them using the debug:autowiring console command. For your configuration above, this should look something like this:
bin/console debug:autowiring EntityManagerInterface
Autowirable Types
=================
The following classes & interfaces can be used as type-hints when autowiring:
(only showing classes/interfaces matching EntityManagerInterface)
EntityManager interface
Doctrine\ORM\EntityManagerInterface (doctrine.orm.default_entity_manager)
Doctrine\ORM\EntityManagerInterface $defaultEntityManager (doctrine.orm.default_entity_manager)
Doctrine\ORM\EntityManagerInterface $secondaryEntityManager (doctrine.orm.secondary_entity_manager)
As described in https://symfony.com/doc/4.4/doctrine/multiple_entity_managers.html:
Entity managers also benefit from autowiring aliases when the framework bundle is used. For example, to inject the customer entity manager, type-hint your method with EntityManagerInterface $customerEntityManager.
So you should only need:
use Doctrine\ORM\EntityManagerInterface;
class TestService
{
public function __construct(
EntityManagerInterface $defaultEntityManager,
EntityManagerInterface $secondaryEntityManager
) {
// ...
}
}
The name $defaultEntityManager isn't mandatory, though it helps to distinguish between the two. Every argument that's typehinted with an EntityManagerInterface and isn't in the list returned by debug:autowiring EntityManagerInterface will result in the default Entitymanager being autowired.
Note: As written in the documentation and shown in the output of debug:autowiring, you need to use EntityManagerInterface for this aliased autowiring, not the actual EntityManager class. In fact, you should always autowire the Entitymanager using EntityManagerInterface.

Symfony: How to use configuration parameters in a service class?

I want to use the parameters in parameters.yml in my service class mailer
but I got this error while instantiate the mailer class:
$mailer = new Mailer();
knowing that the parameters are defined in parameters.yml:
Warning: Missing argument 1 for AppBundle\Service\Mailer::__construct(), called in
namespace AppBundle\Service
class Mailer
{
private $mailer_user;
private $mailer_password;
private $mailer_name;
private $mailer_host;
public function __construct($mailer_user, $mailer_password ,$mailer_name ,$mailer_host)
{
$this->mailer_name = $mailer_user;
$this->mailer_password = $mailer_password;
$this->mailer_user = $mailer_name;
$this->mailer_host = $mailer_host;
}
//.....
}
services.yml:
mailer:
class: 'AppBundle\Service\Mailer'
arguments: [%mailer_user%, %mailer_password% ,%mailer_name% ,%mailer_host%]
To learn "how can I use the parameters in parameters.yml in my service class" (and to see how a service can be used in a Symfony app) just read the Symfony docs paying attention to the Symfony version of the documentation you are reading:
Introduction to Parameters
How to Set external Parameters in the Service Container
Service Container
BTW you should never instanciate a service class directly like you did:
$mailer = new Mailer();
but retrieve the service instance from the Service Container (Symfony will take care to automatically inject all the configured dependancies) like we usually do in a Controller (the example below access the Service using the shortcode provided extending the base Controller provided by the FrameworkBundle included in the Symfony standard version):
$mailer = $this->container->get('mailer');

Service in symfony2 - how service file should look like?

I am trying to create service in symfony2 which will verify if session contains certain information and if not redirect the user to another controller. I want this piece of code to work as a service as I will be using it in many controllers.
I have problem as manual on Symfony2 book does not provide information how service file should look like. Should it be a normal php class?
Please find below dump of my files with information on error that I receive.
In \AppBundle\Services I create file my_isbookchosencheck.php containing:
<?php
namespace AppBundle\my_isbookchosencheck;
class my_isbookchosencheck
{
public function __construct();
{
$session = new Session();
$session->getFlashBag()->add('msg', 'No book choosen. Redirected to proper form');
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
}
}
My service.yml:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
My conntroller file:
/**
* This code is aimed at checking if the book is choseen and therefore whether any further works may be carried out
*/
$checker = $this->get('my_isbookchosencheck');
Error:
FileLoaderLoadException in FileLoader.php line 125: There is no extension able to load the configuration for "my_isbookchosencheck" (in C:/wamp/www/symfony_learn/app/config\services.yml). Looked for namespace "my_isbookchosencheck", found "framework", "security", "twig", "monolog", "swiftmailer", "assetic", "doctrine", "sensio_framework_extra", "fos_user", "knp_paginator", "genemu_form", "debug", "acme_demo", "web_profiler", "sensio_distribution" in C:/wamp/www/symfony_learn/app/config\services.yml (which is being imported from "C:/wamp/www/symfony_learn/app/config\config.yml").
There are few mistakes that you made, which I am going to explain in short, and I will give you an example of the service you want to create.
You created your service in AppBundle\Services, yet your namespace is registered differently - namespace AppBundle\Services\my_isbookchosencheck;. It should be namespace AppBundle\Services;. I would also advise you to use singular names when creating directories - in this case Service would be better, instead of Services.
You're using your __constructor directly to apply some logic and return the result of it. Better way would be to create a custom method, which could be accessed when necessary.
You're creating new instance of Session which means that you wont be able to access anything that was previously added and stored in session. The right way here, would be to inject RequestStack which holds the current Request and get the session from there.
I believe you also registered your service wrong. In your services.yml file, it should be under services: option. This is why you got the error you pasted.
So, let's see how your service should like.
services.yml
services:
book_service:
class: AppBundle\Service\BookService
arguments:
- #request_stack
- #router
BookService.php
namespace AppBundle\Service;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
class BookService {
/* #var $request Request */
private $request;
/* #var $router RouterInterface */
private $router;
public function __construct(RequestStack $requestStack, RouterInterface $router) {
$this->request = $requestStack->getCurrentRequest();
$this->router = $router;
}
public function isBookChoosen() {
$session = $this->request->getSession();
// Now you can access session the proper way.
// If anything was added in session from your controller
// you can access it here as well.
// Apply your logic here and use $this->router->generate()
}
}
Now in your controller you can simply use it like this:
$this->get('book_service')->isBookChoosen()
Well this is a short example, but I hope you got the idea.
try
services:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
in your services.yml, and check that you use the correct namespaces.
Your Class is fine and it should work, however may i suggest that you use
symfony2 session service instead of creating the session object yourself, you can pass it as a constructor argument:
<?php
// namespace edited
namespace AppBundle\Services;
use Symfony\Component\HttpFoundation\Session\Session;
class my_isbookchosencheck
{
public function __construct(Session $session);
{
$session->getFlashBag()->add('msg', 'No book choosen. Redirected to proper form');
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
}
}
and then edit your services.yml accordingly, so the service container will inject the session object:
services:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
arguments: [#session]
Also check out his question on so:
How do you access a users session from a service in Symfony2?
Services are just regular PHP classes, nothing special. But you must register it in order to be recognized by the system. Here are the steps how you do it,
Create a regular PHP class (you can inject other services if it requires)
namespace Acme\DemoBundle\Service;
class MyService
{
private $session;
public function _construct(SessionInterface $session /* here we're injecting the session service which implements the SessionInterface */)
{
$this->session = $session;
}
// other methods go here, which holds the business logic of this class
}
ok, we created a class, we need to register it to be able to use it by service container, here how you do it:
the simplest way is to put it into config.yml file, like this:
services:
my_service:
class: Acme\DemoBundle\Service\MyService
arguments:
- #session
or, another way, is to create a file (e.g. services.yml, may be in config folder), and import it inside the config.yml file (the content of the file is the same as the first way):
imports:
- { resource: services.yml }
or, you can create a services.yml(the content of the file is the same as the first way) file inside you bundle's Resources folder, specify it under the load method of your Extension class (under the DependencyInjection folder), (this way requires some special directory and file structure, read about it in the doc):
class AcmeDemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources'));
$loader->load('services.yml');
}
}
In you case, you're not registering your service, the service container just couldn't find it. Register it by one of the above ways.

generateUrl outside controller

Is there a possibility to use generateUrl() method outside of controllers?
I tried to use it in a custom repository class with $this->get('router'), but it didn't work.
update
I've found a temporary solution here:
http://www.phamviet.net/2012/12/09/symfony-2-inject-service-as-dependency-in-to-repository/
I injected the whole service container into my repository, although it's "not recommended".
But it works for now.
update2
Injecting router instead of the whole container is probably a better idea :)
If you take a look in the source code of Controller::generateUrl(), you see how it's done:
$this->container->get('router')->generate($route, $parameters, $referenceType);
Basically you just enter the name of the route ($route here); if exists, some parameters ($parameters) and the type of reference (one of the constants of the UrlGeneratorInterface)
Don't inject the container into your repository... Really, don't !
If I were you, I would create a service and injects the router in it. In this service, I would create a method, that uses the repository and adds the needed code using the router.
That's way less dirty and easy to use/understand for another developer.
Inject the router itself into your EntityRepsitory (like described on Development Life blog's post Symfony 2: Injecting service as dependency into doctrine repository), then you can use $this->router->generate('acme_route');
in symfony 4 and Sylius when the FormType extends an (ex.) AbstractResourceType
class PostType extends AbstractResourceType
{
private $router;
public function __construct(RouterInterface $router, $dataClass, $validationGroups = [])
{
$this->router = $router;
parent::__construct($dataClass, $validationGroups);
}
}
Services.yaml :
app.post.form.type:
class: App\Form\Admin\Post\PostType
tags:
- { name: form.type }
arguments: ['#router.default', '%app.model.post.class%' ]

Symfony2-How to use access a service from outside of a controller

In my Symfony2 controller, this works fine:
$uploadManager = $this->get('upload.upload_manager');
but when I move it to a custom Listener:
use Doctrine\ORM\Event\LifecycleEventArgs;
use Acme\UploadBundle\Upload\UploadManager;
class PersonChange
{
public function postRemove(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
$uploadManager = $this->get('ep_upload.upload_manager');
echo "the upload dir is " . $uploadManager->getUploadDir();
}
}
I get an error:
Fatal error: Call to undefined method Acme\MainBundle\Listener\PersonChange::get() in /home/frank/...
I know I must need a use statement but don't know what to use.
Update: Defining controllers as services is no longer officially recommended in Symfony.
The get() method in the Controller class is just a helper method to get services from the container, and it was meant to get new Symfony2 developers up to speed faster. Once people get comfortable with the framework and dependency injection, it's recommended to define controllers as services and inject each required service explicitly.
Since your PersonChange class is not a controller and doesn't extend the Controller class, you don't have that get() helper method. Instead, you need to define your class as a service and inject needed services explicitly. Read the Service Container chapter for details.
As I ran into the exact same problem maybe I can help
What Elnur said is perfectly fine and I'll just try to pop up a real life example.
In my case I wanted to access
$lucenemanager = $this->get('ivory.lucene.manager')
Even by extending the controller I couldn't get it to work while the controller does access the container (I still did not understand why)
In config.yml my listener (searchindexer.listener) is declared as follow :
services:
searchindexer.listener:
class: ripr\WfBundle\Listener\SearchIndexer
arguments:
luceneSearch: "#ivory_lucene_search"
tags:
- { name: doctrine.event_listener, event: postPersist }
A service (ivory.lucene.search) is passed as argument in my service/listener.
Then in my class
protected $lucenemanager;
public function __construct($luceneSearch)
{
$this->lucenemanager = $luceneSearch;
}
Then you can use the get method against $this
An approach that always works, despite not being the best practice in OO
global $kernel;
$assetsManager = $kernel->getContainer()->get('acme_assets.assets_manager');‏
If you need to access a Service, define it in the class constructor:
class PersonChange{
protected $uploadManager;
public function __construct(UploadManager $uploadManager){
$this->uploadManager = $uploadManager;
}
// Now you can use $this->uploadManager.
}
Now you can pass the Service as argument when calling the class (example 1) or define the clas itself as a Service (recommended, example 2)
Example 1:
use Acme\PersonChange;
class appController{
function buzzAction(){
$uploadManager = $this->get('upload.upload_manager');
$personChange = new PersonChange($uploadManager);
Example 2 (better):
Define PersonChange as a Service itself, and define the other Service as an argument in services.yml file:
# app/config/services.yml
services:
upload.upload_manager:
class: AppBundle\uploadManager
PersonChange:
class: AppBundle\PersonChange
arguments: ['#upload.upload_manager']
In this way, you don't have to bother with the upload_manager service in the Controller, since it's implicitely passed as an argument for the constructor, so your Controller can be:
class appController{
function buzzAction(){
$personChange = $this->get('PersonChange');

Resources