asdf
This commit is contained in:
parent
48c9c9467d
commit
b12cf019e7
107 changed files with 8372 additions and 186 deletions
31
app/src/Action/Hello.php
Normal file
31
app/src/Action/Hello.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Action;
|
||||
|
||||
use Lubian\NoFramework\Service\Time\Now;
|
||||
use Lubian\NoFramework\Template\Renderer;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
final class Hello
|
||||
{
|
||||
public function __invoke(
|
||||
ResponseInterface $response,
|
||||
Now $now,
|
||||
Renderer $renderer,
|
||||
string $name = 'Stranger',
|
||||
): ResponseInterface {
|
||||
$body = $response->getBody();
|
||||
$data = [
|
||||
'now' => $now()->format('H:i:s'),
|
||||
'name' => $name,
|
||||
];
|
||||
|
||||
$content = $renderer->render('hello', $data);
|
||||
|
||||
$body->write($content);
|
||||
|
||||
return $response
|
||||
->withStatus(200)
|
||||
->withBody($body);
|
||||
}
|
||||
}
|
19
app/src/Action/Other.php
Normal file
19
app/src/Action/Other.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Action;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
final class Other
|
||||
{
|
||||
public function someFunctionName(ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$body = $response->getBody();
|
||||
|
||||
$body->write('This works too!');
|
||||
|
||||
return $response
|
||||
->withStatus(200)
|
||||
->withBody($body);
|
||||
}
|
||||
}
|
35
app/src/Action/Page.php
Normal file
35
app/src/Action/Page.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Action;
|
||||
|
||||
use Lubian\NoFramework\Repository\MarkdownPageRepo;
|
||||
use Lubian\NoFramework\Template\Renderer;
|
||||
use Parsedown;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
use function preg_replace;
|
||||
use function str_replace;
|
||||
|
||||
class Page
|
||||
{
|
||||
public function __invoke(
|
||||
string $page,
|
||||
ResponseInterface $response,
|
||||
MarkdownPageRepo $repo,
|
||||
Parsedown $parsedown,
|
||||
Renderer $renderer,
|
||||
): ResponseInterface {
|
||||
$page = $repo->byTitle($page);
|
||||
$content = $this->linkFilter($page->content);
|
||||
$content = $parsedown->parse($content);
|
||||
$html = $renderer->render('page', ['content' => $content]);
|
||||
$response->getBody()->write($html);
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function linkFilter(string $content): string
|
||||
{
|
||||
$content = preg_replace('/\(\d\d-/m', '(', $content);
|
||||
return str_replace('.md)', ')', $content);
|
||||
}
|
||||
}
|
40
app/src/Bootstrap.php
Normal file
40
app/src/Bootstrap.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework;
|
||||
|
||||
use Lubian\NoFramework\Factory\FileSystemSettingsProvider;
|
||||
use Lubian\NoFramework\Factory\SettingsContainerProvider;
|
||||
use Throwable;
|
||||
use Whoops\Handler\PrettyPageHandler;
|
||||
use Whoops\Run;
|
||||
|
||||
use function assert;
|
||||
use function error_log;
|
||||
use function error_reporting;
|
||||
|
||||
use const E_ALL;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
error_reporting(E_ALL);
|
||||
|
||||
$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php');
|
||||
$container = (new SettingsContainerProvider($settingsProvider))->getContainer();
|
||||
|
||||
$settings = $settingsProvider->getSettings();
|
||||
|
||||
$whoops = new Run;
|
||||
if ($settings->environment === 'dev') {
|
||||
$whoops->pushHandler(new PrettyPageHandler);
|
||||
} else {
|
||||
$whoops->pushHandler(function (Throwable $e): void {
|
||||
error_log('Error: ' . $e->getMessage(), (int) $e->getCode());
|
||||
echo 'An Error happened';
|
||||
});
|
||||
}
|
||||
$whoops->register();
|
||||
|
||||
$app = $container->get(Kernel::class);
|
||||
assert($app instanceof Kernel);
|
||||
|
||||
$app->run();
|
9
app/src/Exception/InternalServerError.php
Normal file
9
app/src/Exception/InternalServerError.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
final class InternalServerError extends Exception
|
||||
{
|
||||
}
|
9
app/src/Exception/MethodNotAllowed.php
Normal file
9
app/src/Exception/MethodNotAllowed.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
final class MethodNotAllowed extends Exception
|
||||
{
|
||||
}
|
9
app/src/Exception/NotFound.php
Normal file
9
app/src/Exception/NotFound.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
final class NotFound extends Exception
|
||||
{
|
||||
}
|
10
app/src/Factory/ContainerProvider.php
Normal file
10
app/src/Factory/ContainerProvider.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Factory;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
interface ContainerProvider
|
||||
{
|
||||
public function getContainer(): ContainerInterface;
|
||||
}
|
28
app/src/Factory/DiactorosRequestFactory.php
Normal file
28
app/src/Factory/DiactorosRequestFactory.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Factory;
|
||||
|
||||
use Laminas\Diactoros\ServerRequestFactory;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
final class DiactorosRequestFactory implements RequestFactory
|
||||
{
|
||||
public function __construct(private readonly ServerRequestFactory $factory)
|
||||
{
|
||||
}
|
||||
|
||||
public function fromGlobals(): ServerRequestInterface
|
||||
{
|
||||
return $this->factory::fromGlobals();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UriInterface|string $uri
|
||||
* @param array<mixed> $serverParams
|
||||
*/
|
||||
public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
|
||||
{
|
||||
return $this->factory->createServerRequest($method, $uri, $serverParams);
|
||||
}
|
||||
}
|
22
app/src/Factory/FileSystemSettingsProvider.php
Normal file
22
app/src/Factory/FileSystemSettingsProvider.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Factory;
|
||||
|
||||
use Lubian\NoFramework\Settings;
|
||||
|
||||
use function assert;
|
||||
|
||||
final class FileSystemSettingsProvider implements SettingsProvider
|
||||
{
|
||||
public function __construct(
|
||||
private string $filePath
|
||||
) {
|
||||
}
|
||||
|
||||
public function getSettings(): Settings
|
||||
{
|
||||
$settings = require $this->filePath;
|
||||
assert($settings instanceof Settings);
|
||||
return $settings;
|
||||
}
|
||||
}
|
25
app/src/Factory/PipelineProvider.php
Normal file
25
app/src/Factory/PipelineProvider.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Factory;
|
||||
|
||||
use Lubian\NoFramework\Http\ContainerPipeline;
|
||||
use Lubian\NoFramework\Http\Pipeline;
|
||||
use Lubian\NoFramework\Http\RoutedRequestHandler;
|
||||
use Lubian\NoFramework\Settings;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class PipelineProvider
|
||||
{
|
||||
public function __construct(
|
||||
private Settings $settings,
|
||||
private RoutedRequestHandler $tip,
|
||||
private ContainerInterface $container,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getPipeline(): Pipeline
|
||||
{
|
||||
$middlewares = require $this->settings->middlewaresFile;
|
||||
return new ContainerPipeline($middlewares, $this->tip, $this->container);
|
||||
}
|
||||
}
|
11
app/src/Factory/RequestFactory.php
Normal file
11
app/src/Factory/RequestFactory.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Factory;
|
||||
|
||||
use Psr\Http\Message\ServerRequestFactoryInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
interface RequestFactory extends ServerRequestFactoryInterface
|
||||
{
|
||||
public function fromGlobals(): ServerRequestInterface;
|
||||
}
|
25
app/src/Factory/SettingsContainerProvider.php
Normal file
25
app/src/Factory/SettingsContainerProvider.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Factory;
|
||||
|
||||
use DI\ContainerBuilder;
|
||||
use Lubian\NoFramework\Settings;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
final class SettingsContainerProvider implements ContainerProvider
|
||||
{
|
||||
public function __construct(
|
||||
private SettingsProvider $settingsProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getContainer(): ContainerInterface
|
||||
{
|
||||
$builder = new ContainerBuilder;
|
||||
$settings = $this->settingsProvider->getSettings();
|
||||
$dependencies = require $settings->dependenciesFile;
|
||||
$dependencies[Settings::class] = fn (): Settings => $settings;
|
||||
$builder->addDefinitions($dependencies);
|
||||
return $builder->build();
|
||||
}
|
||||
}
|
10
app/src/Factory/SettingsProvider.php
Normal file
10
app/src/Factory/SettingsProvider.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Factory;
|
||||
|
||||
use Lubian\NoFramework\Settings;
|
||||
|
||||
interface SettingsProvider
|
||||
{
|
||||
public function getSettings(): Settings;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Http;
|
||||
|
||||
interface AddRoute
|
||||
{
|
||||
/**
|
||||
* @param array<class-string, string>|class-string|callable $handler
|
||||
*/
|
||||
public function addRoute(
|
||||
string $method,
|
||||
string $path,
|
||||
array|string|callable $handler,
|
||||
): self;
|
||||
}
|
38
app/src/Http/BasicEmitter.php
Normal file
38
app/src/Http/BasicEmitter.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Http;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
use function header;
|
||||
use function sprintf;
|
||||
use function strtolower;
|
||||
|
||||
final class BasicEmitter implements Emitter
|
||||
{
|
||||
public function emit(ResponseInterface $response, bool $withoutBody = false): void
|
||||
{
|
||||
foreach ($response->getHeaders() as $name => $values) {
|
||||
$first = strtolower($name) !== 'set-cookie';
|
||||
foreach ($values as $value) {
|
||||
$header = sprintf('%s: %s', $name, $value);
|
||||
header($header, $first);
|
||||
$first = false;
|
||||
}
|
||||
}
|
||||
|
||||
$statusLine = sprintf(
|
||||
'HTTP/%s %s %s',
|
||||
$response->getProtocolVersion(),
|
||||
$response->getStatusCode(),
|
||||
$response->getReasonPhrase()
|
||||
);
|
||||
header($statusLine, true, $response->getStatusCode());
|
||||
|
||||
if ($withoutBody) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo $response->getBody();
|
||||
}
|
||||
}
|
82
app/src/Http/ContainerPipeline.php
Normal file
82
app/src/Http/ContainerPipeline.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Http;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
use function array_reverse;
|
||||
use function assert;
|
||||
use function is_string;
|
||||
|
||||
class ContainerPipeline implements Pipeline
|
||||
{
|
||||
/**
|
||||
* @param array<MiddlewareInterface|class-string> $middlewares
|
||||
* @param RequestHandlerInterface $tip
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(
|
||||
private array $middlewares,
|
||||
private RequestHandlerInterface $tip,
|
||||
private ContainerInterface $container,
|
||||
) {
|
||||
}
|
||||
|
||||
public function dispatch(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$this->buildStack();
|
||||
return $this->tip->handle($request);
|
||||
}
|
||||
|
||||
private function buildStack(): void
|
||||
{
|
||||
foreach (array_reverse($this->middlewares) as $middleware) {
|
||||
$next = $this->tip;
|
||||
if ($middleware instanceof MiddlewareInterface) {
|
||||
$this->tip = $this->wrapMiddleware($middleware, $next);
|
||||
}
|
||||
if (is_string($middleware)) {
|
||||
$this->tip = $this->wrapResolvedMiddleware($middleware, $next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function wrapResolvedMiddleware(string $middleware, RequestHandlerInterface $next): RequestHandlerInterface
|
||||
{
|
||||
return new class ($middleware, $next, $this->container) implements RequestHandlerInterface {
|
||||
public function __construct(
|
||||
private readonly string $middleware,
|
||||
private readonly RequestHandlerInterface $handler,
|
||||
private readonly ContainerInterface $container,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$middleware = $this->container->get($this->middleware);
|
||||
assert($middleware instanceof MiddlewareInterface);
|
||||
return $middleware->process($request, $this->handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private function wrapMiddleware(MiddlewareInterface $middleware, RequestHandlerInterface $next): RequestHandlerInterface
|
||||
{
|
||||
return new class ($middleware, $next) implements RequestHandlerInterface {
|
||||
public function __construct(
|
||||
private readonly MiddlewareInterface $middleware,
|
||||
private readonly RequestHandlerInterface $handler,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return $this->middleware->process($request, $this->handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
10
app/src/Http/Emitter.php
Normal file
10
app/src/Http/Emitter.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Http;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
interface Emitter
|
||||
{
|
||||
public function emit(ResponseInterface $response, bool $withoutBody = false): void;
|
||||
}
|
34
app/src/Http/InvokerRoutedHandler.php
Normal file
34
app/src/Http/InvokerRoutedHandler.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Http;
|
||||
|
||||
use Invoker\InvokerInterface;
|
||||
use Lubian\NoFramework\Exception\InternalServerError;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class InvokerRoutedHandler implements RoutedRequestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly InvokerInterface $invoker,
|
||||
private string $routeAttributeName = '__route_handler',
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$handler = $request->getAttribute($this->routeAttributeName, false);
|
||||
$vars = $request->getAttributes();
|
||||
$vars['request'] = $request;
|
||||
$response = $this->invoker->call($handler, $vars);
|
||||
if (! $response instanceof ResponseInterface) {
|
||||
throw new InternalServerError('Handler returned invalid response');
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void
|
||||
{
|
||||
$this->routeAttributeName = $routeAttributeName;
|
||||
}
|
||||
}
|
11
app/src/Http/Pipeline.php
Normal file
11
app/src/Http/Pipeline.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Http;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
interface Pipeline
|
||||
{
|
||||
public function dispatch(ServerRequestInterface $request): ResponseInterface;
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Http;
|
||||
|
||||
use FastRoute\Dispatcher;
|
||||
use FastRoute\RouteCollector;
|
||||
use Lubian\NoFramework\Exception\InternalServerError;
|
||||
use Lubian\NoFramework\Exception\MethodNotAllowed;
|
||||
use Lubian\NoFramework\Exception\NotFound;
|
||||
use Psr\Http\Message\ResponseFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Throwable;
|
||||
use function FastRoute\simpleDispatcher;
|
||||
|
||||
final class RouteDecorationMiddleware implements MiddlewareInterface, AddRoute
|
||||
{
|
||||
/**
|
||||
* @param array<int, array{string, string, array{class-string, string}|class-string|callable}> $routes
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ResponseFactoryInterface $responseFactory,
|
||||
private readonly string $routeAttributeName = '__route_handler',
|
||||
private array $routes = [],
|
||||
private Dispatcher|null $dispatcher = null,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MethodNotAllowed
|
||||
* @throws NotFound
|
||||
*/
|
||||
private function decorateRequest(ServerRequestInterface $request): ServerRequestInterface
|
||||
{
|
||||
$this->dispatcher ??= $this->createDispatcher();
|
||||
$routeInfo = $this->dispatcher->dispatch(
|
||||
$request->getMethod(),
|
||||
$request->getUri()->getPath()
|
||||
);
|
||||
|
||||
if ($routeInfo[0] === Dispatcher::NOT_FOUND) {
|
||||
throw new NotFound;
|
||||
}
|
||||
|
||||
if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
|
||||
throw new MethodNotAllowed;
|
||||
}
|
||||
|
||||
foreach ($routeInfo[2] as $attributeName => $attributeValue) {
|
||||
$request = $request->withAttribute($attributeName, $attributeValue);
|
||||
}
|
||||
|
||||
return $request->withAttribute($this->routeAttributeName, $routeInfo[1]);
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
try {
|
||||
$request = $this->decorateRequest($request);
|
||||
} catch (NotFound) {
|
||||
$response = $this->responseFactory->createResponse(404);
|
||||
$response->getBody()->write('Not Found');
|
||||
return $response;
|
||||
} catch (MethodNotAllowed) {
|
||||
return $this->responseFactory->createResponse(405);
|
||||
} catch (Throwable $t) {
|
||||
throw new InternalServerError($t->getMessage(), $t->getCode(), $t);
|
||||
}
|
||||
|
||||
if ($handler instanceof RoutedRequestHandler) {
|
||||
$handler->setRouteAttributeName($this->routeAttributeName);
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
private function createDispatcher(): Dispatcher
|
||||
{
|
||||
return simpleDispatcher(function (RouteCollector $r) {
|
||||
foreach ($this->routes as $route) {
|
||||
$r->addRoute($route[0], $route[1], $route[2]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addRoute(string $method, string $path, array|string|callable $handler,): AddRoute
|
||||
{
|
||||
$this->routes[] = [$method, $path, $handler];
|
||||
return $this;
|
||||
}
|
||||
}
|
69
app/src/Http/RouteMiddleware.php
Normal file
69
app/src/Http/RouteMiddleware.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Http;
|
||||
|
||||
use FastRoute\Dispatcher;
|
||||
use Lubian\NoFramework\Exception\InternalServerError;
|
||||
use Lubian\NoFramework\Exception\MethodNotAllowed;
|
||||
use Lubian\NoFramework\Exception\NotFound;
|
||||
use Psr\Http\Message\ResponseFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Throwable;
|
||||
|
||||
final class RouteMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Dispatcher $dispatcher,
|
||||
private readonly ResponseFactoryInterface $responseFactory,
|
||||
private readonly string $routeAttributeName = '__route_handler',
|
||||
) {
|
||||
}
|
||||
|
||||
private function decorateRequest(
|
||||
ServerRequestInterface $request,
|
||||
): ServerRequestInterface {
|
||||
$routeInfo = $this->dispatcher->dispatch(
|
||||
$request->getMethod(),
|
||||
$request->getUri()->getPath(),
|
||||
);
|
||||
|
||||
if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
|
||||
throw new MethodNotAllowed;
|
||||
}
|
||||
|
||||
if ($routeInfo[0] === Dispatcher::FOUND) {
|
||||
foreach ($routeInfo[2] as $attributeName => $attributeValue) {
|
||||
$request = $request->withAttribute($attributeName, $attributeValue);
|
||||
}
|
||||
return $request->withAttribute(
|
||||
$this->routeAttributeName,
|
||||
$routeInfo[1]
|
||||
);
|
||||
}
|
||||
|
||||
throw new NotFound;
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
try {
|
||||
$request = $this->decorateRequest($request);
|
||||
} catch (NotFound) {
|
||||
$response = $this->responseFactory->createResponse(404);
|
||||
$response->getBody()->write('Not Found');
|
||||
return $response;
|
||||
} catch (MethodNotAllowed) {
|
||||
return $this->responseFactory->createResponse(405);
|
||||
} catch (Throwable $t) {
|
||||
throw new InternalServerError($t->getMessage(), $t->getCode(), $t);
|
||||
}
|
||||
|
||||
if ($handler instanceof RoutedRequestHandler) {
|
||||
$handler->setRouteAttributeName($this->routeAttributeName);
|
||||
}
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
10
app/src/Http/RoutedRequestHandler.php
Normal file
10
app/src/Http/RoutedRequestHandler.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Http;
|
||||
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
interface RoutedRequestHandler extends RequestHandlerInterface
|
||||
{
|
||||
public function setRouteAttributeName(string $routeAttributeName = '__route_handler'): void;
|
||||
}
|
|
@ -2,62 +2,31 @@
|
|||
|
||||
namespace Lubian\NoFramework;
|
||||
|
||||
use Lubian\NoFramework\Exception\InternalServerError;
|
||||
use Lubian\NoFramework\Http\AddRoute;
|
||||
use Lubian\NoFramework\Factory\RequestFactory;
|
||||
use Lubian\NoFramework\Http\Emitter;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Lubian\NoFramework\Http\Pipeline;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Throwable;
|
||||
|
||||
use function assert;
|
||||
|
||||
final class Kernel implements RequestHandlerInterface, AddRoute
|
||||
final class Kernel implements RequestHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ContainerInterface $container,
|
||||
private Emitter $emitter,
|
||||
private MiddlewareInterface&AddRoute $routeMiddleware,
|
||||
private RequestHandlerInterface $handler,
|
||||
private readonly RequestFactory $requestFactory,
|
||||
private readonly Pipeline $pipeline,
|
||||
private readonly Emitter $emitter,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return $this->routeMiddleware->process($request, $this->handler);
|
||||
return $this->pipeline->dispatch($request);
|
||||
}
|
||||
|
||||
public function run(ServerRequestInterface |null $request = null): void
|
||||
public function run(): void
|
||||
{
|
||||
$request ??= $this->createRequest();
|
||||
$request = $this->requestFactory->fromGlobals();
|
||||
$response = $this->handle($request);
|
||||
$this->emitter->emit($response);
|
||||
}
|
||||
|
||||
private function createRequest(): ServerRequestInterface
|
||||
{
|
||||
try {
|
||||
$request = $this->container->get(ServerRequestInterface::class);
|
||||
assert($request instanceof ServerRequestInterface);
|
||||
return $request;
|
||||
} catch (Throwable $t) {
|
||||
throw new InternalServerError(
|
||||
'could not get Request from container, please configure the container ' .
|
||||
'in order to use run() wihtout a request',
|
||||
$t->getCode(),
|
||||
$t,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function addRoute(
|
||||
string $method,
|
||||
string $path,
|
||||
array|string|callable $handler): AddRoute
|
||||
{
|
||||
$this->routeMiddleware->addRoute($method, $path, $handler);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
33
app/src/Middleware/CacheMiddleware.php
Normal file
33
app/src/Middleware/CacheMiddleware.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Middleware;
|
||||
|
||||
use Laminas\Diactoros\Response;
|
||||
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;
|
||||
|
||||
final class CacheMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(private CacheInterface $cache){}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
if ($request->getMethod() === 'GET') {
|
||||
$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) {
|
||||
$item->expiresAfter(120);
|
||||
return (string) $callback()->getBody();
|
||||
});
|
||||
$response->getBody()->write($body);
|
||||
return $response;
|
||||
}
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
13
app/src/Model/MarkdownPage.php
Normal file
13
app/src/Model/MarkdownPage.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Model;
|
||||
|
||||
class MarkdownPage
|
||||
{
|
||||
public function __construct(
|
||||
public readonly int $id,
|
||||
public readonly string $title,
|
||||
public readonly string $content,
|
||||
) {
|
||||
}
|
||||
}
|
46
app/src/Repository/CachedMarkdownPageRepo.php
Normal file
46
app/src/Repository/CachedMarkdownPageRepo.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Repository;
|
||||
|
||||
use Lubian\NoFramework\Model\MarkdownPage;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
class CachedMarkdownPageRepo implements MarkdownPageRepo
|
||||
{
|
||||
public function __construct(
|
||||
private CacheInterface $cache,
|
||||
private MarkdownPageRepo $repo,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
$callback = fn () => $this->repo->all();
|
||||
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);
|
||||
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);
|
||||
return $this->cache->get('PAGE' . $title, function (ItemInterface $item) use ($callback) {
|
||||
$item->expiresAfter(30);
|
||||
return $callback();
|
||||
});
|
||||
}
|
||||
}
|
63
app/src/Repository/MarkdownPageFilesystem.php
Normal file
63
app/src/Repository/MarkdownPageFilesystem.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?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 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(100000);
|
||||
$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];
|
||||
}
|
||||
}
|
17
app/src/Repository/MarkdownPageRepo.php
Normal file
17
app/src/Repository/MarkdownPageRepo.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Repository;
|
||||
|
||||
use Lubian\NoFramework\Model\MarkdownPage;
|
||||
|
||||
interface MarkdownPageRepo
|
||||
{
|
||||
/**
|
||||
* @return MarkdownPage[]
|
||||
*/
|
||||
public function all(): array;
|
||||
|
||||
public function byId(int $id): MarkdownPage;
|
||||
|
||||
public function byTitle(string $title): MarkdownPage;
|
||||
}
|
10
app/src/Service/Time/Now.php
Normal file
10
app/src/Service/Time/Now.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Service\Time;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
interface Now
|
||||
{
|
||||
public function __invoke(): DateTimeImmutable;
|
||||
}
|
13
app/src/Service/Time/SystemClockNow.php
Normal file
13
app/src/Service/Time/SystemClockNow.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Service\Time;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
final class SystemClockNow implements Now
|
||||
{
|
||||
public function __invoke(): DateTimeImmutable
|
||||
{
|
||||
return new DateTimeImmutable;
|
||||
}
|
||||
}
|
16
app/src/Settings.php
Normal file
16
app/src/Settings.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework;
|
||||
|
||||
final class Settings
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $environment,
|
||||
public readonly string $dependenciesFile,
|
||||
public readonly string $middlewaresFile,
|
||||
public readonly string $templateDir,
|
||||
public readonly string $templateExtension,
|
||||
public readonly string $pagesPath,
|
||||
) {
|
||||
}
|
||||
}
|
17
app/src/Template/MustacheRenderer.php
Normal file
17
app/src/Template/MustacheRenderer.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Template;
|
||||
|
||||
use Mustache_Engine;
|
||||
|
||||
final class MustacheRenderer implements Renderer
|
||||
{
|
||||
public function __construct(private Mustache_Engine $engine)
|
||||
{
|
||||
}
|
||||
|
||||
public function render(string $template, array $data = []): string
|
||||
{
|
||||
return $this->engine->render($template, $data);
|
||||
}
|
||||
}
|
11
app/src/Template/Renderer.php
Normal file
11
app/src/Template/Renderer.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Template;
|
||||
|
||||
interface Renderer
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function render(string $template, array $data = []): string;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue