klein / klein.php

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

Restful routing with Controller classes #284

Closed GreeKatrina closed 9 years ago

GreeKatrina commented 9 years ago

If this is already do-able, but I couldn't figure it out, I apologize in advance.

I would like to be able to do something like this:

$models = ['order_requests','shipments','users'];

foreach ($models as $model) {
    //Kinda shitty way to convert order_requests to MyApp\Controller\OrderRequest
    $controller_name = '\\MyApp\\Controller\\'.substr(str_replace(' ','',ucwords(str_replace('_',' ',$model))),0,-1);
    $klein->with('/'.$model, function($router) {
        $controller = new $controller_name;
        $router->get('/?', function($req,$res,$service,$app) { return $controller->index($req,$res,$service,$app); });
        $router->get('/new/', function($req,$res,$service,$app) { return $controller->new($req,$res,$service,$app); });
        $router->post('/?', function($req,$res,$service,$app) { return $controller->create($req,$res,$service,$app); });
        $router->get('/[i:id]', function($req,$res,$service,$app) { return $controller->show($req,$res,$service,$app); });
        $router->get('/[i:id]/edit', function($req,$res,$service,$app) { return $controller->edit($req,$res,$service,$app); });
        $router->put('/[i:id]', function($req,$res,$service,$app) { return $controller->update($req,$res,$service,$app); });
        $router->delete('/[i:id]', function($req,$res,$service,$app) { return $controller->delete($req,$res,$service,$app); });
    });
}

Currently when I do this, I will get errors like "undefined variable $controller". If I try to pass $controller into the callback like:

function($router,$controller) {...}

It throws an error too. Not sure of the best way to go about this, or if it's even currently possible to do. Thanks for the help and this library is great. I was looking forever for a router that was light and didn't take forever to set up.

Rican7 commented 9 years ago

Hi @GreeKatrina.

From looking at your code it looks like you're just missing a small bit here, which you've seemed to catch on to considering your attempt to fix it.

PHP's "closure" implementation is pretty unique, in that the variables that are allowed to be accessed inside the closure have to be manually denoted. So it really looks like you just need to tell your closures to "use" the $controller variable from the parent scope... like this:

$controller = new $controller_name;

$router->get('/?', function($req, $res, $service, $app) use ($controller) { return $controller->index($req, $res, $service, $app); });
// ....

... Having said that, I wouldn't suggest dynamically building and calling classes in this manner. Doing things like this is quite "magical" and ends up hiding a lot of implementation details that might make it harder for a team member (or even yourself in a few months) to understand. This kind of behavior can also limit testability pretty heavily.

If you'd like to use a more dynamic approach without hiding the inner complexity while still allowing for a more testable setup, I'd suggest either manually defining all of the routes or using a factory pattern of some sort (kind of like what was described in #279).

I hope Klein suits your needs and you enjoy using it. :)

GreeKatrina commented 9 years ago

Thank you so much @Rican7. That was exactly what I needed. #279 is helpful also. I think I'll go with the Controller Factory as you suggested. As someone who moved from Ruby to PHP, Klein was awesome to find.

Rican7 commented 9 years ago

Oh awesome! I'm glad that helped. :)

Thanks for the kind words! Good luck with your project. :+1: