Symfony 2.2 extend ExceptionController - php

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)

Related

How to write and use a PHP Dependency Injection Conatiner using PHP Reflection API

I'm trying to implement a simple DI in a pure OOP application. I wanted to use Dependency injection to manage many services ( Database, RequestValidator, Cache etc. ). I have read many blogs and liked this one from tech-tajawal but I could not really understand where should I include the container that tech-tajawal wrote. Can someone please show me how to do it?
I want it clean and thus want to use constructor based injection. So If I have a class, let's say AbstractBaseController which will inject a dependency called Request, so I will write:
php:
<?php
namespace src\controllers;
use system\middlewares\Request as Request;
abstract class AbstractBaseController {
private $request;
public function __construct(Request $request) {
$this->request = $request;
return $this;
}
}
But this simply throws
Fatal error: Uncaught TypeError: Argument 1 passed to src\controllers\AbstractBaseController::__construct() must be an instance of system\middlewares\Request, none given`
I think the container from tech-tajawal has to be included in my project root someway but I don't know how.
Please excuse my naivety here as I always was framework dependent.
You should instantiate your container at the very beggining of your application (think of a bootstrap class, or even at the top of index.php itself, considering a very simplistic application), because you will need the container to be ready before all the subsequent instantiations of services.
The only other thing that could probably be executed before the container instantiation are those related to configuration, because those are usually needed for the container to work properly (configuration parameters, PSR-4 autoloading configuration, etc).
For example, suppose that you have a class called MyController that extends the abstract class AbstractBaseController.
Then, on index.php, for example, you could instantiate your container and your controller:
//index.php
$container = new Container();
$controller = $container->get('namespace\of\MyController');
$controller->render();
When you do that, all the dependencies from the constructor would be handled by the autowiring module of your container library.
In a real life application, the instantiation of a controller would be usually handled inside a router, which maps URL addresses, methods and parameters to different classes to be loaded by the container.
One rule of thumb with autowiring, is that you can never call new namespace\of\MyController() directly anymore, because instantiating it manually would require you to pass each of the constructor dependencies (so you are not really using the autowiring feature). The right way to instantiate it is always by using $container->get('namespace\of\MyController').

Symfony 4 extend request for validation

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.

How should I register my Sonata application's new admin class?

I am using the Sonata admin bundle to build a backend for a blog. I have created a new Post entity and used sonata:admin:generate to generate an admin class called PostAdmin. This admin class extends AbstractAdmin. So far so good.
In accordance with https://sonata-project.org/bundles/doctrine-orm-admin/master/doc/reference/form_field_definition.html, I add the following code to my class:
public function validate(ErrorElement $errorElement, $object)
{
die('At least the validate() method is being called.');
$errorElement
->with('author')
->assertNotBlank()
->assertNotNull()
->end();
parent::validate($errorElement, $object); // TODO: Change the autogenerated stub
}
... but my die() statement does not appear to get called. (Also, when I remove the die() call, the assertions appear to get ignored, as I can leave my "author" field blank and still save a record.)
====
UPDATE #1: Per https://symfony.com/doc/3.x/bundles/SonataAdminBundle/reference/conditional_validation.html, I tried throwing an exception instead of dying. Even with this better debugging technique, it appears that the method is not getting called.
UPDATE #2: It looks like none of the methods in my PostAdmin class are being called at all. Is there a place I need to register that PostAdmin class in order for its methods to be called?
I found my answer. It's embarrassing.
There was an old file called PostAdminOld.php that contained a duplicate of my PostAdmin class. Running make install on my project in a fit of desperation, I saw the following message that helped me pin down the problem:
Warning: Ambiguous class resolution, "AppBundle\Admin\PostAdmin" was found in both "/usr/src/app/src/AppBundle/Admin/PostAdminOld.php" and "/usr/src/app/src/AppBundle/Admin/PostAdmin.php", the first will be used.
Warning: Ambiguous class resolution, "AppBundle\Controller\PostAdminController" was found in both "/usr/src/app/src/AppBundle/Controller/PostAdminControllerOld.php" and "/usr/src/app/src/AppBundle/Controller/PostAdminController.php", the first will be used.
... and that was that. Removing the old admin class definition allowed me to see my more recent changes.

PhalconPHP - getRouter in \Phalcon\Di

I've created new project using PhalconPHP 3.1.2. Everything works fine, but I have problem with IDE. In PhpStorm I've added ide/stubs/Phalcon from phalcon-devtools 3.1.2 as external libraries to dispose of warnings and errors.
But there is still one problem: in app/config/router.php (standard catalog structure created by devtools) I got line $router = $di->getRouter(); (also created by devtools) with warning: Method getRouter not found in Phalcon\Di\FactoryDefault.
There is no method in this class indeed: https://github.com/phalcon/phalcon-devtools/blob/3.1.x/ide/stubs/Phalcon/di/FactoryDefault.php -> https://github.com/phalcon/phalcon-devtools/blob/3.1.x/ide/stubs/Phalcon/Di.php
Now I don't have autocomplete of router's methods and this is problem for me. Did I do something wrong?
First of all -- I'm not familiar with Phalcon. These are my comments based on general PHP/PhpStorm experience.
So .. if you look at that PHPDoc in the last link you gave (stubs for Di.php) you will notice the code example there that has $request = $di->getRequest();.
If you just copy-paste that whole sample you will get the same "method not found" error ... as getRquest() is transformed at run time via magic __get() method.
The possible solution here is to create your own stub for your $di:
Create new class that will extend original Di, e.g.
class MyDi extends \Phalcon\Di
Add all those getRoute() / getRequest() etc methods there (could be real methods with empty bodies as in Phalcon's stub files .. or via #method PHPDoc tag)
Place such file anywhere in the project -- it will be used by IDE only.
When you are using your $di -- typehint it with your class, e.g.
/** #var \MyDi $di */
$di = new Di();
In the above code during runtime $di will be an instance of Di but for PhpStorm during development it will be MyDi so all type hints are in place.
As possible alternative -- instead of using magic via $di->getRoute() .. try $di->getShared('route') or similar.
If you use PhpStorm's Advanced Metadata functionality it will allow to get correct type based on parameter value.
$di->getRouter() is magic method, Phalcon\Di implements __call method and it gets router service this way if you use getRouter().
If you want you can use PHPStorm metadata and use $di->get() https://www.google.pl/search?client=opera&q=phpstorm+metadata&sourceid=opera&ie=UTF-8&oe=UTF-8

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

Categories