update readme

This commit is contained in:
lubiana 2022-04-05 00:02:52 +02:00 committed by Andre Lubian
parent b12cf019e7
commit ab3227b75f
88 changed files with 7546 additions and 176 deletions

13
app/cli-config.php Normal file
View file

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Lubian\NoFramework\Factory\FileSystemSettingsProvider;
use Lubian\NoFramework\Factory\SettingsContainerProvider;
$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/config/settings.php');
$container = (new SettingsContainerProvider($settingsProvider))->getContainer();
return ConsoleRunner::createHelperSet($container->get(EntityManagerInterface::class));

View file

@ -12,7 +12,8 @@
"middlewares/trailing-slash": "^2.0",
"middlewares/whoops": "^2.0",
"erusev/parsedown": "^1.7",
"symfony/cache": "^6.0"
"symfony/cache": "^6.0",
"doctrine/orm": "^2.11"
},
"autoload": {
"psr-4": {
@ -33,8 +34,7 @@
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-strict-rules": "^1.1",
"thecodingmachine/phpstan-strict-rules": "^1.0",
"mnapoli/hard-mode": "^0.3.0",
"psalm/phar": "^4.22"
"mnapoli/hard-mode": "^0.3.0"
},
"config": {
"allow-plugins": {
@ -43,7 +43,10 @@
}
},
"scripts": {
"serve": "php -S 0.0.0.0:1234 -t public",
"serve": [
"Composer\\Config::disableProcessTimeout",
"php -S 0.0.0.0:1234 -t public"
],
"phpstan": "./vendor/bin/phpstan analyze",
"baseline": "./vendor/bin/phpstan analyze --generate-baseline",
"check": "./vendor/bin/phpcs",

1742
app/composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,10 @@
<?php declare(strict_types=1);
use Doctrine\ORM\EntityManagerInterface;
use FastRoute\Dispatcher;
use Laminas\Diactoros\ResponseFactory;
use Lubian\NoFramework\Factory\DiactorosRequestFactory;
use Lubian\NoFramework\Factory\DoctrineEm;
use Lubian\NoFramework\Factory\PipelineProvider;
use Lubian\NoFramework\Factory\RequestFactory;
use Lubian\NoFramework\Http\BasicEmitter;
@ -12,6 +14,7 @@ use Lubian\NoFramework\Http\Pipeline;
use Lubian\NoFramework\Http\RoutedRequestHandler;
use Lubian\NoFramework\Http\RouteMiddleware;
use Lubian\NoFramework\Repository\CachedMarkdownPageRepo;
use Lubian\NoFramework\Repository\DoctrineMarkdownPageRepo;
use Lubian\NoFramework\Repository\MarkdownPageFilesystem;
use Lubian\NoFramework\Repository\MarkdownPageRepo;
use Lubian\NoFramework\Service\Time\Now;
@ -31,21 +34,25 @@ use Symfony\Contracts\Cache\CacheInterface;
use function FastRoute\simpleDispatcher;
return [
// alias
Now::class => fn (SystemClockNow $n) => $n,
ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf,
Emitter::class => fn (BasicEmitter $e) => $e,
MiddlewareInterface::class => fn (RouteMiddleware $r) => $r,
RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h,
RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf,
CacheInterface::class => fn (FilesystemAdapter $a) => $a,
MarkdownPageRepo::class => fn (CachedMarkdownPageRepo $r) => $r,
// Factories
ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(),
ServerRequestInterface::class => fn (RequestFactory $rf) => $rf->fromGlobals(),
Now::class => fn (SystemClockNow $n) => $n,
Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e),
MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]),
ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]),
ResponseFactoryInterface::class => fn (ResponseFactory $rf) => $rf,
Emitter::class => fn (BasicEmitter $e) => $e,
RoutedRequestHandler::class => fn (InvokerRoutedHandler $h) => $h,
MiddlewareInterface::class => fn (RouteMiddleware $r) => $r,
Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'),
RequestFactory::class => fn (DiactorosRequestFactory $rf) => $rf,
Pipeline::class => fn (PipelineProvider $p) => $p->getPipeline(),
CacheInterface::class => fn (FilesystemAdapter $a) => $a,
MarkdownPageFilesystem::class => fn (Settings $s) => new MarkdownPageFilesystem($s->pagesPath),
CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r) => new CachedMarkdownPageRepo($c, $r),
MarkdownPageRepo::class => fn (MarkdownPageFilesystem $r) => $r,
CachedMarkdownPageRepo::class => fn (CacheInterface $c, MarkdownPageFilesystem $r, Settings $s) => new CachedMarkdownPageRepo($c, $r, $s),
EntityManagerInterface::class => fn (DoctrineEm $f) => $f->create(),
];

View file

@ -1,12 +1,13 @@
<?php declare(strict_types=1);
use Lubian\NoFramework\Http\RouteMiddleware;
use Lubian\NoFramework\Middleware\CacheMiddleware;
use Middlewares\TrailingSlash;
use Middlewares\Whoops;
return [
Whoops::class,
TrailingSlash::class,
\Lubian\NoFramework\Middleware\CacheMiddleware::class,
CacheMiddleware::class,
RouteMiddleware::class,
];

View file

@ -8,7 +8,8 @@ use Psr\Http\Message\ResponseInterface as Response;
return function (RouteCollector $r): void {
$r->addRoute('GET', '/hello[/{name}]', Hello::class);
$r->addRoute('GET', '/page/{page}', Page::class);
$r->addRoute('GET', '/page', [Page::class, 'list']);
$r->addRoute('GET', '/page/{page}', [Page::class, 'show']);
$r->addRoute('GET', '/another-route', [Other::class, 'someFunctionName']);
$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello'));
};

View file

@ -3,10 +3,21 @@
use Lubian\NoFramework\Settings;
return new Settings(
environment: 'dev',
environment: 'prod',
dependenciesFile: __DIR__ . '/dependencies.php',
middlewaresFile: __DIR__ . '/middlewares.php',
templateDir: __DIR__ . '/../templates',
templateExtension: '.html',
pagesPath: __DIR__ . '/../data/pages/'
pagesPath: __DIR__ . '/../data/pages/',
connection: [
'driver' => 'pdo_sqlite',
'user' => '',
'password' => '',
'path' => __DIR__ . '/../data/db.sqlite',
],
doctrine: [
'devMode' => true,
'metadataDirs' => [__DIR__ . '/../src/Model/'],
'cacheDir' => __DIR__ . '/../data/cache/',
],
);

View file

@ -2,6 +2,8 @@
namespace Lubian\NoFramework\Action;
use Lubian\NoFramework\Model\MarkdownPage;
use Lubian\NoFramework\Repository\MarkdownPageFilesystem;
use Lubian\NoFramework\Repository\MarkdownPageRepo;
use Lubian\NoFramework\Template\Renderer;
use Parsedown;
@ -12,19 +14,33 @@ use function str_replace;
class Page
{
public function __invoke(
public function __construct(
private ResponseInterface $response,
private MarkdownPageRepo $repo,
private Parsedown $parsedown,
private Renderer $renderer,
){}
public function show(
string $page,
ResponseInterface $response,
MarkdownPageRepo $repo,
Parsedown $parsedown,
Renderer $renderer,
): ResponseInterface {
$page = $repo->byTitle($page);
$page = $this->repo->byTitle($page);
$content = $this->linkFilter($page->content);
$content = $parsedown->parse($content);
$html = $renderer->render('page', ['content' => $content]);
$response->getBody()->write($html);
return $response;
$content = $this->parsedown->parse($content);
$html = $this->renderer->render('page', ['content' => $content, 'title' => $page->title]);
$this->response->getBody()->write($html);
return $this->response;
}
public function list(): ResponseInterface
{
$pages = array_map(
fn (MarkdownPage $p) => ['title' => $p->title, 'id' => $p->id],
$this->repo->all()
);
$html = $this->renderer->render('pagelist', ['pages' => $pages]);
$this->response->getBody()->write($html);
return $this->response;
}
private function linkFilter(string $content): string

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Lubian\NoFramework\Factory;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Tools\Setup;
use Lubian\NoFramework\Settings;
final class DoctrineEm
{
public function __construct(private Settings $settings){}
public function create(): EntityManagerInterface
{
$config = Setup::createConfiguration($this->settings->doctrine['devMode']);
$config->setMetadataDriverImpl(
new AttributeDriver(
$this->settings->doctrine['metadataDirs']
)
);
return EntityManager::create(
$this->settings->connection,
$config,
);
}
}

View file

@ -18,8 +18,9 @@ final class SettingsContainerProvider implements ContainerProvider
$builder = new ContainerBuilder;
$settings = $this->settingsProvider->getSettings();
$dependencies = require $settings->dependenciesFile;
$dependencies[Settings::class] = fn (): Settings => $settings;
$dependencies[Settings::class] = $settings;
$builder->addDefinitions($dependencies);
// $builder->enableCompilation('/tmp');
return $builder->build();
}
}

View file

@ -3,6 +3,7 @@
namespace Lubian\NoFramework\Middleware;
use Laminas\Diactoros\Response;
use Lubian\NoFramework\Settings;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
@ -10,23 +11,30 @@ use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use function base64_encode;
final class CacheMiddleware implements MiddlewareInterface
{
public function __construct(private CacheInterface $cache){}
public function __construct(
private CacheInterface $cache,
private Response\Serializer $serializer,
private Settings $settings,
)
{
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($request->getMethod() === 'GET') {
if ($request->getMethod() === 'GET' && !$this->settings->isDev()) {
$key = (string) $request->getUri();
$key = base64_encode($key);
$callback = fn () => $handler->handle($request);
$response = new Response();
$body = $this->cache->get($key, function (ItemInterface $item) use ($callback) {
$cached = $this->cache->get($key, function (ItemInterface $item) use ($callback) {
$item->expiresAfter(120);
return (string) $callback()->getBody();
$response = $callback();
return $this->serializer::toString($response);
});
$response->getBody()->write($body);
return $response;
return $this->serializer::fromString($cached);
}
return $handler->handle($request);
}

View file

@ -2,12 +2,22 @@
namespace Lubian\NoFramework\Model;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
#[Entity]
class MarkdownPage
{
public function __construct(
public readonly int $id,
public readonly string $title,
public readonly string $content,
#[Id, Column, GeneratedValue]
public int|null $id = null,
#[Column]
public string $title,
#[Column(type: Types::TEXT)]
public string $content,
) {
}
}

View file

@ -3,14 +3,16 @@
namespace Lubian\NoFramework\Repository;
use Lubian\NoFramework\Model\MarkdownPage;
use Lubian\NoFramework\Settings;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
class CachedMarkdownPageRepo implements MarkdownPageRepo
{
public function __construct(
private CacheInterface $cache,
private MarkdownPageRepo $repo,
private readonly CacheInterface $cache,
private readonly MarkdownPageRepo $repo,
private readonly Settings $settings,
) {
}
@ -20,6 +22,9 @@ class CachedMarkdownPageRepo implements MarkdownPageRepo
public function all(): array
{
$callback = fn () => $this->repo->all();
if ($this->settings->isDev()) {
return $callback();
}
return $this->cache->get('ALLPAGES', function (ItemInterface $item) use ($callback) {
$item->expiresAfter(30);
return $callback();
@ -29,6 +34,9 @@ class CachedMarkdownPageRepo implements MarkdownPageRepo
public function byId(int $id): MarkdownPage
{
$callback = fn () => $this->repo->byId($id);
if ($this->settings->isDev()) {
return $callback();
}
return $this->cache->get('PAGE' . $id, function (ItemInterface $item) use ($callback) {
$item->expiresAfter(30);
return $callback();
@ -38,9 +46,17 @@ class CachedMarkdownPageRepo implements MarkdownPageRepo
public function byTitle(string $title): MarkdownPage
{
$callback = fn () => $this->repo->byTitle($title);
if ($this->settings->isDev()) {
return $callback();
}
return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) {
$item->expiresAfter(30);
return $callback();
});
}
public function save(MarkdownPage $page): MarkdownPage
{
return $this->repo->save($page);
}
}

View file

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Lubian\NoFramework\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Lubian\NoFramework\Exception\NotFound;
use Lubian\NoFramework\Model\MarkdownPage;
final class DoctrineMarkdownPageRepo implements MarkdownPageRepo
{
/**
* @var EntityRepository<MarkdownPage>
*/
private EntityRepository $repo;
public function __construct(
private EntityManagerInterface $entityManager
){
$this->repo = $this->entityManager->getRepository(MarkdownPage::class);
}
/**
* @inheritDoc
*/
public function all(): array
{
usleep(rand(500, 1500) * 1000);
return $this->repo->findAll();
}
public function byId(int $id): MarkdownPage
{
usleep(rand(500, 1500) * 1000);
$page = $this->repo->findOneBy(['id' => $id]);
if (!$page instanceof MarkdownPage){
throw new NotFound;
}
return $page;
}
public function byTitle(string $title): MarkdownPage
{
usleep(rand(500, 1500) * 1000);
$page = $this->repo->findOneBy(['title' => $title]);
if (!$page instanceof MarkdownPage){
throw new NotFound;
}
return $page;
}
public function save(MarkdownPage $page): MarkdownPage
{
$this->entityManager->persist($page);
$this->entityManager->flush();
return $page;
}
}

View file

@ -31,7 +31,7 @@ final class MarkdownPageFilesystem implements MarkdownPageRepo
$fileNames = glob($this->dataPath . '*.md');
assert(is_array($fileNames));
return array_map(function (string $name): MarkdownPage {
usleep(100000);
usleep(rand(200, 500) * 1000);
$content = file_get_contents($name);
$name = str_replace($this->dataPath, '', $name);
$name = str_replace('.md', '', $name);
@ -60,4 +60,9 @@ final class MarkdownPageFilesystem implements MarkdownPageRepo
}
return $filtered[0];
}
public function save(MarkdownPage $page): MarkdownPage
{
return $page;
}
}

View file

@ -14,4 +14,6 @@ interface MarkdownPageRepo
public function byId(int $id): MarkdownPage;
public function byTitle(string $title): MarkdownPage;
public function save(MarkdownPage $page): MarkdownPage;
}

View file

@ -4,6 +4,10 @@ namespace Lubian\NoFramework;
final class Settings
{
/**
* @param array{driver: string, user: string, password: string, path: string} $connection
* @param array{devMode: bool, metadataDirs: string[], cacheDir: string} $doctrine
*/
public function __construct(
public readonly string $environment,
public readonly string $dependenciesFile,
@ -11,6 +15,19 @@ final class Settings
public readonly string $templateDir,
public readonly string $templateExtension,
public readonly string $pagesPath,
/**
* @var array{driver: string, user: string, password: string, path: string}
*/
public readonly array $connection,
/**
* @var array{devMode: bool, metadataDirs: string[], cacheDir: string}
*/
public readonly array $doctrine,
) {
}
public function isDev(): bool
{
return $this->environment === 'dev';
}
}

View file

@ -0,0 +1,11 @@
{{> partials/head }}
<div class="column col-6 col-mx-auto">
<ul>
{{#pages}}
<li>
<a href="/page/{{title}}">{{id}}: {{title}}</a>
</li>
{{/pages}}
</ul>
</div>
{{> partials/foot }}

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<title>No Framework: {{title}}</title>
<link rel="stylesheet" href="/css/spectre.min.css">
<link rel="stylesheet" href="/css/spectre-exp.min.css">
<link rel="stylesheet" href="/css/spectre-icons.min.css">