How to use Container to access parameters on a custom Exception - php

I have been struggling a bit to understand how can I make use of the Container that gives me access to the parameters located at config/parameters.yml.
The problem in my hands is I created some custom Exceptions and I created a library that slacks me every time one of those exceptions is triggered. For that, I need to send the container to the Logger in order to be able to pull those through within the library.
But I am failing to be able to grab a hold on the container. I have an idea it needs to be injected and that I could achieve such in the config/services.yml but I am failing to understand how.
So far this is what I have come to achieve:
My Custom Exception Parent
Because all exceptions extend from the parent, they will all trigger the Logger. And it is at this point I need the container to exist:
abstract class CriticalLogAlertException extends \RuntimeException
{
protected $logger;
public function __construct ($message = "", $code = 0, Throwable $previous = NULL)
{
parent::__construct($message, $code, $previous);
Log::critical(
$this->message,
[],
AbstractCredentialsFactory::YAML_TYPE,
'PARAMETERS CONTAINER ACCESS NEEDS TO BE ADDED HERE'
);
}
abstract public function generateMessage($message) : string;
}
What I though was on creating on this class a method setContainer() that I could use in my config/services.yml:
public function setContainer(ContainerInterface $container)
{
$this->container - $container;
}
So at this point I could create a property in the abstract class and use that to pass it to the library as it would be already available at class execution. Though I am not too certain this is achievable or correct;
My config/services.yml
Below is the code I added to my services container:
ExceptionContainer:
class: AppBundle\Exception\CriticalLogAlertException
calls:
- [ setContainer,[ #service_container ] ]
Could someone help me understanding if it something I am missing or misunderstanding in order to set it available?
NOTE: If there is anything else required to better understand my problem, please let me know so I can update my question :)
Thank you in advance!

Sooo, this took me a while but I had to dive deeper in order to understand, which meant to go through some fights and research before being able to start understanding it a bit better.
Was left for a while even though I got it working a while back, but for those wondering the same and new to the process let me share a bit :)
Because I was instantiating the exceptions with throw new meant I had direct access to the __construct method. This meant, if my exception was expecting a ContainerInterface I had to provide one.
Now, two case scenarious could work with this.
1. Injection
With Injection I can autowire the requirements either by using an available service (which we can check with below command);
php bin/console debug:autowiring
Or, if injecting a custom class, I needed to specify where, which and what it provides at services.yml
An possible example of what I am refering to add to services.yml can be seen below:
TestException:
class: AppBundle\Exception\TerminationAmlkyc
arguments: ['#service_container']
public: true
2. Instantiation
In my case, I either transferred the ContainerInterface whenever I needed it for a new class or was able to Instanciated as I referenced above.
Because my origin was through a ContainerAwareCommand I could make use of $this->getContainer() to retrieve it and then pass it to the exceptions whenever needed.
throw new TestException($this->getContainer())

Related

How can I automatically register 'controllers as services' in other directories in Symfony 5

Background
I want to use functional cohesion to organise my controllers.
This means that I will not have a Controllers/ directory, which makes it easy for the framework, but I will organise my code by use-case. An arbitrary example:
src/FetchLatestNews/Controller.php
src/FetchLatestNews/News.php
src/FetchLatestNews/NewsRepository.php
...etc
However, Symfony is set up by default to require all controllers in one place. See services.yaml:
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
Following the above template, I could just place the new src/FetchLatestNews/Controller in there and then add each new controller each time. However, I don't want to, nor should I have to, manually update this file every time I create a new use-case.
Question
How can I avoid this error:
maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"
As a user I should not have to care about manually adding service_arguments or any other configuration. When I specify a route, the thing it's pointing to is therefore a controller.
The ideal workflow would be:
Create a controller wherever I like.
Add a route to routes.yaml referencing the controller.
That's it!
Is it possible to achieve this workflow in Symfony? It should be, because anything from a route --> some class will be a controller. Is there a way to use a regex, or some other solution?
Note: this answer suggests an empty interface sort of 'hack'. This is not ideal, either. Any alternatives?
Please note, this answer has been updated again with new information.
The controller.service_arguments tag is only required to allow action injection. Stick with constructor injection and __invoke() and that's all you need.
The public addition below was a requirement, but it turns out you don't need a compiler pass for this and it can be all done at the services.yaml level. Therefore the only thing you need is the following:
services:
_defaults:
autowire: true
autoconfigure: true
public: true // <---- This is the new additional config.
Then, any class can be used in routes.yaml as a controller and construct injection works just fine.
Note: The original answer is below. Thanks to Jakumi for pointing me in the right direction.
You need to add a compiler pass to do two things. On booting and registering things for automatic dependency injection, if a class has Controller in the name:
Add the tag controller.service_arguments, which is the tag you'd normally need to add yourself manually in services.yaml to allow setter injection (you can ignore this if you don't care about setter injection)
Set the class to public, because Symfony had this weird idea that the concept of public or private anything is not to be inferred from the class access modifier (seriously?) - This is the important thing as Symfony will complain that the controller is private otherwise
Don't forget you don't have to use the logic for "Controller in name". It can be "Controller" is at the end of the class name which is probably better.
Anyway, first create a CompilerPass. Put it anywhere you want. I put mine next to Kernel.php.
use Symfony\Component\DependencyInjection\{Compiler\CompilerPassInterface, ContainerBuilder};
class ControllersAsServices implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
foreach ($container->getDefinitions() as $definition) {
if (strpos($definition->getClass(), "Controller") === false) {
continue;
}
$definition->addTag("controller.service_arguments");
$definition->setPublic(true);
}
}
}
Then in your Kernel.php, you need to tell it to use this new one. Override the protected function build and add your compiler passes as the documentation shows:
protected function build(ContainerBuilder $container)
{
$container->addCompilerPass(new ControllersAsServices, PassConfig::TYPE_BEFORE_OPTIMIZATION, -1);
parent::build($container);
}
Clear your dev cache. Nothing changed for me until I did this.
Now, any class with Controller in the name can be autowired as it should have been originally.
Original anser from Jakumi below:
I believe the best approach in your case would be to implement a CompilerPass, to add classes to the container:
https://symfony.com/doc/current/bundles/extension.html#adding-classes-to-compile
in the process there's probably a way to also add the tags.
Symfony caters to the controller.service_arguments tag via the RegisterControllerArgumentLocatorsPass, which resolves the constructor arguments and checks for some traits. If you get your compiler pass' priority higher, you could probably solve this problem easily then ...

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').

Laravel __construct() - without injecting?

I am getting error:
GitHubApp::__construct() must be an instance of App\Project\Repositories\GitProviderRepository
I thought Laravel does some kind of magic when it come to __construct() so i don't have to inject it into new GitHubApp();?
use App\Project\Repositories\GitProviderRepository;
class GitHubApp
{
private $gitProviderRepository;
public function __construct(GitProviderRepository $gitProviderRepository)
{
$this->gitProviderRepository = $gitProviderRepository;
}
}
In other class:
return new GitHubApp();
When calling new GithubApp(), you relied on yourself to build the GithubApp instance, Laravel does not responsible for building that instance.
You have to let Laravel resolve the dependencies for you. There are numbers of way to achieve this:
Using App facade:
App::make(GithubApp::class);
Using app() helper method:
app(GithubApp::class);
Or using the resolve() helper method:
resolve(GithubApp::class);
Behind the scene, your class type and its dependencies will be resolved and instantiated by the Illuminate\Container\Container class (the parent class of Application). Specifically by the make() and build() methods.
Hope this help! :)
You can try as:
return app(GitHubApp::class);
or
return app()->make(GitHubApp::class)
It is done by the powerful IoC container of Laravel to resolve classes without any configuration.
When a type is not bound in the container, it will use PHP's Reflection facilities to inspect the class and read the constructor's type-hints. Using this information, the container can automatically build an instance of the class.
Docs

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

Lumen IoC binding resolution spotty within phpunit tests

I've run into an issue with Lumen v5.0.10 that has me at my wits end. I'm designing an application largely using TDD with the bundled phpunit extensions. I'm basically getting a BindingResolutionException for "App\Contracts\SubscriberInteractionInterface". This is an interface in the directory App\Contracts which has an implementation in App\Services which is registered in the AppServiceProvider like so:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
// Owner manager
$this->app->singleton(
'App\Contracts\OwnerInteractionInterface',
'App\Services\OwnerManager'
);
// Subscriber manager
$this->app->singleton(
'App\Contracts\SubscriberInteractionInterface',
'App\Services\SubscriberManager'
);
// dd($this->app->bound('App\Contracts\SubscriberInteractionInterface'));
}
}
My frustration is that if I uncomment that last line in the function then it shows that App\Contracts\SubscriberInteractionInterface has been bound (and thus may be resolved).
I then have a controller which effectively looks like this
class MyController extends Controller {
public function __construct(LoggerInterface $log)
{
$this->log = $log;
}
public function index(Request $request)
{
if (/* Seems to come from owner */)
{
$owners = App::make('App\Contracts\OwnerInteractionInterface');
return $owners->process($request);
}
if (/* Seems to come from subscriber */)
{
$subscribers = App::make('App\Contracts\SubscriberInteractionInterface');
return $subscribers->process($request);
}
}
}
I use them in this way because I only want the relevant one instantiated (not both as would happen if I type-hinted them) and they also each have type hinted dependencies in their constructors.
The issue is that the route of the tests that needs OwnerInteractionInterface runs just fine but the one that needs SubscriberInteractionInterface does not. The implementations and interfaces are largely similar and as I showed before, they are both registered at the same time and I can confirm that SubscriberInteractionInterface is bound. In fact, if I put the line:
dd(App::bound('App\Contracts\SubscriberInteractionInterface'));
at the top of index() it returns true. The tests happen to be ordered such that the path that uses OwnerInteractionInterface runs first and it resolves fine and then the other test fails with a BindingResolutionException. However, if I omit other tests and run just that one, then everything goes smoothly. The tests are in different files and the only setup I do is to bind a mock for a third party API in place of an entirely different binding from those shown and none of that code touches these classes. This is done within a setUp() function and I make sure to call parent::setUp() within it.
What's going on here? Could it be that binding one concrete instance wipes non-concrete bindings from the IoC? Or is it that the default setup allows some influence to transfer over from one test to another?
I know I sorta have a workaround but the constraint of never running the full test-suite is annoying. Its starting to seem that testing would be easier if I just use the instance directly instead of resolving it from its interface.
Also, does anyone know a way to inspect the IoC for resolvable bindings?
After further attempts at debugging, I've found that if you use app(...) in place of App::make(...) then the issue does not come up. I put in a eval(\Psy\sh()) call in the tearDown of the TestCase class and found that after a few tests you get the following result:
>>> app()->bound('App\Contracts\OwnerInteractionInterface')
=> true
>>> App::bound('App\Contracts\OwnerInteractionInterface')
=> false
>>> App::getFacadeRoot() == app()
=> false
This is to say that somehow, the Laravel\Lumen\Application instance that the App facade uses to resolve your objects is not the same as the current instance that is created by the setUp() method. I think that this instance is the old one from which all bindings have been cleared by a $this->app->flush() call in the tearDown() method so that it can't resolve any custom bindings in any tests that follow the first tearDown() call.
I've tried to hunt down the issue but for now I have to conclude this project with this workaround. I'll update this answer should I find the actual cause.
Instead of use bind, you can use bindIf method. Container will check whether the abstract has been bound or not. If not, it will bind your abstract and vice versa. You can read the api here.
So if you use singleton, you may use bindIf like.
// Owner manager
$this->app->bindIf(
'App\Contracts\OwnerInteractionInterface',
'App\Services\OwnerManager',
true
);
// Subscriber manager
$this->app->bindIf(
'App\Contracts\SubscriberInteractionInterface',
'App\Services\SubscriberManager',
true
);

Categories