2025-12-14 23:10:01 +00:00
|
|
|
<?php
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Phred\Http;
|
|
|
|
|
|
|
|
|
|
use DI\Container;
|
|
|
|
|
use DI\ContainerBuilder;
|
|
|
|
|
use FastRoute\Dispatcher;
|
|
|
|
|
use FastRoute\RouteCollector;
|
|
|
|
|
use Nyholm\Psr7\Factory\Psr17Factory;
|
|
|
|
|
use Psr\Http\Message\ResponseInterface;
|
|
|
|
|
use Psr\Http\Message\ServerRequestInterface as ServerRequest;
|
|
|
|
|
use Relay\Relay;
|
|
|
|
|
|
|
|
|
|
use function FastRoute\simpleDispatcher;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Core HTTP Kernel builds container, routes, and PSR-15 pipeline and processes requests.
|
|
|
|
|
*/
|
|
|
|
|
final class Kernel
|
|
|
|
|
{
|
|
|
|
|
private Container $container;
|
|
|
|
|
private Dispatcher $dispatcher;
|
|
|
|
|
|
|
|
|
|
public function __construct(?Container $container = null, ?Dispatcher $dispatcher = null)
|
|
|
|
|
{
|
|
|
|
|
$this->container = $container ?? $this->buildContainer();
|
|
|
|
|
$this->dispatcher = $dispatcher ?? $this->buildDispatcher();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function container(): Container
|
|
|
|
|
{
|
|
|
|
|
return $this->container;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function dispatcher(): Dispatcher
|
|
|
|
|
{
|
|
|
|
|
return $this->dispatcher;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function handle(ServerRequest $request): ResponseInterface
|
|
|
|
|
{
|
|
|
|
|
$psr17 = new Psr17Factory();
|
|
|
|
|
$middleware = [
|
2025-12-15 15:15:49 +00:00
|
|
|
new Middleware\ProblemDetailsMiddleware(
|
|
|
|
|
\Phred\Support\Config::get('APP_DEBUG', 'false') === 'true',
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
filter_var(\Phred\Support\Config::get('API_PROBLEM_DETAILS', 'true'), FILTER_VALIDATE_BOOLEAN)
|
|
|
|
|
),
|
|
|
|
|
new Middleware\ContentNegotiationMiddleware(),
|
2025-12-14 23:10:01 +00:00
|
|
|
new Middleware\RoutingMiddleware($this->dispatcher, $psr17),
|
2025-12-15 15:15:49 +00:00
|
|
|
new Middleware\DispatchMiddleware($psr17),
|
2025-12-14 23:10:01 +00:00
|
|
|
];
|
|
|
|
|
$relay = new Relay($middleware);
|
|
|
|
|
return $relay->handle($request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function buildContainer(): Container
|
|
|
|
|
{
|
|
|
|
|
$builder = new ContainerBuilder();
|
|
|
|
|
// Add definitions/bindings here as needed.
|
2025-12-15 15:15:49 +00:00
|
|
|
$builder->addDefinitions([
|
|
|
|
|
\Phred\Support\Contracts\ConfigInterface::class => \DI\autowire(\Phred\Support\DefaultConfig::class),
|
|
|
|
|
\Phred\Http\Contracts\ErrorFormatNegotiatorInterface::class => \DI\autowire(\Phred\Http\Support\DefaultErrorFormatNegotiator::class),
|
|
|
|
|
\Phred\Http\Contracts\RequestIdProviderInterface::class => \DI\autowire(\Phred\Http\Support\DefaultRequestIdProvider::class),
|
|
|
|
|
\Phred\Http\Contracts\ExceptionToStatusMapperInterface::class => \DI\autowire(\Phred\Http\Support\DefaultExceptionToStatusMapper::class),
|
|
|
|
|
\Phred\Http\Contracts\ApiResponseFactoryInterface::class => \DI\autowire(\Phred\Http\Responses\DelegatingApiResponseFactory::class),
|
|
|
|
|
\Phred\Http\Responses\RestResponseFactory::class => \DI\autowire(\Phred\Http\Responses\RestResponseFactory::class),
|
|
|
|
|
\Phred\Http\Responses\JsonApiResponseFactory::class => \DI\autowire(\Phred\Http\Responses\JsonApiResponseFactory::class),
|
|
|
|
|
]);
|
2025-12-14 23:10:01 +00:00
|
|
|
return $builder->build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function buildDispatcher(): Dispatcher
|
|
|
|
|
{
|
|
|
|
|
$routesPath = dirname(__DIR__, 2) . '/routes';
|
|
|
|
|
$collector = static function (RouteCollector $r) use ($routesPath): void {
|
|
|
|
|
// Load user-defined routes if present
|
|
|
|
|
$router = new Router($r);
|
|
|
|
|
foreach (['web.php', 'api.php'] as $file) {
|
|
|
|
|
$path = $routesPath . '/' . $file;
|
|
|
|
|
if (is_file($path)) {
|
|
|
|
|
/** @noinspection PhpIncludeInspection */
|
|
|
|
|
(static function ($router) use ($path) { require $path; })($router);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-15 15:15:49 +00:00
|
|
|
// Ensure default demo routes exist for acceptance/demo
|
2025-12-14 23:10:01 +00:00
|
|
|
$r->addRoute('GET', '/_phred/health', [Controllers\HealthController::class, '__invoke']);
|
2025-12-15 15:15:49 +00:00
|
|
|
$r->addRoute('GET', '/_phred/format', [Controllers\FormatController::class, '__invoke']);
|
2025-12-14 23:10:01 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return simpleDispatcher($collector);
|
|
|
|
|
}
|
|
|
|
|
}
|