From 5a2354b877fe09e615dabba92d5a5dff74048b05 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:04:04 -0500 Subject: [PATCH 01/13] [#.x] - added user endpoint in routes and fixed tests --- src/Domain/Services/Providers/RouterProvider.php | 13 ++++++++++++- src/Domain/Services/Providers/RoutesEnum.php | 5 ++++- .../Services/Providers/RouterProviderTest.php | 6 +++++- .../Domain/Services/Providers/RoutesEnumTest.php | 11 +++++++++-- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/Domain/Services/Providers/RouterProvider.php b/src/Domain/Services/Providers/RouterProvider.php index ee17143..fe13b29 100644 --- a/src/Domain/Services/Providers/RouterProvider.php +++ b/src/Domain/Services/Providers/RouterProvider.php @@ -18,9 +18,11 @@ use Phalcon\Api\Domain\ADR\Responder\ResponderInterface; use Phalcon\Api\Domain\Interfaces\RoutesInterface; use Phalcon\Api\Domain\Services\Container; +use Phalcon\Api\Domain\Services\Http\Response; use Phalcon\Di\DiInterface; use Phalcon\Di\ServiceProviderInterface; use Phalcon\Events\Manager as EventsManager; +use Phalcon\Http\Request; use Phalcon\Mvc\Micro; use Phalcon\Mvc\Micro\Collection; @@ -75,6 +77,10 @@ private function attachMiddleware( */ private function attachRoutes(Micro $application): void { + /** @var Request $request */ + $request = $application->getService(Container::REQUEST); + /** @var Response $response */ + $response = $application->getService(Container::RESPONSE); /** @var ResponderInterface $responder */ $responder = $application->getService(Container::RESPONDER_JSON); @@ -84,7 +90,12 @@ private function attachRoutes(Micro $application): void $collection = new Collection(); /** @var DomainInterface $service */ $service = $application->getService($serviceName); - $action = new ActionHandler($service, $responder); + $action = new ActionHandler( + $request, + $response, + $service, + $responder + ); $collection ->setHandler($action) diff --git a/src/Domain/Services/Providers/RoutesEnum.php b/src/Domain/Services/Providers/RoutesEnum.php index adad412..3dc6b1c 100644 --- a/src/Domain/Services/Providers/RoutesEnum.php +++ b/src/Domain/Services/Providers/RoutesEnum.php @@ -24,6 +24,7 @@ enum RoutesEnum: string implements RoutesInterface { case helloGet = ''; + case userGet = 'user'; /** * @return string @@ -39,7 +40,8 @@ public function endpoint(): string public function method(): string { return match ($this) { - self::helloGet => self::GET, + self::helloGet, + self::userGet => self::GET, }; } @@ -67,6 +69,7 @@ public function service(): string { return match ($this) { self::helloGet => Container::HELLO_SERVICE, + self::userGet => Container::USER_GET_SERVICE, }; } diff --git a/tests/Unit/Domain/Services/Providers/RouterProviderTest.php b/tests/Unit/Domain/Services/Providers/RouterProviderTest.php index c6952c1..77810d6 100644 --- a/tests/Unit/Domain/Services/Providers/RouterProviderTest.php +++ b/tests/Unit/Domain/Services/Providers/RouterProviderTest.php @@ -15,7 +15,7 @@ use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Providers\RouterProvider; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; final class RouterProviderTest extends AbstractUnitTestCase @@ -37,6 +37,10 @@ public function testCheckRegistration(): void 'method' => 'GET', 'pattern' => '/', ], + [ + 'method' => 'GET', + 'pattern' => '/user', + ], [ 'method' => 'GET', 'pattern' => '/health', diff --git a/tests/Unit/Domain/Services/Providers/RoutesEnumTest.php b/tests/Unit/Domain/Services/Providers/RoutesEnumTest.php index e7075dd..43c35fa 100644 --- a/tests/Unit/Domain/Services/Providers/RoutesEnumTest.php +++ b/tests/Unit/Domain/Services/Providers/RoutesEnumTest.php @@ -15,7 +15,7 @@ use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Providers\RoutesEnum; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\DataProvider; final class RoutesEnumTest extends AbstractUnitTestCase @@ -30,12 +30,19 @@ public static function getExamples(): array RoutesEnum::GET, Container::HELLO_SERVICE, ], + [ + RoutesEnum::userGet, + 'user', + '/user', + RoutesEnum::GET, + Container::USER_GET_SERVICE, + ], ]; } public function testCheckCount(): void { - $expected = 1; + $expected = 2; $actual = RoutesEnum::cases(); $this->assertCount($expected, $actual); } From 3fae00fa8a870a468894009b0014e285c607c700 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:04:31 -0500 Subject: [PATCH 02/13] [#.x] - new input class --- src/Domain/ADR/Domain/Input.php | 35 ++++++++++++++++++++++++ src/Domain/ADR/Domain/InputInterface.php | 29 ++++++++++++++++++++ src/Domain/ADR/Domain/InputTypes.php | 25 +++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/Domain/ADR/Domain/Input.php create mode 100644 src/Domain/ADR/Domain/InputInterface.php create mode 100644 src/Domain/ADR/Domain/InputTypes.php diff --git a/src/Domain/ADR/Domain/Input.php b/src/Domain/ADR/Domain/Input.php new file mode 100644 index 0000000..7d15456 --- /dev/null +++ b/src/Domain/ADR/Domain/Input.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\ADR\Domain; + +use Phalcon\Http\Request; + +/** + * @phpstan-import-type TRequestQuery from InputTypes + */ +final class Input implements InputInterface +{ + /** + * @param Request $request + * + * @return TRequestQuery + */ + public function __invoke(Request $request): array + { + /** @var TRequestQuery $query */ + $query = $request->getQuery(); + + return $query; + } +} diff --git a/src/Domain/ADR/Domain/InputInterface.php b/src/Domain/ADR/Domain/InputInterface.php new file mode 100644 index 0000000..d4a4b22 --- /dev/null +++ b/src/Domain/ADR/Domain/InputInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\ADR\Domain; + +use Phalcon\Http\Request; + +/** + * @phpstan-import-type TRequestQuery from InputTypes + */ +interface InputInterface +{ + /** + * @param Request $request + * + * @return TRequestQuery + */ + public function __invoke(Request $request): array; +} diff --git a/src/Domain/ADR/Domain/InputTypes.php b/src/Domain/ADR/Domain/InputTypes.php new file mode 100644 index 0000000..f85bfce --- /dev/null +++ b/src/Domain/ADR/Domain/InputTypes.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\ADR\Domain; + +/** + * @phpstan-type THelloInput array{} + * @phpstan-type TUserInput array{ + * userId?: int + * } + * @phpstan-type TRequestQuery array + */ +final class InputTypes +{ +} From 31c252e9e913733552553c3f47684816a17cef57 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:05:04 -0500 Subject: [PATCH 03/13] [#.x] - moved abstract test class --- tests/AbstractUnitTestCase.php | 125 ++++++++++++++++++++++++++++ tests/Unit/AbstractUnitTestCase.php | 41 --------- 2 files changed, 125 insertions(+), 41 deletions(-) create mode 100644 tests/AbstractUnitTestCase.php delete mode 100644 tests/Unit/AbstractUnitTestCase.php diff --git a/tests/AbstractUnitTestCase.php b/tests/AbstractUnitTestCase.php new file mode 100644 index 0000000..8bee0db --- /dev/null +++ b/tests/AbstractUnitTestCase.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests; + +use PDO; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\DataMapper\Pdo\Connection; +use PHPUnit\Framework\TestCase; + +use function uniqid; + +abstract class AbstractUnitTestCase extends TestCase +{ + protected ?Connection $connection = null; + + /** + * @param string $fileName + * @param string $stream + * + * @return void + */ + public function assertFileContentsContains(string $fileName, string $stream): void + { + $contents = file_get_contents($fileName); + $this->assertStringContainsString($stream, $contents); + } + + public function assertInDatabase(string $table, array $criteria = []): void + { + $records = $this->getFromDatabase($table, $criteria); + + $this->assertNotEmpty($records); + } + + public function assertNotInDatabase(string $table, array $criteria = []): void + { + $records = $this->getFromDatabase($table, $criteria); + + $this->assertEmpty($records); + } + + /** + * @param UsersMigration $migration + * @param array $fields + * + * @return array + */ + public function getNewUser(UsersMigration $migration, array $fields = []): array + { + $status = $fields['usr_status_flag'] ?? 1; + $username = $fields['usr_username'] ?? uniqid('name-'); + $password = $fields['usr_password'] ?? $this->getStrongPassword(); + + $migration->insert(null, $status, $username, $password); + + $dbUser = $this->getFromDatabase( + 'co_users', + [ + 'usr_username' => $username, + ] + ); + + return $dbUser[0]; + } + + /** + * Return a long series of strings to be used as a password + * + * @return string + */ + public function getStrongPassword(): string + { + return substr(base64_encode(random_bytes(512)), 0, 128); + } + + /** + * @param Connection $connection + * + * @return void + */ + public function setConnection(Connection $connection): void + { + $this->connection = $connection; + } + + /** + * @param string $table + * @param array $criteria + * + * @return array + */ + protected function getFromDatabase( + string $table, + array $criteria + ): array { + $sql = 'SELECT * FROM ' . $table . ' WHERE '; + $where = []; + foreach ($criteria as $key => $value) { + $val = $value; + if (true === is_string($value)) { + $val = '"' . $value . '"'; + } + + $where[] = $key . ' = ' . $val; + } + + $sql .= implode(' AND ', $where); + + $result = $this->connection?->query($sql); + $records = $result?->fetchAll(PDO::FETCH_ASSOC); + + return $records; + } +} diff --git a/tests/Unit/AbstractUnitTestCase.php b/tests/Unit/AbstractUnitTestCase.php deleted file mode 100644 index 4031277..0000000 --- a/tests/Unit/AbstractUnitTestCase.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace Phalcon\Api\Tests\Unit; - -use PHPUnit\Framework\TestCase; - -abstract class AbstractUnitTestCase extends TestCase -{ - /** - * @param string $fileName - * @param string $stream - * - * @return void - */ - public function assertFileContentsContains(string $fileName, string $stream): void - { - $contents = file_get_contents($fileName); - $this->assertStringContainsString($stream, $contents); - } - - /** - * Return a long series of strings to be used as a password - * - * @return string - */ - public function getStrongPassword(): string - { - return substr(base64_encode(random_bytes(512)), 0, 128); - } -} From 3035432ff69307e689a6b9c87ba686eaf42164aa Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:05:19 -0500 Subject: [PATCH 04/13] [#.x] - new test migration class --- .../Domain/Migrations/AbstractMigration.php | 43 +++++++++++++++++ .../Domain/Migrations/UsersMigration.php | 46 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 tests/Fixtures/Domain/Migrations/AbstractMigration.php create mode 100644 tests/Fixtures/Domain/Migrations/UsersMigration.php diff --git a/tests/Fixtures/Domain/Migrations/AbstractMigration.php b/tests/Fixtures/Domain/Migrations/AbstractMigration.php new file mode 100644 index 0000000..b4d39b7 --- /dev/null +++ b/tests/Fixtures/Domain/Migrations/AbstractMigration.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Fixtures\Domain\Migrations; + +use Phalcon\DataMapper\Pdo\Connection; + +abstract class AbstractMigration +{ + protected string $table = ''; + + public function __construct( + protected readonly Connection $connection + ) { + $this->clear(); + } + + /** + * @return int + */ + public function clear(): int + { + return $this->connection->exec('DELETE FROM ' . $this->table); + } + + /** + * @return string + */ + public function getTable(): string + { + return $this->table; + } +} diff --git a/tests/Fixtures/Domain/Migrations/UsersMigration.php b/tests/Fixtures/Domain/Migrations/UsersMigration.php new file mode 100644 index 0000000..41d0d29 --- /dev/null +++ b/tests/Fixtures/Domain/Migrations/UsersMigration.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Fixtures\Domain\Migrations; + +use PHPUnit\Framework\Assert; + +final class UsersMigration extends AbstractMigration +{ + protected string $table = 'co_users'; + + public function insert( + ?int $id = null, + int $status = 0, + ?string $username = null, + ?string $password = null, + ) { + $id = $id ?: 'null'; + $sql = <<table} ( + usr_id, usr_status_flag, usr_username, usr_password +) VALUES ( + $id, $status, '$username', '$password' +) +SQL; + + $result = $this->connection->exec($sql); + if (!$result) { + Assert::fail( + "Failed to insert id [#$id] into table [$this->table]" + ); + } + + return $result; + } +} From a91d1469517d8906312f0790d6abb3993a56aa90 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:05:38 -0500 Subject: [PATCH 05/13] [#.x] - added user transport class --- src/Domain/DataSource/User/UserTransport.php | 77 ++++++++++++++++++++ src/Domain/DataSource/User/UserTypes.php | 34 +++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/Domain/DataSource/User/UserTransport.php create mode 100644 src/Domain/DataSource/User/UserTypes.php diff --git a/src/Domain/DataSource/User/UserTransport.php b/src/Domain/DataSource/User/UserTransport.php new file mode 100644 index 0000000..21a9a93 --- /dev/null +++ b/src/Domain/DataSource/User/UserTransport.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\DataSource\User; + +use Phalcon\Api\Domain\Exceptions\InvalidConfigurationArgumentException; + +/** + * @method int getId() + * @method int getStatus() + * @method string getUsername() + * @method string getPassword() + * + * @phpstan-import-type TUserRecord from UserTypes + * @phpstan-import-type TUserTransport from UserTypes + */ +final class UserTransport +{ + /** @var TUserTransport */ + private array $store; + + /** + * @param TUserRecord $input + */ + public function __construct(array $input) + { + $this->store = [ + 'id' => (int)($input['usr_id'] ?? 0), + 'status' => (int)($input['usr_status_flag'] ?? 0), + 'username' => (string)($input['usr_username'] ?? ''), + 'password' => (string)($input['usr_password'] ?? ''), + ]; + } + + /** + * @param string $name + * @param array $arguments + * + * @return int|string + */ + public function __call(string $name, array $arguments): int | string + { + return match ($name) { + 'getId' => $this->store['id'], + 'getStatus' => $this->store['status'], + 'getUsername' => $this->store['username'], + 'getPassword' => $this->store['password'], + default => throw new InvalidConfigurationArgumentException( + 'The ' . $name . ' method is not supported. [' + . json_encode($arguments) . ']', + ), + }; + } + + public function isEmpty(): bool + { + return 0 === $this->store['id']; + } + + /** + * @return array + */ + public function toArray(): array + { + return [$this->store['id'] => $this->store]; + } +} diff --git a/src/Domain/DataSource/User/UserTypes.php b/src/Domain/DataSource/User/UserTypes.php new file mode 100644 index 0000000..a8997df --- /dev/null +++ b/src/Domain/DataSource/User/UserTypes.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\DataSource\User; + + +/** + * @phpstan-type TUserRecord array{}|array{ + * usr_id: int, + * usr_status_flag: int, + * usr_username: string, + * usr_password: string + * } + * + * @phpstan-type TUserTransport array{ + * id: int, + * status: int, + * username: string, + * password: string + * } + */ +final class UserTypes +{ +} From 127132e0394d34acce341167ef8a6ca68913d63d Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:06:05 -0500 Subject: [PATCH 06/13] [#.x] - registering services --- src/Domain/Services/Container.php | 121 +++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 35 deletions(-) diff --git a/src/Domain/Services/Container.php b/src/Domain/Services/Container.php index 91c1a29..3916969 100644 --- a/src/Domain/Services/Container.php +++ b/src/Domain/Services/Container.php @@ -14,13 +14,15 @@ namespace Phalcon\Api\Domain\Services; use Phalcon\Api\Domain\ADR\Responder\JsonResponder; -use Phalcon\Api\Domain\Health\HealthService; +use Phalcon\Api\Domain\DataSource\User\UserRepository; use Phalcon\Api\Domain\Hello\HelloService; use Phalcon\Api\Domain\Middleware\HealthMiddleware; use Phalcon\Api\Domain\Middleware\NotFoundMiddleware; use Phalcon\Api\Domain\Middleware\ResponseSenderMiddleware; use Phalcon\Api\Domain\Services\Env\EnvManager; use Phalcon\Api\Domain\Services\Http\Response; +use Phalcon\Api\Domain\User\UserGetService; +use Phalcon\DataMapper\Pdo\Connection; use Phalcon\Di\Di; use Phalcon\Di\Service; use Phalcon\Events\Manager as EventsManager; @@ -42,24 +44,10 @@ class Container extends Di public const EVENTS_MANAGER = 'eventsManager'; /** @var string */ public const FILTER = 'filter'; - /** - * Services - */ - public const HELLO_SERVICE = 'hello.service'; /** @var string */ public const LOGGER = 'logger'; - /** - * Middleware - */ - public const MIDDLEWARE_HEALTH = 'middleware.health'; - public const MIDDLEWARE_NOT_FOUND = 'middleware.not.found'; - public const MIDDLEWARE_RESPONSE_SENDER = 'middleware.response.sender'; /** @var string */ public const REQUEST = 'request'; - /** - * Responders - */ - public const RESPONDER_JSON = 'hello.responder.json'; /** @var string */ public const RESPONSE = 'response'; /** @var string */ @@ -67,28 +55,83 @@ class Container extends Di /** @var string */ public const TIME = 'time'; + /** + * Services + */ + public const HELLO_SERVICE = HelloService::class; + public const USER_GET_SERVICE = 'service.user.get'; + /** + * Middleware + */ + public const MIDDLEWARE_HEALTH = HealthMiddleware::class; + public const MIDDLEWARE_NOT_FOUND = NotFoundMiddleware::class; + public const MIDDLEWARE_RESPONSE_SENDER = ResponseSenderMiddleware::class; + /** + * Repositories + */ + public const REPOSITORY_USER = 'repository.user'; + /** + * Responders + */ + public const RESPONDER_JSON = JsonResponder::class; + public function __construct() { $this->services = [ + self::CONNECTION => $this->getServiceConnection(), self::EVENTS_MANAGER => $this->getServiceEventsManger(), self::FILTER => $this->getServiceFilter(), self::LOGGER => $this->getServiceLogger(), - self::REQUEST => $this->getServiceSimple(Request::class, true), - self::RESPONSE => $this->getServiceSimple(Response::class, true), + self::REQUEST => new Service(Request::class, true), + self::RESPONSE => new Service(Response::class, true), self::ROUTER => $this->getServiceRouter(), - self::HELLO_SERVICE => $this->getServiceSimple(HelloService::class), - - self::MIDDLEWARE_HEALTH => $this->getServiceSimple(HealthMiddleware::class), - self::MIDDLEWARE_NOT_FOUND => $this->getServiceSimple(NotFoundMiddleware::class), - self::MIDDLEWARE_RESPONSE_SENDER => $this->getServiceSimple(ResponseSenderMiddleware::class), - - self::RESPONDER_JSON => $this->getServiceResponderJson(), + self::USER_GET_SERVICE => $this->getServiceUserGet(), + self::REPOSITORY_USER => $this->getServiceRepositoryUser(), ]; parent::__construct(); } + /** + * @return Service + */ + private function getServiceConnection(): Service + { + return new Service( + function () { + /** @var string $dbName */ + $dbName = EnvManager::get('DB_NAME','phalcon'); + /** @var string $host */ + $host = EnvManager::get('DB_HOST','rest-db'); + /** @var string $password */ + $password = EnvManager::get('DB_PASS','secret'); + $port = (int)EnvManager::get('DB_PORT',3306); + /** @var string $username */ + $username = EnvManager::get('DB_USER','root'); + /** @var string $encoding */ + $encoding = EnvManager::get('DB_CHARSET','utf8'); + $queries = ['SET NAMES utf8mb4']; + $dsn = sprintf( + 'mysql:host=%s;port=%s;dbname=%s;charset=%s', + $host, + $port, + $dbName, + $encoding + ); + + return new Connection( + $dsn, + $username, + $password, + [], + $queries + ); + }, + true + ); + } + /** * @return Service */ @@ -141,15 +184,18 @@ function () use ($logName, $logFile) { ); } - private function getServiceResponderJson(): Service + /** + * @return Service + */ + private function getServiceRepositoryUser(): Service { return new Service( [ - 'className' => JsonResponder::class, + 'className' => UserRepository::class, 'arguments' => [ [ 'type' => 'service', - 'name' => self::RESPONSE, + 'name' => self::CONNECTION, ], ], ] @@ -175,15 +221,20 @@ private function getServiceRouter(): Service } /** - * @param string $className - * @param bool $isShared - * * @return Service */ - private function getServiceSimple( - string $className, - bool $isShared = false - ): Service { - return new Service($className, $isShared); + private function getServiceUserGet(): Service + { + return new Service( + [ + 'className' => UserGetService::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::REPOSITORY_USER, + ], + ], + ] + ); } } From 350a5a16cd8b47f1da8398d7ffd65e4a1273b087 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:06:25 -0500 Subject: [PATCH 07/13] [#.x] - refactoring responders --- src/Domain/ADR/Responder/JsonResponder.php | 31 ++++++++++++------- .../ADR/Responder/ResponderInterface.php | 6 +++- src/Domain/ADR/Responder/ResponderTypes.php | 24 ++++++++++++++ 3 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 src/Domain/ADR/Responder/ResponderTypes.php diff --git a/src/Domain/ADR/Responder/JsonResponder.php b/src/Domain/ADR/Responder/JsonResponder.php index 0e9cec1..3afbfeb 100644 --- a/src/Domain/ADR/Responder/JsonResponder.php +++ b/src/Domain/ADR/Responder/JsonResponder.php @@ -13,28 +13,35 @@ namespace Phalcon\Api\Domain\ADR\Responder; +use Exception as BaseException; use Phalcon\Api\Domain\Services\Http\Response; use Phalcon\Domain\Payload; +/** + * @phpstan-import-type TResult from ResponderTypes + */ final class JsonResponder implements ResponderInterface { - public function __construct( - private Response $response - ) { - } - - public function __invoke(Payload $payload): Response - { + /** + * @param Response $response + * @param Payload $payload + * + * @return Response + * @throws BaseException + */ + public function __invoke( + Response $response, + Payload $payload + ): Response { $result = $payload->getResult(); - /** @var string $content */ + /** @var TResult $content */ $content = $result['results']; - $this - ->response - ->withPayloadData([$content]) + $response + ->withPayloadData($content) ->render() ; - return $this->response; + return $response; } } diff --git a/src/Domain/ADR/Responder/ResponderInterface.php b/src/Domain/ADR/Responder/ResponderInterface.php index c96e0db..a458a05 100644 --- a/src/Domain/ADR/Responder/ResponderInterface.php +++ b/src/Domain/ADR/Responder/ResponderInterface.php @@ -13,10 +13,14 @@ namespace Phalcon\Api\Domain\ADR\Responder; +use Phalcon\Api\Domain\Services\Http\Response; use Phalcon\Domain\Payload; use Phalcon\Http\ResponseInterface; interface ResponderInterface { - public function __invoke(Payload $payload): ResponseInterface; + public function __invoke( + Response $response, + Payload $payload + ): ResponseInterface; } diff --git a/src/Domain/ADR/Responder/ResponderTypes.php b/src/Domain/ADR/Responder/ResponderTypes.php new file mode 100644 index 0000000..7a895ee --- /dev/null +++ b/src/Domain/ADR/Responder/ResponderTypes.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\ADR\Responder; + +/** + * @phpstan-type TResultItem array + * @phpstan-type TResult array{ + * result: array + * } + */ +final class ResponderTypes +{ +} From 265f5a3a4c3a4ae4720d04eabe62e480db4924c7 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:06:41 -0500 Subject: [PATCH 08/13] [#.x] - new user repository class --- src/Domain/DataSource/User/UserRepository.php | 50 +++++++++++++++++ .../DataSource/User/UserRepositoryTest.php | 56 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/Domain/DataSource/User/UserRepository.php create mode 100644 tests/Unit/Domain/DataSource/User/UserRepositoryTest.php diff --git a/src/Domain/DataSource/User/UserRepository.php b/src/Domain/DataSource/User/UserRepository.php new file mode 100644 index 0000000..dd4ef84 --- /dev/null +++ b/src/Domain/DataSource/User/UserRepository.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\DataSource\User; + +use Phalcon\DataMapper\Pdo\Connection; +use Phalcon\DataMapper\Query\Select; + +/** + * @phpstan-import-type TUserRecord from UserTypes + */ +final class UserRepository +{ + public function __construct( + private readonly Connection $connection, + ) { + } + + /** + * @param int|string $userId + * + * @return UserTransport + */ + public function findById(int | string $userId): UserTransport + { + $result = []; + if (true !== empty($userId)) { + $select = Select::new($this->connection); + + /** @var TUserRecord $result */ + $result = $select + ->from('co_users') + ->where('usr_id = ', $userId) + ->fetchOne() + ; + } + + return new UserTransport($result); + } +} diff --git a/tests/Unit/Domain/DataSource/User/UserRepositoryTest.php b/tests/Unit/Domain/DataSource/User/UserRepositoryTest.php new file mode 100644 index 0000000..16f4330 --- /dev/null +++ b/tests/Unit/Domain/DataSource/User/UserRepositoryTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Unit\Domain\DataSource\User; + +use Phalcon\Api\Domain\DataSource\User\UserRepository; +use Phalcon\Api\Domain\Services\Container; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\DataMapper\Pdo\Connection; + +final class UserRepositoryTest extends AbstractUnitTestCase +{ + public function testFind(): void + { + $container = new Container(); + /** @var Connection $connection */ + $connection = $container->getShared(Container::CONNECTION); + /** @var UserRepository $repository */ + $repository = $container->get(Container::REPOSITORY_USER); + + $migration = new UsersMigration($connection); + $this->setConnection($connection); + + $dbUser = $this->getNewUser($migration); + $userId = $dbUser['usr_id']; + + $user = $repository->findById($userId); + + $expected = $dbUser['usr_id']; + $actual = $user->getId(); + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_status_flag']; + $actual = $user->getStatus(); + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_username']; + $actual = $user->getUsername(); + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_password']; + $actual = $user->getPassword(); + $this->assertSame($expected, $actual); + } +} From fd89a7c42e4b25cf38ae2af84a45b310ffe08f46 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:07:07 -0500 Subject: [PATCH 09/13] [#.x] - refactored domain to accept input --- src/Domain/ADR/Action/ActionHandler.php | 11 ++++++++++- src/Domain/ADR/Domain/DomainInterface.php | 11 ++++++++++- src/Domain/Hello/HelloService.php | 15 +++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/Domain/ADR/Action/ActionHandler.php b/src/Domain/ADR/Action/ActionHandler.php index 2855f7d..29d604a 100644 --- a/src/Domain/ADR/Action/ActionHandler.php +++ b/src/Domain/ADR/Action/ActionHandler.php @@ -14,11 +14,16 @@ namespace Phalcon\Api\Domain\ADR\Action; use Phalcon\Api\Domain\ADR\Domain\DomainInterface; +use Phalcon\Api\Domain\ADR\Domain\Input; use Phalcon\Api\Domain\ADR\Responder\ResponderInterface; +use Phalcon\Api\Domain\Services\Http\Response; +use Phalcon\Http\Request; final readonly class ActionHandler implements ActionInterface { public function __construct( + private Request $request, + private Response $response, private DomainInterface $service, private ResponderInterface $responder ) { @@ -26,8 +31,12 @@ public function __construct( public function __invoke(): void { + $input = new Input(); + $data = $input->__invoke($this->request); + $this->responder->__invoke( - $this->service->__invoke() + $this->response, + $this->service->__invoke($data) ); } } diff --git a/src/Domain/ADR/Domain/DomainInterface.php b/src/Domain/ADR/Domain/DomainInterface.php index b8747ff..d5c6a20 100644 --- a/src/Domain/ADR/Domain/DomainInterface.php +++ b/src/Domain/ADR/Domain/DomainInterface.php @@ -15,7 +15,16 @@ use Phalcon\Domain\Payload; +/** + * @phpstan-import-type THelloInput from InputTypes + * @phpstan-import-type TUserInput from InputTypes + */ interface DomainInterface { - public function __invoke(): Payload; + /** + * @param THelloInput|TUserInput $input + * + * @return Payload + */ + public function __invoke(array $input): Payload; } diff --git a/src/Domain/Hello/HelloService.php b/src/Domain/Hello/HelloService.php index 505dae1..0102a77 100644 --- a/src/Domain/Hello/HelloService.php +++ b/src/Domain/Hello/HelloService.php @@ -15,19 +15,30 @@ use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\ADR\Domain\DomainInterface; +use Phalcon\Api\Domain\ADR\Domain\InputTypes; use Phalcon\Api\Domain\Constants\Dates; use Phalcon\Domain\Payload; use function date; +/** + * @phpstan-import-type THelloInput from InputTypes + */ final class HelloService implements DomainInterface { - public function __invoke(): Payload + /** + * @param THelloInput $input + * + * @return Payload + */ + public function __invoke(array $input): Payload { return new Payload( DomainStatus::SUCCESS, [ - 'results' => "Hello World!!! - " . date(Dates::DATE_TIME_FORMAT), + 'results' => [ + "Hello World!!! - " . date(Dates::DATE_TIME_FORMAT) + ], ] ); } From 73a9b7f3c4190b6115d6a63ba33186a4d35dc514 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:07:24 -0500 Subject: [PATCH 10/13] [#.x] - added user get service --- src/Domain/User/UserGetService.php | 71 ++++++++++ tests/Unit/Domain/User/UserServiceTest.php | 149 +++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 src/Domain/User/UserGetService.php create mode 100644 tests/Unit/Domain/User/UserServiceTest.php diff --git a/src/Domain/User/UserGetService.php b/src/Domain/User/UserGetService.php new file mode 100644 index 0000000..227b168 --- /dev/null +++ b/src/Domain/User/UserGetService.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\User; + +use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\ADR\Domain\DomainInterface; +use Phalcon\Api\Domain\ADR\Domain\InputTypes; +use Phalcon\Api\Domain\DataSource\User\UserRepository; +use Phalcon\Domain\Payload; + +use function abs; + +/** + * @phpstan-import-type TUserInput from InputTypes + */ +final readonly class UserGetService implements DomainInterface +{ + public function __construct( + private UserRepository $userRepository + ) { + } + + /** + * @param TUserInput $input + * + * @return Payload + */ + public function __invoke(array $input): Payload + { + $userId = abs((int)($input['userId'] ?? 0)); + + /** + * Success + */ + if ($userId > 0) { + $user = $this->userRepository->findById($userId); + + if (true !== $user->isEmpty()) { + return new Payload( + DomainStatus::SUCCESS, + [ + 'results' => $user->toArray(), + ] + ); + } + } + + /** + * 404 + */ + return new Payload( + DomainStatus::NOT_FOUND, + [ + 'results' => [ + 'Record(s) not found', + ], + ] + ); + } +} diff --git a/tests/Unit/Domain/User/UserServiceTest.php b/tests/Unit/Domain/User/UserServiceTest.php new file mode 100644 index 0000000..87c7c02 --- /dev/null +++ b/tests/Unit/Domain/User/UserServiceTest.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Unit\Domain\User; + +use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\Services\Container; +use Phalcon\Api\Domain\User\UserGetService; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\DataMapper\Pdo\Connection; +use PHPUnit\Framework\Attributes\BackupGlobals; + +#[BackupGlobals(true)] +final class UserServiceTest extends AbstractUnitTestCase +{ +// public function testDispatch(): void +// { +// $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); +// $_SERVER = [ +// 'REQUEST_METHOD' => 'GET', +// 'REQUEST_TIME_FLOAT' => $time, +// 'REQUEST_URI' => '/', +// ]; +// +// ob_start(); +// require_once EnvManager::appPath('public/index.php'); +// $response = ob_get_clean(); +// +// $contents = json_decode($response, true); +// +// restore_error_handler(); +// +// $this->assertArrayHasKey('data', $contents); +// $this->assertArrayHasKey('errors', $contents); +// +// $data = $contents['data']; +// $errors = $contents['errors']; +// +// $expected = []; +// $actual = $errors; +// $this->assertSame($expected, $actual); +// +// $expected = 'Hello World!!! - '; +// $actual = $data[0]; +// $this->assertStringContainsString($expected, $actual); +// } +// + public function testServiceEmptyUserId(): void + { + $container = new Container(); + /** @var UserGetService $service */ + $service = $container->get(Container::USER_GET_SERVICE); + + $payload = $service->__invoke([]); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('results', $actual); + + $expected = 'Record(s) not found'; + $actual = $actual['results'][0]; + $this->assertStringContainsString($expected, $actual); + } + + public function testServiceWrongUserId(): void + { + $container = new Container(); + /** @var UserGetService $service */ + $service = $container->get(Container::USER_GET_SERVICE); + + $payload = $service->__invoke( + [ + 'userId' => 999999 + ] + ); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('results', $actual); + + $expected = 'Record(s) not found'; + $actual = $actual['results'][0]; + $this->assertStringContainsString($expected, $actual); + } + + public function testServiceWithUserId(): void + { + $container = new Container(); + /** @var Connection $connection */ + $connection = $container->getShared(Container::CONNECTION); + /** @var UserGetService $service */ + $service = $container->get(Container::USER_GET_SERVICE); + + $migration = new UsersMigration($connection); + $this->setConnection($connection); + $dbUser = $this->getNewUser($migration); + $userId = $dbUser['usr_id']; + + $payload = $service->__invoke( + [ + 'userId' => $userId + ] + ); + + $expected = DomainStatus::SUCCESS; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('results', $actual); + + $user = $actual['results']; + $key = array_key_first($user); + $user = $user[$key]; + + $expected = $dbUser['usr_id']; + $actual = $user['id']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_status_flag']; + $actual = $user['status']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_username']; + $actual = $user['username']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_password']; + $actual = $user['password']; + $this->assertSame($expected, $actual); + } +} From d26a743bce8e56f207887557aa0cab9eb8fb1288 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:07:39 -0500 Subject: [PATCH 11/13] [#.x] - test case refactoring and adjusting tests --- tests/Unit/Domain/Hello/HelloServiceTest.php | 6 +++--- tests/Unit/Domain/Middleware/HealthMiddlewareTest.php | 2 +- tests/Unit/Domain/Middleware/NotFoundMiddlewareTest.php | 2 +- .../Unit/Domain/Middleware/ResponseSenderMiddlewareTest.php | 2 +- tests/Unit/Domain/Services/ContainerTest.php | 2 +- tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php | 2 +- tests/Unit/Domain/Services/Env/EnvFactoryTest.php | 2 +- tests/Unit/Domain/Services/Env/EnvManagerTest.php | 2 +- tests/Unit/Domain/Services/Http/HttpCodesEnumTest.php | 2 +- tests/Unit/Domain/Services/Http/ResponseTest.php | 2 +- .../Domain/Services/Providers/ErrorHandlerProviderTest.php | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/Unit/Domain/Hello/HelloServiceTest.php b/tests/Unit/Domain/Hello/HelloServiceTest.php index 56f216e..64082ac 100644 --- a/tests/Unit/Domain/Hello/HelloServiceTest.php +++ b/tests/Unit/Domain/Hello/HelloServiceTest.php @@ -17,7 +17,7 @@ use Phalcon\Api\Domain\Hello\HelloService; use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Env\EnvManager; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\BackupGlobals; use function ob_get_clean; @@ -66,7 +66,7 @@ public function testService(): void /** @var HelloService $service */ $service = $container->get(Container::HELLO_SERVICE); - $payload = $service->__invoke(); + $payload = $service->__invoke([]); $expected = DomainStatus::SUCCESS; $actual = $payload->getStatus(); @@ -76,7 +76,7 @@ public function testService(): void $this->assertArrayHasKey('results', $actual); $expected = 'Hello World!!! - '; - $actual = $actual['results']; + $actual = $actual['results'][0]; $this->assertStringContainsString($expected, $actual); } } diff --git a/tests/Unit/Domain/Middleware/HealthMiddlewareTest.php b/tests/Unit/Domain/Middleware/HealthMiddlewareTest.php index c1f488e..4d652cb 100644 --- a/tests/Unit/Domain/Middleware/HealthMiddlewareTest.php +++ b/tests/Unit/Domain/Middleware/HealthMiddlewareTest.php @@ -15,7 +15,7 @@ use Phalcon\Api\Domain\Middleware\HealthMiddleware; use Phalcon\Api\Domain\Services\Container; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Middleware/NotFoundMiddlewareTest.php b/tests/Unit/Domain/Middleware/NotFoundMiddlewareTest.php index f0eab64..3c167e8 100644 --- a/tests/Unit/Domain/Middleware/NotFoundMiddlewareTest.php +++ b/tests/Unit/Domain/Middleware/NotFoundMiddlewareTest.php @@ -16,7 +16,7 @@ use Phalcon\Api\Domain\Middleware\NotFoundMiddleware; use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Http\HttpCodesEnum; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Events\Event; use Phalcon\Mvc\Micro; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Middleware/ResponseSenderMiddlewareTest.php b/tests/Unit/Domain/Middleware/ResponseSenderMiddlewareTest.php index 7ab105f..5a1984a 100644 --- a/tests/Unit/Domain/Middleware/ResponseSenderMiddlewareTest.php +++ b/tests/Unit/Domain/Middleware/ResponseSenderMiddlewareTest.php @@ -16,7 +16,7 @@ use Phalcon\Api\Domain\Middleware\ResponseSenderMiddleware; use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Http\Response; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Services/ContainerTest.php b/tests/Unit/Domain/Services/ContainerTest.php index 1edb010..073e821 100644 --- a/tests/Unit/Domain/Services/ContainerTest.php +++ b/tests/Unit/Domain/Services/ContainerTest.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services; use Phalcon\Api\Domain\Services\Container; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Events\Manager as EventsManager; use Phalcon\Filter\Filter; diff --git a/tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php b/tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php index 37b47bf..458b147 100644 --- a/tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php +++ b/tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php @@ -16,7 +16,7 @@ use Phalcon\Api\Domain\Exceptions\InvalidConfigurationArgumentException; use Phalcon\Api\Domain\Services\Env\Adapters\DotEnv; use Phalcon\Api\Domain\Services\Env\EnvManager; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; final class DotEnvTest extends AbstractUnitTestCase { diff --git a/tests/Unit/Domain/Services/Env/EnvFactoryTest.php b/tests/Unit/Domain/Services/Env/EnvFactoryTest.php index 94cc4b8..57319a7 100644 --- a/tests/Unit/Domain/Services/Env/EnvFactoryTest.php +++ b/tests/Unit/Domain/Services/Env/EnvFactoryTest.php @@ -16,7 +16,7 @@ use Phalcon\Api\Domain\Exceptions\InvalidConfigurationArgumentException; use Phalcon\Api\Domain\Services\Env\Adapters\DotEnv; use Phalcon\Api\Domain\Services\Env\EnvFactory; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; final class EnvFactoryTest extends AbstractUnitTestCase { diff --git a/tests/Unit/Domain/Services/Env/EnvManagerTest.php b/tests/Unit/Domain/Services/Env/EnvManagerTest.php index 0b891f2..7af51a7 100644 --- a/tests/Unit/Domain/Services/Env/EnvManagerTest.php +++ b/tests/Unit/Domain/Services/Env/EnvManagerTest.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\Env; use Phalcon\Api\Domain\Services\Env\EnvManager; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\BackupGlobals; use ReflectionClass; diff --git a/tests/Unit/Domain/Services/Http/HttpCodesEnumTest.php b/tests/Unit/Domain/Services/Http/HttpCodesEnumTest.php index d381c16..40d6d06 100644 --- a/tests/Unit/Domain/Services/Http/HttpCodesEnumTest.php +++ b/tests/Unit/Domain/Services/Http/HttpCodesEnumTest.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\Http; use Phalcon\Api\Domain\Services\Http\HttpCodesEnum; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\DataProvider; final class HttpCodesEnumTest extends AbstractUnitTestCase diff --git a/tests/Unit/Domain/Services/Http/ResponseTest.php b/tests/Unit/Domain/Services/Http/ResponseTest.php index 45b97d0..99681d1 100644 --- a/tests/Unit/Domain/Services/Http/ResponseTest.php +++ b/tests/Unit/Domain/Services/Http/ResponseTest.php @@ -15,7 +15,7 @@ use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Http\Response; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use function ob_get_clean; use function ob_start; diff --git a/tests/Unit/Domain/Services/Providers/ErrorHandlerProviderTest.php b/tests/Unit/Domain/Services/Providers/ErrorHandlerProviderTest.php index 0b1154a..9db3fd2 100644 --- a/tests/Unit/Domain/Services/Providers/ErrorHandlerProviderTest.php +++ b/tests/Unit/Domain/Services/Providers/ErrorHandlerProviderTest.php @@ -16,7 +16,7 @@ use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Env\EnvManager; use Phalcon\Api\Domain\Services\Providers\ErrorHandlerProvider; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\BackupGlobals; use ReflectionClass; From 92448357becb78fd85b5d82021abc47c5aa1daa5 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:11:50 -0500 Subject: [PATCH 12/13] [#.x] - phpcs --- src/Domain/DataSource/User/UserTransport.php | 2 +- src/Domain/DataSource/User/UserTypes.php | 1 - src/Domain/Hello/HelloService.php | 2 +- src/Domain/Services/Container.php | 37 +++++++------- .../Domain/Migrations/UsersMigration.php | 2 +- tests/Unit/Domain/User/UserServiceTest.php | 50 +++++++++---------- 6 files changed, 46 insertions(+), 48 deletions(-) diff --git a/src/Domain/DataSource/User/UserTransport.php b/src/Domain/DataSource/User/UserTransport.php index 21a9a93..6652a21 100644 --- a/src/Domain/DataSource/User/UserTransport.php +++ b/src/Domain/DataSource/User/UserTransport.php @@ -26,7 +26,7 @@ */ final class UserTransport { - /** @var TUserTransport */ + /** @var TUserTransport */ private array $store; /** diff --git a/src/Domain/DataSource/User/UserTypes.php b/src/Domain/DataSource/User/UserTypes.php index a8997df..711373a 100644 --- a/src/Domain/DataSource/User/UserTypes.php +++ b/src/Domain/DataSource/User/UserTypes.php @@ -13,7 +13,6 @@ namespace Phalcon\Api\Domain\DataSource\User; - /** * @phpstan-type TUserRecord array{}|array{ * usr_id: int, diff --git a/src/Domain/Hello/HelloService.php b/src/Domain/Hello/HelloService.php index 0102a77..caf9fe0 100644 --- a/src/Domain/Hello/HelloService.php +++ b/src/Domain/Hello/HelloService.php @@ -37,7 +37,7 @@ public function __invoke(array $input): Payload DomainStatus::SUCCESS, [ 'results' => [ - "Hello World!!! - " . date(Dates::DATE_TIME_FORMAT) + "Hello World!!! - " . date(Dates::DATE_TIME_FORMAT), ], ] ); diff --git a/src/Domain/Services/Container.php b/src/Domain/Services/Container.php index 3916969..d648f35 100644 --- a/src/Domain/Services/Container.php +++ b/src/Domain/Services/Container.php @@ -44,22 +44,12 @@ class Container extends Di public const EVENTS_MANAGER = 'eventsManager'; /** @var string */ public const FILTER = 'filter'; - /** @var string */ - public const LOGGER = 'logger'; - /** @var string */ - public const REQUEST = 'request'; - /** @var string */ - public const RESPONSE = 'response'; - /** @var string */ - public const ROUTER = 'router'; - /** @var string */ - public const TIME = 'time'; - /** * Services */ public const HELLO_SERVICE = HelloService::class; - public const USER_GET_SERVICE = 'service.user.get'; + /** @var string */ + public const LOGGER = 'logger'; /** * Middleware */ @@ -70,10 +60,19 @@ class Container extends Di * Repositories */ public const REPOSITORY_USER = 'repository.user'; + /** @var string */ + public const REQUEST = 'request'; /** * Responders */ public const RESPONDER_JSON = JsonResponder::class; + /** @var string */ + public const RESPONSE = 'response'; + /** @var string */ + public const ROUTER = 'router'; + /** @var string */ + public const TIME = 'time'; + public const USER_GET_SERVICE = 'service.user.get'; public function __construct() { @@ -101,18 +100,18 @@ private function getServiceConnection(): Service return new Service( function () { /** @var string $dbName */ - $dbName = EnvManager::get('DB_NAME','phalcon'); + $dbName = EnvManager::get('DB_NAME', 'phalcon'); /** @var string $host */ - $host = EnvManager::get('DB_HOST','rest-db'); + $host = EnvManager::get('DB_HOST', 'rest-db'); /** @var string $password */ - $password = EnvManager::get('DB_PASS','secret'); - $port = (int)EnvManager::get('DB_PORT',3306); + $password = EnvManager::get('DB_PASS', 'secret'); + $port = (int)EnvManager::get('DB_PORT', 3306); /** @var string $username */ - $username = EnvManager::get('DB_USER','root'); + $username = EnvManager::get('DB_USER', 'root'); /** @var string $encoding */ - $encoding = EnvManager::get('DB_CHARSET','utf8'); + $encoding = EnvManager::get('DB_CHARSET', 'utf8'); $queries = ['SET NAMES utf8mb4']; - $dsn = sprintf( + $dsn = sprintf( 'mysql:host=%s;port=%s;dbname=%s;charset=%s', $host, $port, diff --git a/tests/Fixtures/Domain/Migrations/UsersMigration.php b/tests/Fixtures/Domain/Migrations/UsersMigration.php index 41d0d29..6e7c6cb 100644 --- a/tests/Fixtures/Domain/Migrations/UsersMigration.php +++ b/tests/Fixtures/Domain/Migrations/UsersMigration.php @@ -25,7 +25,7 @@ public function insert( ?string $username = null, ?string $password = null, ) { - $id = $id ?: 'null'; + $id = $id ?: 'null'; $sql = <<table} ( usr_id, usr_status_flag, usr_username, usr_password diff --git a/tests/Unit/Domain/User/UserServiceTest.php b/tests/Unit/Domain/User/UserServiceTest.php index 87c7c02..85933d7 100644 --- a/tests/Unit/Domain/User/UserServiceTest.php +++ b/tests/Unit/Domain/User/UserServiceTest.php @@ -76,30 +76,6 @@ public function testServiceEmptyUserId(): void $this->assertStringContainsString($expected, $actual); } - public function testServiceWrongUserId(): void - { - $container = new Container(); - /** @var UserGetService $service */ - $service = $container->get(Container::USER_GET_SERVICE); - - $payload = $service->__invoke( - [ - 'userId' => 999999 - ] - ); - - $expected = DomainStatus::NOT_FOUND; - $actual = $payload->getStatus(); - $this->assertSame($expected, $actual); - - $actual = $payload->getResult(); - $this->assertArrayHasKey('results', $actual); - - $expected = 'Record(s) not found'; - $actual = $actual['results'][0]; - $this->assertStringContainsString($expected, $actual); - } - public function testServiceWithUserId(): void { $container = new Container(); @@ -115,7 +91,7 @@ public function testServiceWithUserId(): void $payload = $service->__invoke( [ - 'userId' => $userId + 'userId' => $userId, ] ); @@ -146,4 +122,28 @@ public function testServiceWithUserId(): void $actual = $user['password']; $this->assertSame($expected, $actual); } + + public function testServiceWrongUserId(): void + { + $container = new Container(); + /** @var UserGetService $service */ + $service = $container->get(Container::USER_GET_SERVICE); + + $payload = $service->__invoke( + [ + 'userId' => 999999, + ] + ); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('results', $actual); + + $expected = 'Record(s) not found'; + $actual = $actual['results'][0]; + $this->assertStringContainsString($expected, $actual); + } } From cff4a1a635ef626e8aa0debba7e6a1b75f02dd6d Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Wed, 8 Oct 2025 11:18:49 -0500 Subject: [PATCH 13/13] [#.x] - fixes from copilot --- tests/AbstractUnitTestCase.php | 25 +++++++++---------- .../Domain/Migrations/UsersMigration.php | 23 +++++++++-------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/tests/AbstractUnitTestCase.php b/tests/AbstractUnitTestCase.php index 8bee0db..3abc5b3 100644 --- a/tests/AbstractUnitTestCase.php +++ b/tests/AbstractUnitTestCase.php @@ -104,21 +104,20 @@ protected function getFromDatabase( string $table, array $criteria ): array { - $sql = 'SELECT * FROM ' . $table . ' WHERE '; - $where = []; + $where = []; + $params = []; foreach ($criteria as $key => $value) { - $val = $value; - if (true === is_string($value)) { - $val = '"' . $value . '"'; - } - - $where[] = $key . ' = ' . $val; + $param = ':' . $key; + $where[] = $key . ' = ' . $param; + $params[$param] = $value; } - - $sql .= implode(' AND ', $where); - - $result = $this->connection?->query($sql); - $records = $result?->fetchAll(PDO::FETCH_ASSOC); + $sql = 'SELECT * FROM ' . $table; + if (!empty($where)) { + $sql .= ' WHERE ' . implode(' AND ', $where); + } + $stmt = $this->connection?->prepare($sql); + $stmt?->execute($params); + $records = $stmt?->fetchAll(PDO::FETCH_ASSOC); return $records; } diff --git a/tests/Fixtures/Domain/Migrations/UsersMigration.php b/tests/Fixtures/Domain/Migrations/UsersMigration.php index 6e7c6cb..e164b4e 100644 --- a/tests/Fixtures/Domain/Migrations/UsersMigration.php +++ b/tests/Fixtures/Domain/Migrations/UsersMigration.php @@ -25,16 +25,19 @@ public function insert( ?string $username = null, ?string $password = null, ) { - $id = $id ?: 'null'; - $sql = <<table} ( - usr_id, usr_status_flag, usr_username, usr_password -) VALUES ( - $id, $status, '$username', '$password' -) -SQL; - - $result = $this->connection->exec($sql); + $sql = "INSERT INTO {$this->table} ( + usr_id, usr_status_flag, usr_username, usr_password + ) VALUES ( + :id, :status, :username, :password + )"; + $stmt = $this->connection->prepare($sql); + $params = [ + ':id' => $id, + ':status' => $status, + ':username' => $username, + ':password' => $password, + ]; + $result = $stmt->execute($params); if (!$result) { Assert::fail( "Failed to insert id [#$id] into table [$this->table]"