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:
{% for u in users %}
- {{ u.firstname }} - {{ u.lastname }} - {{ u.birthday }}
{% endfor %}
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...
{% for error in errors %}
- {{ error }}
{% endfor %}
{% 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/"
}
}
}