From 75c7bafec5dee7679a6653f436e196075fdba3dc Mon Sep 17 00:00:00 2001 From: Oliver Eglseder Date: Sat, 7 Oct 2023 16:30:40 +0200 Subject: [PATCH] [FEATURE] Load config from configurable path, make controllers public services --- composer.json | 9 ++++ config/middlewares.php | 3 ++ config/routes.php | 3 +- config/services.yaml | 7 +++ .../bluecontainer/src/Composer/Plugin.php | 53 ++++++++++++++----- .../src/Helper/PackageIterator.php | 31 +++++++++++ packages/bluesprints/config/middlewares.php | 2 +- packages/bluesprints/config/services.php | 44 +++++++++++++-- packages/bluesprints/config/services.yaml | 3 ++ .../bluesprints/resources/public/index.php | 2 +- .../Server/Middleware/MiddlewareChain.php | 2 - .../Server/Middleware/MiddlewareRegistry.php | 9 +++- .../RequestHandler/ControllerDispatcher.php | 9 +++- .../RequestHandler/MiddlewareHandler.php | 28 ---------- .../src/Mvc/AbstractController.php | 30 +++++------ .../bluesprints/src/Mvc/AbstractModel.php | 21 ++++++-- packages/bluesprints/src/Mvc/Controller.php | 9 ++++ .../DependencyInjection/PublicServicePass.php | 25 +++++++++ packages/bluesprints/src/Paths.php | 19 +++++++ .../RouteCollectorCompilerPass.php | 36 +++++++++++++ .../Middleware/RoutingMiddleware.php | 31 +++-------- src/Controller/Welcome.php | 33 ++++++------ 22 files changed, 292 insertions(+), 117 deletions(-) create mode 100644 config/middlewares.php create mode 100644 config/services.yaml create mode 100644 packages/bluecontainer/src/Helper/PackageIterator.php delete mode 100644 packages/bluesprints/src/Http/Server/RequestHandler/MiddlewareHandler.php create mode 100644 packages/bluesprints/src/Mvc/Controller.php create mode 100644 packages/bluesprints/src/Mvc/DependencyInjection/PublicServicePass.php create mode 100644 packages/bluesprints/src/Paths.php create mode 100644 packages/bluesprints/src/Routing/DependencyInjection/RouteCollectorCompilerPass.php rename packages/bluesprints/src/{Http/Server => Routing}/Middleware/RoutingMiddleware.php (53%) diff --git a/composer.json b/composer.json index 8c4f07f..df029cc 100644 --- a/composer.json +++ b/composer.json @@ -38,6 +38,15 @@ "config": { "allow-plugins": { "vertexvaar/bluecontainer": true + }, + "vertexvaar/bluesprints": { + "logs": "var/logs", + "locks": "var/locks", + "cache": "var/cache", + "database": "var/database", + "config": "config", + "view": "view", + "translations": "translations" } } } diff --git a/config/middlewares.php b/config/middlewares.php new file mode 100644 index 0000000..0b67a5f --- /dev/null +++ b/config/middlewares.php @@ -0,0 +1,3 @@ +io->write('Generating container'); @@ -57,24 +66,40 @@ public function postAutoloadDump(): void } require $autoloadFile; - $containerBuilder = new ContainerBuilder(); $containerBuilder->set('composer', $this->composer); - - $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getPackages(); - foreach ($packages as $package) { - $installPath = $installationManager->getInstallPath($package); - $configPath = $installPath . '/config'; - if (file_exists($configPath) && is_dir($configPath)) { - if (file_exists($configPath . '/services.yaml')) { - $loader = new YamlFileLoader($containerBuilder, new FileLocator($configPath)); - $loader->load('services.yaml'); - } - if (file_exists($configPath . '/services.php')) { - $loader = new PhpFileLoader($containerBuilder, new FileLocator($configPath)); - $loader->load('services.php'); + $containerBuilder->set('io', $this->io); + $diDefinition = new Definition(DI::class); + $diDefinition->setPublic(true); + $diDefinition->setShared(true); + $containerBuilder->setDefinition(ContainerInterface::class, $diDefinition); + + $packageIterator = new PackageIterator($this->composer); + $packageIterator->iterate( + static function (Package $package, string $installPath) use ($containerBuilder): void { + $configPath = $installPath . '/config'; + if (file_exists($configPath) && is_dir($configPath)) { + if (file_exists($configPath . '/services.yaml')) { + $loader = new YamlFileLoader($containerBuilder, new FileLocator($configPath)); + $loader->load('services.yaml'); + } + if (file_exists($configPath . '/services.php')) { + $loader = new PhpFileLoader($containerBuilder, new FileLocator($configPath)); + $loader->load('services.php'); + } } } + ); + + $pathsDefinition = $containerBuilder->getDefinition(Paths::class); + $configPath = $pathsDefinition->getArgument('$config'); + if (file_exists($configPath . '/services.yaml')) { + $loader = new YamlFileLoader($containerBuilder, new FileLocator($configPath)); + $loader->load('services.yaml'); + } + if (file_exists($configPath . '/services.php')) { + $loader = new PhpFileLoader($containerBuilder, new FileLocator($configPath)); + $loader->load('services.php'); } $containerBuilder->compile(); diff --git a/packages/bluecontainer/src/Helper/PackageIterator.php b/packages/bluecontainer/src/Helper/PackageIterator.php new file mode 100644 index 0000000..321776c --- /dev/null +++ b/packages/bluecontainer/src/Helper/PackageIterator.php @@ -0,0 +1,31 @@ +composer->getInstallationManager(); + $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getPackages(); + foreach ($packages as $package) { + $installPath = $installationManager->getInstallPath($package); + $return[] = $closure($package, $installPath); + } + return $return; + } +} diff --git a/packages/bluesprints/config/middlewares.php b/packages/bluesprints/config/middlewares.php index 4d1d035..bd86a10 100644 --- a/packages/bluesprints/config/middlewares.php +++ b/packages/bluesprints/config/middlewares.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use VerteXVaaR\BlueSprints\Http\Server\Middleware\RoutingMiddleware; +use VerteXVaaR\BlueSprints\Routing\Middleware\RoutingMiddleware; return [ 'vertexvaar/bluesprints/routing' => [ diff --git a/packages/bluesprints/config/services.php b/packages/bluesprints/config/services.php index 11f029c..60984b2 100644 --- a/packages/bluesprints/config/services.php +++ b/packages/bluesprints/config/services.php @@ -1,25 +1,59 @@ get('composer'); - $installationManager = $composer->getInstallationManager(); - $packages = $composer->getRepositoryManager()->getLocalRepository()->getPackages(); - $middlewares = []; - foreach ($packages as $package) { - $installPath = $installationManager->getInstallPath($package); + + $packageIterator = new PackageIterator($composer); + $middlewares = $packageIterator->iterate(static function (Package $package, string $installPath): array { + $middlewares = []; if (file_exists($installPath . '/config/middlewares.php')) { $packageMiddlewares = require $installPath . '/config/middlewares.php'; foreach ($packageMiddlewares as $packageMiddleware) { $middlewares[] = new Reference($packageMiddleware['service']); } } + return $middlewares; + }); + + $packageConfig = $composer->getPackage()->getConfig(); + $config = $packageConfig['vertexvaar/bluesprints']; + $pathsDefinition = $containerBuilder->getDefinition(Paths::class); + $pathsDefinition->setArguments([ + '$logs' => $config['logs'] ?? 'var/logs', + '$locks' => $config['locks'] ?? 'var/locks', + '$cache' => $config['cache'] ?? 'var/cache', + '$database' => $config['database'] ?? 'var/database', + '$config' => $config['config'] ?? 'config', + '$view' => $config['view'] ?? 'view', + '$translations' => $config['translations'] ?? 'translations', + ]); + + $packageMiddlewares = []; + $middlewaresPath = $pathsDefinition->getArgument('$config') . '/middlewares.php'; + if (file_exists($middlewaresPath)) { + $packageMiddlewares = require $middlewaresPath; } + + $middlewares = array_replace($packageMiddlewares, ...$middlewares); + $registry = $containerBuilder->getDefinition(MiddlewareRegistry::class); $registry->setArgument('$middlewares', $middlewares); + + $containerBuilder->addCompilerPass(new RouteCollectorCompilerPass()); + + $containerBuilder->registerForAutoconfiguration(Controller::class) + ->addTag('vertexvaar.bluesprints.controller'); + $containerBuilder->addCompilerPass(new PublicServicePass('vertexvaar.bluesprints.controller')); }; diff --git a/packages/bluesprints/config/services.yaml b/packages/bluesprints/config/services.yaml index 1c66785..de4571d 100644 --- a/packages/bluesprints/config/services.yaml +++ b/packages/bluesprints/config/services.yaml @@ -9,4 +9,7 @@ services: VerteXVaaR\BlueSprints\Http\Application: public: true + VerteXVaaR\BlueSprints\Http\HttpResponseEmitter: + public: true + Psr\Http\Server\RequestHandlerInterface: '@VerteXVaaR\BlueSprints\Http\Server\RequestHandler\ControllerDispatcher' diff --git a/packages/bluesprints/resources/public/index.php b/packages/bluesprints/resources/public/index.php index cfd5859..7470448 100644 --- a/packages/bluesprints/resources/public/index.php +++ b/packages/bluesprints/resources/public/index.php @@ -33,4 +33,4 @@ Error::registerErrorHandler(); $di = new DI(); $response = $di->get(Application::class)->run($request); -(new HttpResponseEmitter())->emit($response); +$di->get(HttpResponseEmitter::class)->emit($response); diff --git a/packages/bluesprints/src/Http/Server/Middleware/MiddlewareChain.php b/packages/bluesprints/src/Http/Server/Middleware/MiddlewareChain.php index b808bea..feba8ca 100644 --- a/packages/bluesprints/src/Http/Server/Middleware/MiddlewareChain.php +++ b/packages/bluesprints/src/Http/Server/Middleware/MiddlewareChain.php @@ -8,9 +8,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use VerteXVaaR\BlueSprints\Http\Server\RequestHandler\MiddlewareHandler; -use function array_reverse; use function current; use function next; use function reset; diff --git a/packages/bluesprints/src/Http/Server/Middleware/MiddlewareRegistry.php b/packages/bluesprints/src/Http/Server/Middleware/MiddlewareRegistry.php index 0be01f3..dacc54e 100644 --- a/packages/bluesprints/src/Http/Server/Middleware/MiddlewareRegistry.php +++ b/packages/bluesprints/src/Http/Server/Middleware/MiddlewareRegistry.php @@ -2,9 +2,14 @@ namespace VerteXVaaR\BlueSprints\Http\Server\Middleware; -class MiddlewareRegistry +use Psr\Http\Server\MiddlewareInterface; + +readonly class MiddlewareRegistry { - public function __construct(public readonly array $middlewares) + /** + * @param array $middlewares + */ + public function __construct(public array $middlewares) { } } diff --git a/packages/bluesprints/src/Http/Server/RequestHandler/ControllerDispatcher.php b/packages/bluesprints/src/Http/Server/RequestHandler/ControllerDispatcher.php index 5c33d6f..4f29bc9 100644 --- a/packages/bluesprints/src/Http/Server/RequestHandler/ControllerDispatcher.php +++ b/packages/bluesprints/src/Http/Server/RequestHandler/ControllerDispatcher.php @@ -5,6 +5,7 @@ namespace VerteXVaaR\BlueSprints\Http\Server\RequestHandler; use GuzzleHttp\Psr7\Response; +use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -19,6 +20,10 @@ class ControllerDispatcher implements RequestHandlerInterface { + public function __construct(private readonly ContainerInterface $container) + { + } + public function handle(ServerRequestInterface $request): ResponseInterface { ob_start(); @@ -28,10 +33,10 @@ public function handle(ServerRequestInterface $request): ResponseInterface $response = new Response(); /** @var AbstractController $controller */ - $controller = new $route['controller']($request); + $controller = $this->container->get($route['controller']); $content = ''; try { - $content = $controller->callActionMethod($route); + $content = $controller->callActionMethod($route, $request); } catch (RedirectException $exception) { $response = $response->withHeader('Location', $exception->getUrl())->withStatus($exception->getStatus()); } diff --git a/packages/bluesprints/src/Http/Server/RequestHandler/MiddlewareHandler.php b/packages/bluesprints/src/Http/Server/RequestHandler/MiddlewareHandler.php deleted file mode 100644 index 686f2b0..0000000 --- a/packages/bluesprints/src/Http/Server/RequestHandler/MiddlewareHandler.php +++ /dev/null @@ -1,28 +0,0 @@ -middleware = $middleware; - $this->handler = $handler; - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return $this->middleware->process($request, $this->handler); - } -} diff --git a/packages/bluesprints/src/Mvc/AbstractController.php b/packages/bluesprints/src/Mvc/AbstractController.php index 28af4ea..9c6940e 100644 --- a/packages/bluesprints/src/Mvc/AbstractController.php +++ b/packages/bluesprints/src/Mvc/AbstractController.php @@ -7,10 +7,12 @@ use Psr\Http\Message\ServerRequestInterface; use VerteXVaaR\BlueFluid\Mvc\FluidAdapter; -abstract class AbstractController -{ - protected ServerRequestInterface $request; +use function call_user_func; +use function class_exists; +use function is_callable; +abstract class AbstractController implements Controller +{ protected TemplateRendererInterface $templateRenderer; /** @@ -18,9 +20,15 @@ abstract class AbstractController */ private bool $renderTemplate = true; - final public function __construct(ServerRequestInterface $request) + /** + * @param array $configuration + * + * @return string + * + * @throws RedirectException + */ + public function callActionMethod(array $configuration, ServerRequestInterface $request): string { - $this->request = $request; if (class_exists(FluidAdapter::class)) { $this->templateRenderer = new FluidAdapter(); } else { @@ -29,19 +37,9 @@ final public function __construct(ServerRequestInterface $request) if (is_callable([$this, 'initialize'])) { call_user_func([$this, 'initialize']); } - } - /** - * @param array $configuration - * - * @return string - * - * @throws RedirectException - */ - public function callActionMethod(array $configuration): string - { $this->templateRenderer->setRouteConfiguration($configuration); - $this->{$configuration['action']}(); + $this->{$configuration['action']}($request); if (true === $this->renderTemplate) { return $this->templateRenderer->render(); } diff --git a/packages/bluesprints/src/Mvc/AbstractModel.php b/packages/bluesprints/src/Mvc/AbstractModel.php index 153478b..459f8ba 100644 --- a/packages/bluesprints/src/Mvc/AbstractModel.php +++ b/packages/bluesprints/src/Mvc/AbstractModel.php @@ -5,6 +5,7 @@ namespace VerteXVaaR\BlueSprints\Mvc; use DateTime; +use DateTimeImmutable; use DateTimeInterface; use Exception; use VerteXVaaR\BlueSprints\Utility\Files; @@ -26,7 +27,10 @@ final public static function findByUuid(string $uuid): ?self { $fileContents = Files::readFileContents(self::getFolder() . $uuid); if ($fileContents) { - return unserialize($fileContents); + return unserialize( + $fileContents, + ['allowed_classes' => [static::class, DateTime::class, DateTimeImmutable::class]] + ); } return null; } @@ -51,7 +55,10 @@ final public static function findAll(): array $results = []; foreach ($files as $file) { if (Strings::isValidUuid(basename($file))) { - $results[] = unserialize(file_get_contents($file)); + $results[] = unserialize( + file_get_contents($file), + ['allowed_classes' => [static::class, DateTime::class, DateTimeImmutable::class]] + ); } } return $results; @@ -65,7 +72,10 @@ final public static function findAll(): array final public static function findByProperty(string $property, string $value): array { $indicesFile = self::getIndicesFile(); - $indices = unserialize(Files::readFileContents($indicesFile)); + $indices = unserialize( + Files::readFileContents($indicesFile), + ['allowed_classes' => [static::class, DateTime::class, DateTimeImmutable::class]] + ); $results = []; foreach ($indices as $uuid => $index) { if ($index[$property] === $value) { @@ -120,7 +130,10 @@ final protected function updateIndices(): void { if (!empty($this->getIndexColumns())) { $indicesFile = self::getIndicesFile($this); - $indices = unserialize(Files::readFileContents($indicesFile)); + $indices = unserialize( + Files::readFileContents($indicesFile), + ['allowed_classes' => [static::class, DateTime::class, DateTimeImmutable::class]] + ); if (array_key_exists($this->uuid, $indices)) { $indexEntry = $indices[$this->uuid]; } else { diff --git a/packages/bluesprints/src/Mvc/Controller.php b/packages/bluesprints/src/Mvc/Controller.php new file mode 100644 index 0000000..b687d07 --- /dev/null +++ b/packages/bluesprints/src/Mvc/Controller.php @@ -0,0 +1,9 @@ +findTaggedServiceIds($this->tag)) as $service) { + $container->getDefinition($service)->setPublic(true); + } + } + +} diff --git a/packages/bluesprints/src/Paths.php b/packages/bluesprints/src/Paths.php new file mode 100644 index 0000000..c257660 --- /dev/null +++ b/packages/bluesprints/src/Paths.php @@ -0,0 +1,19 @@ +get('composer'); + /** @var IOInterface $io */ + $io = $container->get('io'); + + $packageConfig = $composer->getPackage()->getConfig(); + $configPath = $packageConfig['vertexvaar/bluesprints']['config'] ?? 'config'; + + if (!file_exists($configPath . '/routes.php')) { + $io->warning(sprintf('No routes found in "%s"', $configPath . '/routes.php')); + $routes = []; + } else { + $routes = require $configPath . '/routes.php'; + } + + $definition = $container->getDefinition(RoutingMiddleware::class); + $definition->setArgument('$routes', $routes); + } +} diff --git a/packages/bluesprints/src/Http/Server/Middleware/RoutingMiddleware.php b/packages/bluesprints/src/Routing/Middleware/RoutingMiddleware.php similarity index 53% rename from packages/bluesprints/src/Http/Server/Middleware/RoutingMiddleware.php rename to packages/bluesprints/src/Routing/Middleware/RoutingMiddleware.php index 502992f..3c2e2c1 100644 --- a/packages/bluesprints/src/Http/Server/Middleware/RoutingMiddleware.php +++ b/packages/bluesprints/src/Routing/Middleware/RoutingMiddleware.php @@ -2,21 +2,18 @@ declare(strict_types=1); -namespace VerteXVaaR\BlueSprints\Http\Server\Middleware; +namespace VerteXVaaR\BlueSprints\Routing\Middleware; use Exception; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use VerteXVaaR\BlueSprints\Utility\Files; -use function array_merge_recursive; use function preg_match; class RoutingMiddleware implements MiddlewareInterface { - public const CONFIGURATION_FILENAME = 'config/routes.php'; public const HTTP_METHOD_GET = 'GET'; public const HTTP_METHOD_HEAD = 'HEAD'; public const HTTP_METHOD_POST = 'POST'; @@ -26,33 +23,17 @@ class RoutingMiddleware implements MiddlewareInterface public const HTTP_METHOD_OPTIONS = 'OPTIONS'; public const HTTP_METHOD_TRACE = 'TRACE'; - /** @var array[][] */ - protected array $configuration = [ - self::HTTP_METHOD_GET => [], - self::HTTP_METHOD_HEAD => [], - self::HTTP_METHOD_POST => [], - self::HTTP_METHOD_PUT => [], - self::HTTP_METHOD_DELETE => [], - self::HTTP_METHOD_CONNECT => [], - self::HTTP_METHOD_OPTIONS => [], - self::HTTP_METHOD_TRACE => [], - ]; - - public function __construct() + /** + * @param array{'GET'|'HEAD'|'POST'|'PUT'|'DELETE'|'CONNECT'|'OPTIONS'|'TRACE': string} $routes + */ + public function __construct(private readonly array $routes) { - if (!Files::fileExists(self::CONFIGURATION_FILENAME)) { - throw new Exception('The Router configuration does not exist', 1431886993); - } - $this->configuration = array_merge_recursive( - $this->configuration, - Files::requireFile(self::CONFIGURATION_FILENAME) - ); } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $path = $request->getUri()->getPath(); - foreach ($this->configuration[$request->getMethod()] as $pattern => $possibleRoute) { + foreach ($this->routes[$request->getMethod()] as $pattern => $possibleRoute) { if (preg_match('~^' . $pattern . '$~', $path)) { $request = $request->withAttribute('route', $possibleRoute); return $handler->handle($request); diff --git a/src/Controller/Welcome.php b/src/Controller/Welcome.php index eafaa2f..f5071ee 100644 --- a/src/Controller/Welcome.php +++ b/src/Controller/Welcome.php @@ -4,6 +4,7 @@ namespace VerteXVaaR\BlueDist\Controller; +use Psr\Http\Message\ServerRequestInterface; use VerteXVaaR\BlueDist\Model\Fruit; use VerteXVaaR\BlueDist\Model\SubFolder\Branch; use VerteXVaaR\BlueDist\Model\SubFolder\Leaf; @@ -58,9 +59,9 @@ protected function createDemoFruits(): void $this->redirect('listFruits'); } - protected function createFruit(): void + protected function createFruit(ServerRequestInterface $request): void { - $arguments = $this->request->getParsedBody(); + $arguments = $request->getParsedBody(); if (isset($arguments['name'], $arguments['color'])) { $fruit = new Fruit(); $fruit->setColor($arguments['color']); @@ -70,15 +71,15 @@ protected function createFruit(): void $this->redirect('listFruits'); } - protected function editFruit(): void + protected function editFruit(ServerRequestInterface $request): void { - $fruit = Fruit::findByUuid($this->request->getQueryParams()['fruit']); + $fruit = Fruit::findByUuid($request->getQueryParams()['fruit']); $this->templateRenderer->setVariable('fruit', $fruit); } - protected function updateFruit(): void + protected function updateFruit(ServerRequestInterface $request): void { - $arguments = $this->request->getParsedBody(); + $arguments = $request->getParsedBody(); if (isset($arguments['uuid'], $arguments['name'], $arguments['color'])) { $fruit = Fruit::findByUuid($arguments['uuid']); $fruit->setName($arguments['name']); @@ -88,9 +89,9 @@ protected function updateFruit(): void $this->redirect('listFruits'); } - protected function createTree(): void + protected function createTree(ServerRequestInterface $request): void { - $arguments = $this->request->getParsedBody(); + $arguments = $request->getParsedBody(); $tree = new Tree(); $tree->setGenus($arguments['genus']); $tree->save(); @@ -102,9 +103,9 @@ protected function newTree(): void { } - protected function growBranches(): void + protected function growBranches(ServerRequestInterface $request): void { - $arguments = $this->request->getParsedBody(); + $arguments = $request->getParsedBody(); $tree = Tree::findByUuid($arguments['tree']); $branches = []; foreach ($arguments['branches'] as $data) { @@ -117,18 +118,18 @@ protected function growBranches(): void $this->redirect('applyLeaves?tree=' . $tree->getUuid()); } - protected function applyLeaves(): void + protected function applyLeaves(ServerRequestInterface $request): void { - $arguments = $this->request->getQueryParams(); + $arguments = $request->getQueryParams(); $this->templateRenderer->setVariable( 'tree', Tree::findByUuid($arguments['tree']) ); } - protected function addLeaf(): void + protected function addLeaf(ServerRequestInterface $request): void { - $arguments = $this->request->getParsedBody(); + $arguments = $request->getParsedBody(); $tree = Tree::findByUuid($arguments['tree']); $branch = $tree->getBranches()[$arguments['branch']]; $leaves = $branch->getLeaves(); @@ -138,9 +139,9 @@ protected function addLeaf(): void $this->redirect('applyLeaves?tree=' . $tree->getUuid()); } - protected function deleteFruit(): void + protected function deleteFruit(ServerRequestInterface $request): void { - $fruit = Fruit::findByUuid($this->request->getParsedBody()['fruit']); + $fruit = Fruit::findByUuid($request->getParsedBody()['fruit']); $fruit->delete(); $this->redirect('listFruits'); }