Programster's Blog

Tutorials focusing on Linux, programming, and open-source

Slim3 Cheatsheet

As with all cheatsheets these are "never done" and continuously added to. If there is something useful you wish to contribute, please feel free to add it in the comments.

Install

Just execute the following command after having installed composer.

composer require slim/slim

Responses

They key thing to remember is that Response objects are immutable. You are never modifying the response object, but creating a new one which you will need to return.

Writing HTML Responses

$myHtml="<html><body><p>Hello world</p></body></html>";
$newResponse = $response->getBody()->write($myHtml);
return $newResponse;

Writing Json Responses

I mostly use Slim to write small API microservices, so this is handy:

$data = array(
    "message" => "something went wrong",
    "someOtherThing" => "someValue",
);

$responseCode = 500; //internal server error

// make the response pretty - optional
$bodyJson = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

$returnResponse = $response->withStatus($responseCode)
                    ->withHeader("Content-Type", "application/json")
                    ->write($bodyJson);

Redirect

$newResponse = $response->withRedirect('/new-url', 301);
return $newResponse;

Routing

Registering Routes:

Below is an example of registering a GET route of /

$app->get('/', function (\Slim\Http\Request $request, Slim\Http\Response $response, $args) {
    $homeController = new HomeController($request, $response, $args);
    return $homeController->index();
});

Route Variables

Having a route like /hello?name=john is pretty ugly. It would be much nicer to have a route like /hello/john. You can use something like below to allow a variable name in the route and pass it to a function

$app->get('/hello/{name}', function (\Slim\Http\Request $request, Slim\Http\Response $response, $args) {
    $homeController = new HomeController($request, $response, $args);
    return $homeController->hello($args['name']);
});

Thus if I went to /hello/world then world would get passed to my HomeController's hello method. Likewise if I went to /hello/there then there would be passed to my HomeController's hello method.

Route Variable Regular Expressions

Lets say you're programming a RESTful API and want to be able to handle the following routes:

  • /resource1/{id}
  • /resource1/someMethod

This would not work because whenever someone went to /resource1/someMethod then the first route would match and someMethod would just be considered as an id for the first route. What you need to do is use a regular expression on the first route. If you are using auto incrementing integers for your IDs then you would use something like:

$app = new \Slim\App();
$app->get('/users/{id:[0-9]+}', function  (\Slim\Http\Request $request, \Slim\Http\Response $response, $args) {
    $userController = new UserController($request, $response, $args);
    return $userController->view($args['id']);
});

This would ensure that the first route would only match and be executed when a number was passed after /users. E.g. /user/1.

If you are using UUIDs instead of integers, then you can just use:

$app->get('/users/{id:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}}', function (Request $request, Response $response, $args) {
    $userController = new UserController($request, $response, $args);
    return $userController->view($args['id']);
});

Route Group

The code example below shows you how you can use groups to easily manage your routes. E.g. create a group for /api, and then a subgroup for each resource. Then you don't have to keep re-typing /api or /resource1 etc and it makes renaming and managing your routes a lot easier.

$app->group('/api', function () use($app) {

    $app->group('/resource1', function () use($app) {

        // e.g. /api/resource1/{resource_id}
        $app->get('/{resource_id}', function (\Slim\Http\Request $request, Slim\Http\Response $response, $args) {
            $controller = new Resource1Controller($request, $response, $args);
            return $controller->load($args['resource_id']);
        });
    });

    $app->group('/resource2', function () use($app) {

        // e.g. /api/resource2/{resource_id}
        $app->get('/{resource_id}', function (\Slim\Http\Request $request, Slim\Http\Response $response, $args) {
            $controller = new Resource2Controller($request, $response, $args);
            return $controller->load($args['resource_id']);
        });
    });
});

Name Your Routes

Naming routes can come in useful, especially when writing middleware. Name your routes like so:

$app->get('/', function (Slim\Http\Request $request, \Slim\Http\Response $response, $args) {
    // do something
})->setName('myRouteName');

Middleware

Middleware is awesome! It's perfect for performing routine operations you need to perform on requests, such as check the user is logged in when accessing certain routes (e.g. not the login page), or logging every request. Create middleware for each type of check/operation you need to perform and stack as many as you want. Thus you don't need to create "god objects" or have massive bootstrap/init files that perform all checks and operations. This structure makes it really easy to add/remove and re-use/share logic. E.g. write your authentication middleware once and easily add it to all your other projects.

I really recommend reading How does middleware work?.

Create Middleware Inline

Below is an example of defining middleware inline (not using a class). This example redirects all routes that have a trailing / to a route without it. Thus you can treat /home/ the same as /home

<?php

$app = new Slim\App();

// Define trailing slash middleware
$trailingSlashMiddleware = function (Slim\Http\Request $request, Slim\Http\Response $response, callable $next) {
    $uri = $request->getUri();
    $path = $uri->getPath();

    if ($path != '/' && substr($path, -1) == '/') 
    {
        // redirect paths with a trailing slash
        // to their non-trailing counterpart
        $uri = $uri->withPath(substr($path, 0, -1));

        if ($request->getMethod() == 'GET') 
        {
            return $response->withRedirect((string)$uri, 307);
        }
        else 
        {
            return $next($request->withUri($uri), $response);
        }
    }

    return $next($request, $response);
});

// Apply the middleware to every request.
$app->add($trailingSlashMiddleware);

This is a modified version of the code found in the documentation.

Apply Middleware To Only Certain Routes

The previous example applied the middleware globally, but if you only want it to execute when certain routes are matched, do it like so:

$myMiddleware = function (Slim\Http\Request $request, Slim\Http\Response $response, callable $next) {
    // do something.
};

$app->get('/', function (\Slim\Http\Request $request, Slim\Http\Response $response, $args) {
    $allGetVars = $request->getQueryParams();
    $myGetVariableValue = $allGetVars['myGetVariableName'];
})->add($myMiddleware);

Alternatively, apply the middleware globally, but name your routes and have the middleware logic itself check the route name and decide whether to apply itself or not. For example, my middleware tutorial for checking if users are logged in has a list of "public routes" that it knows not run authentication against.

Middleware Class

To prevent your codebase getting extremely messy, it's probably worth putting your middleware into classes under a middleware folder. Here is an example middleware class:

class MyAwesomeMiddleware
{
    private $m_someMemberVariable;


    /**
     * Create our middleware. You don't have to have a constructor.
     * @param $someValue - some value to configure our middleware
     */
    public function __construct($someValue="defaultValue")
    {
        $this->m_someMemberVariable = $someValue;
    }


    /**
     * This is the important bit when creating middleware and what gets invoked (hence the name)
     */
    public function __invoke(Slim\Http\Request $request, \Slim\Http\Response $response, $next) 
    {
        $route = $request->getAttribute('route');

        if (!empty($route))
        {
            $routeName = $route->getName();
        }

        if ($this->doSomeCheck($routeName))
        {
            $returnResponse = $next($request, $response);
        }
        else
        {
            // pretend something went wrong, causing the check to fail
            // and return a JSON error message
            $data = array(
                "message" => "something went wrong",
            );

            $responseCode = 500; //internal server error
            $bodyJson = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

            $returnResponse = $response->withStatus($responseCode)
                                ->withHeader("Content-Type", "application/json")
                                ->write($bodyJson);
        }

        return $returnResponse;
    }
}

Then you would add it just like inline middleware. E.g.

$app->add(new MyAwesomeMiddleware());

Misc

Display Errors In Dev

To display error details, simply set displayErrorDetails to true in the settings passed to the App, like shown below:

$slimSettings = array();

if (ENVIRONMENT === 'dev')
{
    $slimSettings['displayErrorDetails'] = true;
}

$slimConfig = array('settings' => $slimSettings);
$app = new Slim\App($slimConfig);

Get $_GET variables:

The code below shows how to access your $_GET variables.

$app->get('/', function (\Slim\Http\Request $request, Slim\Http\Response $response, $args) {
    $allGetVars = $request->getQueryParams();
    $myGetVariableValue = $allGetVars['myGetVariableName'];
});

Get $_POST and $_PUT Variables

$app->get('/', function (\Slim\Http\Request $request, Slim\Http\Response $response, $args) {
    $allPostPutVars = $request->getParsedBody();
    $myVariableValue = $allPostPutVars['myVariableName'];
});

Abstract Slim Controller

Whenever I create a new Slim project, I always like creating this class right off the bat which all my "concrete" controllers extend. This way I access the request, response, and args with $this->m_.

abstract class AbstractSlimController
{
    protected $m_request;
    protected $m_response;
    protected $m_args;


    public function __construct(\Slim\Http\Request $request, Slim\Http\Response $response, $args) {
        $this->m_request = $request;
        $this->m_response = $response;
        $this->m_args = $args;
    }

    // this one is optional - refer to Slim3 - Simplifying Routing At Scale
    // https://blog.programster.org/slim3-simplifying-routing-at-scale
    abstract public function registerRoutes($app);
}

This goes along with the fact that when I register routes, it's like so:

$app->get('/', function (\Slim\Http\Request $request, Slim\Http\Response $response, $args) {
    $controller = new MyController($request, $response, $args);
    return $controller->someControllerMethodToHandleRequest();
});

For larger codebases, I actually register the routes in the controllers, rather than registering them all in the index.php file.

Getting Help

References