Symfony 4 extend request for validation - php

I'm currently working on an API project. I'm used to Laravel and now I need to work with Symfony. I want to use the request like Laravel does for validation.
So I extend the Symfony\Component\HttpFoundation\Request class. There I made some logic to check and sanitize the incoming request.
After that I add my newly created request to the store function in the controller. But that gives me an error:
Argument 1 passed to App\Controller\Ticket\TicketController::store() must be an instance of App\Validation\Ticket\TicketStoreRequest, instance of Symfony\Component\HttpFoundation\Request given,/vendor/symfony/http-kernel/HttpKernel.php on line 149 {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Argument 1 passed to App\\Controller\\Ticket\\TicketController::store() must be an instance of App\\Validation\\Ticket\\TicketStoreRequest, instance of Symfony\\Component\\HttpFoundation\\Request given
After some googling, I found a few options.
Add every controller action to the service.yaml
Create a listener and add the validation on correct route
But all options require extra information on a different place. I hope someone has a better idea.

I found a few hints that might point you in the right direction:
Symfony ships with five value resolvers in the HttpKernel component:
...
RequestValueResolver
Injects the current Request if type-hinted with Request or a class extending Request.
see https://symfony.com/doc/current/controller/argument_value_resolver.html
Then the page goes on to describe the implementation of a custom RequestAttributeValueResolver. This custom resolver can be registered in your
services.yml.
Though in this example a single Class is created for one single attribute type (User), there are ways to create a more dynamic implementation.
In this example the ArgumentMetadata parameter has a method $argument->getType() that should contain a string representation of the type that is checked against:
if (User::class !== $argument->getType()) {
return false;
}
Nothing is stopping you from checking against an array of supported Request-types. This array can be managed as a class member in your custom RequestValueResolver. The only requirement for your custom RequestValueResolver class, is that the supports() method returns true for supported Request types, and that the resolve() function returns an instance of this supported Request type. This should be straightforward because both methods are supplied the 'desired' class through the ArgumentMetaData parameter.
As an alternative, you can implement a custom RequestValueResolver for each custom Request type you want to support, but this does not feel very elegant.
I can not guarantee this will work and I'm also not sure about the differences between implementing the RequestAttributeValueResolver in the example and implementing a custom RequestValueResolver, but I have a feeling it might just work, with a bit of elbow grease.
For reference: https://api.symfony.com/4.1/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.html

Here is a solution that elaborates on my other example. It's not as secure as my other example, but it satisfies your requirements.
public function supports(Request $request, ArgumentMetadata $argument)
{
$desiredRequestClass = $argument->getType();
return class_exists($desiredRequestClass);
}
public function resolve(Request $request, ArgumentMetadata $argument)
{
$desiredRequestClass = $argument->getType();
$customRequest = new $desiredRequestClass();
$customRequest->createFromGlobals();
// TODO: more initialization you might need.
yield $customRequest;
}
It might be advisable to check if $desiredRequestClass is a descendant of Request.

Related

Symfony validator service and TypeErrors in setters

I'm currently confused by the way the Symfony validator service works. The way I currently understand it, it could completely fail and not report errors even before it can even validate an entity if a TypeError occurs while setting values.
The Symfony documentation uses Constraints in entities like this:
namespace App\Entity;
// ...
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* #Assert\NotBlank
*/
private $name;
}
Which are then used like this in the controllers:
public function author(ValidatorInterface $validator)
{
$author = new Author();
// ... do something to the $author object
$author->setBirthDate('this will fail and not report'); // I added this line in myself, see rest of question.
$errors = $validator->validate($author);
if (count($errors) > 0) {
/*
* Uses a __toString method on the $errors variable which is a
* ConstraintViolationList object. This gives us a nice string
* for debugging.
*/
$errorsString = (string) $errors;
return new Response($errorsString);
}
return new Response('The author is valid! Yes!');
}
However, this will not nicely catch the exceptions that will be thrown when arguments of the wrong type are passed towards the setters of the entity variables. For example, the entity could have a field "birthDate" which is a DateTime, and which has a setter setBirthDate(DateTime $foo). When building the object before being able to call the validate() function, one could pass an argument of an incorrect type - say, the user submitted a string or nothing at all - which will obviously raise an exception.
Assuming that the validator service is supposed to be used like this, my question is as follows: how do I cleanly handle the data which could raise TypeErrors in the setters?
Do I not do any type hinting in my setters (accepting everything) and then validate it being a DateTime with the validator later on? Do I use a try/catch block while setting up the entity? Do I manually check the type of the user input before calling the setter? And if any of the last two, how would I cleanly report errors to the user? And even then, I think it would feel wrong to also be doing manual validation there when you're also doing it in the validator service.
I know about Forms and I assume this is not an issue when using those, but I still find the validator confusing either way.
You are missing the point of how the code should work, if you specify that the setter will accept DateTime object and DateTime object only PHP itself will ensure that the accepted argument is an instance of DateTime object. One way to go around that is to create a DTO(Data transfer object) eg. CreateAuthorRequest and create a symfony form type where you accept either string/DateTime(preferably string which will have a regex ensuring that the string is a valid datetime string). Then after you call $form->isValid() you can read the data from the DTO and be sure that it's valid. If you'll still struggle how this should be implemented correctly comment below the answer and I'll provide code samples
Also I wouldn't recommend to put that much annotations in your entity it will get messy pretty quick, prefer XML config over annotations.

How does PHP function dependancy work?

I have an example here from the framework Laravel.
class UserController extends Controller
{
public function store(Request $request)
{
$name = $request->input("name");
}
}
What I don't understand is that Request is explicitly defined within the function signature of store().
Couldn't php's type inference figure out what is being passed into the function?
Would this even be considered dependency injection?
This is method level dependency injection. The Request is being passed into the store method, instead of the store method knowing how to create the Request object.
The type hint in the method signature does two things.
First, for plain PHP, it enforces the fact that the parameter passed to the store method must be a Request object, or an object that inherits from Request. If you attempt to call the store() method with anything other than a Request object (or descendant), you will get an error.
Because of this strict type enforcement, we know that the $request parameter must be a Request object (or descendant), and we can depend on its public interface being available for use (you know the input() method will exist).
Second, for Laravel, it tells the Laravel container what type of object this method needs, so that the correct object can be resolved out of the container and passed to the method. Laravel's controller methods are called through the container, which allows automatic dependency resolution of method calls.
If you were to remove the type hint, this would cause two issues. At an abstract level, it would allow you to pass any type of object, or even scalar values, to the store() method. If you attempt to call input() on a string, you're going to have problems. At a more concrete level, this will break Laravel's automatic dependency resolution of controller methods. Without the type hint, Laravel can't know what object the method requires, so it won't pass in anything, so in reality you'd get an error saying that you can't call input() on null (or an error saying the store method requires one parameter; not sure, didn't test).

How can I efficiently structure this code in Laravel?

I have a class called Sharer, which accepts a Service interface in the construct method. Service can be FacebookService or TwitterService and so on. Each Service class has a post method, that posts whatever array data you pass to that method, using its own connection (either facebook, twitter in this example).
Now the Sharer class normalizes data before sending it to that service's post method. By normalizing, it checks whether the thing we are sending to that post method is just a simple array or a Model class. Model can be either Project model, or Image model, or Video model and so on.
If it is a model class, then it calls that specific model's transformer. Transformers are just helper classes, that accept the model instance and they have two methods - facebook and twitter in this case. And each method returns a formatted array, specific to the connection.
So for example, facebook method grabs the required fields (that are needed to post on facebook) from the model and sends that array back. And twitter method does the same for twitter's required fields.
Now the thing I am a bit stuck with, is calling that specific method on the transformer class. I want to do something like this:
if(we are sharing on facebook) {
then call the facebook method
}
if(we are sharing on twitter) {
then call the twitter method
}
but obviously I want to somehow make it dynamic and not have these if statements. What would be a better approach to this?
I solved it doing this:
$method = $this->resolveMethodToCall();
protected function resolveMethodToCall()
{
$reflection = new ReflectionClass($this->service); // service being either a FacebookService class or TwitterService
$shortName = $reflection->getShortName();
return strtolower(str_replace('Service', '', $shortName));
}
Probably not the best solution, but works well and after this I am checking if the resolved method actually exists on the transformer class and throw an exception if it doesn't.

Symfony - Changing how controllers are instantiated and executed

Note: as of version 2.8, Symfony provided autowire: true for service configuration, and as of version 3.3, Symfony provided alias (instead of autowire_types) to alias a concrete object to an interface for automatic dependency injection into 'controllers as services'. There's also a bundle to allow autowiring for controller 'action' methods, although I've moved away from this and have focussed more on a variation of the ADR pattern (which is, basically, a single 'action' class with an interface method and not shoving a load of actions methods within a single class which eventually makes for an architectural nightmare). This is, effectively, what I've been looking for all these years and now no longer need to 'hook-in' a decent recursive dependency injector (auryn) as the framework now handles what it should have four years previous. I'll leave this answer here in case anyone wants to trace the steps that I did to see how the kernel works and some of the options available at this level.
Note: Although this question primarily targets Symfony 3, it should also be relevant to users of Symfony 2 as the kernel logic doesn't seem to have changed much.
I want to change how controllers are instantiated in Symfony. The logic for their instantiation currently resides in HttpKernel::handle and, more specifically, HttpKernel::handleRaw. I want to replace call_user_func_array($controller, $arguments) with my own injector performing that specific line instead.
The options I have tried thus far:
Extending HttpKernel::handle with my own method and then having this called by symfony
http_kernel:
class: AppBundle\HttpKernel
arguments: ['#event_dispatcher', '#controller_resolver', '#request_stack']
The downside of this is that, because handleRaw is private, I can't extend it without hacky reflection and so I would have to copy and paste a tonne of code.
Creating and registering a new controller resolver
controller_resolver:
class: AppBundle\ControllerResolver
arguments: []
This was a fundamental misunderstanding I had so I thought I'd document it here. The resolver's job is to resolve where to find the controller as a callable. It hasn't actually been called yet. I am more than happy with how Symfony takes the routes from routes.yml and figures out the class and method to call for the controller as a callable.
Adding an event listener on kernel.request
kernel.request:
class: MyCustomRequestListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 33 /** Important, we'll get to why in a minute **/ }
Taking a look at the Http Kernel Component Documentation, we can see that it has the following typical purpose:
To add more information to the Request, initialise parts of the system, or return a Response if possible (e.g. a security layer that denies access).
I figured that by creating a new listener, using my custom injector to create my controller, and then return a response in that listener, would bypass the rest of the code that instantiates the controller. This is what I want! But there's a major flaw with this:
The Symfony Profiler doesn't show up or any of that stuff, it's just my response and that's it. Dead. I found that I can switch the priority from 31 to 33 and have it switch between my code and Symfonys, and I believe this is because of the router listener priority. I feel I'm going down the wrong path here.
Listening on the kernel.controller event.
No, this allows me to change the callable that will be called by call_user_func_array(), not how the controller is actually instantiated, which is my goal.
I've documented my ideas but I'm out. How can I achieve the following?
Change how the controllers are instantiated and then executed, specifically call_user_func_array() which is in a bloody private method (thanks Symfony)
Fall back to the default controller instantiation if mine doesn't work
Allow everything else to work as expected, like the profiler loading
Be able to bundle this up with an extension for other users
Why do I want to do this?
Controllers can have many different methods for different circumstances and each method should be able to typehint for what it individually requires rather than having a constructor take all the things, some of which may not even be used depending on the controller method being executed. Controllers don't really adhere to the Single Responsibility Principle, and they're an 'object edge case'. But they are what they are.
I want to replace how controllers are created with my own recursively autowiring injector, and also how they are executed, again with recursive introspection via my injector, as the default Symfony package does not seem to have this functionality. Even with the latest "autowire" service option in Symfony 2.8+.
The controller resolver actually does two things. The first is to get the controller. The second is to get a list of arguments for a given action.
$arguments = $this->resolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
It is the getArguments method that you could override to implement your special "action method injection" functionality. You just need to determine what arguments the action method needs and return an array of them.
Based on a different question, I also think you might be misunderstanding the autowire functionality. Autowire really only applies to constructor injection. It's not going to help with action method injection.
If the getArguments does not solve your requirement then overriding the handle method is really your only option. Yes there is quite a bit of code to copy/paste from handleRaw but that is because there is quite a bit to do in there. And even if handleRaw was protected you would still have to copy/paste the code just to get at the one line you want to replace.
Why don't you return your own callable from custom ControllerResolverInterface that would instantiate Controller in a way you want and call it?
It would be basically a decorator.
You can extend Symfony\Component\HttpKernel\Controller\ControllerResolver with your own implementation of instantiateController() method, or you can implement ControllerResolverInterface from the scratch.
UPD:
When Symfony makes a call_user_func_array($controller, $arguments); call in handleRaw(), the $controller variable is what you've returned from your custom ControllerResolver. That means you can return any callable from your resolver (it can be [$this, "callController"] f.e.) and inside this callable you would create a new Controller with Auryn and call it.
UPD2:
If you're still struggling with this, I'll add an example because you might miss what I meant here.
use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver;
class AutowiringControllerResolver extends ControllerResolver
{
// ... constructor stuff; assume $injector is a part of this class
protected function createController($controller)
{
$controller = parent::createController($controller);
return function (...$arguments) use ($controller) {
// you can do with resolved $arguments whatever you want
// or you can override getArguments() method and return
// empty array to discard getArguments() functionality completely
return $this->injector->execute($controller);
};
}
protected function instantiateController($classname)
{
return $this->injector->make($classname);
}
}
Listener is too late to solve your needs, since it excludes Dependency Injection container, which is crucial to create a valid object (~= service).
You are probably looking for Controller Autowiring feature.
If that's so, you might find solution or at least inspiration in this bundle: http://www.tomasvotruba.cz/blog/2016/03/10/autowired-controllers-as-services-for-lazy-people/
It meets your needs in these points:
autowire injector
fallback to default (FrameworkBundle's) controller resolver, if not found
it also should keep all the flow working, since there are no hacks during controller resolving process

Symfony 2.2 extend ExceptionController

This question is related to the following change (part of Symfony 2.2 release):
Part 1
In pre-2.2 Symfony, I was overriding ExceptionController to display some custom error pages.
I did that via:
parameters:
twig.exception_listener.controller: My\CustomBundle\CustomExceptionController::showAction
Now, after upgrading to 2.2, I can no longer do that, because an exception is thrown while generating an exception (no pun intended):
ExceptionController::__construct() must be an instance of Twig_Environment, none given, called in...
Since ExceptionController is a service now, how can I override that, and what do I need to change in my old code?
All I did in the custom class, is changed the template reference in showAction method:
$template = new TemplateReference('TwigBundle', 'Exception', $name, $format, 'twig');
Part 2
Since ExceptionController no longer extends ContainerAware, how do I get to the current container? Is it enough to implement ContainerAwareInterface?
You should change a couple of this:
you need to inherit the ExceptionController in your custom Exception controller.
you need to override the twig.controller.exception.class parameter. As you can see in the service file, it uses the twig.controller.exception.class parameter to identify the exception controller class. Now override it with your class:
parameters:
twig.controller.exception.class: My\CustomBundle\CustomExceptionController
you need to edit the signature of the showAction to follow the new signature
Since ExceptionController no longer extends ContainerAware, how do I get to the current container? Is it enough to implement ContainerAwareInterface?
No, services shouldn't never inject the container. You should inject the services you need in the constructor, as is done with the Twig_Environment service.
Inside your Exception controller, you get access to the $this->twig property for the twig service. And the new signature gets a $request parameter, to get the request. I don't think you need more. (you also get $this->debug)

Categories