4

I am creating an API using the Slim framework. Currently I use a single file to create the route and pass a closure to it:

$app->get('/', function($req, $resp){
//Code...
})

But I realise that my file has grown rapidly. What I want to do is use controllers instead, so I will have a controller class and just pass the instance/static methods to the route, like below

class HomeController
{
   public static function index($req, $resp){}
}

and then pass the function to the route

$app->get('/', HomeController::index);

I tried this, but it does not work, and I wonder if there is a way I can use it to manage my files.

alexw
  • 7,044
  • 6
  • 46
  • 81
James Okpe George
  • 3,047
  • 7
  • 42
  • 76

5 Answers5

9

Turn the controller into a functor:

class HomeController
{
    public function __invoke($req, $resp) {}
}

and then route like this:

$app->get('/', HomeController::class);

For reference, see

localheinz
  • 7,720
  • 2
  • 29
  • 39
3

PHP 5.6 Slim 2.6.2

require 'vendor/autoload.php';

class HelloController {
    public static function index()  {
        global $app;

        echo "<pre>";
        var_dump($app->request);
        echo "</pre>";
    }
}

$app = new \Slim\Slim();
$app->get('/', 'HelloController::index');
$app->run();

Update: PHP 5.6 Slim 3.0.0

require 'vendor/autoload.php';

class HelloController {
    public static function hello(\Slim\Http\Request $req, \Slim\Http\Response $response, $args)  {
        echo "<pre>";
        var_dump($args);
        echo "</pre>";
    }
}

$app = new \Slim\App();
$app->get('/hello/{name}', 'HelloController::hello');
$app->run();

The problem with class based routing in Slim 3.0 is access to $this/$app. I think you will need to use global $app to access it.

In my pet project I use route groups with require_once. Something like

$app->group('/dashboard', function () {
    $this->group('/auctions', function () use ($app){
        require_once('routes/dashboard/auctions.php');
    });
    $this->group('/rss', function () {
        require_once('routes/dashboard/rss.php');
    });
    $this->group('/settings', function () {
        require_once('routes/dashboard/settings.php');
    });
});

Looks not as beauty as could be with pure classes but works like expected with all features accessible without additional coding.

CrazyCrow
  • 3,597
  • 1
  • 22
  • 36
  • If you subclass slim\application and include the route file inside there then you can use $this consistently throughout the route configuration. I added an example here http://stackoverflow.com/questions/41981048/how-can-i-create-middleware-on-slim-framework-3/42022544#42022544 Also, it would be possible to have a route configuration class that takes the Application as a constructor dependency – malte Feb 03 '17 at 13:53
  • @Roman when you use proper namespace and using composer, you do not need to call require() on your own because autoload.php will do it for you. – Zamrony P. Juhara Apr 16 '18 at 20:41
2

Smooth & short way to use a controller as an object (not a static way)

in index.php

namespace MyApp;

use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;

require __DIR__ . '/../vendor/autoload.php';

$app->get('/myroute', [new Controller\MyClass, 'get']); // <=== that is pretty short  and neat
$app->post('/myroute', [new Controller\MyClass, 'post']);
$app->map(['GET', 'POST'], '/myotherrout', [new Controller\MyOtherClass, 'run']);

in Controller/MyClass :

namespace MyApp\Controller;

class MyClass{

    public function __construct(){
       //some code
    }

    public function get(\Slim\Http\Request $request, \Slim\Http\Response $response, $args = []) {
       //some super foobar code
    }

    public function post(\Slim\Http\Request $request, \Slim\Http\Response $response, $args = []) {
       //some other code
    }

The Controller\MyClass is resolved through the use of PSR autoload

in Controller/MyOtherClass :

namespace MyApp\Controller;

class MyOtherClass{

    public function __construct(){
       //some code
    }

    public function run(\Slim\Http\Request $request, \Slim\Http\Response $response, $args = []) {
       //some amazing foobar code
    }
Cedric
  • 4,361
  • 10
  • 37
  • 55
  • Very nice indeed! Thanks! How would you inject a service directly into the controller without going through the container using this? – Strnm Apr 10 '18 at 08:00
  • While this works, controller instance creation should be done inside dependency container registration – Zamrony P. Juhara Apr 16 '18 at 20:45
2

Here's an example:

Controller

<?php

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;

/**
 * @property LoggerInterface $logger;
 */
class Controller
{
    /**
     * @var LoggerInterface
     */
    protected $logger

    /**
     * @param LoggerInterface $logger
     */
    public function __construct($logger)
    {
        $this->logger = $logger;
    }

    public function action(ServerRequestInterface $request, ResponseInterface $response, $args=[])
    {
        $this->logger->info((string)$request->getUri());
        /* some actions */
        return $response;
    }
}

Application

<?php

use Slim\App;
use Slim\Container;
use Psr\Container\ContainerInterface;

$autoloader = require(__DIR__.'/vendor/autoload.php');

$container = new Container();

$container['logger'] = function($container) {
    return new Logger();
}

$container['some.controller'] = function ($container) {
    /**
     * @var ContainerInterface $container
     */
    $logger = $container->get('logger');

    return new Controller($logger);
};

$app = new App($container);
$app->get('/some/route', 'some.controller:action');

$app->run();

Profit!

This method is described in the documentation click me

Venantius
  • 2,274
  • 2
  • 24
  • 34
Ivan Dudarev
  • 216
  • 1
  • 4
  • I see you pass $logger to the controller, should you not pass the container? – crafter Mar 13 '19 at 09:33
  • @crafter passing a container to a controller is a bad practice. see https://en.wikipedia.org/wiki/Single_responsibility_principle and https://en.wikipedia.org/wiki/Dependency_injection – Ivan Dudarev Mar 14 '19 at 10:11
  • I hear you. You are correct, that container is too large to drag along into your classes. – crafter Mar 15 '19 at 20:15
1

Nikic's Fast Route is a very minimal router, so some of the niceties of the bigger frameworks are removed. Here's a basic solution:

routes.php

use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;

$app->get('/', function($req, $resp, $args) use ($app){return FooBar::asdf($app, $req, $resp);});

controller use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response;

class FooBar{
    static public function asdf(Slim\App $app, Request $req, Response $resp, $args = [])
    {
        return $resp->withJson(['asf']);
    }
}
user3791372
  • 3,937
  • 3
  • 34
  • 68