Compare commits

...

10 commits

10 changed files with 635 additions and 11 deletions

View file

@ -24,6 +24,7 @@
"require": {
"slim/slim": "4.*",
"slim/psr7": "^1.7",
"php-di/php-di": "^7.1"
"php-di/php-di": "^7.1",
"slim/twig-view": "^3.4"
}
}

461
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "23c8694ee036d4ff156175bf8fa3ad34",
"content-hash": "1d5fe2a473d3977745130ee120c50235",
"packages": [
{
"name": "fig/http-message-util",
@ -867,6 +867,306 @@
],
"time": "2025-08-20T18:16:16+00:00"
},
{
"name": "slim/twig-view",
"version": "3.4.1",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Twig-View.git",
"reference": "b4268d87d0e327feba5f88d32031e9123655b909"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/slimphp/Twig-View/zipball/b4268d87d0e327feba5f88d32031e9123655b909",
"reference": "b4268d87d0e327feba5f88d32031e9123655b909",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
"psr/http-message": "^1.1 || ^2.0",
"slim/slim": "^4.12",
"symfony/polyfill-php81": "^1.29",
"twig/twig": "^3.11"
},
"require-dev": {
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/phpstan": "^1.10.59",
"phpunit/phpunit": "^9.6 || ^10",
"psr/http-factory": "^1.0",
"squizlabs/php_codesniffer": "^3.9"
},
"type": "library",
"autoload": {
"psr-4": {
"Slim\\Views\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Josh Lockhart",
"email": "hello@joshlockhart.com",
"homepage": "http://joshlockhart.com"
},
{
"name": "Pierre Berube",
"email": "pierre@lgse.com",
"homepage": "http://www.lgse.com"
}
],
"description": "Slim Framework 4 view helper built on top of the Twig 3 templating component",
"homepage": "https://www.slimframework.com",
"keywords": [
"framework",
"slim",
"template",
"twig",
"view"
],
"support": {
"issues": "https://github.com/slimphp/Twig-View/issues",
"source": "https://github.com/slimphp/Twig-View/tree/3.4.1"
},
"time": "2024-09-26T05:42:02+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.6-dev"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-25T14:21:43+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-12-23T08:48:59+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.33.0",
@ -950,6 +1250,165 @@
}
],
"time": "2025-01-02T08:10:11+00:00"
},
{
"name": "symfony/polyfill-php81",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php81\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "twig/twig",
"version": "v3.21.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d",
"reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d",
"shasum": ""
},
"require": {
"php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"phpstan/phpstan": "^2.0",
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
},
"type": "library",
"autoload": {
"files": [
"src/Resources/core.php",
"src/Resources/debug.php",
"src/Resources/escaper.php",
"src/Resources/string_loader.php"
],
"psr-4": {
"Twig\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Twig Team",
"role": "Contributors"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "https://twig.symfony.com",
"keywords": [
"templating"
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.21.1"
},
"funding": [
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
"type": "tidelift"
}
],
"time": "2025-05-03T07:21:55+00:00"
}
],
"packages-dev": [

View file

@ -1,16 +1,38 @@
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\RequestInterface as Request;
use Slim\Factory\AppFactory;
session_start([
'cookie_httponly' => true,
'cookie_secure' => !empty($_SERVER['HTTPS']),
'cookie_samesite' => 'Lax',
]);
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
use DI\Container;
use DigiWill\Controllers\UserController;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
use Tests\Fakes\FakeUserRepository;
use DigiWill\Repositories\UserRepository;
use DigiWill\MiddleWare\AuthMiddleware;
$app->get('/', function (Request $request, Response $response, $args) {
$response->getBody()->write("Hello world!");
return $response;
});
$container = new Container([
UserRepository::class => DI\create(FakeUserRepository::class),
]);
$app = AppFactory::createFromContainer($container);
$twig = Twig::create(__DIR__ . '/../templates', ['cache' => false]);
$auth = new AuthMiddleware();
$app->add(TwigMiddleware::create($app, $twig));
$app->addErrorMiddleware(true, false, false);
$app->get('/', [UserController::class, 'home']);
$app->get('/login', [UserController::class, 'login']);
$app->get('/logout', [UserController::class, 'logout']);
$app->post('/login', [UserController::class, 'doLogin']);
$app->get('/dashboard', [UserController::class, 'dashboard'])->add($auth);
$app->run();

View file

@ -0,0 +1,73 @@
<?php
namespace DigiWill\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\RequestInterface as Request;
use Slim\Views\Twig;
use DigiWill\Repositories\UserRepository;
class UserController
{
public function __construct(
private UserRepository $userRepo
) {}
public function home(Request $request, Response $response): Response
{
$this->userRepo->find(1);
$view = Twig::fromRequest($request);
return $view->render($response, 'home.html.twig', [
'name' => '',
]);
}
public function login(Request $request, Response $response): Response
{
$view = Twig::fromRequest($request);
return $view->render($response, 'login.html.twig', [
'name' => '',
]);
}
public function doLogin(Request $request, Response $response): Response
{
$_SESSION['user_id'] = 1;
$_SESSION['user'] = ['id' => 1, 'email' => 'email@email.com'];
return $response->withHeader('Location', '/dashboard')->withStatus(302);
}
public function dashboard(Request $request, Response $response): Response
{
$view = Twig::fromRequest($request);
return $view->render($response, 'dashboard.html.twig', [
'name' => '',
]);
}
public function logout(Request $request, Response $response): Response
{
$_SESSION = [];
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
session_destroy();
return $response
->withHeader('Location', '/')
->withStatus(302);
}
}

View file

@ -4,5 +4,12 @@ namespace DigiWill\Domain;
class User
{
public function __construct(
private ?int $id
) {}
public function getId(): ?int
{
return $this->id;
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace DigiWill\MiddleWare;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as Handler;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Psr7\Response as SlimResponse;
class AuthMiddleware implements MiddlewareInterface
{
public function process(Request $request, Handler $handler): Response
{
if (isset($_SESSION['user_id'])) {
return $handler->handle($request);
}
$uri = $request->getUri()->getPath();
if ($uri !== '/login' && $uri !== '/logout') {
$_SESSION['intended'] = $uri;
}
$resp = new SlimResponse(302);
return $resp->withHeader('Location', '/login');
}
}

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Dashboard</title>
</head>
<body>
<h1>Dashboard</h1>
<h2>Only visible when logged in</h2>
</body>
</html>

10
templates/home.html.twig Normal file
View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Slim!</title>
</head>
<body>
<h1 id="page-title">Hello {{ name }}</h1>
<a href="/login">login</a>
</body>
</html>

13
templates/login.html.twig Normal file
View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<form method="post" action="/login">
<label>Email <input type="email" name="email" required></label><br>
<label>Password <input type="password" name="password" required></label><br>
<button type="submit">Sign in</button>
</form>
</body>
</html>

View file

@ -16,10 +16,12 @@ class FakeUserRepository implements UserRepository
public function find(int $id): ?User
{
return null;
}
public function findByEmail(string $email): ?User
{
return null;
}
public function save(User $user): User