In my Symfony 4.4 project I want to add specific method every time I trigger setSomething() setter method, but perhaps I can use traits?
Can traits have services injected?
Yes you can, but it's not injected automatically like autowiring in services.
Here's an example i made with TranslatorInterface injected in my service and use in my trait :
trait MyTrait
{
private TranslatorInterface $translator;
public function setTranslator(TranslatorInterface $translator): self
{
$this->translator = $translator;
return $this;
}
public functio myTraitFunction()
{
// use $this->translator
}
}
class MyClass
{
use MyTrait;
public function __construct(TranslatorInterface $translator)
{
$this->setTranslator($translator);
}
public function somFunction(): something
{
// use $this->myTraitFunction()
}
}
``
On a side note, maybe useful for future readers. If you have a trait based on a service, or on class member, I would go for an abstract method like
trait MyTrait
{
protected abstract function getTranslator(): TranslatorInterface;
[...]
}
and then implement the method inside the class that uses the trait.
Personally I would prefer not to depend on "setter injection" and bound two methods that are only logically coupled.
If you embrace this, the question is not actually a question as you can inject service in the canonical way.
Related
Symfony's SessionInterface is still a little bit vague / magic for me. So if someone can enlighten me, please.
Naturally, the request contains a session object and can be used within a controller.
class SimpleController extends AbstractController
{
public function index(Request $request): Response
{
$request->getSession()->set('session-var', 10);
}
}
However, within a service you can also include the SessionInterface as a service and work with, presumably, the same ParameterBag.
class SimpleService
{
public function __construct(SessionInterface $session)
{
$session->set('session-var', 10);
}
}
My colleague pointed out that a session is (and should always be) part of the request. This makes sense to me, but also got me thinking: why are you able to us the SessionInterface as a service when it is a object/property of the request.
What I want to achieve in the end is to include a service in my controller and in this service work with the current session.
Code example of my use case would look something like this.
class SimpleController extends AbstractController
{
private $simpleService;
public function __construct(SimpleService $simpleService)
{
$this->simpleService = $simpleService;
}
public function index(Request $request): Response
{
$this->simpleService->doSomething();
}
}
class SimpleService
{
private $session;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public function doSomething()
{
// Do some things..... and save the result in the session.
$this->session->set('just_a_preference', 50);
}
}
So:
Is this the/a correct way to work with sessions in Symfony?
Is 'just_a_preference' saved in the same session parameter bag as my $request->getSession()?
If not, what is the correct way to work with the current request session outside the controller?
If work with Symfony 4.4.
Both methods are fine. They both reference the same object. Accessing the session via the interface is preferred though. I can come up with at least three reasons why:
extend the functionality: the service behind the interface can be easily decorated – eg. LoggingSessionDecorator which would log session activity
proper OOP code: with $request->getSession()->set(…) you violate the Law of Demeter principle, that said:
it's easier to mock the session in your unit tests
In the end – yes, it doesn't matter. Both methods reference the same object
As said in 1, better to use the interface. At least in my opinion.
class EtudiantController extends AbstractController
{
private $etudiant ;
private $form ;
public function __construct()
{
$this->etudiant = new Etudiant();
$this->form = $this->createForm(EtudiantType::class, new Etudiant());
}
}
** i'v got an error when instantiate a form in a constructor using the createForm() function **
Here is the wrong way to solve your problem:
class EtudiantController extends AbstractController
{
private $form;
public function __construct(FormFactoryInterface $formFactory)
{
$this->form = $formFactory->create(TextType::class, new Etudiant());
}
}
I say it is wrong (even though it will work) because creating things like forms really should be done in individual controller actions, not hidden in the constructor. You might be trying to apply Dont Repeat Yourself (DRY) but in cases like this, Don't Confuse Your Future Self takes precedence.
And as far as why injecting the form factory is necessary, I would once again urge you to look at the Symfony source code for AbstractController as well as ControllerTrait. Understanding how dependency injection works is critical to being able to effectively use the framework.
Is it a good practice to have a service getter for frequently used services in a controller? For example I mean:
class SomeController Extends Contorller {
private function getSomethingManager()
{
return $this->get('myvendorname.something.manager');
}
}
Your example is a bit confusing because you can use the Doctrine service directly with your controller. You can inject it in your Action if you use the Autowire function.
public function test(EntityManagerInterface $em) {
}
Then you have the entity manager injected or you can load it over the controller with:
$this->getDoctrine()->getManager()
So this is not a real good example. When you use autowire all classes are registered as service and you can use it.
For database queries you have to use entities and repositories.
https://symfony.com/doc/current/doctrine.html
If you are above Symfony 3.3 you can use a Service Locater. You list all common services in Service Locator class. When you need to fetch a specific service from anywhere (from example, Controller, Command, Service so on), all you have to do is, inject ServiceLocator class and fetch required service via ServiceLocator:locate.
It is pretty simple and useful. It helps you to reduce dependency injection as well. Have a look at the full example in the link above.
class ServiceLocator implements ServiceLocatorInterface, ServiceSubscriberInterface
{
private $locator;
public function __construct(ContainerInterface $locator)
{
$this->locator = $locator;
}
public static function getSubscribedServices()
{
return [
ModelFactoryInterface::class,
CalculatorUtilInterface::class,
EntityManagerInterface::class,
AnotherClass::class,
AndAnother::class,
];
}
public function get(string $id)
{
if (!$this->locator->has($id)) {
throw new ServiceLocatorException(sprintf(
'The entry for the given "%s" identifier was not found.',
$id
));
}
try {
return $this->locator->get($id);
} catch (ContainerExceptionInterface $e) {
throw new ServiceLocatorException(sprintf(
'Failed to fetch the entry for the given "%s" identifier.',
$id
));
}
}
}
And this is how you use it: ServiceLocator->locate(AnotherClass::class);
I'm trying to create a manager to handle basic requests of a controller (list, new, edit, delete). I need to inject the form factory within the constructor of this service. By what name should I call?
I need something like this:
lp_ExpedienteManager:
class: AppBundle\Services\ExpedienteManager\ExpedienteManager
arguments: [ "#doctrine.orm.entity_manager", "#security.token_storage", "#form_factory" ]
Thanks for your time!
For future references, since Symfony 3.3 this service is available as Symfony\Component\Form\FormFactoryInterface. So you can inject in your services like
use Symfony\Component\Form\FormFactoryInterface;
class AccountBridge
{
private $formFactory;
public function __construct(FormFactoryInterface $formFactory)
{
$this->formFactory = $formFactory;
}
public function accountCreateAction(Account $account)
{
$form = $this->formFactory->create(AccountType::class, $account);
}
}
what is the best practice for injecting repositories, entity managers to a service?
I figured I can do it in at least three ways.
1. Injecting repositories, managers through the constructor
This way the service can be covered with tests quite easily. All you need is to pass mocked dependencies to the constructor and you're ready.
class TestService
{
public function __construct(MyEntityRepository $my, AnotherEntityRepository $another, EntityManager $manager)
{
$this->my = $my;
$this->another = $another;
$this->manager = $manager;
}
public function doSomething()
{
$item = $this->my->find(<...>);
<..>
$this->manager->persist($item);
$this->manager->flush();
}
}
2. Passing just the EntityManager
This is a bit more difficult to test if you need like 4 repositories from the same manager. I figured this way you have to mock manager's getRepository calls.
class TestService
{
public function __construct(EntityManager $manager)
{
$this->manager = $manager;
}
public function doSomething()
{
$item = $this->manager->getRepository('my')->find(<...>);
<..>
$this->manager->persist($item);
$this->manager->flush();
}
}
3. Passing the whole registry
This way you don't get circular reference exception with doctrine event subscribers, but it's harder to mock everything.
Also this is the only way sensiolabs insights doesn't give me an architectural violation for injecting EntityManager.
class TestService
{
public function __construct(RegistryInterface $registry)
{
$this->doctrine = $registry;
}
public function doSomething()
{
$item = $this->registry->getManager()->getRepository('my')->find(<...>);
<..>
$this->registry->getManager()->persist($item);
$this->registry->getManager()->flush();
}
}
What is the best practice to do this and why?
I always try to inject my services as specific as possible.
Which means I always inject repositories since that is easier when writing tests. Otherwise you have to mock the registry and or manager too.
I know this is old but I just thought I'd add my 2 cents. I follow what Matthias Noback says in these two blog posts:
Inject a repository instead of the EntityManager
Inject the ManagarRegistry instead of the EntityManager
So I inject the specific repository whenever I need to find an entity, but I also inject the ManagerRegistry if I need to call flush, persist or remove, because otherwise you have to put proxy functions for them in all your repositories.