The Slim Framework support forum has moved to http://discourse.slimframework.com. This Tender forum is no longer maintained or monitored.

Example using __invoke() implementation for route controllers?

eko3alpha's Avatar

eko3alpha

14 Jan, 2016 09:55 PM

I'm a big fan of using Slim 2.0 class based controllers, it really helps keep the project very organized. I then use a BaseController to bring in any framework related libraries I may need.

$app->group('/campaigns', function() use ($app)
{
    $app->get('/'        , 'Controllers\Api\CampaignApiController:get');
    $app->get('/:id/'    , 'Controllers\Api\CampaignApiController:getById')->conditions(['id' =>'\d+']);
    $app->post('/'       , 'Controllers\Api\CampaignApiController:add');
    $app->delete('/:id/' , 'Controllers\Api\CampaignApiController:delete')->conditions(['id' =>'\d+']);
    $app->put('/:id/'    , 'Controllers\Api\CampaignApiController:update')->conditions(['id' =>'\d+']);
});

CampaignApiController

class CampaignApiController extends BaseController {
    public function get(){....}
    public function getById($id){....}
    public function add(){....}
    public function delete($id){....}
    public function update($id){....}
}
I was looking through the documentation for Slim 3.0 and saw that it did support using classes, this made my day, and got me really excited to use Slim 3.0 until I read...

http://www.slimframework.com/docs/objects/router.html

Note that the second parameter is a callback. 
You could specify a Class (which need a __invoke() implementation) instead of a Closure. 
You can then do the mapping somewhere else:


$app->any('/user', 'MyRestfulController');

This clearly isn't the same implementation as in Slim 2.0. I understand what __invoke() does, but I don't understand what was the rational behind using __invoke() instead of keeping it the way 2.0 had it. Unless I'm missing something, this feels like a step backwards.

Using the 3.0 implementation can someone provide me an equivalent to the routing I provided above? What "routing" needs to be done elsewhere? The example provided implies that you can only have one "Class" per route vs a Class:Method pair.

Thanks

  1. 1 Posted by eko3alpha on 15 Jan, 2016 05:41 PM

    eko3alpha's Avatar

    I'm not sure why the markdown is not formating correctly, looks ok on http://dillinger.io/

    So after experimenting with class based routing there is a lot of information left out of the documentation. Here are some takeaways that should be in the documentation.

    Here is an example of using only a class name.

    If you use only a class name you MUST use __invoke(). However it doesn't tell you much else. In fact it doesn't even tell you you can use the Slim2.0 way of using class routes but with a twist.

    $app->get('/welcome/{name1}/{name2}/', 'ClassNameOnly');
    
    With a class name only approach the container gets passed into the class constructor, here you can extract the services you need and even tap into the response, request objects. I have ClassNameOnly accessing the body() via both constructor and __invoke(). Choice is yours to make which one suits you and only done to show you options.
    class ClassNameOnly
    {
        protected $db1;
        protected $body;

    public function __construct($container)
    {
        //extract services & libraries
        $this->db1 = $container->get('db1');
        $this->body = $container->response->getBody();
    }
    
    public function __invoke($req, $res, $args)
    {
        // example model
        $model = new SomeModel($this->db1);
        // do something with $model
    
        $body = $res->getBody();
        $body->write('Hello, ');
        $body->write($args['name1']);
    
        // or
        $this->body->write('<br> Hi, ');
        $this->body->write($args['name2']);
    }
    
    
    
    
    }
    The following URL '/welcome/Mike/Joe/' will yield the following output:
    Hello, Mike
    Hi, Joe
    
    Here is an example of using only a static class method.
    $app->get('/welcome/{name1}/{name2}/', 'ClassNameOnly::sayHi');
    $app->get('/good-bye/{name1}/{name2}/', 'ClassNameOnly::sayBye');
    
    This approach is similar to just using a class name except you can now have multiple static methods in one controller. I am not a fan of this approach, I am including it in here because this way can be used. However since its a static method there is no way to access the main Slim instance via injection that I can see. Maybe someone can improve upon this example.
     class ClassWithStaticMethod
    {
        public static function sayHi($req, $res, $args)
        {
           // YUCK!!!! globals....
            global $app;

        // container
        $c = $app->getContainer();
        $db1 = $c->get('db1');
    
        // example model
        $model = new SomeModel($db1);
        // do something with $model
    
        $body = $res->getBody();
        $body->write('Hello, ');
        $body->write($args['name1']);
        $body->write('<br> Hi, ');
        $body->write($args['name2']);
    }
    
    public static function sayBye($req, $res, $args)
    {
        // wait... global again? whats going on here?
        // not sure how else you could access $app doing
        // it this way
        global $app;
    
        // container - smells of duplication...
        $c = $app->getContainer();
        $db1 = $c->get('db1');
    
        // example model
        $model = new SomeModel($db1);
        // do something with $model
    
        $body = $res->getBody();
        $body->write('Good bye, ');
        $body->write($args['name1']);
        $body->write('<br> Bye, ');
        $body->write($args['name2']);
    }
    
    
    
    
    }
    The following URL '/welcome/Mike/Joe/' will yield the following output:
    Hello, Mike
    Hi, Joe
    
    The following URL '/good-bye/Mike/Joe/' will yield the following output:
    Good bye, Mike
    Bye, Joe
    
    Here is an example of using only a class and method names.

    This is my favorite approach, and one I use in production. There are some differences in Slim3 vs Slim2 class routing. The first major difference in Slim3 is instead of passing the arguments as unique parameters, every method receives exactly 3 parameters.

    Request, Response, ArgumentArray

    in Slim2 you would only receive parameters if you were capturing them in the route. What this means is your method signatures will need to change to accept 3 parameters

    Slim2

    $app->get('/welcome/{name1}/{name2}/'   , 'WelcomeController:sayHi'); 
    // public function sayHi($name1, $name2){ ... }
    

    Slim3

    $app->get('/welcome/{name1}/{name2}/'   , 'WelcomeController:sayHi'); 
    // public function sayHi($req, $res, $args){ $args['name1']; $args['name2']; }
    

    I like how Slim3 passes an instance of itself in the __constructor(), this allows me to setup my base controller with assets all my controllers will share ( db, template engine, input handlers... )

    I dislike that I have to have three parameters for every method. Im sure I could do some trickery to make it behave the old way but its simpler to just do it this way. I have my own input class that handles this stuff so I'd proably just inject the request arguments directly into that. But anyway this is just for demo purposes.

    $app->get('/welcome/{name1}/{name2}/', 'ClassWithMethod:sayHi');
    $app->get('/good-bye/{name1}/{name2}/', 'ClassWithMethod:sayBye');
    

    I typically have a class that extends a base controller that stores shared assets.

    class ClassWithMethod extends BaseController
    {
        public function sayHi($req, $res, $args)
        {
            // example model
            $model1 = new SomeModel($this->db1);
            $model2 = new SomeModel($this->db2);
            // do something with models
    
            $this->body->write('Hello, ');
            $this->body->write($args['name1']);
            $this->body->write('<br> Hi, ');
            $this->body->write($args['name2']);
        }
    
        public function sayBye($req, $res, $args)
        {
            $this->body->write('Good bye, ' . $args['name1']);
            $this->body->write('<br>Bye, ' . $args['name2']);
        }
    }
    
    // here you can get the assets from the Slim container that can be shared throughout your other controllers.
    
    abstract class BaseController
    {
        protected $db1;
        protected $db2;
    
        public function __construct($c)
        {
            $this->db1 = $c->get('db1');
            $this->db2 = $c->get('db2');
            $this->body = $c->response->getBody();
        }
    }
    

    The following URL '/welcome/Mike/Joe/' will yield the following output:

    Hello, Mike
    Hi, Joe
    
    The following URL '/good-bye/Mike/Joe/' will yield the following output:
    Good bye, Mike
    Bye, Joe
    

    I hate technical writing but I thought it was important to be aware of your choices with class based routing. Even with Slim2 you glossed over the fact you could do this. At one point it wasn't even mentioned in the doc! I found out about it in the changelog... I feel class based routing is what allows Slim3 to be much more organized for larger applications. I wish there was more documentation on this. Anyway... hope this helps someone. I think I'll go buy a Slim sticker now :)

Reply to this discussion

Internal reply

Formatting help / Preview (switch to plain text) No formatting (switch to Markdown)

Attaching KB article:

»

Attached Files

You can attach files up to 10MB

If you don't have an account yet, we need to confirm you're human and not a machine trying to post spam.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac