server { listen [::]:80; listen 80; server_name my-slim-website.com; index index.php; error_log /somepath/error.log; access_log /somepath/access.log; root /somepath/www/public; location / { try_files $uri /index.php$is_args$args; } location ~ \.php { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_index index.php; fastcgi_pass unix:/var/run/php/php7.3-fpm.sock; } } $app->get('/', function (Request $request, Response $response): Response { $response->getBody()->write('

Hello World !

'); return $response }); $app->get('/hello/{name}', function (Request $request, Response $response, array $args): Response { $name = $args['name']; // $name = $request->getAttribute('name'); $response->getBody()->write('

'); $response->getBody()->write('Hello '); $response->getBody()->write($name); $response->getBody()->write('

'); return $response; }); return function (App $app) { $app->get('/', function (Request $request, Response $response): Response { ... })->setName('root'); $app->get('/hello/{name}', function (Request $request, Response $response, array $args): Response { ... })->setName('hello') }; return function (App $app) { $app->get('/', function (Request $request, Response $response): Response { $response->getBody()->write('

Hello World !

'); return $response; }); $app->get('/hello/{name}', function (Request $request, Response $response, array $args): Response { $name = $args['name']; // $name = $request->getAttribute('name'); $response->getBody()->write('

'); $response->getBody()->write('Hello '); $response->getBody()->write($name); $response->getBody()->write('

'); return $response; }); }; return function (ContainerBuilder $containerBuilder) { $settings = [ 'displayErrorDetails' => true, // set to false in prod 'logErrors' => false, 'logErrorDetails' => false, ]; $containerBuilder->addDefinitions([ 'settings' => $settings, ]); }; // 4 // Instantiate PHP-DI ContainerBuilder $containerBuilder = new ContainerBuilder(); // Set up settings (require __DIR__ . '/../config/settings.php')($containerBuilder); // Build PHP-DI Container instance $container = $containerBuilder->build(); AppFactory::setContainer($container); // Instantiate the app $app = AppFactory::create(); return function (App $app) { $container = $app->getContainer(); // Settings $settings = $container->get('settings'); // Add Routing Middleware $app->addRoutingMiddleware(); // Add Error Middleware $displayErrorDetails = $settings['displayErrorDetails']; $logErrors = $settings['logErrors']; $logErrorDetails = $settings['logErrorDetails']; $app->addErrorMiddleware($displayErrorDetails, $logErrors, $logErrorDetails); }; return function (ContainerBuilder $containerBuilder) { $settings = [ ... // Logger 'logger' => [ 'name' => 'my-slim-website', 'path' => '/somepath/app.log', 'level' => Logger::DEBUG, ], ]; return function($app) { $container = $app->getContainer(); $container->set(LoggerInterface::class, function($container) { $settings = $container->get('settings'); $loggerSettings = $settings['logger']; $name = $loggerSettings['name']; $path = $loggerSettings['path']; $level = $loggerSettings['level']; $logger = new Logger($name); $processor = new UidProcessor(); $logger->pushProcessor($processor); $handler = new StreamHandler($path, $level); $logger->pushHandler($handler); return $logger; }); }; class BeforeMiddleware { public function __invoke(Request $request, RequestHandler $handler): Response { $response = $handler->handle($request); $existingContent = (string) $response->getBody(); $response = new Response(); $response->getBody()->write('
BEFORE
' . $existingContent); return $response; } class BeforeMiddleware implements Middleware { private $logger; public function __construct(Logger $logger) { $this->logger = $logger; } public function process(Request $request, RequestHandler $handler): Response { $this->logger->info("Hi from the Before Middleware."); ... return function (App $app) { $app->get('/', function (Request $request, Response $response): Response { ... })->setName('root'); $app->get('/hello/{name}', function (Request $request, Response $response, array $args): Response { ... })->setName('hello') ->add(AfterMiddleware::class); }; class CorsMiddleware implements Middleware { public function process(Request $request, RequestHandler $handler): Response { $response = $handler->handle($request); return $response ->withHeader('Access-Control-Allow-Origin', 'http://my-slim-website.com') ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization') ->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') ->withHeader('Access-Control-Allow-Credentials', 'true'); class InvokableAction { public function __invoke(Request $request, Response $response, $args): Response { $id = $args['id']; $response->getBody()->write('

'); $response->getBody()->write('Invoke !'); $response->getBody()->write($id); $response->getBody()->write('

'); return $response; } class CustomController { public function getFoo(Request $request, Response $response, $args): Response { $id = $args['id']; $response->getBody()->write('

'); $response->getBody()->write('Custom !'); $response->getBody()->write($id); $response->getBody()->write('

'); return $response; } public function postFoo(Request $request, Response $response, $args): Response { $postParams = $request->getParsedBody(); $firstname = $postParams['firstname']; $response->getBody()->write('

'); $response->getBody()->write('Custom !'); $response->getBody()->write($firstname); $response->getBody()->write('

'); return $response; } return function (ContainerBuilder $containerBuilder) { $settings = [ ... 'twig' => [ 'path' => dirname(__DIR__) . '/views', 'cache' => false // '/somepath/tmp/cache', ], ]; return function($app) { $container = $app->getContainer(); $container->set('view', function($container) { $settings = $container->get('settings'); $twigSettings = $settings['twig']; $root = dirname(__DIR__); return Twig::create($twigSettings['path'], [ 'cache' => $twigSettings['cache'] ]); }); $container->set(Twig::class, function($container) { return $container->get('view'); }); }; class HelloController { private $twig; public function __construct(Twig $twig) { $this->twig = $twig; } class HelloController { ... public function hello(Request $request, Response $response, $args): Response { $name = $request->getAttribute('name'); $params = [ 'name' => $name, ]; return $this->twig->render($response, 'hello.twig', $params); } My Slim Website {% include "menu.twig" %}
{% block content %}{% endblock %}
class Responder { private $twig; private $responseFactory; public function __construct(Twig $twig, ResponseFactoryInterface $responseFactory) { $this->twig = $twig; $this->responseFactory = $responseFactory; } public function render(Response $response, string $template, array $params = [], int $httpStatusCode = 200): Response { $params2 = array_merge($params, [ 'httpStatusCode' => $httpStatusCode, ]); // PHP 7.4: $params2 = [ ...$params, 'foo' => 'lorem' ] return $this->twig->render( $response->withHeader('Content-Type', 'text/html; charset=utf-8')->withStatus($httpStatusCode), $template, $params2); } return function($app) { $container = $app->getContainer(); ... $container->set(ResponseFactoryInterface::class, function($container) use ($app) { return $app->getResponseFactory(); }); }; class MyErrorHandler { private $responder; private $logger; public function __construct(Responder $responder, Logger $logger) { $this->responder = $responder; $this->logger = $logger; } public function __invoke(Request $request, Throwable $exception, bool $displayErrorDetails, bool $logErrors): Response { $path = $request->getUri()->getPath(); // Log error if ($logErrors) { $this->logger->error(sprintf( 'Error: [%s] %s, Method: %s, Path: %s', $exception->getCode(), $exception->getMessage(), $request->getMethod(), $path )); } // Detect status code $httpStatusCode = $this->getHttpStatusCode($exception); // Error message $genericErrorMessage = $this->getErrorMessage($exception, $httpStatusCode, $displayErrorDetails); $response = $this->responder->createResponse(); return $this->responder->render( $response, '/http_error.twig', ['httpStatusCode' => $httpStatusCode, 'genericErrorMessage' => $genericErrorMessage], $httpStatusCode); } class Responder { ... public function createResponse(): Response { return $this->responseFactory->createResponse()->withHeader('Content-Type', 'text/html; charset=utf-8'); } {% extends 'template.twig' %} {% block content %}

Error...

{{ httpStatusCode }}
{{ genericErrorMessage }}
{% endblock %} return function (ContainerBuilder $containerBuilder) { $settings = [ ... 'db' => [ 'user' => 'mylogin', 'password' => 'mypwd', 'host' => 'localhost', 'name' => 'my_slim_website_db', ], ]; return function($app) { $container = $app->getContainer(); $container->set(PDO::class, function($container): PDO { $settings = $container->get('settings'); $databaseSettings = $settings['db']; $dbUser = $databaseSettings['user']; $dbPass = $databaseSettings['password']; $dbHost = $databaseSettings['host']; $dbName = $databaseSettings['name']; try { $pdo = new PDO("mysql:host=$dbHost;dbname=$dbName", $dbUser, $dbPass); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); return $pdo; } catch (PDOException $e) { print($e); die(); } }); }; class HelloAction { private $responder; private $pdo; public function __construct(Responder $responder, PDO $pdo) { $this->responder = $responder; $this->pdo = $pdo; } public function hello(Request $request, Response $response, $args): Response { $query = 'SELECT id, firstname, lastname, birthday FROM user'; $stmt = $this->pdo->prepare($query); $stmt->execute(); $users = $stmt->fetchAll(PDO::FETCH_OBJ); $name = $request->getAttribute('name'); $params = [ 'name' => $name, 'users' => $users ]; return $this->responder->render($response, 'hello.twig', $params); }
Users:
class LoginController { private $responder; private $pdo; public function __construct(Responder $responder, PDO $pdo) { $this->responder = $responder; $this->pdo = $pdo; } public function getLogin(Request $request, Response $response, $args): Response { $loginForm = new LoginForm(); $params = [ 'form' => $loginForm, ]; return $this->responder->render($response, 'login.twig', $params); } {% block content %}

Login

{% endblock %} class LoginForm { public $login = ''; public $password = ''; public static function createForm(array $postParams = []):LoginForm { $form = new LoginForm(); $form->login = $postParams['loginn'] ?? ''; $form->password = $postParams['password'] ?? ''; return $form; } public function validate():array { $errors = []; if(!v::notEmpty()->validate($this->login)) { $errors[] = 'login'; } if(!v::notEmpty()->validate($this->password)) { $errors[] = 'password'; } return $errors; } public function postLogin(Request $request, Response $response, $args): Response { $loginForm = LoginForm::createForm((array)$request->getParsedBody()); $errors = $loginForm->validate(); if($errors) { ... } public function postLogin(Request $request, Response $response, $args): Response { ... $query = 'SELECT * FROM user WHERE login = :login'; $stmt = $this->pdo->prepare($query); $queryParams = [ 'login' => $loginForm->login, ]; $stmt->execute($queryParams); $u = $stmt->fetch(PDO::FETCH_OBJ); if($u == null) { ... } if ($u->password !== $loginForm->password) { ... } $_SESSION['user'] = $u; return $this->responder->redirectToRoute($request, $response, 'root'); } class Responder { ... public function redirectToRoute(Request $request, Response $response, string $routeName, array $routeParams = [], int $httpStatusCode = 302): Response { $routeParser = RouteContext::fromRequest($request)->getRouteParser(); $url = $routeParser->urlFor($routeName, $routeParams); return $response->withStatus($httpStatusCode)->withHeader('Location', $url); } class FlashMiddleware implements Middleware { public function process(Request $request, RequestHandler $handler): Response { $flash = $_SESSION['flash'; // isset unset($_SESSION['flash']); return $handler->handle($request->withAttribute('flash', $flash));; } } public function postLogin(Request $request, Response $response, $args): Response { $loginForm = LoginForm::createForm($request); $errors = $loginForm->validate(); if($errors) { $_SESSION['flash'] = [ 'errors' => $errors, 'form' => $loginForm, ]; return $this->responder->redirectToRoute($request, $response, 'login'); } ... if($u == null || $u->password !== $loginForm->password) { $_SESSION['flash'] = [ 'errors' => ['Incorrect login and/or password'], 'form' => $loginForm, ]; return $this->responder->redirectToRoute($request, $response, 'login'); } ... public function getLogin(Request $request, Response $response, $args): Response { $loginForm = null; $errors = []; $flash = $request->getAttribute('flash'); if($flash != null) { $loginForm = $flash['form']; $errors = $flash['errors']; } if($loginForm == null) { $loginForm = new LoginForm(); } $params = [ 'form' => $loginForm, 'errors' => $errors, ]; return $this->responder->render($response, 'login.twig', $params); {% if errors %}
Errors...
{% endif %} class AuthMiddleware implements Middleware { private $responder; public function __construct(Responder $responder) { $this->responder = $responder; } public function process(Request $request, RequestHandler $handler): Response { $user = $_SESSION['user']; // isset if($user == null) { $_SESSION['flash'] = [ 'errors' => ['UNAUTHORIZED 401'], ]; $response = $this->responder->createResponse(); return $this->responder->redirectToRoute($request, $response, 'login'); } return $handler->handle($request); } } $routeContext = RouteContext::fromRequest($request); $route = $routeContext->getRoute(); $routeName = $route->getName(); $routeArgs = $route->getArguments(); $_SESSION['flash'] = [ 'errors' => ['UNAUTHORIZED 401'], ]; $_SESSION['route'] = [ 'name' => $routeName, 'args' => $routeArgs, ]; $response = $this->responder->createResponse(); return $this->responder->redirectToRoute($request, $response, 'login'); public function postLogin(Request $request, Response $response, $args): Response { ... $rName = 'root'; $rArgs = []; If(isset($_SESSION['route'])) { $rName = $_SESSION['route']['name']; $rArgs = $_SESSION['route']['args']; unset($_SESSION['route']); } return $this->responder->redirectToRoute($request, $response, $rName, $rArgs); } $container->set(Guard::class, function () use ($responseFactory) { return new Guard($responseFactory); }); $app->add(Guard::class); class MyCsrfHandler { private $responder; private $logger; public function __construct(Responder $responder, Logger $logger) { $this->responder = $responder; $this->logger = $logger; } public function __invoke(Request $request): Response { $this->logger->error('Error: CSRF bla bla'); $httpStatusCode = 400; $genericErrorMessage = 'CSRF lorem ipsum'; $response = $this->responder->createResponse(); return $this->responder->render( $response, '/http_error.twig', ['httpStatusCode' => $httpStatusCode, 'genericErrorMessage' => $genericErrorMessage], $httpStatusCode); public function getLogin(Request $request, Response $response, $args): Response { ... $csrfNameKey = $this->csrf->getTokenNameKey(); $csrfValueKey = $this->csrf->getTokenValueKey(); $csrfName = $this->csrf->getTokenName(); $csrfValue = $this->csrf->getTokenValue(); $csrfValues = [ 'keys' => [ 'name' => $csrfNameKey, 'value' => $csrfValueKey ], 'name' => $csrfName, 'value' => $csrfValue, ]; $params = [ 'form' => $loginForm, 'errors' => $errors, 'csrf' => $csrfValues, ]; ... $container->set(Guard::class, function () use ($responseFactory, $container) { $prefix = 'csrf'; $storage = null; $failureHandler = $container->get(MyCsrfHandler::class); $persistentTokenMode = true; return new Guard($responseFactory, $prefix, $storage, $failureHandler, 200, 16, $persistentTokenMode); }); $settings = [ ... ]; return [ 'settings' => $settings, App::class => function (Container $container): App { AppFactory::setContainer($container); $app = AppFactory::create(); return $app; }, LoggerInterface::class => function(Container $container): Logger { ... $logger = new Logger(...); return $logger; }, PDO::class => function(Container $container): PDO { ... $pdo = new PDO(...); return $pdo; }, 'view' => function(Container $container) { ... return Twig::create(...); }, Twig::class => function(Container $container) { return $container->get('view'); }, ResponseFactoryInterface::class => function(Container $container): ResponseFactoryInterface { $app = $container->get(App::class); return $app->getResponseFactory(); }, Guard::class => function (Container $container): Guard { $responseFactory = $container->get(ResponseFactoryInterface::class); ... return new Guard($responseFactory, ...); }, ]; $containerBuilder = new ContainerBuilder(); $containerBuilder->addDefinitions(__DIR__ . '/../config/container.php'); $container = $containerBuilder->build(); $app = $container->get(App::class); // Middlewares (require __DIR__ . '/../config/middlewares.php')($app); // Routes (require __DIR__ . '/../config/routes.php')($app); // Run $app->run(); { "name": "My Slim Website", "description": "Very simple example of a website using Slim 4.", "require": { "slim/slim": "4.*", "slim/psr7": "^1.0", "slim/twig-view": "^3.1", "slim/csrf": "^1.0", "php-di/php-di": "^6.1", "monolog/monolog": "^2.0", "respect/validation": "^2.0", "egulias/email-validator": "^2.1" }, "autoload": { "psr-4": { "App\\": "src/" } } }