update readme
This commit is contained in:
parent
b12cf019e7
commit
ab3227b75f
88 changed files with 7546 additions and 176 deletions
13
app/cli-config.php
Normal file
13
app/cli-config.php
Normal 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));
|
|
@ -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
1742
app/composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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(),
|
||||
];
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
|
|
|
@ -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'));
|
||||
};
|
||||
|
|
|
@ -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/',
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
32
app/src/Factory/DoctrineEm.php
Normal file
32
app/src/Factory/DoctrineEm.php
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
60
app/src/Repository/DoctrineMarkdownPageRepo.php
Normal file
60
app/src/Repository/DoctrineMarkdownPageRepo.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,4 +14,6 @@ interface MarkdownPageRepo
|
|||
public function byId(int $id): MarkdownPage;
|
||||
|
||||
public function byTitle(string $title): MarkdownPage;
|
||||
|
||||
public function save(MarkdownPage $page): MarkdownPage;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
11
app/templates/pagelist.html
Normal file
11
app/templates/pagelist.html
Normal 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 }}
|
|
@ -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">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue