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.
- Requirements
- Introduction
- Installation
- Directory structure
- Front controller
- Bootstrap
- Container
- Middleware
- Routes
- Configuration
- Composer
- Starting
- Errors and Logging
- Views and Templates
- Database
- Deployment
- PHP 7.x
- MySQL 5.7+
- Apache
- Apache mod_rewrite module
- Composer
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.
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/
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.
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]
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]
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;
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);
};
Content of file config/middleware.php
.
At the moment this file contains no middleware.
<?php
// Slim middleware
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;
});
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;
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.
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
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.
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;
};
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
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;
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 thepublic
folder? - Did you set the write permissions to the
temp
andupload
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:
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! [] []
Adjust the necessary connection settings in the file config/settings.php
.
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);
};
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.
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