Skip to content

Instantly share code, notes, and snippets.

@bishoplee
Created May 21, 2018 14:49
Show Gist options
  • Save bishoplee/3da514d54525e796daa969d275cd40de to your computer and use it in GitHub Desktop.
Save bishoplee/3da514d54525e796daa969d275cd40de to your computer and use it in GitHub Desktop.
Creating your first Slim Framework Application

Creating your first Slim Framework Application

The Slim Framework is a great micro frameworks for Web application, RESTful API's and Websites. This Tutorial shows how to create a very basic but flexible project for every use case.

Table of contents

Requirements

Introduction

I'm sure you've read the official Slim tutorial "First Application Walkthrough" and found that it doesn't work as expected on your server. Most of the people I spoke to on StackOverflow and in the Slim Forum had similar problems due to this tutorial.

I think this tutorial should follow good practices and the directory structure of Slim-Skeleton. The current version of the official tutorial is "outdated" because it does not reflect the best practices of the PHP community and makes it very difficult, especially for beginners.

This tutorial tries to make the start easier and less confusing.

Installation

Composer is the best way to install Slim Framework. Open the console and change to the project root directory. Then enter:

composer require slim/slim

This creates a new folder vendor/ and the files composer.json + composer.lock with all the dependencies of your application.

Please don't commit the vendor/ to your git repository. To set up the git repository correctly, create a file called .gitignore in the project root folder 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 web server and increases the security of the entire application.

Create the following directory structure in the root directory of your project:

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

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

The folder public 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. But more about that later.

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.

The content of public/.htaccess:

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

Internal redirect to the front controller

The .htaccess file in the project root is required in at least two cases.

  • For the development environment

For example, if you are developing with XAMPP, you have only one host and several subdirectories. So that Slim runs in this environment without additional setup, this file was created. This simplifies the entire development environment, as you can easily manage any number of applications at the same time.

  • In case your application is deployed within subfolders of the vhost. (I've seen that many times before).

The content of .htaccess:

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

Bootstrap

Add the following code to the front controller file public/index.php.

<?php

/** @var Slim\App $app */
$app = require __DIR__ . '/../config/bootstrap.php';

// Start
$app->run();

The entire configuration of your application is stored and managed in the config folder.

Create the following config files with all settings, routings, middleware etc.

Content of file config/bootstrap.php

<?php

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

// Instantiate the app
$app = new \Slim\App(['settings' => require __DIR__ . '/../config/settings.php']);

// Set up dependencies
require  __DIR__ . '/container.php';

// Register middleware
require __DIR__ . '/middleware.php';

// Register routes
require __DIR__ . '/routes.php';

return $app;

Container

Slim uses a dependency container to prepare, manage, and inject application dependencies. Slim supports containers that implement PSR-11 or the Container-Interop interface.

The container configuration is an importand part of a good application setup.

Content of file config/container.php

<?php

use Slim\Container;

/** @var \Slim\App $app */
$container = $app->getContainer();

// Activating routes in a subfolder
$container['environment'] = function () {
    $scriptName = $_SERVER['SCRIPT_NAME'];
    $_SERVER['SCRIPT_NAME'] = dirname(dirname($scriptName)) . '/' . basename($scriptName);
    return new Slim\Http\Environment($_SERVER);
};

Middleware

Content of file config/middleware.php.

At the moment this file contains no middleware.

<?php

// Slim middleware

Routes

Content of file config/routes.php

<?php

use Slim\Http\Request;
use Slim\Http\Response;

$app->get('/', function (Request $request, Response $response) {
    $response->getBody()->write("It works! This is the default welcome page.");

    return $response;
})->setName('root');

$app->get('/hello/{name}', function (Request $request, Response $response) {
    $name = $request->getAttribute('name');
    $response->getBody()->write("Hello, $name");

    return $response;
});

Configuration

Content of file config/settings.php

<?php

$settings = [];

// Slim settings
$settings['displayErrorDetails'] = true;
$settings['determineRouteBeforeAppMiddleware'] = true;

// Path settings
$settings['root'] = dirname(__DIR__);
$settings['temp'] = $settings['root'] . '/tmp';
$settings['public'] = $settings['root'] . '/public';

// View settings
$settings['twig'] = [
    'path' => $settings['root'] . '/templates',
    'cache_enabled' => false,
    'cache_path' =>  $settings['temp'] . '/twig-cache'
];

// Database settings
$settings['db']['host'] = 'localhost';
$settings['db']['username'] = 'root';
$settings['db']['password'] = '';
$settings['db']['database'] = 'test';
$settings['db']['charset'] = 'utf8';
$settings['db']['collation'] = 'utf8_unicode_ci';

return $settings;

Composer

By convention all application specific PHP classes are stored in the src folder. We create the namespace App and use the src folder as a starting point for composer:

Content of composer.json

{
  "require": {
    "php": "^7.0",
    "slim/slim": "^3.9"
  },
  "autoload": {
    "psr-4": {
      "App\\": "src"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "App\\Test\\": "tests"
    }
  },
  "config": {
    "sort-packages": true
  }
}

Run composer update so that the changes take effect.

Starting

Now open the browser and navigate to the slim application:

If you are running the web app within a subdirectory just add the name of the subdirectory to the url.

You should see the message: It works! This is the default welcome page.

Then open http://localhost/hello/world or http://localhost/{my-project-name}/hello/world

You should see the message: Hello, world

Views and Templates

The Twig View is a Slim Framework view helper built on top of the Twig templating component. You can use this component to create and render templates in your Slim Framework application.

Twig installation

Run composer

composer require slim/twig-view

Add this code to the file: config/container.php

// Register Twig View helper
$container['view'] = function (Container $container) {
    $settings = $container->get('settings');
    $viewPath = $settings['twig']['path'];

    $twig = new \Slim\Views\Twig($viewPath, [
        'cache' => $settings['twig']['cache_enabled'] ? $settings['twig']['cache_path'] : false
    ]);

    /** @var Twig_Loader_Filesystem $loader */
    $loader = $twig->getLoader();
    $loader->addPath($settings['public'], 'public');

    // Instantiate and add Slim specific extension
    $router = $container->get('router');
    $uri = \Slim\Http\Uri::createFromEnvironment($container->get('environment'));
    $twig->addExtension(new \Slim\Views\TwigExtension($router, $uri));

    return $twig;
};

Twig templates

Add a new template: templates/time.twig

<!DOCTYPE html>
<html>
<head>
    <base href="{{ base_url() }}/"/>
</head>
<body>
Current time: {{ now }}
</body>
</html>

Add a new route in config/routes.php

$app->get('/time', function (Request $request, Response $response) {
    $viewData = [
        'now' => date('Y-m-d H:i:s')
    ];

    return $this->get('view')->render($response, 'time.twig', $viewData);
});

Then open http://localhost/time or http://localhost/{my-project-name}/time

You should see the message: Current time: 2017-12-06 21:52:57

Errors and Logging

Display Error Details

What can I do with a 500 Internal Server Error?

That's probably happened to all of us before: You open your website and find only a more or less meaningful page that reports a "Code 500 - Internal Server Error". But what does that mean?

This is a very general message from the server that an error has occurred, which is almost certainly due to the configuration of the server or the incorrect execution of a server script.

The default error handler can also include detailed error diagnostic information. To enable this you need to set the displayErrorDetails setting to true:

$settings['displayErrorDetails'] = true;

Logging of errors

It becomes more difficult if you have worked in the server configuration files. Tiny errors in the .htaccess file or the filesystem permissions can lead to such errors.

A quick checklist:

  • Does your Apache DocumentRoot points to the public folder?
  • Did you set the write permissions to the temp and upload folder correctly?
  • Are the database connection parameters correct?

To see where the problem is, you should log all errors in a logfile. Here you can find instructions:

Logging errors in Slim 3

Logging

Installation

composer require monolog/monolog

Add this new settings in config/settings.php

// Logger settings
$settings['logger'] = [
    'name' => 'app',
    'file' => $settings['temp'] . '/logs/app.log',
    'level' => \Monolog\Logger::ERROR,
];

Add a new logger entry in config/container.php

use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;

$container['logger'] = function (Container $container) {
    $settings = $container->get('settings');
    $logger = new Logger($settings['logger']['name']);
    
    $level = $settings['logger']['level'];
    if (!isset($level)) {
        $level = Logger::ERROR;
    }
    
    $logFile = $settings['logger']['file'];
    $handler = new RotatingFileHandler($logFile, 0, $level, true, 0775);
    $logger->pushHandler($handler);
    
    return $logger;
};

Add a new route in config/routes.php

use Psr\Log\LoggerInterface;
use Slim\Container;

$app->get('/logger-test', function (Request $request, Response $response) {
    /** @var Container $this */
    /** @var LoggerInterface $logger */

    $logger = $this->get('logger');
    $logger->error('My error message!');

    $response->getBody()->write("Success");

    return $response;
});

Then open http://localhost/logger-test or http://localhost/{my-project-name}/logger-test

Check the content of the logfile in the project sub-directory: tmp/logs/app-2018-04-24.log

You should see the logged error message. Example: [2018-04-24 21:12:47] app.ERROR: My error message! [] []

Database

Database configuration

Adjust the necessary connection settings in the file config/settings.php.

PDO

Add a container entry for the PDO connection:

$container['pdo'] = function (Container $container) {
    $settings = $container->get('settings');
    
    $host = $settings['db']['host'];
    $dbname = $settings['db']['database'];
    $username = $settings['db']['username'];
    $password = $settings['db']['password'];
    $charset = $settings['db']['charset'];
    $collate = $settings['db']['collation'];
    
    $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
    
    $options = [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_PERSISTENT => false,
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES $charset COLLATE $collate"
    ];

    return new PDO($dsn, $username, $password, $options);
};

Query Builder

For security reasons (SQL injections), SQL statements should no longer be written by yourself, but generated using a query builder. For this purpose, the PHP community offers already established and tested libraries. Here is a selection I can recommend:

You should use a query builder only within a Data Mapper or Repository class. Here you can find some examples of a Data Mapper class. TicketMapper, ComponentMapper.php.

The Laravel Illuminate Database query builder brings a super natrual, fluent and smooth query builder to you. The only disadvantage is that you get a lot of global laravel functions and classes as dependencies on board. Caution: Laravel Illuminate does not return arrays, but collections with stdClass instances. You should get along with that, too.

Installation

composer require illuminate/database

Add a new container entry in the file: config/container.php

use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\Connection;

$container['db'] = function (Container $container) {
    $settings = $container->get('settings');
    
    $config = [
        'driver' => 'mysql',
        'host' => $settings['db']['host'],
        'database' => $settings['db']['database'],
        'username' => $settings['db']['username'],
        'password' => $settings['db']['password'],
        'charset'  => $settings['db']['charset'],
        'collation' => $settings['db']['collation'],
    ];
    
    $factory = new ConnectionFactory(new \Illuminate\Container\Container());
    
    return $factory->make($config);
};

$container['pdo'] = function (Container $container) {
    return $container->get('db')->getPdo();
};

Generate and execute a query:

use Illuminate\Database\Connection;

$app->get('/databases', function (Request $request, Response $response) {
    /** @var Container $this */
    /** @var Connection $db */
    
    $db = $this->get('db');

    // fetch all rows as collection
    $rows = $db->table('information_schema.schemata')->get();

    // return a json response
    return $response->withJson($rows);
});

You can build very complex sql queries. Here are just some CRUD examples:

// Retrieving a single Row
$user = $this->db->table('users')->select('id', 'username', 'email')->where('id', '=', 1)->first();

// Retrieving all rows as stdClass
$users = $this->db->table('users')->select('id', 'username', 'email')->get();

// Retrieving all rows as array
$users = $this->db->table('users')->select('id', 'username', 'email')->get()->toArray();

// Insert a new row
$success = $this->db->table('users')->insert(['username' => 'max', 'email' => '[email protected]']);

// Insert a new row and get the new ID (primary key)
$userId = $this->db->table('users')->insertGetId(['username' => 'sofia', 'email' => '[email protected]']);

// Delete user with id = 1
$this->db->table('users')->delete(1);

// Delete all users where users.votes > 100
$this->db->table('users')->where('votes', '>', 100)->delete();

Here you can find the documentation and a Eloquent Cheat Sheet.

Deployment

For deployment on a productive server, there are some important settings and security releated things to consider.

You can use composer to generate an optimized build of your application. All dev-dependencies are removed and the Composer autoloader is optimized for performance.

composer update --no-dev --optimized-autoloader

Furthermore, you should activate caching for Twig to increase the performance of the template engine.

$settings['twig']['cache_enabled'] = true;

Routing can also be accelerated by using a cache file:

$settings['routerCacheFile'] = __DIR__ . '/../tmp/routes.cache.php',

Set the option displayErrorDetails to false in production:

$settings['displayErrorDetails'] = false;

Important: For security reasons you MUST set the Apache DocumentRoot and the <Directory of the webhosting to the public folder. Otherwise, it may happen that someone else accesses the internal files from outside. More details

/etc/apache2/sites-enabled/000-default.conf

DocumentRoot /var/www/example.com/htdocs/public

Tip: Never store secret passwords in a Git / SVN repository. Instead you could store them in a file like env.php and place this file one directory above your application directory. e.g.

/var/www/example.com/env.php
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment