Initial commit

This commit is contained in:
lewis 2024-08-30 13:28:30 +01:00
commit e6ad944f79
22 changed files with 6566 additions and 0 deletions

1
.env.example Normal file
View File

@ -0,0 +1 @@
SQLITE_DB_NAME=app.db

58
.gitignore vendored Normal file
View File

@ -0,0 +1,58 @@
# ---> Symfony
# Cache and logs (Symfony2)
/app/cache/*
/app/logs/*
!app/cache/.gitkeep
!app/logs/.gitkeep
# Email spool folder
/app/spool/*
# Cache, session files and logs (Symfony3)
/var/cache/*
/var/logs/*
/var/sessions/*
!var/cache/.gitkeep
!var/logs/.gitkeep
!var/sessions/.gitkeep
# Logs (Symfony4)
/var/log/*
!var/log/.gitkeep
# Parameters
/app/config/parameters.yml
/app/config/parameters.ini
# Managed by Composer
/app/bootstrap.php.cache
/var/bootstrap.php.cache
/bin/*
!bin/console
!bin/symfony_requirements
!bin/doctrine
/vendor/
# Assets and user uploads
/web/bundles/
/web/uploads/
# PHPUnit
/app/phpunit.xml
/phpunit.xml
# Build data
/build/
# Composer PHAR
/composer.phar
# Backup entities generated with doctrine:generate:entities command
**/Entity/*~
# Embedded web-server pid file
/.web-server-pid
.env
/*.db

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

96
.idea/feacher.iml Normal file
View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="Lewisdale\App\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/masterminds/html5" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/lexer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/deprecations" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/event-manager" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/collections" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/persistence" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/instantiator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/inflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/fig/http-message-util" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/dbal" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpoption/phpoption" />
<excludeFolder url="file://$MODULE_DIR$/vendor/league/uri" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/league/uri-interfaces" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/common" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/orm" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-server-handler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-factory" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-server-middleware" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/type" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/diff" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/lines-of-code" />
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/serializable-closure" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-enumerator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/cli-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/complexity" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/recursion-context" />
<excludeFolder url="file://$MODULE_DIR$/vendor/graham-campbell/result-type" />
<excludeFolder url="file://$MODULE_DIR$/vendor/brick/math" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/environment" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/global-state" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-reflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ramsey/collection" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ramsey/uuid" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ramsey/uuid-doctrine" />
<excludeFolder url="file://$MODULE_DIR$/vendor/myclabs/deep-copy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/php-di" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/invoker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/fast-route" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-timer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/cache-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/php-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-file-iterator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-invoker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-text-template" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/phpunit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-code-coverage" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/css-selector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php80" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php81" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dom-crawler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php72" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/slim/psr7" />
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/twig" />
<excludeFolder url="file://$MODULE_DIR$/vendor/slim/slim" />
<excludeFolder url="file://$MODULE_DIR$/vendor/slim/twig-view" />
<excludeFolder url="file://$MODULE_DIR$/vendor/slim/csrf" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/manifest" />
<excludeFolder url="file://$MODULE_DIR$/vendor/vlucas/phpdotenv" />
<excludeFolder url="file://$MODULE_DIR$/vendor/theseer/tokenizer" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/feacher.iml" filepath="$PROJECT_DIR$/.idea/feacher.iml" />
</modules>
</component>
</project>

113
.idea/php.xml Normal file
View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/masterminds/html5" />
<path value="$PROJECT_DIR$/vendor/doctrine/lexer" />
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
<path value="$PROJECT_DIR$/vendor/doctrine/collections" />
<path value="$PROJECT_DIR$/vendor/doctrine/persistence" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/doctrine/inflector" />
<path value="$PROJECT_DIR$/vendor/fig/http-message-util" />
<path value="$PROJECT_DIR$/vendor/doctrine/dbal" />
<path value="$PROJECT_DIR$/vendor/phpoption/phpoption" />
<path value="$PROJECT_DIR$/vendor/league/uri" />
<path value="$PROJECT_DIR$/vendor/doctrine/cache" />
<path value="$PROJECT_DIR$/vendor/league/uri-interfaces" />
<path value="$PROJECT_DIR$/vendor/doctrine/common" />
<path value="$PROJECT_DIR$/vendor/doctrine/orm" />
<path value="$PROJECT_DIR$/vendor/psr/http-server-handler" />
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
<path value="$PROJECT_DIR$/vendor/psr/http-server-middleware" />
<path value="$PROJECT_DIR$/vendor/psr/cache" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/laravel/serializable-closure" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/graham-campbell/result-type" />
<path value="$PROJECT_DIR$/vendor/brick/math" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/vendor/ramsey/collection" />
<path value="$PROJECT_DIR$/vendor/ramsey/uuid" />
<path value="$PROJECT_DIR$/vendor/ramsey/uuid-doctrine" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/php-di/php-di" />
<path value="$PROJECT_DIR$/vendor/php-di/invoker" />
<path value="$PROJECT_DIR$/vendor/nikic/fast-route" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/symfony/http-client-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/var-exporter" />
<path value="$PROJECT_DIR$/vendor/symfony/css-selector" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/symfony/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php80" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php81" />
<path value="$PROJECT_DIR$/vendor/symfony/dom-crawler" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php72" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/slim/psr7" />
<path value="$PROJECT_DIR$/vendor/twig/twig" />
<path value="$PROJECT_DIR$/vendor/slim/slim" />
<path value="$PROJECT_DIR$/vendor/slim/twig-view" />
<path value="$PROJECT_DIR$/vendor/slim/csrf" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/vlucas/phpdotenv" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.2" />
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
</phpunit_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

18
README.md Normal file
View File

@ -0,0 +1,18 @@
# PHP Project Template
This is a template for PHP projects. It includes a basic project structure, with Doctrine ORM
## Installation
1. Run `composer install`
2. Copy `.env.example` to `.env` and edit the values
3. Run `php bin/doctrine orm:schema-tool:create` to create the database
## Running locally
1. Run `php -S localhost:8000 -t public` to start the web server
## Running tests
1. Run `php vendor/bin/phpunit` to run the tests

16
bin/doctrine Normal file
View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
$dotenv = Dotenv\Dotenv::createImmutable([__DIR__, __DIR__ . "/.."]);
$dotenv->load();
require_once __DIR__ . '/../src/dependencies.php';
global $container;
ConsoleRunner::run(
new SingleManagerProvider($container->get(EntityManager::class))
);

39
composer.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "lewisdale/app",
"type": "project",
"require": {
"slim/slim": "^4.11",
"twig/twig": "^3.5",
"slim/psr7": "^1.6",
"slim/twig-view": "^3.3",
"vlucas/phpdotenv": "^5.5",
"php-di/php-di": "^7.0",
"psr/log": "^3.0",
"doctrine/orm": "^2.14",
"doctrine/dbal": "^3.6",
"symfony/cache": "^6.2",
"ext-pdo": "*",
"ext-readline": "*",
"slim/csrf": "^1.3",
"ramsey/uuid-doctrine": "^2.0",
"ramsey/uuid": "^4.7",
"symfony/dom-crawler": "^6.3",
"symfony/css-selector": "^6.3",
"symfony/http-client": "^6.3",
"league/uri": "^6.8"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"autoload": {
"psr-4": {
"Lewisdale\\App\\": "src/"
}
},
"authors": [
{
"name": "Lewis Dale",
"email": "lewis@ihd.software"
}
]
}

5913
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

9
index.php Normal file
View File

@ -0,0 +1,9 @@
<?php declare(strict_types=1);
require_once __DIR__ . "/vendor/autoload.php";
if (php_sapi_name() === "cli-server" && preg_match('/\.(?:png|jpg|jpeg|gif|css|ico|js|mjs|css\.map|webp)$/', $_SERVER["REQUEST_URI"])) {
return false; // serve the requested resource as-is.
}
require_once __DIR__ . "/src/app.php";

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Lewisdale\App\Controllers;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Slim\Views\Twig;
class SampleController
{
public function __construct(
private readonly Twig $view,
private readonly LoggerInterface $logger
) {}
public function get(ServerRequestInterface $request, ResponseInterface $response)
{
$this->logger->info("SampleController::get() called");
return $this->view->render($response, 'index.twig.html');
}
}

View File

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
namespace Lewisdale\App\Logging;
use DateTime;
use Psr\Log\AbstractLogger;
use Stringable;
class FileLogger extends AbstractLogger
{
function __construct(private readonly string $filename = 'php://stdout') {}
public function log($level, Stringable|string $message, array $context = []): void
{
$timestamp = new DateTime();
$callingClass = debug_backtrace()[2]['class'];
$callingFunction = debug_backtrace()[2]['function'];
$log = $timestamp->format(DATE_ATOM) . " [$level] $callingClass::$callingFunction $message " . json_encode($context);
fwrite(fopen($this->filename, 'w'), $log . PHP_EOL);
}
}

61
src/Models/Data/User.php Normal file
View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Lewisdale\App\Models\Data;
use Lewisdale\App\Models\Traits\AutoUpdate;
use Lewisdale\App\Models\View;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'users')]
#[ORM\UniqueConstraint(name: "email", columns: ["email"])]
#[ORM\HasLifecycleCallbacks]
class User
{
use AutoUpdate;
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private ?int $id;
#[ORM\Column(type: 'string')]
private string $password;
#[ORM\Column(type: 'string')]
private string $email;
#[ORM\Column(type: 'string')]
private string $salt;
function __construct(
string $password,
string $email,
?string $salt = null,
?int $id = null,
) {
$this->password = $password;
$this->email = $email;
$this->id = $id;
$this->salt = $salt ?? bin2hex(random_bytes(32));
}
public function hash(string $password): string {
return password_hash($password . $this->salt, PASSWORD_DEFAULT);
}
public function validate(string $password): bool {
return password_verify($password . $this->salt, $this->password);
}
public function updatePassword(string $password): void {
$this->password = $this->hash($password);
}
public function toViewModel() : View\User {
return new View\User(
$this->email,
$this->createdAt->format(DATE_ATOM),
$this->updatedAt->format(DATE_ATOM),
$this->id
);
}
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Lewisdale\App\Models\Traits;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
trait AutoUpdate
{
#[ORM\Column(type: 'datetime')]
protected DateTime $createdAt;
#[ORM\Column(type: 'datetime')]
protected DateTime $updatedAt;
#[ORM\PrePersist]
public function onPrePersist(): void {
$this->createdAt = new DateTime('now');
$this->updatedAt = new DateTime('now');
}
#[ORM\PreUpdate]
public function onPreUpdate(): void {
$this->updatedAt = new DateTime('now');
}
}

15
src/Models/View/User.php Normal file
View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Lewisdale\App\Models\View;
class User
{
public function __construct(
public string $email,
public string $createdAt,
public string $updatedAt,
public ?int $id = null,
) {}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Lewisdale\App\TwigExtensions;
use Slim\Csrf\Guard;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class CsrfExtension extends AbstractExtension
{
protected Guard $guard;
public function __construct(Guard $guard)
{
$this->guard = $guard;
}
public function getFunctions() : array
{
return [
new TwigFunction('csrf', [$this, 'csrf'])
];
}
public function csrf() : string
{
$nameKey = $this->guard->getTokenNameKey();
$nameValue = $this->guard->getTokenName();
$tokenKey = $this->guard->getTokenValueKey();
$tokenValue = $this->guard->getTokenValue();
return "
<input type=\"hidden\" name=\"$nameKey\" value=\"$nameValue\">
<input type=\"hidden\" name=\"$tokenKey\" value=\"$tokenValue\">
";
}
}

23
src/app.php Normal file
View File

@ -0,0 +1,23 @@
<?php declare(strict_types=1);
use Lewisdale\App\Controllers\SampleController;
use Slim\Views\TwigMiddleware;
require_once __DIR__ . "/dependencies.php";
global $container;
global $app;
$dotenv = Dotenv\Dotenv::createImmutable([__DIR__, __DIR__ . "/.."]);
$dotenv->load();
require_once __DIR__ . "/session.php";
$app->add(TwigMiddleware::createFromContainer($app));
$app->add('csrf');
$app->get("/", [SampleController::class, 'get']);
$app->addErrorMiddleware(true, true, true);
$app->run();

56
src/dependencies.php Normal file
View File

@ -0,0 +1,56 @@
<?php declare(strict_types=1);
use DI\Container;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
use Lewisdale\App\Logging\FileLogger;
use Lewisdale\App\TwigExtensions\CsrfExtension;
use Psr\Log\LoggerInterface;
use Ramsey\Uuid\Doctrine\UuidType;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
require_once __DIR__ . "/../vendor/autoload.php";
$container = new Container();
AppFactory::setContainer($container);
$app = AppFactory::create();
$container->set('csrf', function() use ($app) {
return new Slim\Csrf\Guard($app->getResponseFactory());
});
$container->set('view', function() use ($container) {
$twig = Twig::create(__DIR__ . "/../views");
$twig->addExtension(new CsrfExtension($container->get('csrf')));
return $twig;
});
$container->set(Twig::class, function() use ($container) {
return $container->get('view');
});
$container->set(LoggerInterface::class, $container->get(FileLogger::class));
$container->set(EntityManager::class, static function() {
Type::addType('uuid', UuidType::class);
$config = ORMSetup::createAttributeMetadataConfiguration(
paths: array(__DIR__."/Models/Data"),
isDevMode: true,
);
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
'path' => __DIR__ . '/../' . $_ENV["SQLITE_DB_NAME"],
], $config);
return new EntityManager($connection, $config);
});

14
src/session.php Normal file
View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
session_start();
// Protect against session hijacking
if (!isset($_SESSION['fingerprint'])) {
session_regenerate_id();
$_SESSION['fingerprint'] = $_SERVER['HTTP_USER_AGENT'];
}
if ($_SESSION['fingerprint'] !== $_SERVER['HTTP_USER_AGENT']) {
session_unset();
}

4
views/index.twig.html Normal file
View File

@ -0,0 +1,4 @@
<h1>Hello, world.</h1>
<p>This is a boilerplate PHP app. You should start by adding some routes, controllers, and maybe making me look
good.</p>