Symfony - Changing how controllers are instantiated and executed - php

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

Related

PHPUnit gives error: Target [Illuminate\Contracts\View\Factory] is not instantiable

I created a simple test for my new Laravel 7 application. But when I run php artisan test I get the following error.
Target [Illuminate\Contracts\View\Factory] is not instantiable.
The error doesn't appear when I go to the page in the browser.
$controller = new HomeController();
$request = Request::create('/', 'GET');
$response = $controller->home($request);
$this->assertEquals(200, $response->getStatusCode());
Although "Just write feature tests" may seem like a cop-out ("They're not unit tests!"), it is sound advice if you do not want to get bogged down by framework-specific knowledge.
You see, this is one of those problems that come from using facades, globals, or static methods. All sorts of things happen outside of your code (and thus your test code) in order for things to work.
The problem
To understand what is going on, you first need to know how Laravel utilizes Containers and Factories in order to glue things together.
Next, what happens is:
Your code (in HomeController::home() calls view() somewhere.
view() calls app() to get the factory that creates Views1
app() calls Container::make
Container::make calls Container::resolve1
Container::resolve decides the Factory needs to be built and calls Container::build to do so
Finally Container::build (using PHP's ReflectionClass figures out that \Illuminate\Contracts\View\Factory can not be Instantiated (as it is an interface) and triggers the error you see.
Or, if you're more of a visual thinker:
The reason that the error is triggered is that the framework expects the container to be configured so that a concrete class is known for abstracts (such as interfaces).
The solution
So now we know what is going on, and we want to create a unit-test, what can we do?
One solution might seem to not use view. Just inject the View class yourself! But if you try to do this, you'll quickly find yourself going down a path that will lead to basically recreating loads of framework code in userland. So not such a good idea.
A better solution would be to mock view() (Now it is really a unit!). But that will still require recreating framework code, only, within the test code. Still not that good.[3]
The easiest thing is to simply configure the Container and tell it which class to use. At this point, you could even mock the View class!
Now, purists might complain that this is not "unit" enough, as your tests will still be calling "real" code outside of the code-under-test, but I disagree...
You are using a framework, so use the framework! If your code uses glue provided by the framework, it makes sense for the test to mirror this behavior. As long as you don't call non-glue code, you'll be fine![4]
So, finally, to give you an idea of how this can be done, an example!
The example
Lets say you have a controller that looks a bit like this:
namespace App\Http\Controllers;
class HomeController extends \Illuminate\Routing\Controller
{
public function home()
{
/* ... */
return view('my.view');
}
}
Then your test[5] might look thus:
namespace Tests\Unit\app\Http\Controllers;
use App\Http\Controllers\HomeController;
use Illuminate\Contracts\View\Factory;
class HomeControllerTest extends \PHPUnit\Framework\TestCase
{
public function testHome()
{
/*/ Arange /*/
$mockFactory = $this->createMock(Factory::class);
app()->instance(Factory::class, $mockFactory);
/*/ Assert /*/
$mockFactory->expects(self::once())
->method('make')
->with('my.view')
;
/*/ Act /*/
(new HomeController())->home();
}
}
A more complex example would be to also create a mock View and have that be returned by the mock factory, but I'll leave that as an exercise to the reader.
Footnotes
app() is asked for the interface Illuminate\Contracts\View\Factory, it is not passed a concrete class name
The reason Container::make does nothing other than call another function is that the method name make is defined by PSR-11 and the Laravel container is PSR compliant.
Also, the Feature test logic provided by Laravel already does all of this for you...
Just don't forget to annotate the test with #uses for the glue that is needed, to avoid warnings when PHPUnit is set to strict mode regarding "risky" tests.
Using a variation of the "Arrange, Act, Assert" pattern
This is not how you test endpoints in Laravel. You should let Laravel instantiate the application as it is already setup in the project, the examples you can see here.
What you already wrote can be rewritten to something like this.
$response = $this->call('GET', route('home')); // insert the correct route
$response->assertOk(); // should be 200
For the test to work, you should extend the TestCase.php, that is located in your test folder.
If you're finding this in The Future and you see #Pothcera's wall of text, here's what you need to know:
The ONLY reason he's doing any of that and the ONLY reason you're seeing this in the first place in a Unit test is because he and you haven't changed from PHPUnit\Framework\TestCase to Tests\TestCase in the test file. This exception doesn't exist when you extend the test case that includes app().
My advice would be to simply extend the correct base test case and move on with your life.

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 does Reflection in Laravel work?

How does reflection in Laravel actually work?
I tried to debug it to see how Laravel uses reflection in a controller's constructor or methods to resolve their dependencies and sub-dependencies and then and give it back to us.
But I found it hard, and it's very complicated to see and to even understand 50% of. Jumping from class to class, I can't really see it. I tried a few times by debugging it with low results of understanding.
I am very impressed by this and by reflection, and the way Laravel uses it makes my heart burn—it's just beautiful. And I wish to fully understand that—the whole process—in general, and step by step.
Beginning from hitting the route to finally having, let's say, dd($x), where $x is from a method argument and is a TestClass that has another dependency of TestClass2 that should be constructed through: $x = new TestClass(new TestClass2());
I think those are beautiful mechanics and architecture, and understanding this is something I want so badly.
So again, my question is: how does reflection in Laravel actually work?
It's not about dd guys... Let's say without dd. Just as I said earlier - when we have this object instantiated from the class method. It's not about dumping it, it's just about having it from method injection by reflection.
The dd was only an example. It can even be die(var_dump()); and it will work
Laravel uses PHP's reflection API for several components. Of these, the inverson-of-control (IoC) dependency injection container and controller method injection are most visible to developers.
To more clearly illustrate the use of reflection, here's a dramatically simplified version of the routine Laravel's IoC container class uses to build up an object's dependencies through constructor injection:
function build($className)
{
$reflector = new ReflectionClass($className);
$constructor = $reflector->getConstructor();
foreach ($constructor->getParameters() as $dependency) {
$instances[] = build($dependency->getClass()->name);
}
return $reflector->newInstanceArgs($instances);
}
As we can see, the concept isn't too difficult to understand. The container uses PHP's ReflectionClass to find the names of the classes in an object's constructor, and then loops through each of these names recursively to create instances of each object in the dependency tree. With these instances, build() finally instantiates the original class and passes the dependencies as arguments to the constructor.
Controller method injection uses the same container functionality shown above to resolve instances of dependencies declared as method parameters, but there's a bit of extra logic needed to separate class dependencies from route parameters:
function dispatch(Route $route, Controller $controller, $methodName)
{
$routeParameters = $route->parametersWithoutNulls();
$method = new ReflectionMethod($controller, $methodName);
foreach ($method->getParameters() as $index => $parameter) {
$class = $parameter->getClass();
if ($class !== null) {
$instance = build($class->name);
array_splice($routeParameters, $index, 0, [ $instance ]);
}
}
$controller->callAction($methodName, $routeParameters);
}
Again, this adaptation is slimmed-down to highlight the role reflection plays and relies on our build() function shown previously. The ControllerDispatcher class uses the getParameters() method of PHP's ReflectionMethod to determine which parameters a controller method expects, and then loops through these to find parameters that represent dependencies that it can resolve from the container. Then, it splices each dependency it finds back into the array of route parameters that it passes back to the controller method defined for the route. See RouteDependencyResolverTrait for details.
If we ignore the application bootstrapping process, this dependency injection cascade typically starts for a request when Laravel maps a request to a route, and then determines which controller to pass the request to. Laravel first resolves an instance of the controller from the container, which builds out any constructor-injected dependencies. Then, Laravel finds the appropriate controller method and resolves any more dependencies for the arguments as needed.
As shown here, Laravel uses relatively simple techniques to implement these tools using reflection. However, unlike the examples shown in this answer, the framework adds a lot of additional code to make them as robust and flexible as they are today.

ZF2 - Make loggers globally accessable, ideally lazy loaded or by event

I have various Loggers defined in my module_config. Each controller or service that needs a specific logger needs to get the logger via its factory injected.
Ideally I don't want to worry about this injection and can make the loggers gloablly accessable (without having the service locator in my service/controller). They should be lazy loaded/instanciated, only when I need them.
Is there a way to do so? I was thinking of initializers, treats or using the event manager.
I know, It sounds like having the service locator back.
Any ideas?
I guess the cleanest way is to use a controller plugin to call upon your loggers from a controller.
Implementation is pretty simple:
Create a new PHP class and have it extend Zend\Mvc\Controller\Plugin\AbstractPlugin.
It's constructor should accept the logger(s) as a dependency, and it's __invoke method can be used to "magically" retrieve a logger.
Next, create a factory for it, implementing Zend\ServiceManager\FactoryInterface and use it to inject the dependencies (i.e., the loggers)
Register the factory in your servicemanager configuration:
'controller_plugins' => array(
'factories' => array(
'Logger' => 'MyApp\Controller\Plugin\Logger',
)
),
You are then able to call it in a controller like so:
$this->logger();
(See http://lab.empirio.no/custom-controller-plugin-in-zf2.html) for more information on creating custom controller plugins.
Lazy-loading loggers can be handled by registering them as lazy services under the lazy_services key in your service_manager configuration.
As for your services, you can implement your own version of a ServiceLocator (also known as a plugin manager).
It would work a lot like the ViewHelperManager, ControllerPluginManager, ValidatorPluginManager, etc.
I won't explain how to do that as it's a lot of code and many how-to's exist on the web on how to create one.
EDIT:
As #tasmaniski noted below you could use injection based on marker interfaces using initializers (ZF2 uses this internally through the 'Aware' interfaces).
Do note that initializers are relatively slow especially when used incorrectly, i.e. as a closure in your Module.php or module.config.php file.
Also, initializers get called upon for each service fetched by the ServiceManager's get() mtehod and can thus add significant overhead.
It is however a much easier and faster solution than writing your own implementation of a plugin manager.
I have exactly the same question in my application/architecture.
So the solution was:
In the scope of Controllers, as #FuST said you have to create controller plugin. Than the logger will be available across all controllers.
In the scope of services, I you can do a "Interface Injection".
So in your Service you have to add 3 thing:
// #1 Create interface with setLogger($logger); method
class YourService implement LoggerInterface {
private $logger; // #2 Define private $logger;
// #3 Add method that you will use to inject the $logger later
public function setLogger($logger){
$this->logger = $logger;
}
}
Than create initializer and inject $logger in every class that implement the LoggerInterface.

Where to load controller dependency classes?

I'm making my own primitive MVC framework with PHP, and I'm wondering where I should load/instantiate corresponding controller dependencies?
In the constructor of each controller (tightly coupled) or inject them (loosely coupled)?
The only part of the latter that I'm not too sure of is for the dependencies to be instantiated on bootstrap level, outside of the MVC paradigm, before being injected. Not every controller uses the exact same dependencies besides the default parent ones. I would have to instantiate them all, which would also create a lot of overhead.
I've seen some existing frameworks do it like $this->load->model('model'); // CodeIgniter in the constructor, but I have no clue on why they're doing it like that.
I would suggest you inject the dependencies, so your controllers are less coupled to your framework. This will make a switch to another framework easier.
About instantiating dependencies: I suggest you use (or implement) a dependency injection container. This container should contain factories that can instantiate services.
In an ideal situation your controllers are services too (meaning they too have factories in the dependency injection container).
This way only the controller you need for a particular request will be instantiated, and therefor only its dependencies are instantiated.
When building you own framework, this means that after the routing phase (when the correct controller is known), the framework should grab that controller from the container. The container itself will make sure all dependencies that are needed will be provided.
Have a look at Pimple for an example of a simple dependency injection container.
PS: That line from CodeIgniter looks a lot like the service locator pattern. This pattern is similar to dependency injection, but does not provide full inversion of control.
Q: Where should i load/instantiate corresponding controller dependencies?
There are multiple ways.
The load and instantiation concepts are basically "before/outside" and "after/inside".
Before and outside means, that you load the file containing a class (which you want to instantiate and pass to the controller), before you load the controller.
But how do you know, what the controller needs, before loading the controller? Uh..
Dependency Description Files
A description file comes into play, describing the wiring between your controller and it's dependencies. In other words, you can see the dependencies of your controller by looking at it's dependency description file. This concept is often used by Dependency Injection tools, which analyze the object and pull the dependencies names out automatically. It's also possible to maintain such a wiring configuration file manually. But it's tedious.
Service Locator
A Service Locator is a instantiation helper for dependencies.
Basically, it contains the same information like a dependency description file, but this time in form of a registry. The link between parts of your application becomes this registry.
Both strategies introduce overhead. It's a trade-off. When you change the perspective and look at things from an application with maybe 500+ classes, then you realize that a dependency injection tool is sometimes worth it.
Manual Injection
via Constructor Injection.
After and inside means, that you load the file containing your controller and then start to care about the dependencies.
At this point the class is not instantiated, yet, but the autoloader might do it's dirty deeds behind the scene. He evaluates the use statements at the top of your controller file. The use statements declare namespaced classes, which the autoloader resolves to actuall files and loads them. You might then start to use these classes as dependencies in your controller. This is probably the easiest way to solve your problem and i strongly suggest looking into the topics autoloading with namespaces and use-statements.
When the class is instantiated, you have the following possiblities:
use might use Setter Injection or Reference Injection to set the dependencies to the object. This requires that your Constructor Dependencies are already solved or your constructor is empty.
It's possible to combine these strategies.
Q: What does this do $this->load->model('model'); // CodeIgniter?
CodeIgniter is a legacy application framework. It was created in times, when namespaced autoloading wasn't available. $this->load is a basic class loading helper. This is the opposite of an "auto"loader, (which surprise, surprise) loads things automatically.
CodeIgniters loader class is used to load various other classes, like libraries or files from the view, helpers, models or user defined stuff. This is again the concept of a registry. Here the registry just knowns where things are in your application layout and resolves them. So $this->load->model('model'); means that the modelfunction must have some piecies of information, about the position of model files in your application.
You provide a model name and the path for the file is constructed by model.
And this is exaclty what it does (except a bit of overhead): https://github.com/EllisLab/CodeIgniter/blob/develop/system/core/Loader.php#L223.
Since I'm a Symfony developer, I can only give you a reference to Symfony.
I think you should do like they are doing in Symfony by thinking about what you need in each
Controller object.
At least, you need :
a Request object
and a Model loader object that gives you every Model you need.
Create a BaseController that implements these few functions and then extend it with custom Controllers.
You can also take a look on Silex : http://silex.sensiolabs.org/ a Micro Framework
Hope it helps.
When do you say "In the constructor" you mean to pass in the conatiner and pull the dependencies from them (in the constructor)?
<?php
class SomeController
{
public function __construct($container)
{
$this->service1 = $contanier->get('service1);
}
//...
}
I advice against that, though simpler and easier you will be coupling your controllers to the container thus using a ServiceLocator instead of truly inversion of control.
If you want your controllers to be easy unit-testable you should use inversion of control:
class SomeController
{
public function __construct($service1)
{
$this->service1 = $service1;
}
//...
}
And you can even create your controller as a service inside the container:
// this uses Pimple notation, I hope you get the point
$container['controller'] = function($c) {
return SomeController($c['service1']);
}
Use proxy services to lazy load them
Also if your controllers needs more than some services and you won't be using all of them you can:
1) Use proxy services in order to lazy load the service only when they are really needed
<?php
class ProxyService
{
/**
* #var Service1Type
*/
private $actualService;
public function __construct()
{
$this->actualService = null;
}
private function initialize()
{
$this->actualService = new Service1(); // This operation may take some time thus we deferred as long as possible
}
private function isInitialized()
{
return $this->actualService === null;
}
public function someActionOnThisService()
{
if (!$this->isInitalized()) {
$this->initalize();
}
$this->actualService->someActionOnThisService();
}
There you have a simple proxy object with lazy loading. You may want to check the fantastic Proxy Manager Library if you want to go that route
2) Split your controller
If your contoller has too many dependencies, you may want to split it.
In fact you may want to read the proposal by Paul M. Jones (lead developer of Aura Framework) about MVC-Refinement, IMHO is a good read even though you may not fully agree with it.
Even if you split your controller in order to reduce the dependencies, lazy loading your dependencies is a good idea (obviously you'll have to check weather if its doable in your context: more work in order to gain more speed).
Maybe you need to define __autoload() function before you try to load the Classes which is not loaded yet. Like:
function __autoload($className) {
require "/path/to/the/class/file/$className.php";
}
My example is very very simple to auto require the file which the class definition is in.
You can also use if-else statement or switch statement in that function to fit your own situations smartly.
The __autoload() function is called when PHP doesn't find the class definition, works for new, class_exists(), call_user_method(), etc, and absolutely for your dependences/parents classes. If there is still no class definition after __autoload() is called, PHP will generate an error.
Or you can use spl_autoload_register() function instead of __autoload() more gracefully.
For more information, you might want to see:
http://php.net/manual/en/function.autoload.php
http://php.net/manual/en/function.spl-autoload-register.php

Categories