Pragmatism in the real world

A first look at Slim 4

With Slim 4 we have continued the tradition of allowing you to use the framework in the way that best fits you and your project. You can create a Slim application entirely in a single file suitable for prototyping through to a few files for a simple web hook or serverless action all the way to fully-decoupled application suitable for the enterprise.

From my point of view, the big changes with Slim 4 are:

  • Support for PSR-15 middleware.
  • Routing is now independent middleware allowing middleware to run before and after routing.
  • Error handling is decoupled and optional.
  • No built-in DI container. Slim no longer ships with or depends on one to work.
  • No built-in PSR-7 implementation. Slim’s own one is now in Slim-Psr7.
  • Less “surprise”:
    • No automatic handling of _method POST field.
    • No automatic adding of a Content-Length header to the Response.
    • No automatic output buffering.

There are of course lots of other changes, but you’ll notice that this release is all about flexibility. You should be able to bring your own DI container & PSR-7 implementation to the party and you now much easier control over how errors are handled and display, to the point of ignoring our implementation completely! I also like that you can now have some middleware run before routing and some run after without having to use groups.

A detailed look at creating an app

As you can imagine, this amount of flexibility means a lot more set up, so there’s some useful helpers. Before we look at those though, let’s look at how to create a Slim 4 application instance the long way.

If you’re in a hurry, feel free to jump to the recommended way to get going.

Constructing the app instance

The constructor to Slim\App has this signature:

    public function __construct(
        ResponseFactoryInterface $responseFactory,
        ?ContainerInterface $container = null,
        ?CallableResolverInterface $callableResolver = null,
        ?RouteCollectorInterface $routeCollector = null,
        ?RouteResolverInterface $routeResolver = null
        ?MiddlewareDispatcherInterface $middlewareDispatcher = null
    )

You can see the flexibility most clearly here. When you create a new Slim\App, you need to set a PSR-17 response factory so that Slim can create a response object for you to return in your route handler (if you’re not using PSR-15, handlers that is).

You can also supply a PSR-11 compatible container and if set, Slim 4 will use this to instantiate middleware or route handers that are registered as strings in the same way that Slim 3 works. The difference this time is that you can use any container that you want to. DI is a good thing, so I recommend that you do this for any non-trivial application.

The final four constructor parameters ($callableResolver, $routeCollector, $routeResolver and $middlewareDispatcher) are more specialised and allow you to control how Slim works internally.

To instantiate a Slim\App, we need a PSR-7 implementation, so we’ll use Slim-Psr7, so we start with this Composer command:

$ composer require slim/slim slim/psr7

This installs Slim 4 and Slim-Psr7 for us. It will also install all the required PSR interfaces, FastRoute and a couple of HTTP helper libraries.

We can now create our public/index.php file:

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

$responseFactory = new Slim\Psr7\Factory\ResponseFactory();
$app = new Slim\App($responseFactory);

Add routing middleware

We need to route the URL that the user requests to a handler. This is done by Slim’s RoutingMiddleware, let’s add that next to index.php:

$routingMiddleware = new Slim\Middleware\RoutingMiddleware(
    $app->getRouteResolver(),
    $app->getRouteCollector()->getRouteParser()
);
$app->addMiddleware($routingMiddleware);

The RoutingMiddleware needs a route resolver. As we didn’t pass in one to the App‘s constructor, Slim will create its default one which is suitable for most purposes.

Note also that Slim 4 introduces addMiddleware(). This is a type-safe method for adding PSR-15 middleware. Slim continues to support callables for middleware using add() for backwards compatibility with Slim 3.

Add error middleware

Slim’s error handling is robust, is flexible and handles most situations well, so let’s add that too. It should be added as the last piece of middleware as Slim uses a LIFO middleware stack. We have no other middleware at the moment, so we add it to index.php now:

$errorMiddleware = new Slim\Middleware\ErrorMiddleware(
    $app->getCallableResolver(),
    $responseFactory,
    true,
    true,
    true
);
$app->add($errorMiddleware)

Add a route handler

To respond to a request, we register a route handler against an HTTP method and URI. We can respond to a GET request to `/` using $app->map() like this: $app->map(['GET'], '/', $handler);. However it is more common to use the helper methods on App for each HTTP method: $app->get('/', $handler). $handler can be a callable, a string to be resolved by the DI container or a PSR-15 instance.

We’ll use a closure in index.php:

$app->get('/', function (
    Psr\Http\Message\ServerRequestInterface $request,
    Psr\Http\Message\ResponseInterface $response,
    array $args
): Psr\Http\Message\ResponseInterface {
    $response->getBody()->write("hello world");
    return $response;
});

As you can see a callable route handler is given a request object, a response object and an array of route arguments. It must return a response object. The $response object that you are passed is specifically created for this handler by Slim using the PSR-17 ResponseFactory it was given in the constructor. This means that your route handler can be completely PSR-7 independent should you wish.

It’s far more common in larger applications to use a class for the route handler though as it’s easier to deal with dependencies.

Run the application

Finally, we can run our application. We call the run() method, passing in a PSR-7 ServerRequest object. This is the final part of index.php:

$request = Slim\Psr7\Factory\ServerRequestFactory::createFromGlobals();
$app->run($request);

We can start the built-in PHP server and test:

$ php -S 0.0.0.0:8888 -t public/
$ curl -i http://localhost:8888
HTTP/1.1 200 OK
Host: localhost:8888
Content-type: text/html; charset=UTF-8

hello world

It all works and the response contains the correct status code and content-type header as you would expect.

That’s a lot of code to get hello world working. While giving you all the flexibility to choose your own components, Slim remains true to its roots and provides a helper AppFactory class and useful functions on App to make it much easier.

This is what the entire public/index.php looks like when using the helpers:

<?php

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

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

$app->addRoutingMiddleware();
$app->addErrorMiddleware(true, true, true);

$app->get('/', function (
    Psr\Http\Message\ServerRequestInterface $request,
    Psr\Http\Message\ResponseInterface $response,
    array $args
): Psr\Http\Message\ResponseInterface {
    $response->getBody()->write("hello world");
    return $response;
});

$app->run();

There is much less boiler plate with the recommended approach as the AppFactory and the addXyzMiddleware() methods do all the required setup for us. Also, run() will create a valid request object if we don’t supply one.

PSR-7 independence!

What’s really nice about using AppFactory::create() is that it will work out which PSR-7 implementation you have installed and use that one. This means that you could for instance use Diactoros instead:

$ composer require slim/slim zendframework/zend-diactoros

And our public/index.php works without any changes.

In summary

Slim continues to be a great choice for a micro framework that lets you build the application your way, picking the most suitable components from Packagist as you see fit.

If you want to look at some example applications these are worth a look:

Have a play. If you have questions or comments, Slack or the forums are good places to go.

7 thoughts on “A first look at Slim 4

  1. $app->addErrorMiddleware();

    addErrorMiddleware() has three boolean args none of which have default values.

  2. Thanks, Rob. Helped me clear up a few confusions I had about v4.

    Keep the Slim articles coming. There's a real lack of such pieces and the docs, while good, don't have the same detail and tone as articles like yours.

  3. I'm having a go at converting my Slim 3 app to Slim 4 and I have a question straight away that maybe you can help with?

    What is SlimMiddleware, and why do I need it when I can register Slim on the Container like so:

            $container->set('view', function() {
    		return new Twig('../views', ['cache' => false]);
    	});
    
    

    And use it without the middleware like so:

    	$app->get('/', function (Request $request, Response $response, $args) {
    		return $this->get('view')->render($response, 'home.twig');
    	});
    

    Thanks in advance.

  4. Why do I get this error? Am I lacking something from the installation?
    "`
    #0 C:\xampp7\htdocs\portfolio\Tiquet\vendor\slim\slim\Slim\Middleware\RoutingMiddleware.php(57): Slim\Middleware\RoutingMiddleware->performRouting(Object(Slim\Psr7\Request))
    #1 C:\xampp7\htdocs\portfolio\Tiquet\vendor\slim\slim\Slim\MiddlewareDispatcher.php(123): Slim\Middleware\RoutingMiddleware->process(Object(Slim\Psr7\Request), Object(Slim\Routing\RouteRunner))
    #2 C:\xampp7\htdocs\portfolio\Tiquet\vendor\slim\slim\Slim\Middleware\ErrorMiddleware.php(89): class@anonymous->handle(Object(Slim\Psr7\Request))
    #3 C:\xampp7\htdocs\portfolio\Tiquet\vendor\slim\slim\Slim\MiddlewareDispatcher.php(123): Slim\Middleware\ErrorMiddleware->process(Object(Slim\Psr7\Request), Object(class@anonymous))
    #4 C:\xampp7\htdocs\portfolio\Tiquet\vendor\slim\slim\Slim\MiddlewareDispatcher.php(64): class@anonymous->handle(Object(Slim\Psr7\Request))
    #5 C:\xampp7\htdocs\portfolio\Tiquet\vendor\slim\slim\Slim\App.php(174): Slim\MiddlewareDispatcher->handle(Object(Slim\Psr7\Request))
    #6 C:\xampp7\htdocs\portfolio\Tiquet\vendor\slim\slim\Slim\App.php(158): Slim\App->handle(Object(Slim\Psr7\Request))
    #7 C:\xampp7\htdocs\portfolio\Tiquet\public\index.php(19): Slim\App->run()
    #8 {main}"`

Comments are closed.