Programster's Blog

Tutorials focusing on Linux, programming, and open-source

Slim 4 Cheatsheet

Table Of Contents

Getting Started

Install the framework with:

composer require slim/slim 4.*
composer require slim/psr7

We went with the slim/psr7 implementation of the request and response, but you can use others.

Then get started by putting the following in ./public/index.php

<?php
require_once(__DIR__ . '/../vendor/autoload.php');
$app = Slim\Factory\AppFactory::create();
$app->addErrorMiddleware($displayErrorDetails=true, $logErrors=true, $logErrorDetails=true);

$app->get('/', function (Psr\Http\Message\ServerRequestInterface $request, Psr\Http\Message\ResponseInterface $response) {
    $body = $response->getBody();
    $body->write('Hello world'); // returns number of bytes written
    $newResponse = $response->withBody($body);
    return $newResponse;
});

$app->run();

run with:

php -S localhost ./public/index.php

Routing

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 (Psr\Http\Message\ServerRequestInterface $request, Psr\Http\Message\ResponseInterface $response, array $args) {
    $name = $args['name'];
    $body = $response->getBody();
    $body->write('Hello world'); // returns number of bytes written
    $newResponse = $response->withBody($body);
    return $newResponse;
});

Body Parameters

To get the body parameters, you need to use:

$request->getParsedBody()

... however, for that to work, you need to ensure the body parsing middleware is run by adding:

$app->addBodyParsingMiddleware();

I have not tested if you do not need to make this addition when using x-www-form-urlencoded fields instead of a JSON string body.

Returning JSON Responses

$responseData = array('hello' => 'world');
$responseBody = json_encode($responseData);
$response = new \Slim\Psr7\Response($status=200);
$response->getBody()->write($responseBody);
$response = $response->withHeader('Content-Type', 'application/json');
return $response;

Returning A Redirect

This is easier with just providing you a function:

/**
 * Create a redirect response;
 * @param string $newLocation - where you wish to redirect the user.
 * @param Slim\Psr7\Response $response - the existing response to work with
 * @param bool $useTemporaryRedirect - whether this redirect is temporary, or browser should cache it.
 * @return Slim\Psr7\Response - the redirect response.
 */
function redirect(
    string $newLocation, 
    bool $useTemporaryRedirect=true
) : Slim\Psr7\Response
{
    $httpCode = ($useTemporaryRedirect) ? 302 : 301;
    $response = new \Slim\Psr7\Response($httpCode);
    $response = $response->withHeader('Location', $newLocation);
    return $response;
}

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_.

<?php


abstract class AbstractSlimController
{
    protected Psr\Http\Message\RequestInterface $m_request;
    protected \Psr\Http\Message\ResponseInterface $m_response;
    protected $m_args;


    public function __construct(\Psr\Http\Message\RequestInterface $request, \Psr\Http\Message\ResponseInterface $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 static function registerRoutes($app);
}

Middleware

Middleware changed significantly between Slim 3 and 4. This is because Slim 4 makes use of PSR-15 for middleware. This means that if you write middleware for Slim, you can use it anywhere else that makes use of PSR-15 as well. The key difference is that instead of the body of your middleware being inside __invoke , it is now within a public method called process, which only has parameters for the request and the handler (there is no parameter for response anymore).

Example

Thus, if you wanted some authentication middleware, that checks if the user is logged in as an admin if they go to anywhere under /admin, then it would look something like this:

<?php

use \Psr\Http\Message\ServerRequestInterface;
use \Psr\Http\Server\RequestHandlerInterface;
use \Psr\Http\Message\ResponseInterface;

class MiddlewareAdminAuth implements \Psr\Http\Server\MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $uri = $_SERVER['REQUEST_URI'];

        if (str_starts_with($uri, "/admin/") && AuthLib::isLoggedInAsAdmin() === false)
        {
            $response = new \Slim\Psr7\Response(302);
            $response = $response->withHeader('Location', '/login');
        }
        else
        {
            $response = $handler->handle($request);
        }

        return $response;
    }
}

You would apply the middleware to the entire app (all routes) like so:

$app = Slim\Factory\AppFactory::create();
$app->add(new MiddlewareHttpBasicAuth());

Alternatively, if you want to apply it to individual routes, you can do so:

$app->get('/', function (Request $request, Response $response, $args) {
    $response->getBody()->write('Hello ');
    return $response;
})->add(new MyMiddlewareClass());

Also, you can apply it to a group of routes:

$app->group('/utils', function(\Slim\Routing\RouteCollectorProxy $group) {
    $group->get('/date', function (Request $request, Response $response) {
        // stuff here
    });

    $group->get('/time', function (Request $request, Response $response) {
        // stuff here
    });
})->add(new MyMiddlewareClass());

You can provide functions instead of classes for middleware, but it gets messy. Find out more at the official docs for middleware.

Middleware Order And Routing

It is worth noting that middleware in Slim 4 operates in a last-in-first-out (LIFO) manner (reference), which might matter for you. For example, one can add the following line to have the routing evaluated (more info).

$app->addRoutingMiddleware();

This is the Slim4 replacement to the Slim 3 way of having to provide the determineRouteBeforeAppMiddleware setting when creating the app, as shown below:

$app = new Slim\App([
    'settings' => [
        'displayErrorDetails' = $displayErrorDetails,
        'determineRouteBeforeAppMiddleware' => true
    ]
]);

Having the routing evaluated means that you can find out the name of the route that was hit like so:

class MyMiddlewareClass implements \Psr\Http\Server\MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $routeContext = \Slim\Routing\RouteContext::fromRequest($request);
        $route = $routeContext->getRoute();
        if (empty($route)) { throw new HttpNotFoundException($request); }
        $routeName = $route->getName();

        # do things...
    }
}

Thus, if one wishes to make use of the route details in Middleware, one needs to make sure to add the middleware before adding the $app->addRoutingMiddleware(); line. E.g.

$app = Slim\Factory\AppFactory::create();

// register our global middleware that uses route names
$app->addMiddleware(new MyMiddlewareClass());

// This must come *after* middleware that uses routing
$app->addRoutingMiddleware(); 

MyController::registerRoutes($app);
$app->run();

Add 404 Page

In order to add a 404 page, one needs to tell the error handler how to handle the HttpNotFoundException like so:

$app = Slim\Factory\AppFactory::create();

// register any custom middleware....
$app->addMiddleware(new SomeCustomMiddleware());
$app->addMiddleware(new AnotherCustomMiddleware());

// register the error middleware. This must be registered last so that it gets executed first.
$errorMiddleware = $app->addErrorMiddleware(
    $displayErrorDetails = (ENVIRONMENT === "dev"),
    $logErrors=true,
    $logErrorDetails=true
);

// Create your 404 handler...
$errorMiddleware->setErrorHandler(\Slim\Exception\HttpNotFoundException::class, function (
    \Psr\Http\Message\ServerRequestInterface $request,
    \Throwable $exception,
    bool $displayErrorDetails,
    bool $logErrors,
    bool $logErrorDetails
) {
    $response = new \Slim\Psr7\Response();
    $body = new View404();
    $view = new ViewCustom404Page();
    $response->getBody()->write((string)$view);
    return $response->withStatus(404);
});

// register routes/controllers here....

$app->run();

References

Last updated: 8th August 2023
First published: 15th August 2020

This blog is created by Stuart Page

I'm a freelance web developer and technology consultant based in Surrey, UK, with over 10 years experience in web development, DevOps, Linux Administration, and IT solutions.

Need support with your infrastructure or web services?

Get in touch