Slim 4 - Tutorial

Daniel Opitz
Daniel Opitz
05 Nov 2019

This tutorial shows you how to work with the powerful and lightweight Slim 4 framework.

slim

You can buy all Slim articles as eBook.

Table of contents

Requirements

Introduction

Slim Framework is a great microframework for web applications, RESTful API’s and websites.

Our aim is to create a RESTful API with routing, business logic and database operations.

Standards like PSR and best practices are very important and integrated part of this tutorial.

Installation

Create a new project directory and run this command to install the Slim 4 core components:

composer require slim/slim:"4.*"

Since Slim 4 the most implementations are decoupled from the App core. The Nyholm PSR-7 package is a super strict implementation of PSR-7 that is blazing fast. To install the needed PSR-7 implementations, run:

composer require nyholm/psr7
composer require nyholm/psr7-server

Ok nice, now we have installed the most basic dependencies for our project. Later we will add more dependencies to the project.

Note that you should not commit the vendor/ directory to your git repository. Create a file .gitignore in the project root directory and add the following lines to this file:

vendor/
.idea/

Directory Structure

A good directory structure helps you organize your code, simplifies setup on the webserver and increases the security of the entire application.

In a web application, it is important to distinguish between the public and non-public areas.

The public/ directory serves your application and will therefore also be directly accessible by all browsers, search engines and API clients. All other folders are not public and must not be accessible online. This can be done by defining the public folder in Apache as DocumentRoot of your website.

Create a new directory: public/

In the next steps of this tutorial we will create a project directory structure that will look like this:

.
├── config/             Configuration files
├── public/             Web server files (DocumentRoot)
│   └── .htaccess       Apache redirect rules for the front controller
│   └── index.php       The front controller
├── src/                PHP source code (The App namespace)
├── vendor/             Reserved for composer
├── .htaccess           Internal redirect to the public/ directory
├── .gitignore          Git ignore rules
└── composer.json       Project dependencies and autoloader

Autoloader

One of the most fundamental and important thing is to have a working PSR-4 autoloader.

So the next step is to define the src/ directory as root for the \App namespace.

Create a new directory: src/

Add this autoloader settings into composer.json:

"autoload": {
  "psr-4": {
    "App\\": "src/"
  }
}

The complete composer.json file should look like this:

{
  "require": {
    "nyholm/psr7": "^1.5",
    "nyholm/psr7-server": "^1.0",
    "slim/slim": "^4"
  },
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  }
}

Run composer update for the changes to take effect.

Apache URL Rewrite Rules

This step is optional, and only needed if you plan to develop or host your Slim app on Apache.

Make sure that the Apache mod_rewrite module is installed and enabled.

To run a Slim app with apache we have to add url rewrite rules to redirect the web traffic to a so-called front controller.

The front controller is just a index.php file and the entry point to the application.

Create a .htaccess file in your public/ directory and copy/paste this content:

# Redirect to front controller
RewriteEngine On
# RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

Please do not change the RewriteRule directive. It must be exactly as shown above.

Note that only one some webhosts, you may need to uncomment the line # RewriteBase / to make it work.

Create a second .htaccess file in your project root-directory and copy/paste this content:

RewriteEngine on
RewriteRule ^$ public/ [L]
RewriteRule (.*) public/$1 [L]

Do not skip this step. This second .htaccess file is important to run your Slim app in a sub-directory and within your development environment.

Configuration

The directory for all configuration files is: config/

Create a directory: config/

The file config/settings.php is the main configuration file and combines the default settings with environment specific settings.

Create a configuration file config/settings.php and copy/paste this content:

<?php

// Should be set to 0 in production
error_reporting(E_ALL);

// Should be set to '0' in production
ini_set('display_errors', '1');

// Settings
$settings = [];

// ...

return $settings;

DI Container

Next, we need a PSR-11 dependencies injection container (DI container) implementation for dependency injection and autowiring.

Dependency injection is passing dependency to other objects. Dependency injection makes testing easier.

Autowiring means, that you can declare all dependencies explicitly in your class constructor and let the DI container inject these dependencies automatically.

One of the best DI container implementation is PHP-DI. To install the package, run:

composer require php-di/php-di

DI Container Definitions

Create a new file for the DI container entries config/container.php and copy/paste this content:

<?php

use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Slim\App;
use Slim\Factory\AppFactory;

return [
    'settings' => function () {
        return require __DIR__ . '/settings.php';
    },

    App::class => function (ContainerInterface $container) {
        AppFactory::setContainer($container);

        return AppFactory::create();
    },

    ResponseFactoryInterface::class => function (ContainerInterface $container) {
        return $container->get(Psr17Factory::class);
    },
];

Note that we use the DI container to build the Slim App instance and to load the application settings. This allows us to configure the infrastructure services, like the database connection, mailer etc. within the DI container. All settings are just passed as an simple array, so we have no special class identifier (FQCN) here.

The App::class identifier is needed to ensure that we use the same App object across the application and to ensure that the App object uses the same DI container object.

The ResponseFactoryInterface::class identifier is optional, but will be needed later for some custom middlewares that depend on this specific interface.

Bootstrap

The app startup process contains the code that is executed when the application (request) is started.

The bootstrap procedure includes the composer autoloader and then continues to build the DI container, creates the app and registers the routes + middleware entries.

Create the bootstrap file config/bootstrap.php and copy/paste this content:

<?php

use DI\ContainerBuilder;
use Slim\App;

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

$containerBuilder = new ContainerBuilder();

// Add DI container definitions
$containerBuilder->addDefinitions(__DIR__ . '/container.php');

// Create DI container instance
$container = $containerBuilder->build();

// Create Slim App instance
$app = $container->get(App::class);

// Register routes
(require __DIR__ . '/routes.php')($app);

// Register middleware
(require __DIR__ . '/middleware.php')($app);

return $app;

Front Controller

The front controller is the entry point to your slim application and handles all requests by channeling requests through a single handler object.

For security reasons you should always place your front-controller (index.php) into the public/ directory. You should never place the front controller directly into the project root directory.

Create the front-controller file public/index.php and copy/paste this content:

<?php

(require __DIR__ . '/../config/bootstrap.php')->run();

Be careful: The public/ directory is only the DoumentRoot of your webserver, but it’s never part of your base path and the official url.

Good URLs:

Bad URLs:

Middleware

A middleware can be executed before and after your Slim application to read or manipulate the request and response object.

To get Slim running we need to add the Slim RoutingMiddleware and ErrorMiddleware.

The RoutingMiddleware will route and dispatch the incoming requests and the ErrorMiddleware is able to catch all runtime exceptions.

The BodyParsingMiddleware is just optional, but recommend if you work with JSON or form data.

Create a file config/middleware.php to set up the global middlewares and copy/paste this content:

<?php

use Slim\App;

return function (App $app) {
    // Parse json, form data and xml
    $app->addBodyParsingMiddleware();

    // Add the Slim built-in routing middleware
    $app->addRoutingMiddleware();

    // Catch exceptions and errors
    $app->addErrorMiddleware(true, true, true);
};

Routes

A “route” is a URL path that can be mapped to a specific handler. Such a handler can be a simple function or an invokable class. Under the hood Slim uses the nikic/FastRoute package, but it also adds some nice features for routing names, groups and middlewares etc.

The application routes will be defined in plain PHP files.

Create a file config/routes.php and copy/paste this content:

<?php

use Slim\App;

return function (App $app) {
    // empty
};

The first route

Open the file config/routes.php and insert the code for the first route:

<?php

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;

return function (App $app) {
    $app->get('/', function (
        ServerRequestInterface $request,
        ResponseInterface $response
    ) {
        $response->getBody()->write('Hello, World!');

        return $response;
    });
};

Now open your website, e.g. http://localhost and you should see the message Hello, World!.

Base Path

When you set up your first Slim project, you may get an 404 error (not found).

This can happen when you run your Slim app in a sub-directory, and not directly within the DocumentRoot of the webserver. To fix this you have to set the correct “base path”.

Ideally the DoumentRoot of your production server points directly to the public/ directory. In this case you don’t need to configure the Slim base path.

In all other cases you have to make sure, that your base path is correct. For example, the DocumentRoot directory is /var/www/domain.com/htdocs/, but the application is stored under /var/www/domain.com/htdocs/my-app/, then you have to set /my-app as base path.

To be more precise: In this context “sub-directory” means a sub-directory of the project, and not the public/ directory. For example when you place your app not directly under the webservers DocumentRoot.

You can manually set the base path in Slim using the setBasePath method:

$app->setBasePath('/slim4-tutorial');

The problem is, that the basePath can be different for each host (dev, testing, staging, prod etc…).

Luckily, the BasePathMiddleware is able to detect and configure the Slim App basePath.

To install the package, run:

composer require selective/basepath

Add the following DI container definition into config/container.php:

use Psr\Container\ContainerInterface;
use Selective\BasePath\BasePathMiddleware;
use Slim\App;
// ...

return [
    // ...

    BasePathMiddleware::class => function (ContainerInterface $container) {
        return new BasePathMiddleware($container->get(App::class));
    },
];

Then add the BasePathMiddleware::class, right after the RoutingMiddleware, to the middleware stack in config/middleware.php:

use Selective\BasePath\BasePathMiddleware;

$app->add(BasePathMiddleware::class);

The result:

<?php

use Selective\BasePath\BasePathMiddleware;
use Slim\App;
use Slim\Middleware\ErrorMiddleware;

return function (App $app) {
    // Parse json, form data and xml
    $app->addBodyParsingMiddleware();

    // Add the Slim built-in routing middleware
    $app->addRoutingMiddleware();

    $app->add(BasePathMiddleware::class);

    // Catch exceptions and errors
    $app->add(ErrorMiddleware::class);
};

Now that you have installed the BasePathMiddleware, remove this line (if exists): $app->setBasePath('...');.

Single Action Controller

Slim provides some methods for adding controller logic directly in a route callback. The request/response object (and optionally the route arguments) are passed by Slim to a callack function as follows:

<?php

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

// ...

$app->get('/', function (ServerRequestInterface $request, ResponseInterface $response) {
    $response->getBody()->write('Hello, World!');
    
    return $response;
});

While such interfaces look intuitive, they are not suitable for complexer scenarios.

Anonymous functions as routing handlers are quite “expensive”, because PHP has to create all functions for each request. The use of class names is more lightweight, faster and scales better for larger applications. Unless your logic is very simple, I don’t recommend using callback functions. This is the moment where a Single Action Controller come into play.

Each Single Action Controller is represented by its own class and has only one public method.

The Action does only these things:

All other logic, including all forms of input validation, error handling, and so on, are therefore pushed out of the Action and into the Domain (for domain logic concerns) or the response renderer (for presentation logic concerns).

A response could be rendered to HTML for a standard web request; or it might be something like JSON for a RESTful API.

Create a sub-directory: src/Action

Create this action class in: src/Action/HomeAction.php

<?php

namespace App\Action;

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

final class HomeAction
{
    public function __invoke(
        ServerRequestInterface $request, 
        ResponseInterface $response
    ): ResponseInterface {
        $response->getBody()->write('Hello, World!');

        return $response;
    }
}

Then open config/routes.php and replace the route closure for / with this line:

$app->get('/', \App\Action\HomeAction::class);

The complete config/routes.php should look like this now:

<?php

use Slim\App;

return function (App $app) {
    $app->get('/', \App\Action\HomeAction::class);
};

Now open the URL, e.g. http://localhost and you should see the message Hello, World!.

Creating a JSON Response

To create a valid JSON response you can write the json encoded string to the response body and set the Content-Type header to application/json:

<?php

namespace App\Action;

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

final class HomeAction
{
    public function __invoke(
        ServerRequestInterface $request, 
        ResponseInterface $response
    ): ResponseInterface {
        $response->getBody()->write(json_encode(['hello' => 'world']));

        return $response->withHeader('Content-Type', 'application/json');
    }
}

Open your website, e.g. http://localhost and you should see the JSON response {"hello":"world"}.

Conclusion

Remember the relationships:

The source code with for this tutorial can be found here:

If you have Slim-Framework specific questions, visit: