diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index 4439b32..1f74c75 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,10 @@ ## Develop Installation -1. `npm install` -2. Setup [Sample](sample/README.md) +1. `nvm install` +2. `nvm use` +3. `npm ci --also=dev` +4. Setup [Sample](sample/README.md) ## Author diff --git a/core/src/Domain/Exception/HarmonyException.php b/core/src/Domain/Exception/HarmonyException.php new file mode 100644 index 0000000..8a72e39 --- /dev/null +++ b/core/src/Domain/Exception/HarmonyException.php @@ -0,0 +1,8 @@ +add($path); + } + } + + /** + * @throws FileNotExistException + */ + public function add(string $path): void { + $this->paths[] = new ValidFilePath($path); + } + + /** + * @return ValidFilePath[] + */ + public function getEnvPaths(): array { + return $this->paths; + } +} diff --git a/core/src/Module/Config/Env/DotEnvPathsContainerInterface.php b/core/src/Module/Config/Env/DotEnvPathsContainerInterface.php new file mode 100644 index 0000000..e9f178f --- /dev/null +++ b/core/src/Module/Config/Env/DotEnvPathsContainerInterface.php @@ -0,0 +1,12 @@ +[] */ + public function getProviders(): array; +} diff --git a/core/src/Module/Config/ProviderInterface.php b/core/src/Module/Config/ProviderInterface.php new file mode 100644 index 0000000..918b305 --- /dev/null +++ b/core/src/Module/Config/ProviderInterface.php @@ -0,0 +1,23 @@ +[] + */ + public function getCommands(): array; + + /** + * @return array + */ + public function getResolverDefinitions(): array; +} diff --git a/core/src/Module/Config/ResolverInterface.php b/core/src/Module/Config/ResolverInterface.php new file mode 100644 index 0000000..9b0ee55 --- /dev/null +++ b/core/src/Module/Config/ResolverInterface.php @@ -0,0 +1,10 @@ + + */ + public function __invoke(): array; +} diff --git a/core/src/Module/FileSystem/Exception/FileNotExistException.php b/core/src/Module/FileSystem/Exception/FileNotExistException.php new file mode 100644 index 0000000..41bf7cb --- /dev/null +++ b/core/src/Module/FileSystem/Exception/FileNotExistException.php @@ -0,0 +1,11 @@ +validateOrFail(); + } + + /** + * @throws FileNotExistException + */ + protected function validateOrFail(): void { + if (!file_exists($this->value)) { + throw new FileNotExistException($this->value); + } + } +} diff --git a/core/src/Module/Kernel/ConsoleKernel.php b/core/src/Module/Kernel/ConsoleKernel.php new file mode 100644 index 0000000..b64800d --- /dev/null +++ b/core/src/Module/Kernel/ConsoleKernel.php @@ -0,0 +1,35 @@ +commands as $command) { + $commandName = $command::getDefaultName(); + + if (is_string($commandName)) { + $commands[$commandName] = function () use ($command): Command { + return new $command(); + }; + } + } + + $commandLoader = new FactoryCommandLoader($commands); + + $application = new Application(); + $application->setCommandLoader($commandLoader); + + return $application->run(); + } +} diff --git a/core/src/Module/Kernel/HttpKernel.php b/core/src/Module/Kernel/HttpKernel.php new file mode 100644 index 0000000..0df2f19 --- /dev/null +++ b/core/src/Module/Kernel/HttpKernel.php @@ -0,0 +1,29 @@ +request = $request; + + $matcher = new UrlMatcher($this->routes, $this->context); + $parameters = $matcher->match($this->request->getPathInfo()); + /** @var class-string $controllerActionClass */ + $controllerActionClass = $parameters["_controller"]; + + /** @var ControllerActionInterface $controllerAction */ + $controllerAction = $this->diContainer->get($controllerActionClass); + + $response = $controllerAction($this->request); + $response->prepare($this->request); + + return $response; + } +} diff --git a/core/src/Module/Kernel/Kernel.php b/core/src/Module/Kernel/Kernel.php new file mode 100644 index 0000000..d178d03 --- /dev/null +++ b/core/src/Module/Kernel/Kernel.php @@ -0,0 +1,112 @@ +[] */ + protected array $commands = []; + + protected RouteCollection $routes; + protected UrlGenerator $urlGenerator; + + protected RequestContext $context; + + public function __construct( + protected ?DotEnvPathsContainerInterface $dotEnvs = null, + protected ?ModulesToLoadInterface $modulesToLoad = null, + ) { + $this->loadEnv(); + $this->loadModules(); + $this->loadDI(); + $this->loadCommands(); + $this->loadRouting(); + } + + protected function loadEnv(): void { + if ($this->dotEnvs === null) { + return; + } + + $dotenv = new Dotenv(); + $dotEnvPaths = $this->dotEnvs->getEnvPaths(); + + foreach ($dotEnvPaths as $path) { + $dotenv->load($path->value); + + unset($path); + } + } + + protected function loadModules(): void { + if ($this->modulesToLoad === null) { + return; + } + + foreach ($this->modulesToLoad->getProviders() as $module) { + $this->modules[] = new $module(); + + unset($module); + } + } + + /** + * @throws Exception + */ + protected function loadDI(): void { + $diBuilder = new ContainerBuilder(); + + foreach ($this->modules as $module) { + $definitions = $module->getResolverDefinitions(); + $diBuilder->addDefinitions($definitions); + + unset($definitions); + } + + $this->diContainer = $diBuilder->build(); + } + + protected function loadCommands(): void { + foreach ($this->modules as $module) { + $commands = $module->getCommands(); + $this->commands += $commands; + + unset($commands); + } + } + + protected function loadRouting(): void { + $this->routes = new RouteCollection(); + + foreach ($this->modules as $module) { + $routes = $module->getRoutes(); + + foreach ($routes as $route) { + $symfonyRoute = new Route($route->uri, [ + "_controller" => $route->controllerAction, + ]); + $this->routes->add($route->name, $symfonyRoute); + } + } + + $this->context = new RequestContext(); + $this->urlGenerator = new UrlGenerator($this->routes, $this->context); + } +} diff --git a/core/src/Module/Pdo/PdoWrapper.php b/core/src/Module/Pdo/PdoWrapper.php index 2542c4d..22ed1b9 100644 --- a/core/src/Module/Pdo/PdoWrapper.php +++ b/core/src/Module/Pdo/PdoWrapper.php @@ -18,14 +18,18 @@ public function __construct(protected readonly PDO $pdoConnection) { } /** - * @param string $sql + * @param string $sql * @param array $params * * @return object|null * @throws PdoConnectionNotReadyException */ - public function findOne(string $sql, array $params): ?object { - $query = $this->execute($sql, $params); + public function findOne( + string $sql, + array $params, + string $returnClass = null, + ): ?object { + $query = $this->execute($sql, $params, $returnClass); $item = $query->fetch(); if ($item === false || !is_object($item)) { @@ -36,16 +40,28 @@ public function findOne(string $sql, array $params): ?object { } /** - * @param string $sql + * @param string $sql * @param array $params * * @return array * @throws PdoConnectionNotReadyException * @throws PdoFetchAllException */ - public function findAll(string $sql, array $params): array { - $query = $this->execute($sql, $params); - $items = $query->fetchAll(); + public function findAll( + string $sql, + array $params, + string $returnClass = null, + ): array { + $query = $this->execute($sql, $params, $returnClass); + + if ($returnClass) { + $items = $query->fetchAll( + PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, + $returnClass, + ); + } else { + $items = $query->fetchAll(); + } if (empty($items)) { throw new PdoFetchAllException(); @@ -55,7 +71,7 @@ public function findAll(string $sql, array $params): array { } /** - * @param string $sql + * @param string $sql * @param array $params * * @throws PdoConnectionNotReadyException @@ -67,7 +83,7 @@ public function insert(string $sql, array $params): int { } /** - * @param string $sql + * @param string $sql * @param array $params * * @return bool @@ -99,15 +115,26 @@ public function rollbackTransaction(): void { } /** - * @param string $sql + * @param string $sql * @param array $params * * @return PDOStatement * @throws PdoConnectionNotReadyException */ - public function execute(string $sql, array $params): PDOStatement { + public function execute( + string $sql, + array $params, + string $returnClass = null, + ): PDOStatement { $query = $this->pdoConnection->prepare($sql); + if ($returnClass) { + $query->setFetchMode( + PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, + $returnClass, + ); + } + if (empty($query)) { throw new PdoConnectionNotReadyException(); } diff --git a/core/src/Module/Router/ControllerActionInterface.php b/core/src/Module/Router/ControllerActionInterface.php new file mode 100644 index 0000000..292d5d3 --- /dev/null +++ b/core/src/Module/Router/ControllerActionInterface.php @@ -0,0 +1,10 @@ + */ + protected string $genericClass, ) { } @@ -56,7 +58,11 @@ public function get(Query $query): mixed { default => throw new QueryNotSupportedException(), }; - $item = $this->pdo->findOne($sql->sql(), $sql->params()); + $item = $this->pdo->findOne( + $sql->sql(), + $sql->params(), + $this->genericClass, + ); if ($item === null) { throw new DataNotFoundException(); @@ -88,7 +94,11 @@ public function getAll(Query $query): array { default => throw new QueryNotSupportedException(), }; - $items = $this->pdo->findAll($sql->sql(), $sql->params()); + $items = $this->pdo->findAll( + $sql->sql(), + $sql->params(), + $this->genericClass, + ); if (empty($items)) { throw new DataNotFoundException(); @@ -100,7 +110,7 @@ public function getAll(Query $query): array { /** * @psalm-suppress LessSpecificImplementedReturnType * - * @param Query $query + * @param Query $query * @param mixed|null $entity * * @return mixed @@ -132,7 +142,7 @@ public function put(Query $query, mixed $entity = null): mixed { } /** - * @param Query $query + * @param Query $query * @param mixed|null $entity * * @return mixed @@ -153,7 +163,7 @@ public function getId(Query $query, mixed $entity = null): mixed { /** * @psalm-suppress LessSpecificImplementedReturnType * - * @param Query $query + * @param Query $query * @param object[]|null $entities * * @return mixed[] diff --git a/core/src/Repository/DataSource/Sql/DataSource/SqlInterface.php b/core/src/Repository/DataSource/Sql/DataSource/SqlInterface.php index 854cdfb..fb28c48 100644 --- a/core/src/Repository/DataSource/Sql/DataSource/SqlInterface.php +++ b/core/src/Repository/DataSource/Sql/DataSource/SqlInterface.php @@ -4,23 +4,31 @@ interface SqlInterface { /** - * @param string $sql + * @param string $sql * @param array $params * * @return object|null */ - public function findOne(string $sql, array $params): ?object; + public function findOne( + string $sql, + array $params, + string $returnClass, + ): ?object; /** - * @param string $sql + * @param string $sql * @param array $params * * @return object[] */ - public function findAll(string $sql, array $params): array; + public function findAll( + string $sql, + array $params, + string $returnClass, + ): array; /** - * @param string $sql + * @param string $sql * @param array $params * * @return int|string @@ -28,7 +36,7 @@ public function findAll(string $sql, array $params): array; public function insert(string $sql, array $params): int|string; /** - * @param string $sql + * @param string $sql * @param array $params * * @return bool @@ -42,10 +50,14 @@ public function endTransaction(): void; public function rollbackTransaction(): void; /** - * @param string $sql + * @param string $sql * @param array $params * * @return mixed */ - public function execute(string $sql, array $params): mixed; + public function execute( + string $sql, + array $params, + string $returnClass, + ): mixed; } diff --git a/core/src/Repository/DataSource/Sql/Helper/SqlSchema.php b/core/src/Repository/DataSource/Sql/Helper/SqlSchema.php index 4cc9f8c..2c1459c 100644 --- a/core/src/Repository/DataSource/Sql/Helper/SqlSchema.php +++ b/core/src/Repository/DataSource/Sql/Helper/SqlSchema.php @@ -10,4 +10,6 @@ public function getIdColumn(): string; public function getKeyColumn(): string; public function softDeleteEnabled(): bool; + + public function getReturnClass(): ?string; } diff --git a/core/src/Repository/DataSource/Sql/Helper/VoidSqlSchema.php b/core/src/Repository/DataSource/Sql/Helper/VoidSqlSchema.php index 972fff9..d79e279 100644 --- a/core/src/Repository/DataSource/Sql/Helper/VoidSqlSchema.php +++ b/core/src/Repository/DataSource/Sql/Helper/VoidSqlSchema.php @@ -2,7 +2,7 @@ namespace Harmony\Core\Repository\DataSource\Sql\Helper; -use Harmony\Core\Shared\Error\MethodNotImplementedException; +use Harmony\Core\Domain\Exception\MethodNotImplementedException; class VoidSqlSchema implements SqlSchema { /** @@ -32,4 +32,11 @@ public function getKeyColumn(): string { public function softDeleteEnabled(): bool { throw new MethodNotImplementedException(); } + + /** + * @throws MethodNotImplementedException + */ + public function getReturnClass(): ?string { + throw new MethodNotImplementedException(); + } } diff --git a/core/src/Shared/Error/MethodNotImplementedException.php b/core/src/Shared/Error/MethodNotImplementedException.php deleted file mode 100644 index 5e7dc5e..0000000 --- a/core/src/Shared/Error/MethodNotImplementedException.php +++ /dev/null @@ -1,16 +0,0 @@ -isReceivedObjectLikeExpected($received, $expected)) { - throw new InvalidObjectException($expected, get_class($received)); - } - - return true; - } - - /** - * @param string $received - * @param string $expected - * - * @return bool - */ - protected function isReceivedClassLikeExpected( - string $received, - string $expected, - ): bool { - return is_subclass_of($received, $expected); - } - - /** - * @param string $received - * @param string $expected - * - * @return bool - */ - protected function isReceivedClassLikeExpectedOrFail( - string $received, - string $expected, - ): bool { - if (!$this->isReceivedClassLikeExpected($received, $expected)) { - throw new InvalidObjectException($expected, $received); - } - - return true; - } - - /** - * @param string $className - */ - protected function isClassOrFail(string $className): void { - if (!class_exists($className)) { - throw new InvalidArgumentException("Class not found: " . $className); - } - } -} diff --git a/sample/docker/docker-compose.yml b/sample/docker/docker-compose.yml index c8e93aa..d5ed2a5 100644 --- a/sample/docker/docker-compose.yml +++ b/sample/docker/docker-compose.yml @@ -17,6 +17,8 @@ services: platform: linux/x86_64 container_name: db-sample-docker restart: unless-stopped + volumes: + - ./mysql/init:/docker-entrypoint-initdb.d ports: - "3306:3306" environment: diff --git a/sample/docker/mysql/init/initdb.sql.gz b/sample/docker/mysql/init/initdb.sql.gz new file mode 100644 index 0000000..8b370f5 Binary files /dev/null and b/sample/docker/mysql/init/initdb.sql.gz differ diff --git a/sample/docker/nginx/local.conf b/sample/docker/nginx/local.conf index d3bd610..811458f 100644 --- a/sample/docker/nginx/local.conf +++ b/sample/docker/nginx/local.conf @@ -4,17 +4,56 @@ server { # server_name mydomain.local; root /var/www/html/public; - index index.php index.html index.htm; + location / { + # try to serve file directly, fallback to index.php + try_files $uri /index.php$is_args$args; + } - location ~ \.php$ { - try_files $uri =404; + # optionally disable falling back to PHP script for the asset directories; + # nginx will return a 404 error when files are not found instead of passing the + # request to Symfony (improves performance but Symfony's 404 page is not displayed) + # location /bundles { + # try_files $uri =404; + # } + + location ~ ^/index\.php(/|$) { fastcgi_pass php-sample:9000; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param SERVER_NAME $server_name; + fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; + + # optionally set the value of the environment variables used in the application + # fastcgi_param APP_ENV prod; + # fastcgi_param APP_SECRET ; + # fastcgi_param DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name"; + + # When you are using symlinks to link the document root to the + # current version of your application, you should pass the real + # application path instead of the path to the symlink to PHP + # FPM. + # Otherwise, PHP's OPcache may not properly detect changes to + # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126 + # for more information). + # Caveat: When PHP-FPM is hosted on a different machine from nginx + # $realpath_root may not resolve as you expect! In this case try using + # $document_root instead. + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + # Prevents URIs that include the front controller. This will 404: + # http://domain.tld/index.php/some-path + # Remove the internal directive to allow URIs like this + internal; + } + + # return 404 for all other php files not matching the front controller + # this prevents access to other php files you don't want to be accessible. + location ~ \.php$ { + return 404; } location ~* \.(htaccess|htpasswd|svn|git) { deny all; } + + error_log /var/log/nginx/project_error.log; + access_log /var/log/nginx/project_access.log; } diff --git a/sample/src/.env b/sample/src/.env new file mode 100644 index 0000000..ae255d7 --- /dev/null +++ b/sample/src/.env @@ -0,0 +1 @@ +APP_ENV=local diff --git a/sample/src/app/AppProvider.php b/sample/src/app/AppProvider.php new file mode 100644 index 0000000..111a523 --- /dev/null +++ b/sample/src/app/AppProvider.php @@ -0,0 +1,14 @@ +write("\nSome text output...\n"); + return Command::SUCCESS; + } +} diff --git a/sample/src/app/Product/Controller/ProductAction.php b/sample/src/app/Product/Controller/ProductAction.php new file mode 100644 index 0000000..216a373 --- /dev/null +++ b/sample/src/app/Product/Controller/ProductAction.php @@ -0,0 +1,137 @@ + $getProductInteractor + * @param GetAllInteractor $getAllProductInteractor + * @param PutInteractor $putProductInteractor + * @param PutAllInteractor $putAllProductInteractor + * @param DeleteInteractor $deleteProductInteractor + */ + public function __construct( + protected GetInteractor $getProductInteractor, + protected GetAllInteractor $getAllProductInteractor, + protected PutInteractor $putProductInteractor, + protected PutAllInteractor $putAllProductInteractor, + protected DeleteInteractor $deleteProductInteractor, + ) { + } + + public function __invoke(Request $request): Response { + $response = new Response($this->render(), Response::HTTP_OK, [ + "content-type" => "text/html", + ]); + + return $response; + } + + public function render(): ?string { + $productOne = new Product( + 1, + "PlayStation 5", + "VideoGames Console from Sony.", + 599.99, + ); + + $productTwo = new Product( + 2, + "XBox X", + "VideoGames Console from Microsoft", + 450.5, + ); + + $listProducts = [$productOne, $productTwo]; + + $variables = []; + + $variables["resultPutProductAction"] = $this->putProductAction($productOne); + $variables["resultGetProductAction"] = $this->getProductAction( + $productOne->id, + ); + $variables["resultPutAllProductAction"] = $this->putAllProductsAction( + $listProducts, + ); + $variables["resultGetAllProductAction"] = $this->getAllProductsAction(); + $variables["resultDeleteProductAction"] = $this->deleteProductAction( + $productOne->id, + ); + + return $this->renderView($variables, "index"); + } + + protected function putProductAction(Product $product): Product { + $query = new KeyQuery((string) $product->id); + + return ($this->putProductInteractor)($product, $query); + } + + protected function getProductAction(int $id_product): Product { + $query = new KeyQuery((string) $id_product); + $product = ($this->getProductInteractor)($query); + + return $product; + } + + /** + * @param Product[] $products + * + * @return Product[] + */ + protected function putAllProductsAction(array $products): array { + $query = new AllQuery(); + $result = ($this->putAllProductInteractor)($products, $query); + + return $result; + } + + /** + * @return Product[] + */ + protected function getAllProductsAction(): array { + $query = new AllQuery(); + $products = ($this->getAllProductInteractor)($query); + + return $products; + } + + protected function deleteProductAction(int $id_product): string { + $query = new KeyQuery((string) $id_product); + ($this->deleteProductInteractor)($query); + + return "deleted"; + } + + /** + * @param array $variables + * @param string $template + * + * @return string|null + */ + protected function renderView(array $variables, string $template): ?string { + $template_path = __DIR__ . "/../views/" . $template . ".template.view"; + + if (file_exists($template_path)) { + ob_start(); + extract($variables, EXTR_OVERWRITE); + include $template_path; + $result = ob_get_clean(); + } + + return isset($result) && is_string($result) ? $result : null; + } +} diff --git a/sample/src/app/Product/ProductProvider.php b/sample/src/app/Product/ProductProvider.php index ac8bbed..0c4d4a9 100644 --- a/sample/src/app/Product/ProductProvider.php +++ b/sample/src/app/Product/ProductProvider.php @@ -7,17 +7,19 @@ use Harmony\Core\Domain\Interactor\GetInteractor; use Harmony\Core\Domain\Interactor\PutAllInteractor; use Harmony\Core\Domain\Interactor\PutInteractor; +use Harmony\Core\Module\Config\ProviderInterface; use Harmony\Core\Repository\DataSource\DeleteDataSource; use Harmony\Core\Repository\DataSource\GetDataSource; use Harmony\Core\Repository\DataSource\PutDataSource; use Harmony\Core\Repository\RepositoryMapper; use Harmony\Core\Repository\SingleDataSourceRepository; +use Sample\Product\Command\TestCommand; use Sample\Product\Data\Entity\ProductEntity; use Sample\Product\Data\Mapper\ProductEntityToProductMapper; use Sample\Product\Data\Mapper\ProductToProductEntityMapper; use Sample\Product\Domain\Model\Product; -class ProductProvider { +class ProductProvider implements ProviderInterface { /** @var RepositoryMapper */ protected RepositoryMapper $productRepository; @@ -92,4 +94,25 @@ public function providePutAllInteractor(): PutAllInteractor { public function provideDeleteInteractor(): DeleteInteractor { return new DeleteInteractor($this->productRepository); } + + /** + * @inheritDoc + */ + public function getRoutes(): array { + return (new ProductRoutes())(); + } + + /** + * @inheritDoc + */ + public function getCommands(): array { + return [TestCommand::class]; + } + + /** + * @inheritDoc + */ + public function getResolverDefinitions(): array { + return (new ProductResolver())(); + } } diff --git a/sample/src/app/Product/ProductResolver.php b/sample/src/app/Product/ProductResolver.php new file mode 100644 index 0000000..81e51fa --- /dev/null +++ b/sample/src/app/Product/ProductResolver.php @@ -0,0 +1,106 @@ +"; + protected const KEY_PRODUCT_GET = "GetInteractor"; + protected const KEY_PRODUCT_GET_ALL = "GetAllInteractor"; + protected const KEY_PRODUCT_PUT = "PutInteractor"; + protected const KEY_PRODUCT_PUT_ALL = "PutAllInteractor"; + protected const KEY_PRODUCT_DELETE = "DeleteInteractor"; + + /** + * @inheritDoc + */ + public function __invoke(): array { + return [ + self::KEY_PRODUCT_REPOSITORY => factory([ + self::class, + "factoryRepositoryInMemory", + ]), + self::KEY_PRODUCT_GET => function (ContainerInterface $di) { + /** @var RepositoryMapper $repository */ + $repository = $di->get(self::KEY_PRODUCT_REPOSITORY); + return new GetInteractor($repository); + }, + self::KEY_PRODUCT_GET_ALL => function (ContainerInterface $di) { + /** @var RepositoryMapper $repository */ + $repository = $di->get(self::KEY_PRODUCT_REPOSITORY); + return new GetAllInteractor($repository); + }, + self::KEY_PRODUCT_PUT => function (ContainerInterface $di) { + /** @var RepositoryMapper $repository */ + $repository = $di->get(self::KEY_PRODUCT_REPOSITORY); + return new PutInteractor($repository); + }, + self::KEY_PRODUCT_PUT_ALL => function (ContainerInterface $di) { + /** @var RepositoryMapper $repository */ + $repository = $di->get(self::KEY_PRODUCT_REPOSITORY); + return new PutAllInteractor($repository); + }, + self::KEY_PRODUCT_DELETE => function (ContainerInterface $di) { + /** @var RepositoryMapper $repository */ + $repository = $di->get(self::KEY_PRODUCT_REPOSITORY); + return new DeleteInteractor($repository); + }, + ProductAction::class => function (ContainerInterface $di) { + /** @var GetInteractor $getInteractor */ + $getInteractor = $di->get(self::KEY_PRODUCT_GET); + /** @var GetAllInteractor $getAllInteractor */ + $getAllInteractor = $di->get(self::KEY_PRODUCT_GET_ALL); + /** @var PutInteractor $putInteractor */ + $putInteractor = $di->get(self::KEY_PRODUCT_PUT); + /** @var PutAllInteractor $putAllInteractor */ + $putAllInteractor = $di->get(self::KEY_PRODUCT_PUT_ALL); + /** @var DeleteInteractor $deleteInteractor */ + $deleteInteractor = $di->get(self::KEY_PRODUCT_DELETE); + + return new ProductAction( + $getInteractor, + $getAllInteractor, + $putInteractor, + $putAllInteractor, + $deleteInteractor, + ); + }, + ]; + } + + public function factoryRepositoryInMemory(): RepositoryMapper { + $productInMemoryDataSource = new InMemoryDataSource(ProductEntity::class); + + $productRepository = new SingleDataSourceRepository( + $productInMemoryDataSource, + $productInMemoryDataSource, + $productInMemoryDataSource, + ); + + $productRepositoryMapper = new RepositoryMapper( + $productRepository, + $productRepository, + $productRepository, + new ProductToProductEntityMapper(), + new ProductEntityToProductMapper(), + ); + + return $productRepositoryMapper; + } +} diff --git a/sample/src/app/Product/ProductRoutes.php b/sample/src/app/Product/ProductRoutes.php new file mode 100644 index 0000000..4e72bbb --- /dev/null +++ b/sample/src/app/Product/ProductRoutes.php @@ -0,0 +1,16 @@ + function () { + return new QueryFactory(new MySqlEngine()); + }, + PdoWrapper::class => function () { + return (new PdoFactory())( + (string) getenv("MYSQL_USER"), + (string) getenv("MYSQL_PASSWORD"), + (string) getenv("MYSQL_HOST"), + (string) getenv("MYSQL_DATABASE"), + ); + }, + ]; + } +} diff --git a/sample/src/app/User/Api/UserAction.php b/sample/src/app/User/Api/UserAction.php new file mode 100644 index 0000000..f183bb1 --- /dev/null +++ b/sample/src/app/User/Api/UserAction.php @@ -0,0 +1,23 @@ +getAllInteractor)(0, 5, "messi"); + $response = new JsonResponse($users, Response::HTTP_OK); + + return $response; + } +} diff --git a/sample/src/app/User/Data/DataSource/Sql/Mapper/UserEntityToSqlDataMapper.php b/sample/src/app/User/Data/DataSource/Sql/Mapper/UserEntityToSqlDataMapper.php new file mode 100644 index 0000000..87959b5 --- /dev/null +++ b/sample/src/app/User/Data/DataSource/Sql/Mapper/UserEntityToSqlDataMapper.php @@ -0,0 +1,20 @@ +id, $from->name, $from->email); + + return $to; + } +} diff --git a/sample/src/app/User/Data/DataSource/Sql/Mapper/UserSqlDataToEntityMapper.php b/sample/src/app/User/Data/DataSource/Sql/Mapper/UserSqlDataToEntityMapper.php new file mode 100644 index 0000000..355ba38 --- /dev/null +++ b/sample/src/app/User/Data/DataSource/Sql/Mapper/UserSqlDataToEntityMapper.php @@ -0,0 +1,18 @@ +id, $from->name, $from->email); + } +} diff --git a/sample/src/app/User/Data/DataSource/Sql/UserSqlData.php b/sample/src/app/User/Data/DataSource/Sql/UserSqlData.php new file mode 100644 index 0000000..16fb51b --- /dev/null +++ b/sample/src/app/User/Data/DataSource/Sql/UserSqlData.php @@ -0,0 +1,15 @@ +id, $from->name, $from->email); + } +} diff --git a/sample/src/app/User/Data/Mapper/UserModelToEntityMapper.php b/sample/src/app/User/Data/Mapper/UserModelToEntityMapper.php new file mode 100644 index 0000000..37a133b --- /dev/null +++ b/sample/src/app/User/Data/Mapper/UserModelToEntityMapper.php @@ -0,0 +1,18 @@ +id, $from->name, $from->email); + } +} diff --git a/sample/src/app/User/Data/Query/UserPaginationSqlQuery.php b/sample/src/app/User/Data/Query/UserPaginationSqlQuery.php new file mode 100644 index 0000000..354a8e1 --- /dev/null +++ b/sample/src/app/User/Data/Query/UserPaginationSqlQuery.php @@ -0,0 +1,40 @@ +offset; + } + + public function limit(): int { + return $this->limit; + } + + /** + * @return array + */ + public function where(): array { + return [ + UserSqlSchema::COLUMN_NAME => $this->userName, + ]; + } + + public function orderBy(): string { + return UserSqlSchema::COLUMN_NAME; + } + + public function ascending(): bool { + return false; + } +} diff --git a/sample/src/app/User/Domain/Interactor/GetAllUsersByNameInteractor.php b/sample/src/app/User/Domain/Interactor/GetAllUsersByNameInteractor.php new file mode 100644 index 0000000..3a4693e --- /dev/null +++ b/sample/src/app/User/Domain/Interactor/GetAllUsersByNameInteractor.php @@ -0,0 +1,29 @@ + $getAllInteractor + */ + public function __construct(protected GetAllInteractor $getAllInteractor) { + } + + /** + * @param int $offset + * @param int $limit + * @param string $userName + * + * @return User[] + */ + public function __invoke(int $offset, int $limit, string $userName): array { + $query = new UserPaginationSqlQuery($offset, $limit, $userName); + $items = ($this->getAllInteractor)($query); + + return $items; + } +} diff --git a/sample/src/app/User/Domain/Model/User.php b/sample/src/app/User/Domain/Model/User.php new file mode 100644 index 0000000..42572f4 --- /dev/null +++ b/sample/src/app/User/Domain/Model/User.php @@ -0,0 +1,15 @@ +"; + protected const KEY_USER_GET_ALL = "GetAllInteractor"; + + public function __invoke(): array { + return [ + self::KEY_USER_REPOSITORY => function (ContainerInterface $di) { + $pdo = $di->get(PdoWrapper::class); + $queryFactory = $di->get(QueryFactory::class); + + $sqlBuilder = new SqlBuilder(new UserSqlSchema(), $queryFactory); + $dataSource = new RawSqlDataSource( + $pdo, + $sqlBuilder, + (new UserSqlSchema())->getReturnClass(), + ); + + $dataSourceMapper = new DataSourceMapper( + $dataSource, + $dataSource, + $dataSource, + new UserEntityToSqlDataMapper(), + new UserSqlDataToEntityMapper(), + ); + + $productRepository = new SingleDataSourceRepository( + $dataSourceMapper, + $dataSourceMapper, + $dataSourceMapper, + ); + + $productRepositoryMapper = new RepositoryMapper( + $productRepository, + $productRepository, + $productRepository, + new UserModelToEntityMapper(), + new UserEntityToModelMapper(), + ); + + return $productRepositoryMapper; + }, + self::KEY_USER_GET_ALL => function (ContainerInterface $di) { + return new GetAllInteractor($di->get(self::KEY_USER_REPOSITORY)); + }, + GetAllUsersByNameInteractor::class => function (ContainerInterface $di) { + return new GetAllUsersByNameInteractor( + $di->get(self::KEY_USER_GET_ALL), + ); + }, + ]; + } +} diff --git a/sample/src/app/User/UserRoutes.php b/sample/src/app/User/UserRoutes.php new file mode 100644 index 0000000..7932e49 --- /dev/null +++ b/sample/src/app/User/UserRoutes.php @@ -0,0 +1,13 @@ +handleCommand(); + diff --git a/sample/src/public/index.php b/sample/src/public/index.php index 4acf638..841158a 100644 --- a/sample/src/public/index.php +++ b/sample/src/public/index.php @@ -1,21 +1,23 @@ provideGetInteractor(), - $productProvider->provideGetAllInteractor(), - $productProvider->providePutInteractor(), - $productProvider->providePutAllInteractor(), - $productProvider->provideDeleteInteractor(), -); +try { + $dotEnvPaths = new DotEnvPathsContainer([__DIR__ . "/../.env"]); + $kernel = new HttpKernel($dotEnvPaths, new AppProvider()); -echo $controllerAction(); + $request = Request::createFromGlobals(); + $response = $kernel->handleRequest($request); + $response->send(); +} catch (Exception $error) { + if (function_exists("dump")) { + dump($error->getMessage(), $error); + } else { + var_dump($error->getMessage(), $error); + } +} diff --git a/sample/src/public/index_no_kernel.php b/sample/src/public/index_no_kernel.php new file mode 100644 index 0000000..4acf638 --- /dev/null +++ b/sample/src/public/index_no_kernel.php @@ -0,0 +1,21 @@ +provideGetInteractor(), + $productProvider->provideGetAllInteractor(), + $productProvider->providePutInteractor(), + $productProvider->providePutAllInteractor(), + $productProvider->provideDeleteInteractor(), +); + +echo $controllerAction();