klein / klein.php

A fast & flexible router
MIT License
2.66k stars 290 forks source link

How could we use DI with this package #393

Open mhipo1364 opened 6 years ago

mhipo1364 commented 6 years ago

Hi guys

I have a problem with calling callbacks in this package, if I write controller full qualified name in the respond method, my controller's action will be called statically, and if I want to instantiate my controller in respond method, I have to inject all dependencies of my controller when I want to instantiate it.

eimajenthat commented 6 years ago

Well, if you have a class with dependencies, and you want to call an instance method (ie. not call it statically), you have to instantiate the object, and therefore have to inject your dependencies. How you do that depends on your application, and isn't directly connected to your the routing framework. You could use a standalone DI library, like:

https://github.com/auraphp/Aura.Di

Or a lot of the bigger frameworks, like Symfony and Laravel have their own solutions you might be able to re-appropriate. Or you could make your own solution from scratch.

However you do it, there is probably a function to call to retrieve your dependency-injected object, like:

<?php
$object = $di->newInstance('Vendor\Package\ClassName'); // Aura DI
App::make('foo'); // Laravel
$this->get('bar'); // Symfony

Whatever DI magic you use to get your object, you can call that in the route closure:

<?php
require_once __DIR__ . '/vendor/autoload.php';
$klein = new \Klein\Klein();
$klein->route('GET', '/foo', function() { $controller = App::make('foo_controller');});
$klein->dispatch();

If your DI system lives in a locally scoped object try the use keyword:

<?php
$klein->route('GET', '/foo', function() use ($di) { $controller = $di->newInstance('Name\Space\FooController');});

If have a lot of controllers that all load from your DI system, you might abstract this out to a method, something like:

<?php
function do_di_and_routing($controller, $action) {
    $controller_instance = $di->newInstance('Name\Space\' . $controller);
    return function() use ($controller_instance, $action) {
        return $controller_instance->$action();
        // alternatively, there are certain edge cases where this might be preferable, I think...
        // but I can't remember why
        // return call_user_func(array($controller_instance, $action));
    }
}

$klein->route('GET', '/foo/bar', function do_di_and_routing('FooController', 'bar'));
$klein->route('GET', '/foo/baz', function do_di_and_routing('FooController', 'baz'));
$klein->route('GET', '/foo/bagels', function do_di_and_routing('FooController', 'bagels'));

The key there is that the function returns a function (a closure, but that's really a function), which can be passed to the route method. Using closures/functions/whatever gives you an absurd amount of flexibility, where you can do all kinds of crazy stuff. Some of it you probably should do, but I think this is relatively safe.