add chapter about data repositories, and start work on perfomance chapter
This commit is contained in:
parent
eb20213b94
commit
9a1f78947b
165 changed files with 14028 additions and 2028 deletions
|
@ -2,20 +2,17 @@
|
|||
|
||||
namespace Lubian\NoFramework\Action;
|
||||
|
||||
use Lubian\NoFramework\Exception\InternalServerError;
|
||||
use Lubian\NoFramework\Model\MarkdownPage;
|
||||
use Lubian\NoFramework\Repository\MarkdownPageRepo;
|
||||
use Lubian\NoFramework\Template\MarkdownParser;
|
||||
use Lubian\NoFramework\Template\Renderer;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function file_get_contents;
|
||||
use function glob;
|
||||
use function assert;
|
||||
use function is_string;
|
||||
use function preg_replace;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
use function substr;
|
||||
|
||||
class Page
|
||||
{
|
||||
|
@ -23,30 +20,25 @@ class Page
|
|||
private ResponseInterface $response,
|
||||
private MarkdownParser $parser,
|
||||
private Renderer $renderer,
|
||||
private string $pagesPath = __DIR__ . '/../../data/pages/'
|
||||
private MarkdownPageRepo $repo,
|
||||
) {
|
||||
}
|
||||
|
||||
public function show(
|
||||
string $page,
|
||||
): ResponseInterface {
|
||||
$page = array_values(
|
||||
array_filter(
|
||||
$this->getPages(),
|
||||
fn (string $filename) => str_contains($filename, $page)
|
||||
)
|
||||
)[0];
|
||||
$markdown = file_get_contents($page);
|
||||
$page = $this->repo->byName($page);
|
||||
|
||||
// fix the next and previous buttons to work with our routing
|
||||
$markdown = preg_replace('/\(\d\d-/m', '(', $markdown);
|
||||
$markdown = str_replace('.md)', ')', $markdown);
|
||||
$content = preg_replace('/\(\d\d-/m', '(', $page->content);
|
||||
assert(is_string($content));
|
||||
$content = str_replace('.md)', ')', $content);
|
||||
|
||||
$page = str_replace([$this->pagesPath, '.md'], ['', ''], $page);
|
||||
$data = [
|
||||
'title' => substr($page, 3),
|
||||
'content' => $this->parser->parse($markdown),
|
||||
'title' => $page->title,
|
||||
'content' => $this->parser->parse($content),
|
||||
];
|
||||
|
||||
$html = $this->renderer->render('page/show', $data);
|
||||
$this->response->getBody()->write($html);
|
||||
return $this->response;
|
||||
|
@ -54,27 +46,15 @@ class Page
|
|||
|
||||
public function list(): ResponseInterface
|
||||
{
|
||||
$pages = array_map(function (string $page) {
|
||||
$page = str_replace([$this->pagesPath, '.md'], ['', ''], $page);
|
||||
$pages = array_map(function (MarkdownPage $page) {
|
||||
return [
|
||||
'id' => substr($page, 0, 2),
|
||||
'title' => substr($page, 3),
|
||||
'id' => $page->id,
|
||||
'title' => $page->content,
|
||||
];
|
||||
}, $this->getPages());
|
||||
}, $this->repo->all());
|
||||
|
||||
$html = $this->renderer->render('page/list', ['pages' => $pages]);
|
||||
$this->response->getBody()->write($html);
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getPages(): array
|
||||
{
|
||||
$files = glob($this->pagesPath . '*.md');
|
||||
if ($files === false) {
|
||||
throw new InternalServerError('cannot read pages');
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
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;
|
||||
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,
|
||||
private Response\Serializer $serializer,
|
||||
private Settings $settings,
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
if ($request->getMethod() === 'GET' && ! $this->settings->isDev()) {
|
||||
$key = (string) $request->getUri();
|
||||
$key = base64_encode($key);
|
||||
$callback = fn () => $handler->handle($request);
|
||||
$cached = $this->cache->get($key, function (ItemInterface $item) use ($callback) {
|
||||
$item->expiresAfter(120);
|
||||
$response = $callback();
|
||||
return $this->serializer::toString($response);
|
||||
});
|
||||
return $this->serializer::fromString($cached);
|
||||
}
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
|
@ -2,19 +2,11 @@
|
|||
|
||||
namespace Lubian\NoFramework\Model;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
||||
#[Entity]
|
||||
class MarkdownPage
|
||||
{
|
||||
public function __construct(
|
||||
#[Id,
|
||||
Column,
|
||||
GeneratedValue]
|
||||
public int |null $id = null,
|
||||
#[Column]
|
||||
public string $title,
|
||||
#[Column(type: Types::TEXT)]
|
||||
public string $content,
|
||||
) {
|
||||
}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
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 readonly CacheInterface $cache,
|
||||
private readonly MarkdownPageRepo $repo,
|
||||
private readonly Settings $settings,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
<?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;
|
||||
|
||||
use function random_int;
|
||||
use function usleep;
|
||||
|
||||
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(random_int(500, 1500) * 1000);
|
||||
return $this->repo->findAll();
|
||||
}
|
||||
|
||||
public function byId(int $id): MarkdownPage
|
||||
{
|
||||
usleep(random_int(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(random_int(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;
|
||||
}
|
||||
}
|
61
app/src/Repository/FileSystemMarkdownPageRepo.php
Normal file
61
app/src/Repository/FileSystemMarkdownPageRepo.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Repository;
|
||||
|
||||
use Lubian\NoFramework\Exception\InternalServerError;
|
||||
use Lubian\NoFramework\Exception\NotFound;
|
||||
use Lubian\NoFramework\Model\MarkdownPage;
|
||||
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function file_get_contents;
|
||||
use function glob;
|
||||
use function str_replace;
|
||||
use function substr;
|
||||
|
||||
final class FileSystemMarkdownPageRepo implements MarkdownPageRepo
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $dataPath
|
||||
) {
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function all(): array
|
||||
{
|
||||
$files = glob($this->dataPath . '*.md');
|
||||
if ($files === false) {
|
||||
throw new InternalServerError('cannot read pages');
|
||||
}
|
||||
return array_map(function (string $filename) {
|
||||
$content = file_get_contents($filename);
|
||||
if ($content === false) {
|
||||
throw new InternalServerError('cannot read pages');
|
||||
}
|
||||
$idAndTitle = str_replace([$this->dataPath, '.md'], ['', ''], $filename);
|
||||
return new MarkdownPage(
|
||||
(int) substr($idAndTitle, 0, 2),
|
||||
substr($idAndTitle, 3),
|
||||
$content
|
||||
);
|
||||
}, $files);
|
||||
}
|
||||
|
||||
public function byName(string $name): MarkdownPage
|
||||
{
|
||||
$pages = array_values(
|
||||
array_filter(
|
||||
$this->all(),
|
||||
fn (MarkdownPage $p) => $p->title === $name,
|
||||
)
|
||||
);
|
||||
|
||||
if (count($pages) !== 1) {
|
||||
throw new NotFound;
|
||||
}
|
||||
|
||||
return $pages[0];
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Repository;
|
||||
|
||||
use Lubian\NoFramework\Exception\NotFound;
|
||||
use Lubian\NoFramework\Model\MarkdownPage;
|
||||
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function file_get_contents;
|
||||
use function glob;
|
||||
use function is_array;
|
||||
use function random_int;
|
||||
use function str_replace;
|
||||
use function substr;
|
||||
use function usleep;
|
||||
|
||||
final class MarkdownPageFilesystem implements MarkdownPageRepo
|
||||
{
|
||||
public function __construct(private readonly string $dataPath)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MarkdownPage[]
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
$fileNames = glob($this->dataPath . '*.md');
|
||||
assert(is_array($fileNames));
|
||||
return array_map(function (string $name): MarkdownPage {
|
||||
usleep(random_int(200, 500) * 1000);
|
||||
$content = file_get_contents($name);
|
||||
$name = str_replace($this->dataPath, '', $name);
|
||||
$name = str_replace('.md', '', $name);
|
||||
$id = (int) substr($name, 0, 2);
|
||||
$title = substr($name, 3);
|
||||
return new MarkdownPage($id, $title, $content);
|
||||
}, $fileNames);
|
||||
}
|
||||
|
||||
public function byId(int $id): MarkdownPage
|
||||
{
|
||||
$callback = fn (MarkdownPage $p): bool => $p->id === $id;
|
||||
$filtered = array_values(array_filter($this->all(), $callback));
|
||||
if (count($filtered) === 0) {
|
||||
throw new NotFound;
|
||||
}
|
||||
return $filtered[0];
|
||||
}
|
||||
|
||||
public function byTitle(string $title): MarkdownPage
|
||||
{
|
||||
$callback = fn (MarkdownPage $p): bool => $p->title === $title;
|
||||
$filtered = array_values(array_filter($this->all(), $callback));
|
||||
if (count($filtered) === 0) {
|
||||
throw new NotFound;
|
||||
}
|
||||
return $filtered[0];
|
||||
}
|
||||
|
||||
public function save(MarkdownPage $page): MarkdownPage
|
||||
{
|
||||
return $page;
|
||||
}
|
||||
}
|
|
@ -2,18 +2,14 @@
|
|||
|
||||
namespace Lubian\NoFramework\Repository;
|
||||
|
||||
use Lubian\NoFramework\Exception\NotFound;
|
||||
use Lubian\NoFramework\Model\MarkdownPage;
|
||||
|
||||
interface MarkdownPageRepo
|
||||
{
|
||||
/**
|
||||
* @return MarkdownPage[]
|
||||
*/
|
||||
/** @return MarkdownPage[] */
|
||||
public function all(): array;
|
||||
|
||||
public function byId(int $id): MarkdownPage;
|
||||
|
||||
public function byTitle(string $title): MarkdownPage;
|
||||
|
||||
public function save(MarkdownPage $page): MarkdownPage;
|
||||
/** @throws NotFound */
|
||||
public function byName(string $name): MarkdownPage;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,6 @@ 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,
|
||||
|
@ -15,19 +11,6 @@ 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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Template;
|
||||
|
||||
use League\CommonMark\Environment\Environment;
|
||||
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
|
||||
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
|
||||
use League\CommonMark\MarkdownConverter;
|
||||
|
||||
final class GithubMarkdownRenderer implements MarkdownRenderer
|
||||
{
|
||||
private MarkdownConverter $engine;
|
||||
|
||||
public function __construct(
|
||||
CommonMarkCoreExtension $commonMarkCoreExtension,
|
||||
GithubFlavoredMarkdownExtension $githubFlavoredMarkdownExtension,
|
||||
) {
|
||||
$environment = new Environment([]);
|
||||
$environment->addExtension($commonMarkCoreExtension);
|
||||
$environment->addExtension($githubFlavoredMarkdownExtension);
|
||||
$this->engine = new MarkdownConverter($environment);
|
||||
}
|
||||
|
||||
public function render(string $markdown): string
|
||||
{
|
||||
return (string) $this->engine->convert($markdown);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue