How to use dependency injection in validator constraints with Doctrine & Symfony - php

I'm working on an platform which contains lot of page with form and i'm looking for a way to inject (DI) service in my Entity/Constraint Validator.
I tried many way, but never managed to do 100% what i wanted it.
Let me explain en detail :
my stack : Php 8.0, Symfony 5.4, Doctrine 2
To make it simple, i will take the example of create page Form.
The form is build with a dedicated FormType (let's say DogType).
The Entiy related to this FormType is Dog
I'm using static loader to load constraints loadValidatorMetadata()
The field dog_race has a constraint ChoiceType.
The data of choice should be filled with a method from a service AnimalManager, method getRaces. The constructor of this service is also using DI to get some other service such as Logger etc.
So, i know we can do something like that :
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
[...]
$metadata->addPropertyConstraints('dog_race',
[
new Assert\Choice([
'callback' => [DogManager::class, 'getRaces'],
'message' => 'doc.incorrect_race'
])
]
)
}
But this callback works only with static method.
If i'm not mistaken, we can use only static loader, php attribut and annotation, right ?
But in all cases, i don't see how i can inject a service for the constraint.
My question is, how can i inject my service DogManager to be used in the constraint ?
If not possible, any recommendation/alternative to do something similar ?
I have the feeling i have to create custom constraint just to be able to inject my service.. :(
Thanks

Related

Symfony registering services with ContainerControllerResolver

I am trying to use the Symfony dependency injection component and I was just wondering if anyone would be able to help with the registering of the services.
I have recently moved to ContainerControllerResolver opposed to ControllerResolver so that my routes correctly instantiate the dependencies.
This is working well enough however I have ran into an issue when a class is needed more than once.
So for ContainerControllerResolver to work you seem to have to use the full class path. Where as when you use ControllerResolver you can set your own unique string ID.
I can't see anyway to set a unique ID when using ContainerControllerResolver and you don't seem able to pass arguments to the Reference class.
Is there anyway I could rewrite the below so they are seperate instances? The ControllerResolver way of being able to set unique IDs makes sense to me but I'm just a little lost when you have to pass the full class.
$containerBuilder->register(App\Models\Database::class, App\Models\Database::class)->setArguments([
DB1, DB1USER, DB1PASS
]);
$containerBuilder->register(App\Models\News::class, App\Models\News::class)->setArguments([
new Reference(App\Models\Database::class)
]);
$containerBuilder->register(App\Models\Database::class, App\Models\Database::class)->setArguments([
DB2, DB2USER, DB2PASS
]);
$containerBuilder->register(App\Models\Reports::class, App\Models\Reports::class)->setArguments([
new Reference(App\Models\Database::class)
]);
I think I got the wrong gist of things from another post. They mentioned it is more ideal to use the controller name in the ID when using the ContainerControllerResolver. I've been able to use service IDs in the first param of the register and I've just updated my routes _controller param to go to that same name rather than the class namespace path:
$routes = new RouteCollection();
$routes->add('news', new Route('/news', [
'_controller' => 'controller.news::newsOutput',
]));

Add data return in all actions with #Template annotation

I need to return default data in all actions in my Symfony project.
For example search form in side bar, viewers counter etc...
So i need to return some default data in all actions
return array(
'form' => $form->createView(),
'short_search' => $shortSearch->createView(),
);
I found Add data to return of all actions in a Symfony controller solution, but it fails when I'm using #Template annotation.
Of course i can call render function from twig, but it seems like it's not fast and good idea.
What component I should override in this case???
The Controllers section of the Symfony Best Practices document advises against using the #Template() annotation, so the easy fix to your problem would be to simply not use #Template().
The reason overriding the base Controller's render method doesn't work is because you're not actually calling it, and neither is the framework. Instead, the SensioFrameworkExtraBundle #Template annotation works by installing an event listener for KernelEvents::VIEW (kernel.view) and (after having used a different event to guess the template name, if necessary), directly uses the templating service to render the response.
In the generic case, what you can do instead is install an event listener on kernel.view with a higher priority, and using the GetResponseForControllerResultEvent event provided to add in your parameters. This event listener might look something like
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$parameters = $event->getControllerResult();
//modify parameters
$event->setControllerResult($parameters);
}
with whatever services necessary to get the additional parameters passed in via dependency injection. You may also want to look at the implementation for #Template's TemplateListener for reference.
The Symfony Cookbook has more information on how to set up event listeners.
In your specific case, you're probably going to be generating your $form and $shortSearch entirely from within that event handler, so at the least, your event handler is going to need at least the form service injected.
In my opinion, this is all largely more trouble than it's worth, and it would be better to just remove the #Template annotation instead. (As a bonus, you'll get a minor performance boost, especially if you disable the annotations entirely, because you won't have the overhead of calling those event listeners on every request.)

Using $input->all() instead of Input::all() Laravel-5

I'm trying to use $input->all() as opposed to Input::all() in Laravel-5, however it doesn't seem to like it, even though I am passing the Input reference to the function, like so:
/**
* Search for a specified resource.
*
* #return Response
*/
public function search(Booking $booking, Input $input)
{
dd($input->all()); // this doesn't work
dd(Input::all()); // this DOES work
}
The error I get is:
Call to undefined method Illuminate\Support\Facades\Input::all()
Does anyone have a solution to this problem?
I don't think you're supposed to inject Facades into your Controllers. Input is a facade for Illuminate\Http\Request and it's service container binding is request. So according to the documentation, in Laravel 5 you can do Request::all() and in Laravel 5.1 you can do $request->all()
http://laravel.com/docs/5.0/requests#retrieving-input
http://laravel.com/docs/5.1/requests#retrieving-input
EDIT: This post gives some more in-depth information: https://stackoverflow.com/a/29961400/2433843
EDIT3: I think it would be great if someone could explain WHY exactly you can't inject Facades into your Controllers. I understand DI and Facades are two different things entirely, and L5+ is pushing the developers towards DI. I just don't exactly understand why injecting a facade wouldn't work, since it points towards another class, and it works when you do not inject it. Not to forget Facades and Aliases are two seperate things too. I hope someone can elaborate on this.
One more important thing about using Request or Input to access the User Input is the version of Laravel that you are using.
In the Laravel 4.2 and prior, you could have access the Input::all(), Input::get() but from Laravel 5 onwards, it's been suggested to use the Input via Request facade
Ref: https://laravel.com/docs/5.2/requests
In case if you want to make use of Input in Laravel 5.0 and onwards, then you need to add this facade in the config/app.php file under the aliases section as 'Input' => Illuminate\Support\Facades\Input::class
Once you add the facade under alias, you should start using the 'Input::all()'
Hope this helps some others, who are having the confusion as whether to use 'Input' or 'Request' for Laravel 5.0 onwards.

Symfony2 impersonate listener with parameters injection

i'm currently working on a custom logic impersonate listenener.
i overrided the Symfony\Component\Security\Http\Firewall\SwitchUserListener because i would like to perform call to some entities repositories ( not User entity ) before authorize the switch event :
for example , i would like to authorize the switch if and only if the user to impersonate has already gave rights to user requesting for the switch.
is it possible to inject new parameters such as doctrine service or some arrays values to an overrided listener ?
the call to my custom SwitchUserListenener :
in services.yml
parameters:
security.authentication.switchuser_listener.class: acme\appBundle\EventListener\SwitchUserListener
Your solution may be here:
https://github.com/symfony/symfony/blob/2.6/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php#L597
The service security.authentication.switchuser_listener defined by Symfony is an abstract service. It is not actually the service being used as the listener, but it is the same class.
Go up in the code a bit to:
https://github.com/symfony/symfony/blob/2.6/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php#L233
foreach ($firewalls as $name => $firewall) {
list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds);
This is the name of the firewall you created where the user switch will be possible.
Now you have a couple of options to override the listener.
You can continue to override the class parameter, then manipulate the arguments as Symfony is already doing:
$listener->replaceArgument(1, new Reference($userProvider));
$listener->replaceArgument(3, $id);
$listener->replaceArgument(6, $config['parameter']);
$listener->replaceArgument(7, $config['role']);
Or more simply, compile additional method calls in your extension.
The other solution, would be to create a CompilerPass which systematically deletes the original listeners and replaces them with your own. This might be achievable in your own bundles extension provided it is loaded after the SecurityBundle.
$container->removeDefinition($id);
EDIT:
Another solution, perhaps more simple than the above, is to create another event listener.
One which fires on the request. The important thing here is the listener must have a higher priority to the listeners you are overriding (I used listener(s) because there are potentially more than one switch user listener).
The tricky part will be getting a list of the overridden listeners IDs, because you are going to need to loop over them and call your own methods to inject your new dependencies after the listeners are instantiated, but before they triggered.

How do I pass a parameter to a validation constraint in Symfony2 - in yml

I am trying to add a bundle-wide parameter to my application so that I can add it to my Validation Constraint file (validation.yml):
myApp\myBundle\Entity\Contact:
properties:
name:
- NotBlank: { message: "%myvariable%" }
I added my parameter normally in config.yml:
parameters:
# Validation config
myvariable: Please tell us your name.
But the page just renders the %myvariable% text, rather than the desired string. I also wish to use this parameter in my FormBuilderInterface when adding the validation messages to the page for usage in JavaScript. Does yml allow this? If not, how do I include such a parameter at a higher level?
No, it's not currently possible.
It has nothing to do with YAML or XML or even service definitions. Validator component reads validation rules by itself - as you can see, the structure is quite different than for service definitions. Unfortunately, it does not replace the parameters in constraints.
The main logic resides in \Symfony\Component\Validator\Mapping\Loader\YamlFileLoader which is created by \Symfony\Component\Validator\ValidatorBuilder::getValidator.
You could make this happen by:
Overriding definition of validator.builder service.
It's constructed using %validator.builder.factory.class%::createValidatorBuilder, but as you have to get parameter bag somehow, there is not enough dependencies - class factory is in use, not service factory.
Creating new class, which extends ValidatorBuilder.
It should take parameter bag into constructor or via setter. It should be configured in step (1) to be passed here.
This class would create file loaders of another class (see 3), also pass that parameter bag into it.
Creating new classes for YamlFileLoader and YamlFilesLoader. Additional 2 for each format that you would want to support.
It would additionally take parameter bag into constructor and override some functionality. For example, I think all parameter handling could be done in newConstraint method - iterate through options, resolve parameters, then call parent method with replaced options.
It's nice that Symfony could be extended like that (possibly not so nicely in this use-case), but I guess it would be easier to just write your own constraint with custom constraint validator, which would inject that parameter into it.
Also consider a wrapper around validator service - if you just need to replace the validation messages, you could replace the validator service, injecting original one into it. See http://symfony.com/doc/current/service_container/service_decoration.html for more information.

Categories