How should I configure an object dynamically when using Symfony's DI? - symfony

I'm using a YAML configuration to wire my dependencies, and I need to provide some runtime information to get a useful object back. I was going to run a setter method from my code once the object has been injected, but I Was wondering if there was a better way of doing it (or if there's something I'm missing).
This is the gist of my configuration:
services:
example_object : "myObject"
arguments : ["%object_parameter1%"]
parameters:
object_parameter1 : Some Static Data
object_parameter2 : #Rutime info required

For retrieving the current logged in user in any service, inject the security.context. In this case I use setter injection to simply user mock injection.
namespace Acme\ExampleBundle\Foo;
use Symfony\Component\Security\Core\SecurityContextInterface;
class MyService
{
private $param;
private $user;
public function __construct($param)
{
$this->param = $param;
}
/**
* Retrieve the current logged in user from the security context.
*/
public function setUserFromContext(SecurityContextInterface $context)
{
$this->user = $context->getToken()->getUser();
}
/**
* Set any user object.
*
* Usefull for testing, to inject a simple user mock.
*/
public function setUser($user)
{
$this->user = $user;
}
public function doSomething()
{
// do something with the user object
}
}
Define the service:
services:
my_service:
class: Acme\ExampleBundle\Foo\MyService
arguments: ["%object_parameter1%"]
calls:
- [ setUserFromContext, [#security.context] ]

You should not try to add dynamic values directly into the DI configuration. Symfony services configuration is reflected by compiled DI container and recompilation is very heavy operation.
If you do not want to couple your service with Symfony's security system directly, you can add your custom "user provider" service as a dependency. Then you will need to rewrite this service if the source of information will change. It may be also easily mocked.
You can also use a factory to inject a user object instead of user provider service.

Related

Symfony 3 service configuration providing dynamic configuration

I would like to know how I could avoid setter injection under the following use case.
I have a collection of tagged services:
interface EmailFormatter
class CvEmailFormatter implements EmailFormatter
class RegistrationEmailFormatter implements EmailFormatter
class LostPasswordEmailFormatter implements EmailFormatter
I use a CompilerPass to inject these into a mailer service by invoking its addEmailFormatter() method.
Each EmailFormatter instance requires some configuration that must be retrieved at run time from the current user:
/**
* #var FormatterConfig[]
* #ORM\OneToMany(targetEntity="AppBundle\Entity\FormatterConfig", mappedBy="user", cascade={"persist"}, orphanRemoval=true)
*/
protected $formatterConfigs;
Right now I am checking to see if the user has created a configuration for a particular EmailFormatter inside the mailer service's addEmailFormatter() method. If they have then setConfig() injects the configuration object. If no config exists yet the EmailFormatter does not get added at all.
public function addEmailFormatter(EmailFormatter $formatter, $alias)
{
$formatterConfig = $this->user->getFormatterConfig($alias);
if (null !== $formatterConfig) {
$this->formatters[$alias] = $formatter;
$formatter->setConfig($formatterConfig);
}
}
Could this be a legitimate use case given I am working with a mutable collection of services?

Access configuration value inside a symfony 2 entity

What is the best way to access configuration values inside an entity in a symfony 2 application?
I've searched about this and i've found two solutions:
Define the entity as a service and inject the service container to access configuration values
And this approach which defines a class in the same bundle of the entity with static methods that allows to get the parameter value
Is there any other solution? What's the best workaround?
Your entity shouldn't really access anything else, apart from associated entities. It shouldn't really have any connection outwardly to the outside world.
One way of doing what you want would be to use a subscriber or listener to listen to the entity load event and then pass that value in to the entity using the usual setter.
For example....
Your Entity
namespace Your\Bundle\Entity;
class YourClass
{
private $parameter;
public function setParameter($parameter)
{
$this->parameter = $parameter;
return $this;
}
public function getParameter()
{
return $this->parameter;
}
...
}
Your Listener
namespace Your\Bundle\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Your\Bundle\Entity\YourEntity;
class SetParameterSubscriber implements EventSubscriber
{
protected $parameter;
public function __construct($parameter)
{
$this->parameter = $parameter;
}
public function getSubscribedEvents()
{
return array(
'postLoad',
);
}
public function postLoad(LifecycleEventArgs $args)
{
/** #var YourEntity $entity */
$entity = $args->getEntity();
// so it only does it to your YourEntity entity
if ($entity instanceof YourEntity) {
$entity->setParameter($this->parameter);
}
}
}
Your services file.
parameters:
your_bundle.subscriber.set_parameter.class:
Your\Bundle\EventListener\SetParameterSubscriber
// Should all be on one line but split for readability
services:
your_bundle.subscriber.set_parameter:
class: %your_bundle.subscriber.set_parameter.class%
arguments:
- %THE PARAMETER YOU WANT TO SET%
tags:
- { name: doctrine.event_subscriber }
You shouldn't need a configuration in your entity.
For example you have File entity and you need to save a file represented by this entity to a disk. You need some parameter, let say "upload_dir". You can pass somehow this parameter to the entity and define a method inside this entity which saves a file to upload dir. But better way would be create a service which would be responsible for saving files. Then you can inject configurtion into it and in save method pass entity object as an argument.

Accessing generateURL() from Entity class

I would like to access generateUrl in my entity class. You can access generateUrl in controller class like this:
$url = $this->generateUrl('your_route_name', array(/* parameters */));
Accoring do this article, I should try to 'create a service, and inject the router component in it'. Then I am reading Symfony Docs: Service Container and try configuration, but I still can not make it.
in app/config/config.yml
services:
router:
class: ???
arguments: ???
How can I make router as service?
update
Why I want to use generateUrl in Entity class?
I am using Eko/FeedBundle. It requires to implements getFeedItemLink() in Entity. I need to give URL as return value of this function.
The short answer is: you don't use the router inside entities and you don't create entities as services. You should create a separate service which is responsible for creating urls (if you wrote more about what you are trying achieve it would be simpler to give you more appropriate example).
For example:
use Symfony\Bundle\FrameworkBundle\Routing\Router;
class YourCustomUrlGenerator
{
private $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function generateUrl(YourEntity $entity)
{
// generate the url
}
}
Then define your service for DIC:
services:
custorm_url_generator:
class: Namespace\YourCustomUrlGenerator
arguments: [#router]

Injecting #session into EntityRepository

I wanted to override the default Doctrine\ORM\EntityRepository class in my Symfony2 project so that I can have access to the #session service so that all of my repositories have access to a certain session variable if it is set.
On investigation it appeared to be less simple than I had hoped, as the EntityRepository is instantiated from within the Doctrine\ORM\EntityManager, and this class is instantiated itself using a static "create" method.
I followed the answer in Injecting dependency into entity repository but have hit a roadblock in actually implementing the custom manager class (specifically where the answer's author says "But since you're making a custom entity manager, you can wire it up to the service container and inject whatever dependencies you need").
I have defined my overridden EntityManager class, with an overridden "create" function and have also overridden the "getRepository" function. It is in this function that I believe I need to add the session to the Repository as it is created using a "setSession" method on my overridden EntityRepository class, but I am unsure as to how to actually get the session into the manager in the first place, as the other constructor arguments for the EntityManager class (Connection $conn, Configuration $config, EventManager $eventManager) are supplied in the Symfony\Bundle\DoctrineBundle\DependencyInjection\DoctrineExtension "ormLoad" method.
I have also specified
doctrine.orm.entity_manager.class: Me\MyBundle\Doctrine\ORM\EntityManager
in my config.yml file.
How can I have Symfony use my custom EntityManager class when creating repositories, and inject the session into it as well?
Florian, here, explained how to create repository via service:
my_service:
class: Doctrine\Common\Persistence\ObjectRepository
factory_service: doctrine # this is an instance of Registry
factory_method: getRepository
arguments: [ %mytest.entity% ]
You could add calls to invoke setSession (as deferred DI):
my_service:
...
calls:
- [setSession, ["#session"]]
Is this you're trying to do?
I ended up going with something slightly different:
I overrode the doctrine.orm.entity_manager.class parameter with my custom class which simple extended the default class with an additional $session parameter (complete with getter and setter), along with overridden create and getRepository functions (to return instances of my class instead of the default.
I then overrode the EntityRepository class and implemented a "getSession" method which returned
$this->_em->getSession();
and finally, in a custom event listener which has access to the entity manager, I called
$this->entityManager->setSession($session);
which gave me access to the session from every repository.
In Symfony 4+ you can just make it a ServiceEntityRepository and with autowiring there's no need for any services.yaml changes.
namespace App\Repository;
use App\Entity\YourEntity;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class YourRepository extends ServiceEntityRepository
{
private $session;
public function __construct(ManagerRegistry $registry, SessionInterface $session)
{
$this->session = $session;
parent::__construct($registry, YourEntity::class);
}
public function findSomethingUsingSession()
{
$someValue = $this->session->get('some_index');
// ...
}
}
Then in your controller (for example)
$repository = $this->getDoctrine()->getRepository(YourEntity::class);
$result = $repository->findSomethingUsingSession();
Or use dependency injection (recommended)
public function someAction(YourRepository $repository)
{
$result = $repository->findSomethingUsingSession();
// ...
}

FOSUserBundle: Get EntityManager instance overriding Form Handler

I am starting with Symfony2 and I am trying to override FOS\UserBundle\Form\Handler\RegistrationFormHandler of FOSUserBundle.
My code is:
<?php
namespace Testing\CoreBundle\Form\Handler;
use FOS\UserBundle\Model\UserInterface;
use FOS\UserBundle\Form\Handler\RegistrationFormHandler as BaseHandler;
use Testing\CoreBundle\Entity\User as UserDetails;
class RegistrationFormHandler extends BaseHandler
{
protected function onSuccess(UserInterface $user, $confirmation)
{
// I need an instance of Entity Manager but I don't know where get it!
$em = $this->container->get('doctrine')->getEntityManager();
// or something like: $em = $this->getDoctrine()->getEntityManager
$userDetails = new UserDetails;
$em->persist($userDetails);
$user->setId($userDetails->getId());
parent::onSuccess($user, $confirmation);
}
}
So, the point is that I need an instance of Doctrine's Entity Manager but I don't know where/how get it in this case!
Any idea?
Thanks in advance!
You should not use EntityManager directly in most of the cases. Use a proper manager/provider service instead.
In case of FOSUserBundle service implementing UserManagerInterface is such a manager. It is accessible through fos_user.user_manager key in the service container (which is an allias to fos_user.user_manager.default). Of course registration form handler uses that service, it is accessible through userManager property.
You should not treat your domain-model (i.a. Doctrine's entities) as if it was exact representation of the database-model. This means, that you should assign objects to other objects (not their ids).
Doctrine is capable of handling nested objects within your entities (UserDetails and User objects have a direct relationship). Eventually you will have to configure cascade options for User entity.
Finally, UserDetails seems to be a mandatory dependency for each User. Therefore you should override UserManagerInterface::createUser() not the form handler - you are not dealing with user's details there anyway.
Create your own UserManagerInterface implementation:
class MyUserManager extends \FOS\UserBundle\Entity\UserManager {
/**
* {#inheritdoc}
*/
public function createUser() {
$user = parent::createUser();
$user->setUserDetails(new UserDetails());
// some optional code required for a proper
// initialization of User/UserDetails object
// that might require access to other objects
// not available inside the entity
return $user;
}
}
Register your own manager as a serive inside DIC:
<service id="my_project.user_manager" class="\MyProject\UserManager" parent="fos_user.user_manager.default" />
Configure FOSUserBundle to use your own implementation:
# /app/config/config.yml
fos_user:
...
service:
user_manager: my_project.user_manager

Resources