From 015f5121f382c09705ceec93bbcbcfe3098aec67 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:37:56 -0600 Subject: [PATCH 01/74] [#.x] - removed transport repository replaced it smaller objects --- .../DataSource/TransportRepository.php | 115 --------------- .../DataSource/TransportRepositoryTest.php | 139 ------------------ 2 files changed, 254 deletions(-) delete mode 100644 src/Domain/Components/DataSource/TransportRepository.php delete mode 100644 tests/Unit/Domain/Components/DataSource/TransportRepositoryTest.php diff --git a/src/Domain/Components/DataSource/TransportRepository.php b/src/Domain/Components/DataSource/TransportRepository.php deleted file mode 100644 index c7edf02..0000000 --- a/src/Domain/Components/DataSource/TransportRepository.php +++ /dev/null @@ -1,115 +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\Domain\Components\DataSource; - -use Phalcon\Api\Domain\Components\DataSource\User\UserTransport; -use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; -use Phalcon\Encryption\Security\JWT\Token\Token; - -/** - * @phpstan-import-type TLoginResponsePayload from UserTypes - * @phpstan-import-type TUserDbRecord from UserTypes - * @phpstan-import-type TUserRecord from UserTypes - * - * Note: The 'readonly' keyword was intentionally removed from this class. - * Properties $sessionToken and $sessionUser are mutable to support session - * management, allowing updates to the current session state. This change - * removes immutability guarantees, but is necessary for the intended use. - */ -final class TransportRepository -{ - private ?Token $sessionToken = null; - - private ?UserTransport $sessionUser = null; - - /** - * Returns the session token. - * - * @return Token|null - */ - public function getSessionToken(): ?Token - { - return $this->sessionToken; - } - - /** - * Returns the session user. - * - * @return UserTransport|null - */ - public function getSessionUser(): ?UserTransport - { - return $this->sessionUser; - } - - /** - * @param UserTransport $user - * @param string $token - * @param string $refreshToken - * - * @return TLoginResponsePayload - */ - public function newLoginUser( - UserTransport $user, - string $token, - string $refreshToken - ): array { - return [ - 'authenticated' => true, - 'user' => [ - 'id' => $user->getId(), - 'name' => $user->getFullName(), - 'email' => $user->getEmail(), - ], - 'jwt' => [ - 'token' => $token, - 'refreshToken' => $refreshToken, - ], - ]; - } - - /** - * @param TUserRecord $dbUser - * - * @return UserTransport - */ - public function newUser(array $dbUser): UserTransport - { - return new UserTransport($dbUser); - } - - /** - * Sets the session Token - * - * @param Token $token - * - * @return void - */ - public function setSessionToken(Token $token): void - { - $this->sessionToken = $token; - } - - /** - * Populates the session user with the user data. - * - * @param TUserDbRecord $user - * - * @return void - */ - public function setSessionUser(array $user): void - { - $this->sessionUser = $this->newUser($user); - } -} diff --git a/tests/Unit/Domain/Components/DataSource/TransportRepositoryTest.php b/tests/Unit/Domain/Components/DataSource/TransportRepositoryTest.php deleted file mode 100644 index 90b6120..0000000 --- a/tests/Unit/Domain/Components/DataSource/TransportRepositoryTest.php +++ /dev/null @@ -1,139 +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\Domain\Components\DataSource; - -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; -use Phalcon\Api\Tests\AbstractUnitTestCase; - -final class TransportRepositoryTest extends AbstractUnitTestCase -{ - public function testNewUser(): void - { - /** @var TransportRepository $transport */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); - - $user = $transport->newUser([]); - $actual = $user->isEmpty(); - $this->assertTrue($actual); - - $dbUser = $this->getNewUserData(); - $user = $transport->newUser($dbUser); - - $fullName = trim( - $dbUser['usr_name_last'] - . ', ' - . $dbUser['usr_name_first'] - . ' ' - . $dbUser['usr_name_middle'] - ); - - $expected = [ - $dbUser['usr_id'] => [ - 'id' => $dbUser['usr_id'], - 'status' => $dbUser['usr_status_flag'], - 'email' => $dbUser['usr_email'], - 'password' => $dbUser['usr_password'], - 'namePrefix' => $dbUser['usr_name_prefix'], - 'nameFirst' => $dbUser['usr_name_first'], - 'nameMiddle' => $dbUser['usr_name_middle'], - 'nameLast' => $dbUser['usr_name_last'], - 'nameSuffix' => $dbUser['usr_name_suffix'], - 'issuer' => $dbUser['usr_issuer'], - 'tokenPassword' => $dbUser['usr_token_password'], - 'tokenId' => $dbUser['usr_token_id'], - 'preferences' => $dbUser['usr_preferences'], - 'createdDate' => $dbUser['usr_created_date'], - 'createdUserId' => $dbUser['usr_created_usr_id'], - 'updatedDate' => $dbUser['usr_updated_date'], - 'updatedUserId' => $dbUser['usr_updated_usr_id'], - 'fullName' => $fullName, - ], - ]; - $actual = $user->toArray(); - $this->assertSame($expected, $actual); - - $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_email']; - $actual = $user->getEmail(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_password']; - $actual = $user->getPassword(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_name_prefix']; - $actual = $user->getNamePrefix(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_name_first']; - $actual = $user->getNameFirst(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_name_middle']; - $actual = $user->getNameMiddle(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_name_last']; - $actual = $user->getNameLast(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_name_suffix']; - $actual = $user->getNameSuffix(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_issuer']; - $actual = $user->getIssuer(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_token_password']; - $actual = $user->getTokenPassword(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_token_id']; - $actual = $user->getTokenId(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_preferences']; - $actual = $user->getPreferences(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_created_date']; - $actual = $user->getCreatedDate(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_created_usr_id']; - $actual = $user->getCreatedUserId(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_updated_date']; - $actual = $user->getUpdatedDate(); - $this->assertSame($expected, $actual); - - $expected = $dbUser['usr_updated_usr_id']; - $actual = $user->getUpdatedUserId(); - $this->assertSame($expected, $actual); - - $expected = $fullName; - $actual = $user->getFullName(); - $this->assertSame($expected, $actual); - } -} From 5f518608e4b142e6956b67ef5e1cb397a68441f3 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:38:22 -0600 Subject: [PATCH 02/74] [#.x] - new payload class with helper methods --- src/Domain/Components/Payload.php | 82 +++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/Domain/Components/Payload.php diff --git a/src/Domain/Components/Payload.php b/src/Domain/Components/Payload.php new file mode 100644 index 0000000..203e8d3 --- /dev/null +++ b/src/Domain/Components/Payload.php @@ -0,0 +1,82 @@ + + * + * 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\Components; + +use PayloadInterop\DomainStatus; +use Phalcon\Domain\Payload as PhalconPayload; + +final class Payload extends PhalconPayload +{ + private function __construct( + string $status, + array $data = [], + array $errors = [] + ) { + $result = []; + + if (true !== empty($data)) { + $result = [ + 'data' => $data, + ]; + } + + if (true !== empty($errors)) { + $result = [ + 'errors' => $errors, + ]; + } + + parent::__construct($status, $result); + } + + public static function created(array $data): self + { + return new self(DomainStatus::CREATED, $data); + } + + public static function deleted(array $data): self + { + return new self(DomainStatus::DELETED, $data); + } + + public static function error(array $errors): self + { + return new self(status: DomainStatus::ERROR, errors: $errors); + } + + public static function invalid(array $errors): self + { + return new self(status: DomainStatus::INVALID, errors: $errors); + } + + public static function notFound(array $errors): self + { + return new self(status: DomainStatus::NOT_FOUND, errors: $errors); + } + + public static function success(array $data): self + { + return new self(DomainStatus::SUCCESS, $data); + } + + public static function unauthorized(array $errors): self + { + return new self(status: DomainStatus::UNAUTHORIZED, errors: $errors); + } + + public static function updated(array $data): self + { + return new self(DomainStatus::UPDATED, $data); + } +} From 619108f934be4005f9f861482b6b8aae94f8eedc Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:38:41 -0600 Subject: [PATCH 03/74] [#.x] - added user sanitizer for input --- .../DataSource/User/UserSanitizer.php | 95 +++++++++++++++++ .../DataSource/User/UserSanitizerTest.php | 100 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 src/Domain/Components/DataSource/User/UserSanitizer.php create mode 100644 tests/Unit/Domain/Components/DataSource/User/UserSanitizerTest.php diff --git a/src/Domain/Components/DataSource/User/UserSanitizer.php b/src/Domain/Components/DataSource/User/UserSanitizer.php new file mode 100644 index 0000000..2a1f9c9 --- /dev/null +++ b/src/Domain/Components/DataSource/User/UserSanitizer.php @@ -0,0 +1,95 @@ + + * + * 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\Components\DataSource\User; + +use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; +use Phalcon\Filter\Filter; + +final class UserSanitizer implements SanitizerInterface +{ + public function __construct( + private readonly Filter $filter, + ) { + } + + /** + * Return a sanitized array of the input + * + * @param array $input + * + * @return array + */ + public function sanitize(array $input): array + { + $fields = [ + 'id' => 0, + 'status' => 0, + 'email' => null, + 'password' => null, + 'namePrefix' => null, + 'nameFirst' => null, + 'nameLast' => null, + 'nameMiddle' => null, + 'nameSuffix' => null, + 'issuer' => null, + 'tokenPassword' => null, + 'tokenId' => null, + 'preferences' => null, + 'createdDate' => null, + 'createdUserId' => null, + 'updatedDate' => null, + 'updatedUserId' => null, + ]; + + /** + * Sanitize all the fields. The fields can be `null` meaning they + * were not defined with the input or a value. If the value exists + * we will sanitize it + */ + $sanitized = []; + foreach ($fields as $name => $defaultValue) { + $value = $input[$name] ?? $defaultValue; + + if (null !== $value) { + $sanitizer = $this->getSanitizer($name); + if (true !== empty($sanitizer)) { + $value = $this->filter->sanitize($value, $sanitizer); + } + } + $sanitized[$name] = $value; + } + + return $sanitized; + } + + /** + * @param string $name + * + * @return string + */ + private function getSanitizer(string $name): string + { + return match ($name) { + 'id', + 'status', + 'createdUserId', + 'updatedUserId' => Filter::FILTER_ABSINT, + 'email' => Filter::FILTER_EMAIL, + 'password', + 'tokenId', + 'tokenPassword' => '', // Password will be distorted + default => Filter::FILTER_STRING, + }; + } +} diff --git a/tests/Unit/Domain/Components/DataSource/User/UserSanitizerTest.php b/tests/Unit/Domain/Components/DataSource/User/UserSanitizerTest.php new file mode 100644 index 0000000..8b6ef93 --- /dev/null +++ b/tests/Unit/Domain/Components/DataSource/User/UserSanitizerTest.php @@ -0,0 +1,100 @@ + + * + * 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\Components\DataSource\User; + +use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Filter\Filter; + +final class UserSanitizerTest extends AbstractUnitTestCase +{ + public function testEmpty(): void + { + /** @var Filter $filter */ + $filter = $this->container->getShared(Container::FILTER); + $sanitizer = new UserSanitizer($filter); + + $expected = [ + 'id' => 0, + 'status' => 0, + 'email' => null, + 'password' => null, + 'namePrefix' => null, + 'nameFirst' => null, + 'nameLast' => null, + 'nameMiddle' => null, + 'nameSuffix' => null, + 'issuer' => null, + 'tokenPassword' => null, + 'tokenId' => null, + 'preferences' => null, + 'createdDate' => null, + 'createdUserId' => null, + 'updatedDate' => null, + 'updatedUserId' => null, + ]; + $actual = $sanitizer->sanitize([]); + $this->assertSame($expected, $actual); + } + + public function testObject(): void + { + /** @var Filter $filter */ + $filter = $this->container->getShared(Container::FILTER); + $sanitizer = new UserSanitizer($filter); + + $userData = [ + 'id' => '123', + 'status' => '4', + 'email' => 'John.Doe (newsletter) +spam@example.COM', + 'password' => 'some ', + 'namePrefix' => 'some ', + 'nameFirst' => 'some ', + 'nameLast' => 'some ', + 'nameMiddle' => 'some ', + 'nameSuffix' => 'some ', + 'issuer' => 'some ', + 'tokenPassword' => 'some ', + 'tokenId' => 'some ', + 'preferences' => 'some ', + 'createdDate' => 'some ', + 'createdUserId' => '123', + 'updatedDate' => 'some ', + 'updatedUserId' => '123', + ]; + + $expected = [ + 'id' => 123, + 'status' => 4, + 'email' => 'John.Doenewsletter+spam@example.COM', + 'password' => 'some ', + 'namePrefix' => 'some <value>', + 'nameFirst' => 'some <value>', + 'nameLast' => 'some <value>', + 'nameMiddle' => 'some <value>', + 'nameSuffix' => 'some <value>', + 'issuer' => 'some <value>', + 'tokenPassword' => 'some ', + 'tokenId' => 'some ', + 'preferences' => 'some <value>', + 'createdDate' => 'some <value>', + 'createdUserId' => 123, + 'updatedDate' => 'some <value>', + 'updatedUserId' => 123, + ]; + $actual = $sanitizer->sanitize($userData); + $this->assertSame($expected, $actual); + } +} From 0026f43903e086095c992ff0beedfb4fc5757233 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:39:01 -0600 Subject: [PATCH 04/74] [#.x] - mapper class to convert objects (domain/db etc.) --- .../Components/DataSource/User/UserMapper.php | 101 +++++++ .../DataSource/User/UserMapperTest.php | 260 ++++++++++++++++++ 2 files changed, 361 insertions(+) create mode 100644 src/Domain/Components/DataSource/User/UserMapper.php create mode 100644 tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php diff --git a/src/Domain/Components/DataSource/User/UserMapper.php b/src/Domain/Components/DataSource/User/UserMapper.php new file mode 100644 index 0000000..2555b2d --- /dev/null +++ b/src/Domain/Components/DataSource/User/UserMapper.php @@ -0,0 +1,101 @@ + + * + * 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\Components\DataSource\User; + +final class UserMapper +{ + /** + * Map Domain User -> DB row (usr_*) + * + * @return array + */ + public function db(User $user): array + { + return [ + 'usr_id' => $user->id, + 'usr_status_flag' => $user->status, + 'usr_email' => $user->email, + 'usr_password' => $user->password, + 'usr_name_prefix' => $user->namePrefix, + 'usr_name_first' => $user->nameFirst, + 'usr_name_middle' => $user->nameMiddle, + 'usr_name_last' => $user->nameLast, + 'usr_name_suffix' => $user->nameSuffix, + 'usr_issuer' => $user->issuer, + 'usr_token_password' => $user->tokenPassword, + 'usr_token_id' => $user->tokenId, + 'usr_preferences' => $user->preferences, + 'usr_created_date' => $user->createdDate, + 'usr_created_usr_id' => $user->createdUserId, + 'usr_updated_date' => $user->updatedDate, + 'usr_updated_usr_id' => $user->updatedUserId, + ]; + } + + /** + * Map DB row (usr_*) -> Domain User + * + * @param array $row + */ + public function domain(array $row): User + { + return new User( + (int)($row['usr_id'] ?? 0), + (int)($row['usr_status_flag'] ?? 0), + (string)($row['usr_email'] ?? ''), + $row['usr_password'] ?? '', + $row['usr_name_prefix'] ?? null, + $row['usr_name_first'] ?? null, + $row['usr_name_middle'] ?? null, + $row['usr_name_last'] ?? null, + $row['usr_name_suffix'] ?? null, + $row['usr_issuer'] ?? null, + $row['usr_token_password'] ?? null, + $row['usr_token_id'] ?? null, + $row['usr_preferences'] ?? null, + $row['usr_created_date'] ?? null, + isset($row['usr_created_usr_id']) ? (int)$row['usr_created_usr_id'] : null, + $row['usr_updated_date'] ?? null, + isset($row['usr_updated_usr_id']) ? (int)$row['usr_updated_usr_id'] : null, + ); + } + + /** + * Map input row -> Domain User + * + * @param array $row + */ + public function input(array $row): User + { + return new User( + $row['id'], + $row['status'], + $row['email'], + $row['password'], + $row['namePrefix'], + $row['nameFirst'], + $row['nameMiddle'], + $row['nameLast'], + $row['nameSuffix'], + $row['issuer'], + $row['tokenPassword'], + $row['tokenId'], + $row['preferences'], + $row['createdDate'], + $row['createdUserId'], + $row['updatedDate'], + $row['updatedUserId'], + ); + } +} diff --git a/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php b/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php new file mode 100644 index 0000000..a44e353 --- /dev/null +++ b/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php @@ -0,0 +1,260 @@ + + * + * 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\Components\DataSource\User; + +use Faker\Factory as FakerFactory; +use Phalcon\Api\Domain\Components\Constants\Dates; +use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +use function json_encode; + +final class UserMapperTest extends AbstractUnitTestCase +{ + public function testDb(): void + { + $faker = FakerFactory::create(); + + $preferences = [ + 'theme' => $faker->randomElement(['dark', 'light']), + 'lang' => $faker->languageCode(), + ]; + $preferencesJson = json_encode($preferences); + + $createdDate = $faker->date(Dates::DATE_TIME_FORMAT); + $updatedDate = $faker->date(Dates::DATE_TIME_FORMAT); + + $user = new User( + $faker->numberBetween(1, 1000), + $faker->numberBetween(0, 9), + $faker->safeEmail(), + $faker->password(), + $faker->optional()->title(), + $faker->firstName(), + $faker->optional()->word(), + $faker->lastName(), + $faker->optional()->suffix(), + $faker->company(), + $faker->optional()->password(), + $faker->uuid(), + $preferencesJson, + $createdDate, + $faker->numberBetween(1, 1000), + $updatedDate, + $faker->numberBetween(1, 1000) + ); + + $mapper = new UserMapper(); + $row = $mapper->db($user); + + $expected = [ + 'usr_id' => $user->id, + 'usr_status_flag' => $user->status, + 'usr_email' => $user->email, + 'usr_password' => $user->password, + 'usr_name_prefix' => $user->namePrefix, + 'usr_name_first' => $user->nameFirst, + 'usr_name_middle' => $user->nameMiddle, + 'usr_name_last' => $user->nameLast, + 'usr_name_suffix' => $user->nameSuffix, + 'usr_issuer' => $user->issuer, + 'usr_token_password' => $user->tokenPassword, + 'usr_token_id' => $user->tokenId, + 'usr_preferences' => $user->preferences, + 'usr_created_date' => $user->createdDate, + 'usr_created_usr_id' => $user->createdUserId, + 'usr_updated_date' => $user->updatedDate, + 'usr_updated_usr_id' => $user->updatedUserId, + ]; + + $actual = $row; + $this->assertSame($expected, $actual); + } + + public function testDomain(): void + { + $faker = FakerFactory::create(); + $mapper = new UserMapper(); + + // Empty row: defaults should be applied + $emptyUser = $mapper->domain([]); + + $expected = 0; + $actual = $emptyUser->id; + $this->assertSame($expected, $actual); + + $expected = 0; + $actual = $emptyUser->status; + $this->assertSame($expected, $actual); + + $expected = ''; + $actual = $emptyUser->email; + $this->assertSame($expected, $actual); + + $expected = ''; + $actual = $emptyUser->password; + $this->assertSame($expected, $actual); + + $expected = null; + $actual = $emptyUser->preferences; + $this->assertSame($expected, $actual); + + $expected = null; + $actual = $emptyUser->createdDate; + $this->assertSame($expected, $actual); + + $expected = null; + $actual = $emptyUser->createdUserId; + $this->assertSame($expected, $actual); + + $expected = null; + $actual = $emptyUser->updatedDate; + $this->assertSame($expected, $actual); + + $expected = null; + $actual = $emptyUser->updatedUserId; + $this->assertSame($expected, $actual); + + // Row with present created/updated user ids as strings should be cast to int + $row = [ + 'usr_id' => (string)$faker->numberBetween(1, 1000), + 'usr_status_flag' => (string)$faker->numberBetween(0, 9), + 'usr_email' => $faker->safeEmail(), + 'usr_created_usr_id' => (string)$faker->numberBetween(1, 1000), + 'usr_updated_usr_id' => (string)$faker->numberBetween(1, 1000), + ]; + + $user = $mapper->domain($row); + + $expected = (int)$row['usr_id']; + $actual = $user->id; + $this->assertSame($expected, $actual); + + $expected = (int)$row['usr_status_flag']; + $actual = $user->status; + $this->assertSame($expected, $actual); + + $expected = $row['usr_email']; + $actual = $user->email; + $this->assertSame($expected, $actual); + + $expected = (int)$row['usr_created_usr_id']; + $actual = $user->createdUserId; + $this->assertSame($expected, $actual); + + $expected = (int)$row['usr_updated_usr_id']; + $actual = $user->updatedUserId; + $this->assertSame($expected, $actual); + } + + public function testInput(): void + { + $faker = FakerFactory::create(); + $mapper = new UserMapper(); + + $preferences = ['k' => $faker->word()]; + $preferencesJson = json_encode($preferences); + + $input = [ + 'id' => $faker->numberBetween(1, 1000), + 'status' => $faker->numberBetween(0, 9), + 'email' => $faker->safeEmail(), + 'password' => $faker->password(), + 'namePrefix' => $faker->optional()->title(), + 'nameFirst' => $faker->firstName(), + 'nameMiddle' => $faker->optional()->word(), + 'nameLast' => $faker->lastName(), + 'nameSuffix' => null, + 'issuer' => $faker->company(), + 'tokenPassword' => $faker->optional()->password(), + 'tokenId' => $faker->uuid(), + 'preferences' => $preferencesJson, + 'createdDate' => $faker->date('Y-m-d'), + 'createdUserId' => $faker->numberBetween(1, 1000), + 'updatedDate' => $faker->date('Y-m-d'), + 'updatedUserId' => $faker->numberBetween(1, 1000), + ]; + + $user = $mapper->input($input); + + $expected = $input['id']; + $actual = $user->id; + $this->assertSame($expected, $actual); + + $expected = $input['status']; + $actual = $user->status; + $this->assertSame($expected, $actual); + + $expected = $input['email']; + $actual = $user->email; + $this->assertSame($expected, $actual); + + $expected = $input['password']; + $actual = $user->password; + $this->assertSame($expected, $actual); + + $expected = $input['namePrefix']; + $actual = $user->namePrefix; + $this->assertSame($expected, $actual); + + $expected = $input['nameFirst']; + $actual = $user->nameFirst; + $this->assertSame($expected, $actual); + + $expected = $input['nameMiddle']; + $actual = $user->nameMiddle; + $this->assertSame($expected, $actual); + + $expected = $input['nameLast']; + $actual = $user->nameLast; + $this->assertSame($expected, $actual); + + $expected = null; + $actual = $user->nameSuffix; + $this->assertSame($expected, $actual); + + $expected = $input['issuer']; + $actual = $user->issuer; + $this->assertSame($expected, $actual); + + $expected = $input['tokenPassword']; + $actual = $user->tokenPassword; + $this->assertSame($expected, $actual); + + $expected = $input['tokenId']; + $actual = $user->tokenId; + $this->assertSame($expected, $actual); + + $expected = $input['preferences']; + $actual = $user->preferences; + $this->assertSame($expected, $actual); + + $expected = $input['createdDate']; + $actual = $user->createdDate; + $this->assertSame($expected, $actual); + + $expected = $input['createdUserId']; + $actual = $user->createdUserId; + $this->assertSame($expected, $actual); + + $expected = $input['updatedDate']; + $actual = $user->updatedDate; + $this->assertSame($expected, $actual); + + $expected = $input['updatedUserId']; + $actual = $user->updatedUserId; + $this->assertSame($expected, $actual); + } +} From e79345d3e65534a4c7f59a568144bfd287c8cc6f Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:39:23 -0600 Subject: [PATCH 05/74] [#.x] - added user validator (input) --- .../DataSource/User/UserValidator.php | 50 ++++++++++++++ .../DataSource/User/UserValidatorTest.php | 69 +++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 src/Domain/Components/DataSource/User/UserValidator.php create mode 100644 tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php diff --git a/src/Domain/Components/DataSource/User/UserValidator.php b/src/Domain/Components/DataSource/User/UserValidator.php new file mode 100644 index 0000000..47be3f0 --- /dev/null +++ b/src/Domain/Components/DataSource/User/UserValidator.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\Components\DataSource\User; + +final class UserValidator +{ + /** + * Validate a UserInput and return an array of errors. + * Empty array means valid. + * + * @param UserInput $input + * + * @return array + */ + public function validate(UserInput $input): array + { + $errors = []; + $required = [ + 'email', + 'password', + 'issuer', + 'tokenPassword', + 'tokenId', + ]; + + foreach ($required as $name) { + $value = $input->$name; + if (true === empty($value)) { + $errors[] = ['Field ' . $name . ' cannot be empty.']; + } + } + + /** + * @todo add validators + */ + + return $errors; + } +} diff --git a/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php b/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php new file mode 100644 index 0000000..a71e7b2 --- /dev/null +++ b/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php @@ -0,0 +1,69 @@ + + * + * 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\Components\DataSource\User; + +use Faker\Factory as FakerFactory; +use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Components\DataSource\User\UserInput; +use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; +use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +final class UserValidatorTest extends AbstractUnitTestCase +{ + public function testError(): void + { + /** @var UserSanitizer $sanitizer */ + $sanitizer = $this->container->get(Container::USER_SANITIZER); + + $input = []; + $userInput = UserInput::new($sanitizer, $input); + + $validator = new UserValidator(); + $actual = $validator->validate($userInput); + + $expected = [ + ['Field email cannot be empty.'], + ['Field password cannot be empty.'], + ['Field issuer cannot be empty.'], + ['Field tokenPassword cannot be empty.'], + ['Field tokenId cannot be empty.'], + ]; + + $this->assertSame($expected, $actual); + } + + public function testSuccess(): void + { + /** @var UserSanitizer $sanitizer */ + $sanitizer = $this->container->get(Container::USER_SANITIZER); + $faker = FakerFactory::create(); + + $input = [ + 'email' => $faker->safeEmail(), + 'password' => $faker->password(), + 'issuer' => $faker->company(), + 'tokenPassword' => $faker->password(), + 'tokenId' => $faker->uuid(), + ]; + + $userInput = UserInput::new($sanitizer, $input); + + $validator = new UserValidator(); + $actual = $validator->validate($userInput); + + $expected = []; + $this->assertSame($expected, $actual); + } +} From cf62a202d5b394b66308937d3de7ac54ce73d9f3 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:39:41 -0600 Subject: [PATCH 06/74] [#.x] - added sanitizer interface --- .../DataSource/SanitizerInterface.php | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/Domain/Components/DataSource/SanitizerInterface.php diff --git a/src/Domain/Components/DataSource/SanitizerInterface.php b/src/Domain/Components/DataSource/SanitizerInterface.php new file mode 100644 index 0000000..b4fd17b --- /dev/null +++ b/src/Domain/Components/DataSource/SanitizerInterface.php @@ -0,0 +1,26 @@ + + * + * 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\Components\DataSource; + +interface SanitizerInterface +{ + /** + * Return a sanitized array of the input + * + * @param array $input + * + * @return array + */ + public function sanitize(array $input): array; +} From 9a45dc9a1a2d5f82396a8a13e80acc15fad51ea7 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:39:53 -0600 Subject: [PATCH 07/74] [#.x] - added auth input and sanitizer --- .../Components/DataSource/Auth/AuthInput.php | 37 ++++++++++ .../DataSource/Auth/AuthSanitizer.php | 74 +++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/Domain/Components/DataSource/Auth/AuthInput.php create mode 100644 src/Domain/Components/DataSource/Auth/AuthSanitizer.php diff --git a/src/Domain/Components/DataSource/Auth/AuthInput.php b/src/Domain/Components/DataSource/Auth/AuthInput.php new file mode 100644 index 0000000..b4250fb --- /dev/null +++ b/src/Domain/Components/DataSource/Auth/AuthInput.php @@ -0,0 +1,37 @@ + + * + * 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\Components\DataSource\Auth; + +use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; + +final class AuthInput +{ + public function __construct( + public readonly ?string $email, + public readonly ?string $password, + public readonly ?string $token + ) { + } + + public static function new(SanitizerInterface $sanitizer, array $input): self + { + $sanitized = $sanitizer->sanitize($input); + + return new self( + $sanitized['email'] ?? null, + $sanitized['password'] ?? null, + $sanitized['token'] ?? null, + ); + } +} diff --git a/src/Domain/Components/DataSource/Auth/AuthSanitizer.php b/src/Domain/Components/DataSource/Auth/AuthSanitizer.php new file mode 100644 index 0000000..6cbc431 --- /dev/null +++ b/src/Domain/Components/DataSource/Auth/AuthSanitizer.php @@ -0,0 +1,74 @@ + + * + * 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\Components\DataSource\Auth; + +use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; +use Phalcon\Filter\Filter; + +final class AuthSanitizer implements SanitizerInterface +{ + public function __construct( + private readonly Filter $filter, + ) { + } + + /** + * Return a sanitized array of the input + * + * @param array $input + * + * @return array + */ + public function sanitize(array $input): array + { + $fields = [ + 'email' => null, + 'password' => null, + 'token' => null, + ]; + + /** + * Sanitize all the fields. The fields can be `null` meaning they + * were not defined with the input or a value. If the value exists + * we will sanitize it + */ + $sanitized = []; + foreach ($fields as $name => $defaultValue) { + $value = $input[$name] ?? $defaultValue; + + if (null !== $value) { + $sanitizer = $this->getSanitizer($name); + if (true !== empty($sanitizer)) { + $value = $this->filter->sanitize($value, $sanitizer); + } + } + $sanitized[$name] = $value; + } + + return $sanitized; + } + + /** + * @param string $name + * + * @return string + */ + private function getSanitizer(string $name): string + { + return match ($name) { + 'email' => Filter::FILTER_EMAIL, + default => '', + }; + } +} From 7d4ec11b71c905f7ef4a7fdb1bb2d2d0ab437734 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:40:10 -0600 Subject: [PATCH 08/74] [#.x] - new user domain object and tests --- .../Components/DataSource/User/User.php | 52 ++++++++++++ .../Components/DataSource/User/UserTest.php | 80 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 src/Domain/Components/DataSource/User/User.php create mode 100644 tests/Unit/Domain/Components/DataSource/User/UserTest.php diff --git a/src/Domain/Components/DataSource/User/User.php b/src/Domain/Components/DataSource/User/User.php new file mode 100644 index 0000000..f6a599a --- /dev/null +++ b/src/Domain/Components/DataSource/User/User.php @@ -0,0 +1,52 @@ + + * + * 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\Components\DataSource\User; + +use function trim; + +final class User +{ + public function __construct( + public readonly int $id, + public readonly int $status, + public readonly string $email, + public readonly string $password, + public readonly ?string $namePrefix, + public readonly ?string $nameFirst, + public readonly ?string $nameMiddle, + public readonly ?string $nameLast, + public readonly ?string $nameSuffix, + public readonly ?string $issuer, + public readonly ?string $tokenPassword, + public readonly ?string $tokenId, + public readonly ?string $preferences, + public readonly ?string $createdDate, + public readonly ?int $createdUserId, + public readonly ?string $updatedDate, + public readonly ?int $updatedUserId, + ) { + } + + public function fullName(): string + { + return trim( + ($this->nameLast ?? '') . ', ' . ($this->nameFirst ?? '') . ' ' . ($this->nameMiddle ?? '') + ); + } + + public function toArray(): array + { + return [$this->id => get_object_vars($this)]; + } +} diff --git a/tests/Unit/Domain/Components/DataSource/User/UserTest.php b/tests/Unit/Domain/Components/DataSource/User/UserTest.php new file mode 100644 index 0000000..0ce4a01 --- /dev/null +++ b/tests/Unit/Domain/Components/DataSource/User/UserTest.php @@ -0,0 +1,80 @@ + + * + * 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\Components\DataSource\User; + +use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +use function rand; + +final class UserTest extends AbstractUnitTestCase +{ + public function testObject(): void + { + $userData = $this->getNewUserData(); + $userData['usr_id'] = rand(1, 100); + + $user = new User( + $userData['usr_id'], + $userData['usr_status_flag'], + $userData['usr_email'], + $userData['usr_password'], + $userData['usr_name_prefix'], + $userData['usr_name_first'], + $userData['usr_name_middle'], + $userData['usr_name_last'], + $userData['usr_name_suffix'], + $userData['usr_issuer'], + $userData['usr_token_password'], + $userData['usr_token_id'], + $userData['usr_preferences'], + $userData['usr_created_date'], + $userData['usr_created_usr_id'], + $userData['usr_updated_date'], + $userData['usr_updated_usr_id'], + ); + + $expected = ($userData['usr_name_last'] ?? '') + . ', ' + . ($userData['usr_name_first'] ?? '') + . ' ' + . ($userData['usr_name_middle'] ?? ''); + $actual = $user->fullName(); + $this->assertSame($expected, $actual); + + $expected = [ + $userData['usr_id'] => [ + 'id' => $userData['usr_id'], + 'status' => $userData['usr_status_flag'], + 'email' => $userData['usr_email'], + 'password' => $userData['usr_password'], + 'namePrefix' => $userData['usr_name_prefix'], + 'nameFirst' => $userData['usr_name_first'], + 'nameMiddle' => $userData['usr_name_middle'], + 'nameLast' => $userData['usr_name_last'], + 'nameSuffix' => $userData['usr_name_suffix'], + 'issuer' => $userData['usr_issuer'], + 'tokenPassword' => $userData['usr_token_password'], + 'tokenId' => $userData['usr_token_id'], + 'preferences' => $userData['usr_preferences'], + 'createdDate' => $userData['usr_created_date'], + 'createdUserId' => $userData['usr_created_usr_id'], + 'updatedDate' => $userData['usr_updated_date'], + 'updatedUserId' => $userData['usr_updated_usr_id'], + ], + ]; + $actual = $user->toArray(); + $this->assertSame($expected, $actual); + } +} From e01c835df75e72bd7bf564db200c859d4f085509 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:40:23 -0600 Subject: [PATCH 09/74] [#.x] - added input class for users --- .../Components/DataSource/User/UserInput.php | 70 ++++++++++ .../DataSource/User/UserInputTest.php | 130 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 src/Domain/Components/DataSource/User/UserInput.php create mode 100644 tests/Unit/Domain/Components/DataSource/User/UserInputTest.php diff --git a/src/Domain/Components/DataSource/User/UserInput.php b/src/Domain/Components/DataSource/User/UserInput.php new file mode 100644 index 0000000..15f6545 --- /dev/null +++ b/src/Domain/Components/DataSource/User/UserInput.php @@ -0,0 +1,70 @@ + + * + * 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\Components\DataSource\User; + +use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; + +final class UserInput +{ + public function __construct( + public readonly ?int $id, + public readonly ?int $status, + public readonly ?string $email, + public readonly ?string $password, + public readonly ?string $namePrefix, + public readonly ?string $nameFirst, + public readonly ?string $nameMiddle, + public readonly ?string $nameLast, + public readonly ?string $nameSuffix, + public readonly ?string $issuer, + public readonly ?string $tokenPassword, + public readonly ?string $tokenId, + public readonly ?string $preferences, + public readonly ?string $createdDate, + public readonly ?int $createdUserId, + public readonly ?string $updatedDate, + public readonly ?int $updatedUserId, + ) { + } + + public static function new(SanitizerInterface $sanitizer, array $input): self + { + $sanitized = $sanitizer->sanitize($input); + + return new self( + $sanitized['id'], + $sanitized['status'], + $sanitized['email'], + $sanitized['password'], + $sanitized['namePrefix'], + $sanitized['nameFirst'], + $sanitized['nameMiddle'], + $sanitized['nameLast'], + $sanitized['nameSuffix'], + $sanitized['issuer'], + $sanitized['tokenPassword'], + $sanitized['tokenId'], + $sanitized['preferences'], + $sanitized['createdDate'], + $sanitized['createdUserId'], + $sanitized['updatedDate'], + $sanitized['updatedUserId'] + ); + } + + public function toArray(): array + { + return [$this->id => get_object_vars($this)]; + } +} diff --git a/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php b/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php new file mode 100644 index 0000000..2424f60 --- /dev/null +++ b/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php @@ -0,0 +1,130 @@ + + * + * 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\Components\DataSource\User; + +use Faker\Factory as FakerFactory; +use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Components\DataSource\User\UserInput; +use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +use function json_encode; + +final class UserInputTest extends AbstractUnitTestCase +{ + public function testToArray(): void + { + /** @var UserSanitizer $sanitizer */ + $sanitizer = $this->container->get(Container::USER_SANITIZER); + $faker = FakerFactory::create(); + + // Build an input with many fields present + $input = [ + 'id' => $faker->numberBetween(1, 1000), + 'status' => $faker->numberBetween(0, 9), + 'email' => " Foo.Bar+tag@Example.COM ", + 'password' => $faker->password(), + 'namePrefix' => $faker->title(), + 'nameFirst' => $faker->firstName(), + 'nameMiddle' => $faker->word(), + 'nameLast' => $faker->lastName(), + 'nameSuffix' => $faker->randomElement([null, $faker->suffix()]), + 'issuer' => $faker->company(), + 'tokenPassword' => $faker->password(), + 'tokenId' => $faker->uuid(), + 'preferences' => json_encode(['k' => $faker->word()]), + 'createdDate' => $faker->date('Y-m-d'), + 'createdUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast + 'updatedDate' => $faker->date('Y-m-d'), + 'updatedUserId' => (string)$faker->numberBetween(1, 1000), // string to ensure ABSINT cast + ]; + + $sanitized = $sanitizer->sanitize($input); + $userInput = UserInput::new($sanitizer, $input); + + // For each property use $expected and $actual then assertSame + $expected = $sanitized['id']; + $actual = $userInput->id; + $this->assertSame($expected, $actual); + + $expected = $sanitized['status']; + $actual = $userInput->status; + $this->assertSame($expected, $actual); + + $expected = $sanitized['email']; + $actual = $userInput->email; + $this->assertSame($expected, $actual); + + $expected = $sanitized['password']; + $actual = $userInput->password; + $this->assertSame($expected, $actual); + + $expected = $sanitized['namePrefix']; + $actual = $userInput->namePrefix; + $this->assertSame($expected, $actual); + + $expected = $sanitized['nameFirst']; + $actual = $userInput->nameFirst; + $this->assertSame($expected, $actual); + + $expected = $sanitized['nameMiddle']; + $actual = $userInput->nameMiddle; + $this->assertSame($expected, $actual); + + $expected = $sanitized['nameLast']; + $actual = $userInput->nameLast; + $this->assertSame($expected, $actual); + + $expected = $sanitized['nameSuffix']; + $actual = $userInput->nameSuffix; + $this->assertSame($expected, $actual); + + $expected = $sanitized['issuer']; + $actual = $userInput->issuer; + $this->assertSame($expected, $actual); + + $expected = $sanitized['tokenPassword']; + $actual = $userInput->tokenPassword; + $this->assertSame($expected, $actual); + + $expected = $sanitized['tokenId']; + $actual = $userInput->tokenId; + $this->assertSame($expected, $actual); + + $expected = $sanitized['preferences']; + $actual = $userInput->preferences; + $this->assertSame($expected, $actual); + + $expected = $sanitized['createdDate']; + $actual = $userInput->createdDate; + $this->assertSame($expected, $actual); + + $expected = $sanitized['createdUserId']; + $actual = $userInput->createdUserId; + $this->assertSame($expected, $actual); + + $expected = $sanitized['updatedDate']; + $actual = $userInput->updatedDate; + $this->assertSame($expected, $actual); + + $expected = $sanitized['updatedUserId']; + $actual = $userInput->updatedUserId; + $this->assertSame($expected, $actual); + + // Verify toArray behaviour: key is the id value per implementation + $expected = [$userInput->id => get_object_vars($userInput)]; + $actual = $userInput->toArray(); + $this->assertSame($expected, $actual); + } +} From 40ee867788692c55ff8dec42fc77aa16901bfa18 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:40:54 -0600 Subject: [PATCH 10/74] [#.x] - refactored middleware after domain transport object changes --- .../ValidateTokenClaimsMiddleware.php | 14 ++++---- .../ValidateTokenRevokedMiddleware.php | 14 ++++---- .../ValidateTokenStructureMiddleware.php | 7 ++-- .../ValidateTokenUserMiddleware.php | 15 ++++---- .../ValidateTokenClaimsMiddlewareTest.php | 35 +++++++++++-------- .../ValidateTokenRevokedMiddlewareTest.php | 34 ++++++++++-------- .../ValidateTokenStructureMiddlewareTest.php | 6 +++- .../ValidateTokenUserMiddlewareTest.php | 17 ++++++--- 8 files changed, 82 insertions(+), 60 deletions(-) diff --git a/src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php index f659890..bb72854 100644 --- a/src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php @@ -14,14 +14,14 @@ namespace Phalcon\Api\Domain\Components\Middleware; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; -use Phalcon\Api\Domain\Components\DataSource\User\UserTransport; +use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Encryption\Security\JWT\Token\Token; use Phalcon\Events\Exception as EventsException; use Phalcon\Http\Response\Exception; use Phalcon\Mvc\Micro; +use Phalcon\Support\Registry; /** * Validates the token claims @@ -39,20 +39,20 @@ public function call(Micro $application): bool { /** @var JWTToken $jwtToken */ $jwtToken = $application->getSharedService(Container::JWT_TOKEN); - /** @var TransportRepository $transport */ - $transport = $application->getSharedService(Container::REPOSITORY_TRANSPORT); + /** @var Registry $registry */ + $registry = $application->getSharedService(Container::REGISTRY); /** * Get the token object */ /** @var Token $tokenObject */ - $tokenObject = $transport->getSessionToken(); + $tokenObject = $registry->get('token'); /** * Our used is in the transport, so we can get it without a * database call */ - /** @var UserTransport $sessionUser */ - $sessionUser = $transport->getSessionUser(); + /** @var User $sessionUser */ + $sessionUser = $registry->get('user'); /** * This is where we validate everything. Even though the user diff --git a/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php index 81fddca..ea9be43 100644 --- a/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php @@ -15,12 +15,12 @@ use Phalcon\Api\Domain\Components\Cache\Cache; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; -use Phalcon\Api\Domain\Components\DataSource\User\UserTransport; +use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Env\EnvManager; use Phalcon\Http\RequestInterface; use Phalcon\Mvc\Micro; +use Phalcon\Support\Registry; final class ValidateTokenRevokedMiddleware extends AbstractMiddleware { @@ -37,17 +37,17 @@ public function call(Micro $application): bool $cache = $application->getSharedService(Container::CACHE); /** @var EnvManager $env */ $env = $application->getSharedService(Container::ENV); - /** @var TransportRepository $userTransport */ - $userTransport = $application->getSharedService(Container::REPOSITORY_TRANSPORT); + /** @var Registry $registry */ + $registry = $application->getSharedService(Container::REGISTRY); - /** @var UserTransport $user */ - $user = $userTransport->getSessionUser(); + /** @var User $user */ + $domainUser = $registry->get('user'); /** * Get the token object */ $token = $this->getBearerTokenFromHeader($request, $env); - $cacheKey = $cache->getCacheTokenKey($user, $token); + $cacheKey = $cache->getCacheTokenKey($domainUser, $token); $exists = $cache->has($cacheKey); if (true !== $exists) { diff --git a/src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php index d69eee0..eed5801 100644 --- a/src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php @@ -23,6 +23,7 @@ use Phalcon\Http\Request; use Phalcon\Http\Response\Exception; use Phalcon\Mvc\Micro; +use Phalcon\Support\Registry; final class ValidateTokenStructureMiddleware extends AbstractMiddleware { @@ -63,9 +64,9 @@ public function call(Micro $application): bool /** * If we are down here the token is an object and is valid */ - /** @var TransportRepository $transport */ - $transport = $application->getSharedService(Container::REPOSITORY_TRANSPORT); - $transport->setSessionToken($token); + /** @var Registry $registry */ + $registry = $application->getSharedService(Container::REGISTRY); + $registry->set('token', $token); return true; } diff --git a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php index 9720a05..bc1b12f 100644 --- a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php @@ -23,6 +23,7 @@ use Phalcon\Events\Exception as EventsException; use Phalcon\Http\Response\Exception; use Phalcon\Mvc\Micro; +use Phalcon\Support\Registry; /** * @phpstan-import-type TUserDbRecord from UserTypes @@ -43,18 +44,17 @@ public function call(Micro $application): bool $jwtToken = $application->getSharedService(Container::JWT_TOKEN); /** @var QueryRepository $repository */ $repository = $application->getSharedService(Container::REPOSITORY); - /** @var TransportRepository $transport */ - $transport = $application->getSharedService(Container::REPOSITORY_TRANSPORT); + /** @var Registry $registry */ + $registry = $application->getSharedService(Container::REGISTRY); /** * Get the token object */ /** @var Token $tokenObject */ - $tokenObject = $transport->getSessionToken(); - /** @var TUserRecord $dbUser */ - $dbUser = $jwtToken->getUser($repository, $tokenObject); + $tokenObject = $registry->get('token'); + $domainUser = $jwtToken->getUser($repository, $tokenObject); - if (true === empty($dbUser)) { + if (null === $domainUser) { $this->halt( $application, HttpCodesEnum::Unauthorized->value, @@ -70,8 +70,7 @@ public function call(Micro $application): bool * If we are here everything is fine and we need to keep the user * as a "session" user in the transport */ - /** @var TUserDbRecord $dbUser */ - $transport->setSessionUser($dbUser); + $registry->set('user', $domainUser); return true; } diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenClaimsMiddlewareTest.php b/tests/Unit/Domain/Components/Middleware/ValidateTokenClaimsMiddlewareTest.php index 453701a..bcbfec2 100644 --- a/tests/Unit/Domain/Components/Middleware/ValidateTokenClaimsMiddlewareTest.php +++ b/tests/Unit/Domain/Components/Middleware/ValidateTokenClaimsMiddlewareTest.php @@ -14,10 +14,11 @@ namespace Phalcon\Api\Tests\Unit\Domain\Components\Middleware; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Mvc\Micro; +use Phalcon\Support\Registry; use PHPUnit\Framework\Attributes\BackupGlobals; use PHPUnit\Framework\Attributes\DataProvider; @@ -67,26 +68,28 @@ public function testValidateTokenClaimsFailure( array $userData, array $expectedErrors ): void { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); $migration = new UsersMigration($this->getConnection()); $user = $this->getNewUser($migration); - $tokenUser = $user; [$micro, $middleware, $jwtToken] = $this->setupTest(); /** * Make the signature non valid */ - $tokenUser = array_replace($tokenUser, $userData); + $tokenUser = array_replace($user, $userData); $token = $this->getUserToken($tokenUser); $tokenObject = $jwtToken->getObject($token); + $domainUser = $userMapper->domain($user); /** - * Store the user in the session + * Store the user in the registry */ - /** @var TransportRepository $transport */ - $transport = $this->container->getShared(Container::REPOSITORY_TRANSPORT); - $transport->setSessionUser($user); - $transport->setSessionToken($tokenObject); + /** @var Registry $registry */ + $registry = $this->container->get(Container::REGISTRY); + $registry->set('user', $domainUser); + $registry->set('token', $tokenObject); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); $_SERVER = [ @@ -113,22 +116,24 @@ public function testValidateTokenClaimsFailure( public function testValidateTokenClaimsSuccess(): void { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); $migration = new UsersMigration($this->getConnection()); $user = $this->getNewUser($migration); - $tokenUser = $user; + $tokenUser = $userMapper->domain($user); [$micro, $middleware, $jwtToken] = $this->setupTest(); - $token = $this->getUserToken($tokenUser); + $token = $this->getUserToken($user); $tokenObject = $jwtToken->getObject($token); /** - * Store the user in the session + * Store the user in the registry */ - /** @var TransportRepository $transport */ - $transport = $this->container->getShared(Container::REPOSITORY_TRANSPORT); - $transport->setSessionUser($user); - $transport->setSessionToken($tokenObject); + /** @var Registry $registry */ + $registry = $this->container->get(Container::REGISTRY); + $registry->set('user', $tokenUser); + $registry->set('token', $tokenObject); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); $_SERVER = [ diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php b/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php index 6c65fdd..8ce166e 100644 --- a/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php +++ b/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php @@ -15,11 +15,12 @@ use Phalcon\Api\Domain\Components\Cache\Cache; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserTransport; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Mvc\Micro; +use Phalcon\Support\Registry; use PHPUnit\Framework\Attributes\BackupGlobals; #[BackupGlobals(true)] @@ -27,20 +28,22 @@ final class ValidateTokenRevokedMiddlewareTest extends AbstractUnitTestCase { public function testValidateTokenRevokedFailureInvalidToken(): void { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); $migration = new UsersMigration($this->getConnection()); $user = $this->getNewUser($migration); - $tokenUser = $user; + $tokenUser = $userMapper->domain($user); [$micro, $middleware] = $this->setupTest(); - $token = $this->getUserToken($tokenUser); + $token = $this->getUserToken($user); /** - * Store the user in the session + * Store the user in the registry */ - /** @var UserTransport $userRepository */ - $userRepository = $micro->getSharedService(Container::REPOSITORY_TRANSPORT); - $userRepository->setSessionUser($user); + /** @var Registry $registry */ + $registry = $this->container->get(Container::REGISTRY); + $registry->set('user', $tokenUser); // There is no entry in the cache for this token, so this should fail. $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); @@ -68,23 +71,26 @@ public function testValidateTokenRevokedFailureInvalidToken(): void public function testValidateTokenRevokedSuccess(): void { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); $migration = new UsersMigration($this->getConnection()); $user = $this->getNewUser($migration); - $tokenUser = $user; + $tokenUser = $userMapper->domain($user); [$micro, $middleware] = $this->setupTest(); - $token = $this->getUserToken($tokenUser); + $token = $this->getUserToken($user); /** - * Store the user in the session + * Store the user in the registry */ - /** @var UserTransport $userRepository */ - $userRepository = $micro->getSharedService(Container::REPOSITORY_TRANSPORT); - $userRepository->setSessionUser($user); + /** @var Registry $registry */ + $registry = $this->container->get(Container::REGISTRY); + $registry->set('user', $tokenUser); + /** @var Cache $cache */ $cache = $micro->getSharedService(Container::CACHE); - $sessionUser = $userRepository->getSessionUser(); + $sessionUser = $registry->get('user'); $cacheKey = $cache->getCacheTokenKey($sessionUser, $token); $payload = [ 'token' => $token, diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenStructureMiddlewareTest.php b/tests/Unit/Domain/Components/Middleware/ValidateTokenStructureMiddlewareTest.php index 539aa48..4a7ebbf 100644 --- a/tests/Unit/Domain/Components/Middleware/ValidateTokenStructureMiddlewareTest.php +++ b/tests/Unit/Domain/Components/Middleware/ValidateTokenStructureMiddlewareTest.php @@ -14,6 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Components\Middleware; use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; @@ -82,13 +83,16 @@ public function testValidateTokenStructureFailureNoDots(): void public function testValidateTokenStructureSuccess(): void { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); $userData = $this->getNewUserData(); + $domainUser = $userMapper->domain($userData); [$micro, $middleware] = $this->setupTest(); /** @var JWTToken $jwtToken */ $jwtToken = $micro->getSharedService(Container::JWT_TOKEN); - $token = $jwtToken->getForUser($userData); + $token = $jwtToken->getForUser($domainUser); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); $_SERVER = [ 'REQUEST_METHOD' => 'GET', diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenUserMiddlewareTest.php b/tests/Unit/Domain/Components/Middleware/ValidateTokenUserMiddlewareTest.php index 827d519..a207a99 100644 --- a/tests/Unit/Domain/Components/Middleware/ValidateTokenUserMiddlewareTest.php +++ b/tests/Unit/Domain/Components/Middleware/ValidateTokenUserMiddlewareTest.php @@ -14,10 +14,12 @@ namespace Phalcon\Api\Tests\Unit\Domain\Components\Middleware; use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Mvc\Micro; +use Phalcon\Support\Registry; use PHPUnit\Framework\Attributes\BackupGlobals; #[BackupGlobals(true)] @@ -30,8 +32,9 @@ public function testValidateTokenUserFailureRecordNotFound(): void $userData['usr_id'] = 1; $token = $this->getUserToken($userData); $tokenObject = $jwtToken->getObject($token); - $transport = $this->container->getShared(Container::REPOSITORY_TRANSPORT); - $transport->setSessionToken($tokenObject); + /** @var Registry $registry */ + $registry = $this->container->get(Container::REGISTRY); + $registry->set('token', $tokenObject); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); $_SERVER = [ @@ -58,15 +61,19 @@ public function testValidateTokenUserFailureRecordNotFound(): void public function testValidateTokenUserSuccess(): void { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); $migration = new UsersMigration($this->getConnection()); $user = $this->getNewUser($migration); + $domainUser = $userMapper->domain($user); [$micro, $middleware, $jwtToken] = $this->setupTest(); - $token = $jwtToken->getForUser($user); + $token = $jwtToken->getForUser($domainUser); $tokenObject = $jwtToken->getObject($token); - $transport = $this->container->getShared(Container::REPOSITORY_TRANSPORT); - $transport->setSessionToken($tokenObject); + /** @var Registry $registry */ + $registry = $this->container->get(Container::REGISTRY); + $registry->set('token', $tokenObject); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); $_SERVER = [ From a1d655aae20733f042fccd4d8e6e35619062934e Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:41:15 -0600 Subject: [PATCH 11/74] [#.x] - refactored jwt token for new DTOs --- src/Domain/Components/Encryption/JWTToken.php | 47 +++++++++---------- .../Components/Encryption/JWTTokenTest.php | 39 +++++++++------ 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/src/Domain/Components/Encryption/JWTToken.php b/src/Domain/Components/Encryption/JWTToken.php index c4c067b..47908f6 100644 --- a/src/Domain/Components/Encryption/JWTToken.php +++ b/src/Domain/Components/Encryption/JWTToken.php @@ -17,7 +17,7 @@ use InvalidArgumentException; use Phalcon\Api\Domain\Components\Cache\Cache; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; -use Phalcon\Api\Domain\Components\DataSource\User\UserTransport; +use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Enums\Common\FlagsEnum; use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; @@ -53,11 +53,11 @@ public function __construct( /** * Returns the string token * - * @param TUserTokenDbRecord $user + * @param User $user * * @return string */ - public function getForUser(array $user): string + public function getForUser(User $user): string { return $this->generateTokenForUser($user); } @@ -87,11 +87,11 @@ public function getObject(string $token): Token /** * Returns the string token * - * @param TUserTokenDbRecord $user + * @param User $user * * @return string */ - public function getRefreshForUser(array $user): string + public function getRefreshForUser(User $user): string { return $this->generateTokenForUser($user, true); } @@ -100,12 +100,12 @@ public function getRefreshForUser(array $user): string * @param QueryRepository $repository * @param Token $token * - * @return TUserRecord + * @return User|null */ public function getUser( QueryRepository $repository, Token $token, - ): array { + ): ?User { /** @var string $issuer */ $issuer = $token->getClaims()->get(JWTEnum::Issuer->value); /** @var string $tokenId */ @@ -120,37 +120,34 @@ public function getUser( 'usr_token_id' => $tokenId, ]; - /** @var TUserRecord $user */ - $user = $repository->user()->findOneBy($criteria); - - return $user; + return $repository->user()->findOneBy($criteria); } /** * Returns an array with the validation errors for this token * - * @param Token $tokenObject - * @param UserTransport $user + * @param Token $tokenObject + * @param User $user * * @return TValidatorErrors */ public function validate( Token $tokenObject, - UserTransport $user + User $user ): array { $validator = new Validator($tokenObject); $signer = new Hmac(); $now = new DateTimeImmutable(); $validator - ->validateId($user->getTokenId()) + ->validateId($user->tokenId) ->validateAudience($this->getTokenAudience()) - ->validateIssuer($user->getIssuer()) + ->validateIssuer($user->issuer) ->validateNotBefore($now->getTimestamp()) ->validateIssuedAt($now->getTimestamp()) ->validateExpiration($now->getTimestamp()) - ->validateSignature($signer, $user->getTokenPassword()) - ->validateClaim(JWTEnum::UserId->value, $user->getId()) + ->validateSignature($signer, $user->tokenPassword) + ->validateClaim(JWTEnum::UserId->value, $user->id) ; /** @var TValidatorErrors $errors */ @@ -162,13 +159,13 @@ public function validate( /** * Returns the string token * - * @param TUserTokenDbRecord $user - * @param bool $isRefresh + * @param User $user + * @param bool $isRefresh * * @return string */ private function generateTokenForUser( - array $user, + User $user, bool $isRefresh = false ): string { /** @var int $expiration */ @@ -187,13 +184,13 @@ private function generateTokenForUser( $tokenBuilder = new Builder(new Hmac()); /** @var string $issuer */ - $issuer = $user['usr_issuer']; + $issuer = $user->issuer; /** @var string $tokenPassword */ - $tokenPassword = $user['usr_token_password']; + $tokenPassword = $user->tokenPassword; /** @var string $tokenId */ - $tokenId = $user['usr_token_id']; + $tokenId = $user->tokenId; /** @var string $userId */ - $userId = $user['usr_id']; + $userId = $user->id; $tokenObject = $tokenBuilder ->setIssuer($issuer) diff --git a/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php b/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php index 476ff38..17b517e 100644 --- a/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php +++ b/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php @@ -15,8 +15,8 @@ use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; -use Phalcon\Api\Domain\Components\DataSource\User\UserTransport; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Exceptions\TokenValidationException; use Phalcon\Api\Tests\AbstractUnitTestCase; @@ -37,18 +37,24 @@ public function setUp(): void public function testGetForUserReturnsTokenString(): void { - $user = $this->getUserData(); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + $userData = $this->getUserData(); + $domainUser = $userMapper->domain($userData); - $token = $this->jwtToken->getForUser($user); + $token = $this->jwtToken->getForUser($domainUser); $this->assertIsString($token); $this->assertNotEmpty($token); } public function testGetObjectReturnsPlainToken(): void { - $user = $this->getUserData(); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + $userData = $this->getUserData(); + $domainUser = $userMapper->domain($userData); - $tokenString = $this->jwtToken->getForUser($user); + $tokenString = $this->jwtToken->getForUser($domainUser); $plain = $this->jwtToken->getObject($tokenString); $this->assertInstanceOf(Token::class, $plain); @@ -73,9 +79,12 @@ public function testGetObjectThrowsOnInvalidTokenStructure(): void public function testGetUserReturnsUserArray(): void { - $user = $this->getUserData(); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + $userData = $this->getUserData(); + $domainUser = $userMapper->domain($userData); - $tokenString = $this->jwtToken->getForUser($user); + $tokenString = $this->jwtToken->getForUser($domainUser); $plain = $this->jwtToken->getObject($tokenString); $userRepository = $this @@ -91,7 +100,7 @@ public function testGetUserReturnsUserArray(): void $userRepository->expects($this->once()) ->method('findOneBy') - ->willReturn($user) + ->willReturn($domainUser) ; $mockRepository = $this @@ -110,20 +119,20 @@ public function testGetUserReturnsUserArray(): void ; $result = $this->jwtToken->getUser($mockRepository, $plain); - $this->assertEquals($user, $result); + $this->assertEquals($domainUser, $result); } public function testValidateSuccess(): void { - $user = $this->getUserData(); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + $userData = $this->getUserData(); + $domainUser = $userMapper->domain($userData); - $tokenString = $this->jwtToken->getForUser($user); + $tokenString = $this->jwtToken->getForUser($domainUser); $plain = $this->jwtToken->getObject($tokenString); - $userTransport = new UserTransport($user); - sleep(1); - - $actual = $this->jwtToken->validate($plain, $userTransport); + $actual = $this->jwtToken->validate($plain, $domainUser); $this->assertSame([], $actual); } From e117370b8304f05d5b2aabb2b9b625172048723e Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:41:34 -0600 Subject: [PATCH 12/74] [#.x] - added interface for user repository --- .../User/UserRepositoryInterface.php | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/Domain/Components/DataSource/User/UserRepositoryInterface.php diff --git a/src/Domain/Components/DataSource/User/UserRepositoryInterface.php b/src/Domain/Components/DataSource/User/UserRepositoryInterface.php new file mode 100644 index 0000000..4f6702a --- /dev/null +++ b/src/Domain/Components/DataSource/User/UserRepositoryInterface.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\Components\DataSource\User; + +/** + * @phpstan-import-type TUserRecord from UserTypes + * @phpstan-import-type TUserInsert from UserTypes + * @phpstan-import-type TUserUpdate from UserTypes + */ +interface UserRepositoryInterface +{ + /** + * @param array $criteria + * + * @return int + */ + public function deleteBy(array $criteria): int; + + /** + * @param int $recordId + * + * @return int + */ + public function deleteById(int $recordId): int; + + /** + * @param string $email + * + * @return User|null + */ + public function findByEmail(string $email): ?User; + + /** + * @param int $recordId + * + * @return User|null + */ + public function findById(int $recordId): ?User; + + /** + * @param array $criteria + * + * @return User|null + */ + public function findOneBy(array $criteria): ?User; + + /** + * @param User $user + * + * @return int + */ + public function insert(User $user): int; + + /** + * @param User $user + * + * @return int + */ + public function update(User $user): int; +} From cf80dbd3dfdeef77cc85ec0b92ad7c84fd462c58 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:42:10 -0600 Subject: [PATCH 13/74] [#.x] - introduced abstract class - refactoring --- .../DataSource/AbstractRepository.php | 77 ++++--- .../DataSource/User/UserRepository.php | 194 ++++++++++++------ 2 files changed, 165 insertions(+), 106 deletions(-) diff --git a/src/Domain/Components/DataSource/AbstractRepository.php b/src/Domain/Components/DataSource/AbstractRepository.php index 34c6314..c60fe7e 100644 --- a/src/Domain/Components/DataSource/AbstractRepository.php +++ b/src/Domain/Components/DataSource/AbstractRepository.php @@ -16,7 +16,6 @@ use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\DataMapper\Pdo\Connection; use Phalcon\DataMapper\Query\Delete; -use Phalcon\DataMapper\Query\Select; /** * @phpstan-import-type TUserRecord from UserTypes @@ -69,42 +68,42 @@ public function deleteById(int $recordId): int ); } - /** - * @param int $recordId - * - * @return TUserRecord - */ - public function findById(int $recordId): array - { - $result = []; - if ($recordId > 0) { - return $this->findOneBy( - [ - $this->idField => $recordId, - ] - ); - } - - return $result; - } - - - /** - * @param array $criteria - * - * @return TUserRecord - */ - public function findOneBy(array $criteria): array - { - $select = Select::new($this->connection); - - /** @var TUserRecord $result */ - $result = $select - ->from($this->table) - ->whereEquals($criteria) - ->fetchOne() - ; - - return $result; - } +// /** +// * @param int $recordId +// * +// * @return TUserRecord +// */ +// public function findById(int $recordId): array +// { +// $result = []; +// if ($recordId > 0) { +// return $this->findOneBy( +// [ +// $this->idField => $recordId, +// ] +// ); +// } +// +// return $result; +// } +// +// +// /** +// * @param array $criteria +// * +// * @return TUserRecord +// */ +// public function findOneBy(array $criteria): array +// { +// $select = Select::new($this->connection); +// +// /** @var TUserRecord $result */ +// $result = $select +// ->from($this->table) +// ->whereEquals($criteria) +// ->fetchOne() +// ; +// +// return $result; +// } } diff --git a/src/Domain/Components/DataSource/User/UserRepository.php b/src/Domain/Components/DataSource/User/UserRepository.php index 36d903a..2b9d846 100644 --- a/src/Domain/Components/DataSource/User/UserRepository.php +++ b/src/Domain/Components/DataSource/User/UserRepository.php @@ -13,22 +13,25 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; +use Phalcon\Api\Domain\Components\Constants\Dates; use Phalcon\Api\Domain\Components\DataSource\AbstractRepository; use Phalcon\Api\Domain\Components\Enums\Common\FlagsEnum; +use Phalcon\DataMapper\Pdo\Connection; use Phalcon\DataMapper\Query\Insert; +use Phalcon\DataMapper\Query\Select; use Phalcon\DataMapper\Query\Update; +use function array_filter; + /** * @phpstan-import-type TUserRecord from UserTypes - * @phpstan-import-type TUserInsert from UserTypes - * @phpstan-import-type TUserUpdate from UserTypes * * The 'final' keyword was intentionally removed from this class to allow * extension for testing purposes (e.g., mocking in unit tests). * * Please avoid extending this class in production code unless absolutely necessary. */ -class UserRepository extends AbstractRepository +class UserRepository extends AbstractRepository implements UserRepositoryInterface { /** * @var string @@ -39,111 +42,168 @@ class UserRepository extends AbstractRepository */ protected string $table = 'co_users'; + public function __construct( + Connection $connection, + private readonly UserMapper $mapper, + ) { + parent::__construct($connection); + } + + /** * @param string $email * - * @return TUserRecord + * @return User|null */ - public function findByEmail(string $email): array + public function findByEmail(string $email): ?User { - $result = []; if (true !== empty($email)) { return $this->findOneBy( [ - 'usr_email' => $email, + 'usr_email' => $email, 'usr_status_flag' => FlagsEnum::Active->value, ] ); } - return $result; + return null; } /** - * @param TUserInsert $userData + * @param int $recordId * - * @return int + * @return User|null */ - public function insert(array $userData): int + public function findById(int $recordId): ?User { - $createdUserId = $userData['createdUserId']; - $updatedUserId = $userData['updatedUserId']; - - $columns = [ - 'usr_status_flag' => $userData['status'], - 'usr_email' => $userData['email'], - 'usr_password' => $userData['password'], - 'usr_name_prefix' => $userData['namePrefix'], - 'usr_name_first' => $userData['nameFirst'], - 'usr_name_middle' => $userData['nameMiddle'], - 'usr_name_last' => $userData['nameLast'], - 'usr_name_suffix' => $userData['nameSuffix'], - 'usr_issuer' => $userData['issuer'], - 'usr_token_password' => $userData['tokenPassword'], - 'usr_token_id' => $userData['tokenId'], - 'usr_preferences' => $userData['preferences'], - 'usr_created_date' => $userData['createdDate'], - 'usr_updated_date' => $userData['updatedDate'], - ]; - - $insert = Insert::new($this->connection); + if ($recordId > 0) { + return $this->findOneBy( + [ + $this->idField => $recordId, + ] + ); + } - $insert - ->into($this->table) - ->columns($columns) + return null; + } + + + /** + * @param array $criteria + * + * @return User|null + */ + public function findOneBy(array $criteria): ?User + { + $select = Select::new($this->connection); + + /** @var TUserRecord $result */ + $result = $select + ->from($this->table) + ->whereEquals($criteria) + ->fetchOne() ; - if ($createdUserId > 0) { - $insert->column('usr_created_usr_id', $createdUserId); + if (empty($result)) { + return null; + } + + return $this->mapper->domain($result); + } + + + /** + * @param User $user + * + * @return int + */ + public function insert(User $user): int + { + $row = $this->mapper->db($user); + $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); + + /** + * @todo this should not be here - the insert should just add data not validate + */ + if (true === empty($row['usr_created_date'])) { + $row['usr_created_date'] = $now; } - if ($updatedUserId > 0) { - $insert->column('usr_updated_usr_id', $updatedUserId); + if (true === empty($row['usr_updated_date'])) { + $row['usr_updated_date'] = $now; } - $insert->perform(); + /** + * Remove usr_id just in case + */ + unset($row['usr_id']); + + /** + * Cleanup empty fields if needed + */ + $columns = $this->cleanupFields($row); + $insert = Insert::new($this->connection); + $insert + ->into($this->table) + ->columns($columns) + ->perform() + ; return (int)$insert->getLastInsertId(); } /** - * @param TUserUpdate $userData + * @param User $user * * @return int */ - public function update(array $userData): int + public function update(User $user): int { - $userId = $userData['id']; - $updatedUserId = $userData['updatedUserId']; - - $columns = [ - 'usr_status_flag' => $userData['status'], - 'usr_email' => $userData['email'], - 'usr_password' => $userData['password'], - 'usr_name_prefix' => $userData['namePrefix'], - 'usr_name_first' => $userData['nameFirst'], - 'usr_name_middle' => $userData['nameMiddle'], - 'usr_name_last' => $userData['nameLast'], - 'usr_name_suffix' => $userData['nameSuffix'], - 'usr_issuer' => $userData['issuer'], - 'usr_token_password' => $userData['tokenPassword'], - 'usr_token_id' => $userData['tokenId'], - 'usr_preferences' => $userData['preferences'], - 'usr_updated_date' => $userData['updatedDate'], - ]; - - $update = Update::new($this->connection); + $row = $this->mapper->db($user); + $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); + $userId = $row['usr_id']; + /** + * @todo this should not be here - the update should just add data not validate + */ + /** + * Set updated date to now if it has not been set + */ + if (true === empty($row['usr_updated_date'])) { + $row['usr_updated_date'] = $now; + } + + /** + * Remove createdDate and createdUserId - cannot be changed. This + * needs to be here because we don't want to touch those fields. + */ + unset($row['usr_created_date'], $row['usr_created_usr_id']); + + /** + * Cleanup empty fields if needed + */ + $columns = $this->cleanupFields($row); + $update = Update::new($this->connection); $update ->table($this->table) ->columns($columns) ->where('usr_id = ', $userId) + ->perform() ; - if ($updatedUserId > 0) { - $update->column('usr_updated_usr_id', $updatedUserId); - } + return $userId; + } - $update->perform(); + /** + * @param array $row + * + * @return array + */ + private function cleanupFields(array $row): array + { + unset($row['usr_id']); - return (int)$userId; + return array_filter( + $row, + static fn($v) => $v !== null && $v !== '' + ); } } From baa077d0d357743a9469b7e10d4e1ddeeb990059 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:42:43 -0600 Subject: [PATCH 14/74] [#.x] - adjusted for new dependencies --- .../Services/User/AbstractUserService.php | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Domain/Services/User/AbstractUserService.php b/src/Domain/Services/User/AbstractUserService.php index f6d5c84..684a323 100644 --- a/src/Domain/Services/User/AbstractUserService.php +++ b/src/Domain/Services/User/AbstractUserService.php @@ -15,17 +15,62 @@ use Phalcon\Api\Domain\ADR\DomainInterface; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; +use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; +use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\DataSource\User\UserInput; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; +use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; use Phalcon\Api\Domain\Components\Encryption\Security; -use Phalcon\Filter\Filter; +use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Components\Payload; + +use function array_shift; abstract class AbstractUserService implements DomainInterface { + protected HttpCodesEnum $errorMessage; + public function __construct( protected readonly QueryRepository $repository, - protected readonly TransportRepository $transport, - protected readonly Filter $filter, + protected readonly UserMapper $mapper, + protected readonly UserValidator $validator, + protected readonly SanitizerInterface $sanitizer, protected readonly Security $security, + ) { } + + /** + * @param string $message + * + * @return Payload + */ + protected function getErrorPayload(string $message): Payload + { + return Payload::error( + [ + $this->errorMessage->text() . $message, + ] + ); + } + + /** + * @param UserInput $inputObject + * + * @return array + */ + protected function processPassword(UserInput $inputObject): User + { + $inputData = $inputObject->toArray(); + $inputData = array_shift($inputData); + + if (null !== $inputData['password']) { + $plain = $inputData['password']; + $hashed = $this->security->hash($plain); + + $inputData['password'] = $hashed; + } + + return $this->mapper->input($inputData); + } } From 61649a76573527795041b3b4287ba900377b9e8a Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:43:03 -0600 Subject: [PATCH 15/74] [#.x] - adjusted for new functionality --- tests/Fixtures/Domain/Services/ServiceFixture.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/Fixtures/Domain/Services/ServiceFixture.php b/tests/Fixtures/Domain/Services/ServiceFixture.php index 8e04a05..fc9dee9 100644 --- a/tests/Fixtures/Domain/Services/ServiceFixture.php +++ b/tests/Fixtures/Domain/Services/ServiceFixture.php @@ -13,19 +13,13 @@ namespace Phalcon\Api\Tests\Fixtures\Domain\Services; -use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\ADR\DomainInterface; -use Phalcon\Domain\Payload; +use Phalcon\Api\Domain\Components\Payload; final readonly class ServiceFixture implements DomainInterface { public function __invoke(array $input): Payload { - return new Payload( - DomainStatus::SUCCESS, - [ - 'data' => $input, - ] - ); + return Payload::success($input); } } From c5f465a4c4b76bb94032adc331fc68df84855ce9 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:43:38 -0600 Subject: [PATCH 16/74] [#.x] - refactored for new DTOs --- src/Domain/Services/Auth/LoginPostService.php | 67 ++++++++++--------- .../Services/Auth/LoginPostServiceTest.php | 31 ++++++++- 2 files changed, 66 insertions(+), 32 deletions(-) diff --git a/src/Domain/Services/Auth/LoginPostService.php b/src/Domain/Services/Auth/LoginPostService.php index d6f9a4f..fe00570 100644 --- a/src/Domain/Services/Auth/LoginPostService.php +++ b/src/Domain/Services/Auth/LoginPostService.php @@ -13,11 +13,12 @@ namespace Phalcon\Api\Domain\Services\Auth; -use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; +use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Domain\Payload; +use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TUserDbRecord from UserTypes @@ -35,16 +36,15 @@ public function __invoke(array $input): Payload /** * Get email and password from the input and sanitize them */ - $email = (string)($input['email'] ?? ''); - $password = (string)($input['password'] ?? ''); - $email = $this->filter->string($email); - $password = $this->filter->string($password); + $inputObject = AuthInput::new($this->sanitizer, $input); + $email = $inputObject->email; + $password = $inputObject->password; /** * Check if email or password are empty */ if (true === empty($email) || true === empty($password)) { - return $this->getUnauthorizedPayload( + return Payload::unauthorized( [HttpCodesEnum::AppIncorrectCredentials->error()] ); } @@ -52,18 +52,22 @@ public function __invoke(array $input): Payload /** * Find the user in the database */ - $dbUser = $this->repository->user()->findByEmail($email); - $dbUserId = (int)($dbUser['usr_id'] ?? 0); - $dbPassword = $dbUser['usr_password'] ?? ''; + $domainUser = $this->repository->user()->findByEmail($email); /** - * Check if the user exists and if the password matches + * Check if the user exists */ - if ( - $dbUserId < 1 || - true !== $this->security->verify($password, $dbPassword) - ) { - return $this->getUnauthorizedPayload( + if (null === $domainUser) { + return Payload::unauthorized( + [HttpCodesEnum::AppIncorrectCredentials->error()] + ); + } + + /** + * Check if the password matches + */ + if (true !== $this->security->verify($password, $domainUser->password)) { + return Payload::unauthorized( [HttpCodesEnum::AppIncorrectCredentials->error()] ); } @@ -71,15 +75,8 @@ public function __invoke(array $input): Payload /** * Get a new token for this user */ - /** @var TUserDbRecord $dbUser */ - $token = $this->jwtToken->getForUser($dbUser); - $refreshToken = $this->jwtToken->getRefreshForUser($dbUser); - $domainUser = $this->transport->newUser($dbUser); - $results = $this->transport->newLoginUser( - $domainUser, - $token, - $refreshToken - ); + $token = $this->jwtToken->getForUser($domainUser); + $refreshToken = $this->jwtToken->getRefreshForUser($domainUser); /** * Store the token in cache @@ -90,11 +87,19 @@ public function __invoke(array $input): Payload /** * Send the payload back */ - return new Payload( - DomainStatus::SUCCESS, - [ - 'data' => $results, - ] - ); + $results = [ + 'authenticated' => true, + 'user' => [ + 'id' => $domainUser->id, + 'name' => $domainUser->fullName(), + 'email' => $domainUser->email, + ], + 'jwt' => [ + 'token' => $token, + 'refreshToken' => $refreshToken, + ], + ]; + + return Payload::success($results); } } diff --git a/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php b/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php index 2049fb1..b3d60be 100644 --- a/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php +++ b/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php @@ -13,6 +13,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\Auth; +use Faker\Factory; use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; @@ -109,7 +110,7 @@ public function testServiceWithCredentials(): void $this->assertNotEmpty($jwt['refreshToken']); } - public function testServiceWrongCredentials(): void + public function testServiceWrongCredentialsForUser(): void { /** @var LoginPostService $service */ $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); @@ -140,4 +141,32 @@ public function testServiceWrongCredentials(): void $actual = $actual['errors'][0]; $this->assertSame($expected, $actual); } + + public function testServiceWrongCredentials(): void + { + $faker = Factory::create(); + /** @var LoginPostService $service */ + $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); + + /** + * Issue a wrong password + */ + $payload = [ + 'email' => $faker->email(), + 'password' => $faker->password(), + ]; + + $payload = $service->__invoke($payload); + + $expected = DomainStatus::UNAUTHORIZED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $expected = HttpCodesEnum::AppIncorrectCredentials->error(); + $actual = $actual['errors'][0]; + $this->assertSame($expected, $actual); + } } From 496fca0d774f29adb71cb4ef96b8eadce12b807e Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:43:52 -0600 Subject: [PATCH 17/74] [#.x] - refactored for new DTOs --- .../Services/Auth/LogoutPostService.php | 32 ++- .../Services/Auth/RefreshPostService.php | 46 ++--- .../Services/Auth/LogoutPostServiceTest.php | 185 ++++++++++-------- .../Services/Auth/RefreshPostServiceTest.php | 172 ++++++++-------- 4 files changed, 224 insertions(+), 211 deletions(-) diff --git a/src/Domain/Services/Auth/LogoutPostService.php b/src/Domain/Services/Auth/LogoutPostService.php index 1bec8d1..0a50524 100644 --- a/src/Domain/Services/Auth/LogoutPostService.php +++ b/src/Domain/Services/Auth/LogoutPostService.php @@ -13,12 +13,12 @@ namespace Phalcon\Api\Domain\Services\Auth; -use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Domain\Payload; +use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TUserDbRecord from UserTypes @@ -40,8 +40,8 @@ public function __invoke(array $input): Payload /** * Get the token */ - $token = (string)($input['token'] ?? ''); - $token = $this->filter->string($token); + $inputObject = AuthInput::new($this->sanitizer, $input); + $token = $inputObject->token; /** * Validation @@ -49,7 +49,7 @@ public function __invoke(array $input): Payload * Empty token */ if (true === empty($token)) { - return $this->getUnauthorizedPayload( + return Payload::unauthorized( [HttpCodesEnum::AppTokenNotPresent->error()] ); } @@ -62,7 +62,7 @@ public function __invoke(array $input): Payload $tokenObject = $this->jwtToken->getObject($token); $isRefresh = $tokenObject->getClaims()->get(JWTEnum::Refresh->value); if (false === $isRefresh) { - return $this->getUnauthorizedPayload( + return Payload::unauthorized( [HttpCodesEnum::AppTokenNotValid->error()] ); } @@ -70,22 +70,21 @@ public function __invoke(array $input): Payload /** * Get the user - if empty return error */ - $user = $this + $domainUser = $this ->jwtToken ->getUser($this->repository, $tokenObject) ; - if (true === empty($user)) { - return $this->getUnauthorizedPayload( + + if (null === $domainUser) { + return Payload::unauthorized( [HttpCodesEnum::AppTokenInvalidUser->error()] ); } - $domainUser = $this->transport->newUser($user); - /** @var TValidationErrors $errors */ $errors = $this->jwtToken->validate($tokenObject, $domainUser); if (true !== empty($errors)) { - return $this->getUnauthorizedPayload($errors); + return Payload::unauthorized($errors); } /** @@ -96,13 +95,10 @@ public function __invoke(array $input): Payload /** * Send the payload back */ - return new Payload( - DomainStatus::SUCCESS, + return Payload::success( [ - 'data' => [ - 'authenticated' => false, - ], - ] + 'authenticated' => false, + ], ); } } diff --git a/src/Domain/Services/Auth/RefreshPostService.php b/src/Domain/Services/Auth/RefreshPostService.php index 2431936..63b978f 100644 --- a/src/Domain/Services/Auth/RefreshPostService.php +++ b/src/Domain/Services/Auth/RefreshPostService.php @@ -13,12 +13,12 @@ namespace Phalcon\Api\Domain\Services\Auth; -use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Domain\Payload; +use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TUserDbRecord from UserTypes @@ -37,8 +37,8 @@ public function __invoke(array $input): Payload /** * Get email and password from the input and sanitize them */ - $token = (string)($input['token'] ?? ''); - $token = $this->filter->string($token); + $inputObject = AuthInput::new($this->sanitizer, $input); + $token = $inputObject->token; /** * Validation @@ -46,7 +46,7 @@ public function __invoke(array $input): Payload * Empty token */ if (true === empty($token)) { - return $this->getUnauthorizedPayload( + return Payload::unauthorized( [HttpCodesEnum::AppTokenNotPresent->error()] ); } @@ -59,7 +59,7 @@ public function __invoke(array $input): Payload $tokenObject = $this->jwtToken->getObject($token); $isRefresh = $tokenObject->getClaims()->get(JWTEnum::Refresh->value); if (false === $isRefresh) { - return $this->getUnauthorizedPayload( + return Payload::unauthorized( [HttpCodesEnum::AppTokenNotValid->error()] ); } @@ -67,35 +67,24 @@ public function __invoke(array $input): Payload /** * Get the user - if empty return error */ - $user = $this + $domainUser = $this ->jwtToken ->getUser($this->repository, $tokenObject) ; - if (true === empty($user)) { - return $this->getUnauthorizedPayload( + if (null === $domainUser) { + return Payload::unauthorized( [HttpCodesEnum::AppTokenInvalidUser->error()] ); } - $domainUser = $this->transport->newUser($user); - /** @var TValidationErrors $errors */ $errors = $this->jwtToken->validate($tokenObject, $domainUser); if (true !== empty($errors)) { - return $this->getUnauthorizedPayload($errors); + return Payload::unauthorized($errors); } - /** - * @todo change this to be the domain user - */ - $userPayload = [ - 'usr_issuer' => $domainUser->getIssuer(), - 'usr_token_password' => $domainUser->getTokenPassword(), - 'usr_token_id' => $domainUser->getTokenId(), - 'usr_id' => $domainUser->getId(), - ]; - $newToken = $this->jwtToken->getForUser($userPayload); - $newRefreshToken = $this->jwtToken->getRefreshForUser($userPayload); + $newToken = $this->jwtToken->getForUser($domainUser); + $newRefreshToken = $this->jwtToken->getRefreshForUser($domainUser); /** * Invalidate old tokens, store new tokens in cache @@ -107,14 +96,11 @@ public function __invoke(array $input): Payload /** * Send the payload back */ - return new Payload( - DomainStatus::SUCCESS, + return Payload::success( [ - 'data' => [ - 'token' => $newToken, - 'refreshToken' => $newRefreshToken, - ], - ] + 'token' => $newToken, + 'refreshToken' => $newRefreshToken, + ], ); } } diff --git a/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php b/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php index 75f9637..57b9be8 100644 --- a/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php +++ b/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php @@ -16,7 +16,7 @@ use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\Components\Cache\Cache; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Services\Auth\LoginPostService; @@ -51,49 +51,56 @@ public function testServiceEmptyToken(): void public function testServiceInvalidToken(): void { - $user = $this->getNewUserData(); - $errors = [ + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + $user = $this->getNewUserData(); + $user['usr_id'] = 1; + $domainUser = $userMapper->domain($user); + $errors = [ ['Incorrect token data'], ]; /** * Set up mock services */ - $mockItem = $this->getMockBuilder(Item::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'get', - ] - ) - ->getMock() + $mockItem = $this + ->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'get', + ] + ) + ->getMock() ; $mockItem->method('get')->willReturn(true); - $mockToken = $this->getMockBuilder(Token::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getClaims', - ] - ) - ->getMock() + $mockToken = $this + ->getMockBuilder(Token::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getClaims', + ] + ) + ->getMock() ; $mockToken->method('getClaims')->willReturn($mockItem); - $mockJWT = $this->getMockBuilder(JWTToken::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getObject', - 'getUser', - 'validate', - ] - ) - ->getMock() + $mockJWT = $this + ->getMockBuilder(JWTToken::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getObject', + 'getUser', + 'validate', + ] + ) + ->getMock() ; $mockJWT->method('getObject')->willReturn($mockToken); - $mockJWT->method('getUser')->willReturn($user); + $mockJWT->method('getUser')->willReturn($domainUser); $mockJWT->method('validate')->willReturn($errors); @@ -124,36 +131,39 @@ public function testServiceNotRefreshToken(): void /** * Set up mock services */ - $mockItem = $this->getMockBuilder(Item::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'get', - ] - ) - ->getMock() + $mockItem = $this + ->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'get', + ] + ) + ->getMock() ; $mockItem->method('get')->willReturn(false); - $mockToken = $this->getMockBuilder(Token::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getClaims', - ] - ) - ->getMock() + $mockToken = $this + ->getMockBuilder(Token::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getClaims', + ] + ) + ->getMock() ; $mockToken->method('getClaims')->willReturn($mockItem); - $mockJWT = $this->getMockBuilder(JWTToken::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getObject', - ] - ) - ->getMock() + $mockJWT = $this + ->getMockBuilder(JWTToken::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getObject', + ] + ) + ->getMock() ; $mockJWT->method('getObject')->willReturn($mockToken); @@ -185,14 +195,14 @@ public function testServiceNotRefreshToken(): void public function testServiceSuccess(): void { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); /** @var LogoutPostService $service */ $logoutService = $this->container->get(Container::AUTH_LOGOUT_POST_SERVICE); /** @var Cache $cache */ $cache = $this->container->getShared(Container::CACHE); /** @var LoginPostService $service */ - $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); - /** @var TransportRepository $transport */ - $transport = $this->container->getShared(Container::REPOSITORY_TRANSPORT); + $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); $migration = new UsersMigration($this->getConnection()); /** @@ -226,7 +236,7 @@ public function testServiceSuccess(): void $this->assertTrue($actual); $token = $data['jwt']['token']; - $domainUser = $transport->newUser($dbUser); + $domainUser = $userMapper->domain($dbUser); $tokenKey = $cache->getCacheTokenKey($domainUser, $token); $actual = $cache->has($tokenKey); @@ -271,43 +281,50 @@ public function testServiceSuccess(): void public function testServiceWrongUser(): void { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + $domainUser = $userMapper->domain([]); + /** * Set up mock services */ - $mockItem = $this->getMockBuilder(Item::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'get', - ] - ) - ->getMock() + $mockItem = $this + ->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'get', + ] + ) + ->getMock() ; $mockItem->method('get')->willReturn(true); - $mockToken = $this->getMockBuilder(Token::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getClaims', - ] - ) - ->getMock() + $mockToken = $this + ->getMockBuilder(Token::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getClaims', + ] + ) + ->getMock() ; $mockToken->method('getClaims')->willReturn($mockItem); - $mockJWT = $this->getMockBuilder(JWTToken::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getObject', - 'getUser', - ] - ) - ->getMock() + $mockJWT = $this + ->getMockBuilder(JWTToken::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getObject', + 'getUser', + ] + ) + ->getMock() ; $mockJWT->method('getObject')->willReturn($mockToken); - $mockJWT->method('getUser')->willReturn([]); + $mockJWT->method('getUser')->willReturn(null); /** diff --git a/tests/Unit/Domain/Services/Auth/RefreshPostServiceTest.php b/tests/Unit/Domain/Services/Auth/RefreshPostServiceTest.php index f274adb..406b74d 100644 --- a/tests/Unit/Domain/Services/Auth/RefreshPostServiceTest.php +++ b/tests/Unit/Domain/Services/Auth/RefreshPostServiceTest.php @@ -15,6 +15,7 @@ use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Services\Auth\LoginPostService; @@ -49,49 +50,56 @@ public function testServiceEmptyToken(): void public function testServiceInvalidToken(): void { - $user = $this->getNewUserData(); - $errors = [ + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + $user = $this->getNewUserData(); + $user['usr_id'] = 1; + $domainUser = $userMapper->domain($user); + $errors = [ ['Incorrect token data'], ]; /** * Set up mock services */ - $mockItem = $this->getMockBuilder(Item::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'get', - ] - ) - ->getMock() + $mockItem = $this + ->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'get', + ] + ) + ->getMock() ; $mockItem->method('get')->willReturn(true); - $mockToken = $this->getMockBuilder(Token::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getClaims', - ] - ) - ->getMock() + $mockToken = $this + ->getMockBuilder(Token::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getClaims', + ] + ) + ->getMock() ; $mockToken->method('getClaims')->willReturn($mockItem); - $mockJWT = $this->getMockBuilder(JWTToken::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getObject', - 'getUser', - 'validate', - ] - ) - ->getMock() + $mockJWT = $this + ->getMockBuilder(JWTToken::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getObject', + 'getUser', + 'validate', + ] + ) + ->getMock() ; $mockJWT->method('getObject')->willReturn($mockToken); - $mockJWT->method('getUser')->willReturn($user); + $mockJWT->method('getUser')->willReturn($domainUser); $mockJWT->method('validate')->willReturn($errors); @@ -122,36 +130,39 @@ public function testServiceNotRefreshToken(): void /** * Set up mock services */ - $mockItem = $this->getMockBuilder(Item::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'get', - ] - ) - ->getMock() + $mockItem = $this + ->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'get', + ] + ) + ->getMock() ; $mockItem->method('get')->willReturn(false); - $mockToken = $this->getMockBuilder(Token::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getClaims', - ] - ) - ->getMock() + $mockToken = $this + ->getMockBuilder(Token::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getClaims', + ] + ) + ->getMock() ; $mockToken->method('getClaims')->willReturn($mockItem); - $mockJWT = $this->getMockBuilder(JWTToken::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getObject', - ] - ) - ->getMock() + $mockJWT = $this + ->getMockBuilder(JWTToken::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getObject', + ] + ) + ->getMock() ; $mockJWT->method('getObject')->willReturn($mockToken); @@ -231,40 +242,43 @@ public function testServiceWrongUser(): void /** * Set up mock services */ - $mockItem = $this->getMockBuilder(Item::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'get', - ] - ) - ->getMock() + $mockItem = $this + ->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'get', + ] + ) + ->getMock() ; $mockItem->method('get')->willReturn(true); - $mockToken = $this->getMockBuilder(Token::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getClaims', - ] - ) - ->getMock() + $mockToken = $this + ->getMockBuilder(Token::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getClaims', + ] + ) + ->getMock() ; $mockToken->method('getClaims')->willReturn($mockItem); - $mockJWT = $this->getMockBuilder(JWTToken::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'getObject', - 'getUser', - ] - ) - ->getMock() + $mockJWT = $this + ->getMockBuilder(JWTToken::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getObject', + 'getUser', + ] + ) + ->getMock() ; $mockJWT->method('getObject')->willReturn($mockToken); - $mockJWT->method('getUser')->willReturn([]); + $mockJWT->method('getUser')->willReturn(null); /** From 38f7827848d70fa09caac33f8e396cefc68247b1 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:44:20 -0600 Subject: [PATCH 18/74] [#.x] - refactored for new DTOs --- .../Services/User/UserDeleteService.php | 25 +- src/Domain/Services/User/UserGetService.php | 30 +-- src/Domain/Services/User/UserPostService.php | 124 +--------- src/Domain/Services/User/UserPutService.php | 131 ++-------- .../DataSource/User/UserRepositoryTest.php | 37 +-- .../Services/User/UserServiceDispatchTest.php | 9 +- .../Services/User/UserServicePostTest.php | 101 ++++---- .../Services/User/UserServicePutTest.php | 228 ++++++++++++------ 8 files changed, 277 insertions(+), 408 deletions(-) diff --git a/src/Domain/Services/User/UserDeleteService.php b/src/Domain/Services/User/UserDeleteService.php index 77b45bd..72b55f1 100644 --- a/src/Domain/Services/User/UserDeleteService.php +++ b/src/Domain/Services/User/UserDeleteService.php @@ -13,9 +13,9 @@ namespace Phalcon\Api\Domain\Services\User; -use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Domain\Payload; +use Phalcon\Api\Domain\Components\DataSource\User\UserInput; +use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TUserInput from InputTypes @@ -29,7 +29,8 @@ final class UserDeleteService extends AbstractUserService */ public function __invoke(array $input): Payload { - $userId = $this->filter->absint($input['id'] ?? 0); + $inputObject = UserInput::new($this->sanitizer, $input); + $userId = $inputObject->id; /** * Success @@ -38,13 +39,10 @@ public function __invoke(array $input): Payload $rows = $this->repository->user()->deleteById($userId); if ($rows > 0) { - return new Payload( - DomainStatus::DELETED, + return Payload::deleted( [ - 'data' => [ - 'Record deleted successfully [#' . $userId . '].', - ], - ] + 'Record deleted successfully [#' . $userId . '].', + ], ); } } @@ -52,13 +50,6 @@ public function __invoke(array $input): Payload /** * 404 */ - return new Payload( - DomainStatus::NOT_FOUND, - [ - 'errors' => [ - 'Record(s) not found', - ], - ] - ); + return Payload::notFound(['Record(s) not found']); } } diff --git a/src/Domain/Services/User/UserGetService.php b/src/Domain/Services/User/UserGetService.php index c71735b..b487174 100644 --- a/src/Domain/Services/User/UserGetService.php +++ b/src/Domain/Services/User/UserGetService.php @@ -13,9 +13,9 @@ namespace Phalcon\Api\Domain\Services\User; -use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Domain\Payload; +use Phalcon\Api\Domain\Components\DataSource\User\UserInput; +use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TUserInput from InputTypes @@ -29,35 +29,23 @@ final class UserGetService extends AbstractUserService */ public function __invoke(array $input): Payload { - $userId = $this->filter->absint($input['id'] ?? 0); + $inputObject = UserInput::new($this->sanitizer, $input); + $userId = $inputObject->id; /** * Success */ if ($userId > 0) { - $dbUser = $this->repository->user()->findById($userId); - $user = $this->transport->newUser($dbUser); - - if (true !== $user->isEmpty()) { - return new Payload( - DomainStatus::SUCCESS, - [ - 'data' => $user->toArray(), - ] - ); + $user = $this->repository->user()->findById($userId); + + if (null !== $user) { + return Payload::success($user->toArray()); } } /** * 404 */ - return new Payload( - DomainStatus::NOT_FOUND, - [ - 'errors' => [ - 'Record(s) not found', - ], - ] - ); + return Payload::notFound(['Record(s) not found']); } } diff --git a/src/Domain/Services/User/UserPostService.php b/src/Domain/Services/User/UserPostService.php index 499cc04..da19d0d 100644 --- a/src/Domain/Services/User/UserPostService.php +++ b/src/Domain/Services/User/UserPostService.php @@ -13,12 +13,11 @@ namespace Phalcon\Api\Domain\Services\User; -use PayloadInterop\DomainStatus; use PDOException; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Constants\Dates; +use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Domain\Payload; +use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TUserSanitizedInsertInput from InputTypes @@ -27,6 +26,8 @@ */ final class UserPostService extends AbstractUserService { + protected HttpCodesEnum $errorMessage = HttpCodesEnum::AppCannotCreateDatabaseRecord; + /** * @param TUserInput $input * @@ -34,28 +35,20 @@ final class UserPostService extends AbstractUserService */ public function __invoke(array $input): Payload { - $inputData = $this->sanitizeInput($input); - $errors = $this->validateInput($inputData); + $inputObject = UserInput::new($this->sanitizer, $input); + $errors = $this->validator->validate($inputObject); /** * Errors exist - return early */ - if (!empty($errors)) { - return new Payload( - DomainStatus::INVALID, - [ - 'errors' => $errors, - ] - ); + if (true !== empty($errors)) { + return Payload::invalid($errors); } /** * The password needs to be hashed */ - $password = $inputData['password']; - $hashed = $this->security->hash($password); - - $inputData['password'] = $hashed; + $domainUser = $this->processPassword($inputObject); /** * Insert the record @@ -64,7 +57,7 @@ public function __invoke(array $input): Payload $userId = $this ->repository ->user() - ->insert($inputData) + ->insert($domainUser) ; } catch (PDOException $ex) { /** @@ -80,104 +73,11 @@ public function __invoke(array $input): Payload /** * Get the user from the database */ - $dbUser = $this->repository->user()->findById($userId); - $domainUser = $this->transport->newUser($dbUser); + $domainUser = $this->repository->user()->findById($userId); /** * Return the user back */ - return new Payload( - DomainStatus::CREATED, - [ - 'data' => $domainUser->toArray(), - ] - ); - } - - /** - * @param string $message - * - * @return Payload - */ - private function getErrorPayload(string $message): Payload - { - return new Payload( - DomainStatus::ERROR, - [ - 'errors' => [ - HttpCodesEnum::AppCannotCreateDatabaseRecord->text() - . $message, - ], - ] - ); - } - - /** - * @param TUserInput $input - * - * @return TUserSanitizedInsertInput - */ - private function sanitizeInput(array $input): array - { - /** - * Only the fields we want - * - * @todo add sanitizers here - * @todo maybe this is another domain object? - */ - $sanitized = [ - 'status' => $input['status'] ?? 0, - 'email' => $input['email'] ?? '', - 'password' => $input['password'] ?? '', - 'namePrefix' => $input['namePrefix'] ?? '', - 'nameFirst' => $input['nameFirst'] ?? '', - 'nameLast' => $input['nameLast'] ?? '', - 'nameMiddle' => $input['nameMiddle'] ?? '', - 'nameSuffix' => $input['nameSuffix'] ?? '', - 'issuer' => $input['issuer'] ?? '', - 'tokenPassword' => $input['tokenPassword'] ?? '', - 'tokenId' => $input['tokenId'] ?? '', - 'preferences' => $input['preferences'] ?? '', - 'createdDate' => $input['createdDate'] ?? null, - 'createdUserId' => $input['createdUserId'] ?? 0, - 'updatedDate' => $input['updatedDate'] ?? null, - 'updatedUserId' => $input['updatedUserId'] ?? 0, - ]; - - if (empty($sanitized['createdDate'])) { - $sanitized['createdDate'] = Dates::toUTC('now', Dates::DATE_TIME_FORMAT); - } - - if (empty($sanitized['updatedDate'])) { - $sanitized['updatedDate'] = Dates::toUTC('now', Dates::DATE_TIME_FORMAT); - } - - return $sanitized; - } - - /** - * @param TUserSanitizedInsertInput $inputData - * - * @return TValidationErrors|array{} - */ - private function validateInput(array $inputData): array - { - $errors = []; - $required = [ - 'email', - 'password', - 'issuer', - 'tokenPassword', - 'tokenId', - ]; - - foreach ($required as $name) { - $field = $inputData[$name]; - if (true === empty($field)) { - $errors[] = ['Field ' . $name . ' cannot be empty.']; - } - } - - return $errors; + return Payload::created($domainUser->toArray()); } } diff --git a/src/Domain/Services/User/UserPutService.php b/src/Domain/Services/User/UserPutService.php index 22ad499..44c7366 100644 --- a/src/Domain/Services/User/UserPutService.php +++ b/src/Domain/Services/User/UserPutService.php @@ -13,12 +13,11 @@ namespace Phalcon\Api\Domain\Services\User; -use PayloadInterop\DomainStatus; use PDOException; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Constants\Dates; +use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Domain\Payload; +use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TUserSanitizedUpdateInput from InputTypes @@ -27,6 +26,8 @@ */ final class UserPutService extends AbstractUserService { + protected HttpCodesEnum $errorMessage = HttpCodesEnum::AppCannotUpdateDatabaseRecord; + /** * @param TUserInput $input * @@ -34,41 +35,39 @@ final class UserPutService extends AbstractUserService */ public function __invoke(array $input): Payload { - $inputData = $this->sanitizeInput($input); - $errors = $this->validateInput($inputData); + $inputObject = UserInput::new($this->sanitizer, $input); + $errors = $this->validator->validate($inputObject); /** * Errors exist - return early */ - if (!empty($errors)) { - return new Payload( - DomainStatus::INVALID, - [ - 'errors' => $errors, - ] - ); + if (true !== empty($errors)) { + return Payload::invalid($errors); } /** - * The password needs to be hashed + * Check if the user exists, If not, return an error */ - $password = $inputData['password']; - $hashed = $this->security->hash($password); + $userId = $inputObject->id; + $domainUser = $this->repository->user()->findById($userId); - $inputData['password'] = $hashed; + if (null === $domainUser) { + return Payload::notFound(['Record(s) not found']); + } /** - * Update the record + * The password needs to be hashed */ + $domainUser = $this->processPassword($inputObject); + /** - * @todo get the user from the database to make sure that it is valid + * Update the record */ - try { $userId = $this ->repository ->user() - ->update($inputData) + ->update($domainUser) ; } catch (PDOException $ex) { /** @@ -84,99 +83,11 @@ public function __invoke(array $input): Payload /** * Get the user from the database */ - $dbUser = $this->repository->user()->findById($userId); - $domainUser = $this->transport->newUser($dbUser); + $domainUser = $this->repository->user()->findById($userId); /** * Return the user back */ - return new Payload( - DomainStatus::UPDATED, - [ - 'data' => $domainUser->toArray(), - ] - ); - } - - /** - * @param string $message - * - * @return Payload - */ - private function getErrorPayload(string $message): Payload - { - return new Payload( - DomainStatus::ERROR, - [ - 'errors' => [ - HttpCodesEnum::AppCannotUpdateDatabaseRecord->text() - . $message, - ], - ] - ); - } - - /** - * @param TUserInput $input - * - * @return TUserSanitizedUpdateInput - */ - private function sanitizeInput(array $input): array - { - /** - * Only the fields we want - * - * @todo add sanitizers here - * @todo maybe this is another domain object? - */ - $sanitized = [ - 'id' => $input['id'] ?? 0, - 'status' => $input['status'] ?? 0, - 'email' => $input['email'] ?? '', - 'password' => $input['password'] ?? '', - 'namePrefix' => $input['namePrefix'] ?? '', - 'nameFirst' => $input['nameFirst'] ?? '', - 'nameLast' => $input['nameLast'] ?? '', - 'nameMiddle' => $input['nameMiddle'] ?? '', - 'nameSuffix' => $input['nameSuffix'] ?? '', - 'issuer' => $input['issuer'] ?? '', - 'tokenPassword' => $input['tokenPassword'] ?? '', - 'tokenId' => $input['tokenId'] ?? '', - 'preferences' => $input['preferences'] ?? '', - 'updatedDate' => $input['updatedDate'] ?? null, - 'updatedUserId' => $input['updatedUserId'] ?? 0, - ]; - - if (empty($sanitized['updatedDate'])) { - $sanitized['updatedDate'] = Dates::toUTC('now', Dates::DATE_TIME_FORMAT); - } - - return $sanitized; - } - - /** - * @param TUserSanitizedUpdateInput $inputData - * - * @return TValidationErrors|array{} - */ - private function validateInput(array $inputData): array - { - $errors = []; - $required = [ - 'email', - 'password', - 'issuer', - 'tokenPassword', - 'tokenId', - ]; - - foreach ($required as $name) { - $field = $inputData[$name]; - if (true === empty($field)) { - $errors[] = ['Field ' . $name . ' cannot be empty.']; - } - } - - return $errors; + return Payload::updated($domainUser->toArray()); } } diff --git a/tests/Unit/Domain/Components/DataSource/User/UserRepositoryTest.php b/tests/Unit/Domain/Components/DataSource/User/UserRepositoryTest.php index 1443a5e..569a63c 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserRepositoryTest.php +++ b/tests/Unit/Domain/Components/DataSource/User/UserRepositoryTest.php @@ -15,6 +15,7 @@ use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; +use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; @@ -56,74 +57,74 @@ public function testFindById(): void $this->runAssertions($migrationUser, $repositoryUser); } - private function runAssertions(array $dbUser, array $user): void + private function runAssertions(array $dbUser, User $user): void { $expected = $dbUser['usr_id']; - $actual = $user['usr_id']; + $actual = $user->id; $this->assertSame($expected, $actual); $expected = $dbUser['usr_status_flag']; - $actual = $user['usr_status_flag']; + $actual = $user->status; $this->assertSame($expected, $actual); $expected = $dbUser['usr_email']; - $actual = $user['usr_email']; + $actual = $user->email; $this->assertSame($expected, $actual); $expected = $dbUser['usr_password']; - $actual = $user['usr_password']; + $actual = $user->password; $this->assertSame($expected, $actual); $expected = $dbUser['usr_name_prefix']; - $actual = $user['usr_name_prefix']; + $actual = $user->namePrefix; $this->assertSame($expected, $actual); $expected = $dbUser['usr_name_first']; - $actual = $user['usr_name_first']; + $actual = $user->nameFirst; $this->assertSame($expected, $actual); $expected = $dbUser['usr_name_middle']; - $actual = $user['usr_name_middle']; + $actual = $user->nameMiddle; $this->assertSame($expected, $actual); $expected = $dbUser['usr_name_last']; - $actual = $user['usr_name_last']; + $actual = $user->nameLast; $this->assertSame($expected, $actual); $expected = $dbUser['usr_name_suffix']; - $actual = $user['usr_name_suffix']; + $actual = $user->nameSuffix; $this->assertSame($expected, $actual); $expected = $dbUser['usr_issuer']; - $actual = $user['usr_issuer']; + $actual = $user->issuer; $this->assertSame($expected, $actual); $expected = $dbUser['usr_token_password']; - $actual = $user['usr_token_password']; + $actual = $user->tokenPassword; $this->assertSame($expected, $actual); $expected = $dbUser['usr_token_id']; - $actual = $user['usr_token_id']; + $actual = $user->tokenId; $this->assertSame($expected, $actual); $expected = $dbUser['usr_preferences']; - $actual = $user['usr_preferences']; + $actual = $user->preferences; $this->assertSame($expected, $actual); $expected = $dbUser['usr_created_date']; - $actual = $user['usr_created_date']; + $actual = $user->createdDate; $this->assertSame($expected, $actual); $expected = $dbUser['usr_created_usr_id']; - $actual = $user['usr_created_usr_id']; + $actual = $user->createdUserId; $this->assertSame($expected, $actual); $expected = $dbUser['usr_updated_date']; - $actual = $user['usr_updated_date']; + $actual = $user->updatedDate; $this->assertSame($expected, $actual); $expected = $dbUser['usr_updated_usr_id']; - $actual = $user['usr_updated_usr_id']; + $actual = $user->updatedUserId; $this->assertSame($expected, $actual); } } diff --git a/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php b/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php index 2557457..0167686 100644 --- a/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php +++ b/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php @@ -16,6 +16,7 @@ use Phalcon\Api\Domain\Components\Cache\Cache; use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\TransportRepository; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\Enums\Http\RoutesEnum; use Phalcon\Api\Domain\Components\Env\EnvManager; use Phalcon\Api\Tests\AbstractUnitTestCase; @@ -31,14 +32,14 @@ public function testDispatchGet(): void $env = $this->container->getShared(Container::ENV); /** @var Cache $cache */ $cache = $this->container->getShared(Container::CACHE); - /** @var TransportRepository $transport */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); $migration = new UsersMigration($this->getConnection()); $dbUser = $this->getNewUser($migration); $userId = $dbUser['usr_id']; $token = $this->getUserToken($dbUser); - $domainUser = $transport->newUser($dbUser); + $domainUser = $userMapper->domain($dbUser); /** * Store the token in the cache @@ -75,7 +76,7 @@ public function testDispatchGet(): void $actual = $errors; $this->assertSame($expected, $actual); - $user = $transport->newUser($dbUser); + $user = $userMapper->domain($dbUser); $expected = $user->toArray(); $actual = $data; $this->assertSame($expected, $actual); diff --git a/tests/Unit/Domain/Services/User/UserServicePostTest.php b/tests/Unit/Domain/Services/User/UserServicePostTest.php index 0cbd8f4..653de11 100644 --- a/tests/Unit/Domain/Services/User/UserServicePostTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePostTest.php @@ -18,14 +18,15 @@ use PDOException; use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; -use Phalcon\Api\Domain\Components\Encryption\Security; use Phalcon\Api\Domain\Services\User\UserPostService; use Phalcon\Api\Tests\AbstractUnitTestCase; -use Phalcon\Filter\Filter; use PHPUnit\Framework\Attributes\BackupGlobals; +use function array_shift; +use function htmlspecialchars; + #[BackupGlobals(true)] final class UserServicePostTest extends AbstractUnitTestCase { @@ -41,10 +42,7 @@ public function testServiceFailureNoIdReturned(): void ) ->getMock() ; - $userRepository - ->method('insert') - ->willReturn(0) - ; + $userRepository->method('insert')->willReturn(0); $repository = $this ->getMockBuilder(QueryRepository::class) @@ -56,30 +54,24 @@ public function testServiceFailureNoIdReturned(): void ) ->getMock() ; - $repository - ->method('user') - ->willReturn($userRepository) - ; + $repository->method('user')->willReturn($userRepository); - /** - * Difference of achieving the same thing - see test above on - * how the class is used without getting it from the DI container - */ $this->container->setShared(Container::REPOSITORY, $repository); /** @var UserPostService $service */ $service = $this->container->get(Container::USER_POST_SERVICE); - /** @var TransportRepository $service */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getNewUserData(); + $userData = $this->getNewUserData(); + $userData['usr_id'] = 1; /** * $userData is a db record. We need a domain object here */ - $domainUser = $transport->newUser($userData); + $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = $domainData[0]; + $domainData = array_shift($domainData); $payload = $service->__invoke($domainData); @@ -129,23 +121,22 @@ public function testServiceFailurePdoError(): void ->willReturn($userRepository) ; - /** @var Filter $filter */ - $filter = $this->container->get(Container::FILTER); - /** @var Security $security */ - $security = $this->container->get(Container::SECURITY); - /** @var TransportRepository $transport */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); + $this->container->set(Container::REPOSITORY, $repository); - $service = new UserPostService($repository, $transport, $filter, $security); + /** @var UserPostService $service */ + $service = $this->container->get(Container::USER_POST_SERVICE); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getNewUserData(); + $userData = $this->getNewUserData(); + $userData['usr_id'] = 1; /** * $userData is a db record. We need a domain object here */ - $domainUser = $transport->newUser($userData); + $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = $domainData[0]; + $domainData = array_shift($domainData); $payload = $service->__invoke($domainData); @@ -167,8 +158,8 @@ public function testServiceFailureValidation(): void { /** @var UserPostService $service */ $service = $this->container->get(Container::USER_POST_SERVICE); - /** @var TransportRepository $transport */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); $userData = $this->getNewUserData(); @@ -183,7 +174,7 @@ public function testServiceFailureValidation(): void /** * $userData is a db record. We need a domain object here */ - $domainUser = $transport->newUser($userData); + $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); $domainData = $domainData[0]; @@ -213,8 +204,8 @@ public function testServiceSuccess(): void { /** @var UserPostService $service */ $service = $this->container->get(Container::USER_POST_SERVICE); - /** @var TransportRepository $transport */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); $userData = $this->getNewUserData(); $userData['usr_created_usr_id'] = 4; @@ -223,9 +214,9 @@ public function testServiceSuccess(): void /** * $userData is a db record. We need a domain object here */ - $domainUser = $transport->newUser($userData); + $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = $domainData[0]; + $domainData = array_shift($domainData); $payload = $service->__invoke($domainData); @@ -255,23 +246,23 @@ public function testServiceSuccess(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = $domainData['namePrefix']; + $expected = htmlspecialchars($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = $domainData['nameFirst']; + $expected = htmlspecialchars($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = $domainData['nameMiddle']; + $expected = htmlspecialchars($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = $domainData['nameLast']; + $expected = htmlspecialchars($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = $domainData['nameSuffix']; + $expected = htmlspecialchars($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual); @@ -287,9 +278,8 @@ public function testServiceSuccess(): void $actual = $data['tokenId']; $this->assertSame($expected, $actual); - $expected = $domainData['preferences']; - $actual = $data['preferences']; - $this->assertSame($expected, $actual); + $actual = $data['preferences']; + $this->assertNull($actual); $expected = $domainData['createdDate']; $actual = $data['createdDate']; @@ -314,8 +304,8 @@ public function testServiceSuccessEmptyDates(): void $today = $now->format('Y-m-d'); /** @var UserPostService $service */ $service = $this->container->get(Container::USER_POST_SERVICE); - /** @var TransportRepository $transport */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); $userData = $this->getNewUserData(); unset( @@ -326,7 +316,7 @@ public function testServiceSuccessEmptyDates(): void /** * $userData is a db record. We need a domain object here */ - $domainUser = $transport->newUser($userData); + $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); $domainData = $domainData[0]; @@ -358,23 +348,23 @@ public function testServiceSuccessEmptyDates(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = $domainData['namePrefix']; + $expected = htmlspecialchars($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = $domainData['nameFirst']; + $expected = htmlspecialchars($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = $domainData['nameMiddle']; + $expected = htmlspecialchars($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = $domainData['nameLast']; + $expected = htmlspecialchars($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = $domainData['nameSuffix']; + $expected = htmlspecialchars($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual); @@ -390,9 +380,8 @@ public function testServiceSuccessEmptyDates(): void $actual = $data['tokenId']; $this->assertSame($expected, $actual); - $expected = $domainData['preferences']; - $actual = $data['preferences']; - $this->assertSame($expected, $actual); + $actual = $data['preferences']; + $this->assertNull($actual); $actual = $data['createdDate']; $this->assertStringContainsString($today, $actual); diff --git a/tests/Unit/Domain/Services/User/UserServicePutTest.php b/tests/Unit/Domain/Services/User/UserServicePutTest.php index 7fe18af..1f6a945 100644 --- a/tests/Unit/Domain/Services/User/UserServicePutTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePutTest.php @@ -13,35 +13,111 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\User; +use DateTimeImmutable; use PayloadInterop\DomainStatus; use PDOException; use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; -use Phalcon\Api\Domain\Components\Encryption\Security; use Phalcon\Api\Domain\Services\User\UserPutService; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; -use Phalcon\Filter\Filter; use PHPUnit\Framework\Attributes\BackupGlobals; +use function array_shift; + #[BackupGlobals(true)] final class UserServicePutTest extends AbstractUnitTestCase { + public function testServiceFailureRecordNotFound(): void + { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + $userData = $this->getNewUserData(); + + $userData['usr_id'] = 1; + + $findByUser = $userMapper->domain($userData); + + $userRepository = $this + ->getMockBuilder(UserRepository::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'findById' + ] + ) + ->getMock() + ; + $userRepository->method('findById')->willReturn(null); + + $repository = $this + ->getMockBuilder(QueryRepository::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'user', + ] + ) + ->getMock() + ; + $repository->method('user')->willReturn($userRepository); + + $this->container->setShared(Container::REPOSITORY, $repository); + + /** @var UserPutService $service */ + $service = $this->container->get(Container::USER_PUT_SERVICE); + + /** + * Update user + */ + $userData = $this->getNewUserData(); + $userData['usr_id'] = 1; + + $updateUser = $userMapper->domain($userData); + $updateUser = $updateUser->toArray(); + $updateUser = array_shift($updateUser); + + $payload = $service->__invoke($updateUser); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('errors', $actual); + + $errors = $actual['errors']; + + $expected = ['Record(s) not found']; + $actual = $errors; + $this->assertSame($expected, $actual); + } + public function testServiceFailureNoIdReturned(): void { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + $userData = $this->getNewUserData(); + + $userData['usr_id'] = 1; + + $findByUser = $userMapper->domain($userData); + $userRepository = $this ->getMockBuilder(UserRepository::class) ->disableOriginalConstructor() ->onlyMethods( [ 'update', + 'findById' ] ) ->getMock() ; $userRepository->method('update')->willReturn(0); + $userRepository->method('findById')->willReturn($findByUser); $repository = $this ->getMockBuilder(QueryRepository::class) @@ -55,25 +131,22 @@ public function testServiceFailureNoIdReturned(): void ; $repository->method('user')->willReturn($userRepository); - /** @var Filter $filter */ - $filter = $this->container->get(Container::FILTER); - /** @var Security $security */ - $security = $this->container->get(Container::SECURITY); - /** @var TransportRepository $transport */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); - - $service = new UserPutService($repository, $transport, $filter, $security); + $this->container->setShared(Container::REPOSITORY, $repository); - $userData = $this->getNewUserData(); + /** @var UserPutService $service */ + $service = $this->container->get(Container::USER_PUT_SERVICE); /** - * $userData is a db record. We need a domain object here + * Update user */ - $domainUser = $transport->newUser($userData); - $domainData = $domainUser->toArray(); - $domainData = $domainData[0]; + $userData = $this->getNewUserData(); + $userData['usr_id'] = 1; - $payload = $service->__invoke($domainData); + $updateUser = $userMapper->domain($userData); + $updateUser = $updateUser->toArray(); + $updateUser = array_shift($updateUser); + + $payload = $service->__invoke($updateUser); $expected = DomainStatus::ERROR; $actual = $payload->getStatus(); @@ -91,16 +164,26 @@ public function testServiceFailureNoIdReturned(): void public function testServiceFailurePdoError(): void { + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + $userData = $this->getNewUserData(); + + $userData['usr_id'] = 1; + + $findByUser = $userMapper->domain($userData); + $userRepository = $this ->getMockBuilder(UserRepository::class) ->disableOriginalConstructor() ->onlyMethods( [ 'update', + 'findById' ] ) ->getMock() ; + $userRepository->method('findById')->willReturn($findByUser); $userRepository ->method('update') ->willThrowException(new PDOException('abcde')) @@ -121,25 +204,21 @@ public function testServiceFailurePdoError(): void ->willReturn($userRepository) ; - /** @var Filter $filter */ - $filter = $this->container->get(Container::FILTER); - /** @var Security $security */ - $security = $this->container->get(Container::SECURITY); - /** @var TransportRepository $transport */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); - - $service = new UserPutService($repository, $transport, $filter, $security); + $this->container->set(Container::REPOSITORY, $repository); + /** @var UserPutService $service */ + $service = $this->container->get(Container::USER_PUT_SERVICE); - $userData = $this->getNewUserData(); + $userData = $this->getNewUserData(); + $userData['usr_id'] = 1; /** * $userData is a db record. We need a domain object here */ - $domainUser = $transport->newUser($userData); - $domainData = $domainUser->toArray(); - $domainData = $domainData[0]; + $updateUser = $userMapper->domain($userData); + $updateUser = $updateUser->toArray(); + $updateUser = array_shift($updateUser); - $payload = $service->__invoke($domainData); + $payload = $service->__invoke($updateUser); $expected = DomainStatus::ERROR; $actual = $payload->getStatus(); @@ -159,10 +238,9 @@ public function testServiceFailureValidation(): void { /** @var UserPutService $service */ $service = $this->container->get(Container::USER_PUT_SERVICE); - /** @var TransportRepository $transport */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); - - $userData = $this->getNewUserData(); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + $userData = $this->getNewUserData(); unset( $userData['usr_email'], @@ -175,7 +253,7 @@ public function testServiceFailureValidation(): void /** * $userData is a db record. We need a domain object here */ - $domainUser = $transport->newUser($userData); + $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); $domainData = $domainData[0]; @@ -205,13 +283,18 @@ public function testServiceSuccess(): void { /** @var UserPutService $service */ $service = $this->container->get(Container::USER_PUT_SERVICE); - /** @var TransportRepository $transport */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); - - $migration = new UsersMigration($this->getConnection()); - $dbUser = $this->getNewUser($migration); - $userId = $dbUser['usr_id']; - $userData = $this->getNewUserData(); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); + + $migration = new UsersMigration($this->getConnection()); + $dbUser = $this->getNewUser($migration); + $userId = $dbUser['usr_id']; + $userData = $this->getNewUserData(); + $userData['usr_id'] = $userId; + /** + * Don't hash the password + */ + $userData['usr_password'] = $this->getStrongPassword(); $userData['usr_created_usr_id'] = 4; $userData['usr_updated_usr_id'] = 5; @@ -219,11 +302,9 @@ public function testServiceSuccess(): void /** * $userData is a db record. We need a domain object here */ - $domainUser = $transport->newUser($userData); + $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = $domainData[0]; - - $domainData['id'] = $userId; + $domainData = array_shift($domainData); $payload = $service->__invoke($domainData); @@ -251,23 +332,23 @@ public function testServiceSuccess(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = $domainData['namePrefix']; + $expected = htmlspecialchars($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = $domainData['nameFirst']; + $expected = htmlspecialchars($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = $domainData['nameMiddle']; + $expected = htmlspecialchars($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = $domainData['nameLast']; + $expected = htmlspecialchars($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = $domainData['nameSuffix']; + $expected = htmlspecialchars($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual); @@ -283,7 +364,7 @@ public function testServiceSuccess(): void $actual = $data['tokenId']; $this->assertSame($expected, $actual); - $expected = $domainData['preferences']; + $expected = ''; $actual = $data['preferences']; $this->assertSame($expected, $actual); @@ -295,9 +376,9 @@ public function testServiceSuccess(): void $actual = $data['createdUserId']; $this->assertSame($expected, $actual); - $expected = $dbUser['usr_updated_date']; + $expected = $domainData['updatedDate']; $actual = $data['updatedDate']; - $this->assertNotSame($expected, $actual); + $this->assertSame($expected, $actual); $expected = 5; $actual = $data['updatedUserId']; @@ -306,15 +387,24 @@ public function testServiceSuccess(): void public function testServiceSuccessEmptyDates(): void { + $now = new DateTimeImmutable(); + $today = $now->format('Y-m-d'); /** @var UserPutService $service */ $service = $this->container->get(Container::USER_PUT_SERVICE); - /** @var TransportRepository $transport */ - $transport = $this->container->get(Container::REPOSITORY_TRANSPORT); + /** @var UserMapper $userMapper */ + $userMapper = $this->container->get(Container::USER_MAPPER); $migration = new UsersMigration($this->getConnection()); $dbUser = $this->getNewUser($migration); - $userId = $dbUser['usr_id']; - $userData = $this->getNewUserData(); + + $userId = $dbUser['usr_id']; + $userData = $this->getNewUserData(); + $userData['usr_id'] = $userId; + /** + * Don't hash the password + */ + $userData['usr_password'] = $this->getStrongPassword(); + unset( $userData['usr_updated_date'], ); @@ -322,11 +412,9 @@ public function testServiceSuccessEmptyDates(): void /** * $userData is a db record. We need a domain object here */ - $domainUser = $transport->newUser($userData); + $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = $domainData[0]; - - $domainData['id'] = $userId; + $domainData = array_shift($domainData); $payload = $service->__invoke($domainData); @@ -354,23 +442,23 @@ public function testServiceSuccessEmptyDates(): void $actual = str_starts_with($data['password'], '$argon2i$'); $this->assertTrue($actual); - $expected = $domainData['namePrefix']; + $expected = htmlspecialchars($domainData['namePrefix']); $actual = $data['namePrefix']; $this->assertSame($expected, $actual); - $expected = $domainData['nameFirst']; + $expected = htmlspecialchars($domainData['nameFirst']); $actual = $data['nameFirst']; $this->assertSame($expected, $actual); - $expected = $domainData['nameMiddle']; + $expected = htmlspecialchars($domainData['nameMiddle']); $actual = $data['nameMiddle']; $this->assertSame($expected, $actual); - $expected = $domainData['nameLast']; + $expected = htmlspecialchars($domainData['nameLast']); $actual = $data['nameLast']; $this->assertSame($expected, $actual); - $expected = $domainData['nameSuffix']; + $expected = htmlspecialchars($domainData['nameSuffix']); $actual = $data['nameSuffix']; $this->assertSame($expected, $actual); @@ -386,7 +474,7 @@ public function testServiceSuccessEmptyDates(): void $actual = $data['tokenId']; $this->assertSame($expected, $actual); - $expected = $domainData['preferences']; + $expected = ''; $actual = $data['preferences']; $this->assertSame($expected, $actual); @@ -398,9 +486,9 @@ public function testServiceSuccessEmptyDates(): void $actual = $data['createdUserId']; $this->assertSame($expected, $actual); - $expected = $dbUser['usr_updated_date']; + $today = date('Y-m-d '); $actual = $data['updatedDate']; - $this->assertNotSame($expected, $actual); + $this->assertStringContainsString($today, $actual); $expected = $dbUser['usr_updated_usr_id']; $actual = $data['updatedUserId']; From d54d8d6aedc4df1baf19f7ec4979185ae7c7d231 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:44:42 -0600 Subject: [PATCH 19/74] [#.x] - removed old (kinda) dto --- .../DataSource/User/UserTransport.php | 135 ------------------ 1 file changed, 135 deletions(-) delete mode 100644 src/Domain/Components/DataSource/User/UserTransport.php diff --git a/src/Domain/Components/DataSource/User/UserTransport.php b/src/Domain/Components/DataSource/User/UserTransport.php deleted file mode 100644 index 98490cf..0000000 --- a/src/Domain/Components/DataSource/User/UserTransport.php +++ /dev/null @@ -1,135 +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\Domain\Components\DataSource\User; - -use Phalcon\Api\Domain\Components\Exceptions\InvalidConfigurationArgumentException; - -/** - * The domain representation of a user. - * - * @method int getId() - * @method int getTenantId() - * @method string getStatus() - * @method string getEmail() - * @method string getPassword() - * @method string getNamePrefix() - * @method string getNameFirst() - * @method string getNameMiddle() - * @method string getNameLast() - * @method string getNameSuffix() - * @method string getIssuer() - * @method string getTokenPassword() - * @method string getTokenId() - * @method string getPreferences() - * @method string getCreatedDate() - * @method int|null getCreatedUserId() - * @method string getUpdatedDate() - * @method int|null getUpdatedUserId() - * - * @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), - 'email' => (string)($input['usr_email'] ?? ''), - 'password' => (string)($input['usr_password'] ?? ''), - 'namePrefix' => (string)($input['usr_name_prefix'] ?? ''), - 'nameFirst' => (string)($input['usr_name_first'] ?? ''), - 'nameMiddle' => (string)($input['usr_name_middle'] ?? ''), - 'nameLast' => (string)($input['usr_name_last'] ?? ''), - 'nameSuffix' => (string)($input['usr_name_suffix'] ?? ''), - 'issuer' => (string)($input['usr_issuer'] ?? ''), - 'tokenPassword' => (string)($input['usr_token_password'] ?? ''), - 'tokenId' => (string)($input['usr_token_id'] ?? ''), - 'preferences' => (string)($input['usr_preferences'] ?? ''), - 'createdDate' => (string)($input['usr_created_date'] ?? ''), - 'createdUserId' => (int)($input['usr_created_usr_id'] ?? 0), - 'updatedDate' => (string)($input['usr_updated_date'] ?? ''), - 'updatedUserId' => (int)($input['usr_updated_usr_id'] ?? 0), - ]; - - $this->store['fullName'] = $this->getFullName(); - } - - /** - * @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'], - 'getEmail' => $this->store['email'], - 'getPassword' => $this->store['password'], - 'getNamePrefix' => $this->store['namePrefix'], - 'getNameFirst' => $this->store['nameFirst'], - 'getNameMiddle' => $this->store['nameMiddle'], - 'getNameLast' => $this->store['nameLast'], - 'getNameSuffix' => $this->store['nameSuffix'], - 'getIssuer' => $this->store['issuer'], - 'getTokenPassword' => $this->store['tokenPassword'], - 'getTokenId' => $this->store['tokenId'], - 'getPreferences' => $this->store['preferences'], - 'getCreatedDate' => $this->store['createdDate'], - 'getCreatedUserId' => $this->store['createdUserId'], - 'getUpdatedDate' => $this->store['updatedDate'], - 'getUpdatedUserId' => $this->store['updatedUserId'], - default => throw InvalidConfigurationArgumentException::new( - 'The ' . $name . ' method is not supported. [' - . json_encode($arguments) . ']', - ), - }; - } - - /** - * @return string - */ - public function getFullName(): string - { - return trim( - $this->getNameLast() - . ', ' - . $this->getNameFirst() - . ' ' - . $this->getNameMiddle() - ); - } - - public function isEmpty(): bool - { - return 0 === $this->store['id']; - } - - /** - * @return array - */ - public function toArray(): array - { - return [$this->store['id'] => $this->store]; - } -} From 19efaf1c72d240bd0a56d1fdaa32ba0ee55dbcf3 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:45:07 -0600 Subject: [PATCH 20/74] [#.x] - corrected references and new objects --- src/Domain/Components/Cache/Cache.php | 24 +++++------ src/Domain/Components/Container.php | 60 +++++++++++++++++++++------ 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/Domain/Components/Cache/Cache.php b/src/Domain/Components/Cache/Cache.php index 1d1dcbd..e1f38ef 100644 --- a/src/Domain/Components/Cache/Cache.php +++ b/src/Domain/Components/Cache/Cache.php @@ -15,7 +15,7 @@ use DateTimeImmutable; use Phalcon\Api\Domain\Components\Constants\Dates; -use Phalcon\Api\Domain\Components\DataSource\User\UserTransport; +use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\Env\EnvManager; use Phalcon\Cache\Adapter\Redis; use Phalcon\Cache\Cache as PhalconCache; @@ -48,12 +48,12 @@ class Cache extends PhalconCache private const MASK_TOKEN_USER = 'tk-%s-%s'; /** - * @param UserTransport $domainUser - * @param string $token + * @param User $domainUser + * @param string $token * * @return string */ - public function getCacheTokenKey(UserTransport $domainUser, string $token): string + public function getCacheTokenKey(User $domainUser, string $token): string { $tokenString = ''; if (true !== empty($token)) { @@ -62,21 +62,21 @@ public function getCacheTokenKey(UserTransport $domainUser, string $token): stri return sprintf( self::MASK_TOKEN_USER, - $domainUser->getId(), + $domainUser->id, $tokenString ); } /** - * @param EnvManager $env - * @param UserTransport $domainUser + * @param EnvManager $env + * @param User $domainUser * * @return bool * @throws InvalidArgumentException */ public function invalidateForUser( EnvManager $env, - UserTransport $domainUser + User $domainUser ): bool { /** * We could store the tokens in the database but this way is faster @@ -104,16 +104,16 @@ public function invalidateForUser( } /** - * @param EnvManager $env - * @param UserTransport $domainUser - * @param string $token + * @param EnvManager $env + * @param User $domainUser + * @param string $token * * @return bool * @throws InvalidArgumentException */ public function storeTokenInCache( EnvManager $env, - UserTransport $domainUser, + User $domainUser, string $token ): bool { $cacheKey = $this->getCacheTokenKey($domainUser, $token); diff --git a/src/Domain/Components/Container.php b/src/Domain/Components/Container.php index a8e97c5..861fee6 100644 --- a/src/Domain/Components/Container.php +++ b/src/Domain/Components/Container.php @@ -14,8 +14,11 @@ namespace Phalcon\Api\Domain\Components; use Phalcon\Api\Domain\Components\Cache\Cache; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthSanitizer; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; +use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; +use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Encryption\Security; use Phalcon\Api\Domain\Components\Env\EnvManager; @@ -46,6 +49,7 @@ use Phalcon\Logger\Logger; use Phalcon\Mvc\Router; use Phalcon\Storage\SerializerFactory; +use Phalcon\Support\Registry; class Container extends Di { @@ -66,6 +70,8 @@ class Container extends Di /** @var string */ public const LOGGER = 'logger'; /** @var string */ + public const REGISTRY = 'registry'; + /** @var string */ public const REQUEST = 'request'; /** @var string */ public const RESPONSE = 'response'; @@ -96,10 +102,13 @@ class Container extends Di public const MIDDLEWARE_VALIDATE_TOKEN_STRUCTURE = ValidateTokenStructureMiddleware::class; public const MIDDLEWARE_VALIDATE_TOKEN_USER = ValidateTokenUserMiddleware::class; /** - * Repositories + * Repositories/Sanitizers/Validators */ - public const REPOSITORY = 'repository'; - public const REPOSITORY_TRANSPORT = TransportRepository::class; + public const REPOSITORY = 'repository'; + public const AUTH_SANITIZER = 'auth.sanitizer'; + public const USER_MAPPER = UserMapper::class; + public const USER_VALIDATOR = UserValidator::class; + public const USER_SANITIZER = 'user.sanitizer'; /** * Responders */ @@ -115,11 +124,14 @@ public function __construct() self::FILTER => $this->getServiceFilter(), self::JWT_TOKEN => $this->getServiceJWTToken(), self::LOGGER => $this->getServiceLogger(), + self::REGISTRY => new Service(Registry::class, true), self::REQUEST => new Service(Request::class, true), self::RESPONSE => new Service(Response::class, true), self::ROUTER => $this->getServiceRouter(), - self::REPOSITORY => $this->getServiceRepository(), + self::REPOSITORY => $this->getServiceRepository(), + self::AUTH_SANITIZER => $this->getServiceSanitizer(AuthSanitizer::class), + self::USER_SANITIZER => $this->getServiceSanitizer(UserSanitizer::class), self::AUTH_LOGIN_POST_SERVICE => $this->getServiceAuthPost(LoginPostService::class), self::AUTH_LOGOUT_POST_SERVICE => $this->getServiceAuthPost(LogoutPostService::class), @@ -148,10 +160,6 @@ private function getServiceAuthPost(string $className): Service 'type' => 'service', 'name' => self::REPOSITORY, ], - [ - 'type' => 'service', - 'name' => self::REPOSITORY_TRANSPORT, - ], [ 'type' => 'service', 'name' => self::CACHE, @@ -166,7 +174,7 @@ private function getServiceAuthPost(string $className): Service ], [ 'type' => 'service', - 'name' => self::FILTER, + 'name' => self::AUTH_SANITIZER, ], [ 'type' => 'service', @@ -362,6 +370,10 @@ private function getServiceRepository(): Service 'type' => 'service', 'name' => self::CONNECTION, ], + [ + 'type' => 'service', + 'name' => self::USER_MAPPER, + ], ], ] ); @@ -402,11 +414,15 @@ private function getServiceUser(string $className): Service ], [ 'type' => 'service', - 'name' => self::REPOSITORY_TRANSPORT, + 'name' => self::USER_MAPPER, ], [ 'type' => 'service', - 'name' => self::FILTER, + 'name' => self::USER_VALIDATOR, + ], + [ + 'type' => 'service', + 'name' => self::USER_SANITIZER, ], [ 'type' => 'service', @@ -416,4 +432,24 @@ private function getServiceUser(string $className): Service ] ); } + + /** + * @param class-string $className + * + * @return Service + */ + private function getServiceSanitizer(string $className): Service + { + return new Service( + [ + 'className' => $className, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::FILTER, + ], + ], + ] + ); + } } From 791169d53b94267973713398d43abfa3f5603672 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:45:41 -0600 Subject: [PATCH 21/74] [#.x] - adjusting signature --- .../Services/Auth/AbstractAuthService.php | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/Domain/Services/Auth/AbstractAuthService.php b/src/Domain/Services/Auth/AbstractAuthService.php index 3094540..446b8c8 100644 --- a/src/Domain/Services/Auth/AbstractAuthService.php +++ b/src/Domain/Services/Auth/AbstractAuthService.php @@ -13,18 +13,15 @@ namespace Phalcon\Api\Domain\Services\Auth; -use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\ADR\DomainInterface; use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Components\Cache\Cache; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; +use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Encryption\Security; use Phalcon\Api\Domain\Components\Env\EnvManager; -use Phalcon\Domain\Payload; -use Phalcon\Filter\Filter; /** * @phpstan-import-type TUserDbRecord from UserTypes @@ -35,27 +32,11 @@ abstract class AbstractAuthService implements DomainInterface { public function __construct( protected readonly QueryRepository $repository, - protected readonly TransportRepository $transport, protected readonly Cache $cache, protected readonly EnvManager $env, protected readonly JWTToken $jwtToken, - protected readonly Filter $filter, + protected readonly SanitizerInterface $sanitizer, protected readonly Security $security, ) { } - - /** - * @param TValidationErrors $errors - * - * @return Payload - */ - protected function getUnauthorizedPayload(array $errors): Payload - { - return new Payload( - DomainStatus::UNAUTHORIZED, - [ - 'errors' => $errors, - ] - ); - } } From 0499ee716b0a530c294ea381157e3e74d193dfc3 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:45:57 -0600 Subject: [PATCH 22/74] [#.x] - adjusting objects --- src/Domain/ADR/DomainInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Domain/ADR/DomainInterface.php b/src/Domain/ADR/DomainInterface.php index 52be69d..2c0a249 100644 --- a/src/Domain/ADR/DomainInterface.php +++ b/src/Domain/ADR/DomainInterface.php @@ -13,7 +13,7 @@ namespace Phalcon\Api\Domain\ADR; -use Phalcon\Domain\Payload; +use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TLoginInput from InputTypes From a19d744449c31b741569a31f91df1d3f139725c5 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 11:46:17 -0600 Subject: [PATCH 23/74] [#.x] - switched to interfaces --- src/Domain/Components/DataSource/QueryRepository.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Domain/Components/DataSource/QueryRepository.php b/src/Domain/Components/DataSource/QueryRepository.php index 5e5f58b..120eec3 100644 --- a/src/Domain/Components/DataSource/QueryRepository.php +++ b/src/Domain/Components/DataSource/QueryRepository.php @@ -13,28 +13,31 @@ namespace Phalcon\Api\Domain\Components\DataSource; +use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; +use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; use Phalcon\DataMapper\Pdo\Connection; class QueryRepository { - private ?UserRepository $user = null; + private ?UserRepositoryInterface $user = null; /** * @param Connection $connection */ public function __construct( private readonly Connection $connection, + private readonly UserMapper $userMapper, ) { } /** * @return UserRepository */ - public function user(): UserRepository + public function user(): UserRepositoryInterface { if (null === $this->user) { - $this->user = new UserRepository($this->connection); + $this->user = new UserRepository($this->connection, $this->userMapper); } return $this->user; From a9a6f119d1aa7332ca50fdbba680f5cfaa9b5b03 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 14:20:05 -0600 Subject: [PATCH 24/74] [#.x] - changed toArray to return just the object as an array --- .../Components/DataSource/User/User.php | 12 +++++- .../Components/DataSource/User/UserInput.php | 41 ++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/Domain/Components/DataSource/User/User.php b/src/Domain/Components/DataSource/User/User.php index f6a599a..dd58248 100644 --- a/src/Domain/Components/DataSource/User/User.php +++ b/src/Domain/Components/DataSource/User/User.php @@ -13,8 +13,12 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; +use function get_object_vars; use function trim; +/** + * @phpstan-import-type TUser from UserTypes + */ final class User { public function __construct( @@ -45,8 +49,14 @@ public function fullName(): string ); } + /** + * @return TUser + */ public function toArray(): array { - return [$this->id => get_object_vars($this)]; + /** @var TUser $vars */ + $vars = get_object_vars($this); + + return $vars; } } diff --git a/src/Domain/Components/DataSource/User/UserInput.php b/src/Domain/Components/DataSource/User/UserInput.php index 15f6545..e983dc7 100644 --- a/src/Domain/Components/DataSource/User/UserInput.php +++ b/src/Domain/Components/DataSource/User/UserInput.php @@ -13,10 +13,36 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; +use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; +use function get_object_vars; + +/** + * @phpstan-import-type TUserInput from InputTypes + * @phpstan-import-type TUser from UserTypes + */ final class UserInput { + /** + * @param int|null $id + * @param int|null $status + * @param string|null $email + * @param string|null $password + * @param string|null $namePrefix + * @param string|null $nameFirst + * @param string|null $nameMiddle + * @param string|null $nameLast + * @param string|null $nameSuffix + * @param string|null $issuer + * @param string|null $tokenPassword + * @param string|null $tokenId + * @param string|null $preferences + * @param string|null $createdDate + * @param int|null $createdUserId + * @param string|null $updatedDate + * @param int|null $updatedUserId + */ public function __construct( public readonly ?int $id, public readonly ?int $status, @@ -38,8 +64,15 @@ public function __construct( ) { } + /** + * @param SanitizerInterface $sanitizer + * @param TUserInput $input + * + * @return self + */ public static function new(SanitizerInterface $sanitizer, array $input): self { + /** @var TUser $sanitized */ $sanitized = $sanitizer->sanitize($input); return new self( @@ -63,8 +96,14 @@ public static function new(SanitizerInterface $sanitizer, array $input): self ); } + /** + * @return TUser + */ public function toArray(): array { - return [$this->id => get_object_vars($this)]; + /** @var TUser $vars */ + $vars = get_object_vars($this); + + return $vars; } } From 8c614019368c9acc0e1123883af0f6ca54581a0e Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 14:20:38 -0600 Subject: [PATCH 25/74] [#.x] - adjustments for payload and processPassword --- .../Services/User/AbstractUserService.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Domain/Services/User/AbstractUserService.php b/src/Domain/Services/User/AbstractUserService.php index 684a323..a907c59 100644 --- a/src/Domain/Services/User/AbstractUserService.php +++ b/src/Domain/Services/User/AbstractUserService.php @@ -19,6 +19,7 @@ use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; +use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; use Phalcon\Api\Domain\Components\Encryption\Security; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; @@ -26,10 +27,20 @@ use function array_shift; +/** + * @phpstan-import-type TUser from UserTypes + */ abstract class AbstractUserService implements DomainInterface { protected HttpCodesEnum $errorMessage; + /** + * @param QueryRepository $repository + * @param UserMapper $mapper + * @param UserValidator $validator + * @param SanitizerInterface $sanitizer + * @param Security $security + */ public function __construct( protected readonly QueryRepository $repository, protected readonly UserMapper $mapper, @@ -49,7 +60,7 @@ protected function getErrorPayload(string $message): Payload { return Payload::error( [ - $this->errorMessage->text() . $message, + [$this->errorMessage->text() . $message], ] ); } @@ -57,12 +68,12 @@ protected function getErrorPayload(string $message): Payload /** * @param UserInput $inputObject * - * @return array + * @return User */ protected function processPassword(UserInput $inputObject): User { + /** @var TUser $inputData */ $inputData = $inputObject->toArray(); - $inputData = array_shift($inputData); if (null !== $inputData['password']) { $plain = $inputData['password']; From 9cf6bdcc7ba3f867404ad968dbcf1262a4f1bdc0 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 14:21:38 -0600 Subject: [PATCH 26/74] [#.x] - refactored payload:notFound, adjusted return payload to include the ID --- src/Domain/Services/User/UserDeleteService.php | 2 +- src/Domain/Services/User/UserGetService.php | 4 ++-- src/Domain/Services/User/UserPostService.php | 6 ++++-- src/Domain/Services/User/UserPutService.php | 9 ++++++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Domain/Services/User/UserDeleteService.php b/src/Domain/Services/User/UserDeleteService.php index 72b55f1..b380eff 100644 --- a/src/Domain/Services/User/UserDeleteService.php +++ b/src/Domain/Services/User/UserDeleteService.php @@ -50,6 +50,6 @@ public function __invoke(array $input): Payload /** * 404 */ - return Payload::notFound(['Record(s) not found']); + return Payload::notFound(); } } diff --git a/src/Domain/Services/User/UserGetService.php b/src/Domain/Services/User/UserGetService.php index b487174..9950ec6 100644 --- a/src/Domain/Services/User/UserGetService.php +++ b/src/Domain/Services/User/UserGetService.php @@ -39,13 +39,13 @@ public function __invoke(array $input): Payload $user = $this->repository->user()->findById($userId); if (null !== $user) { - return Payload::success($user->toArray()); + return Payload::success([$user->id => $user->toArray()]); } } /** * 404 */ - return Payload::notFound(['Record(s) not found']); + return Payload::notFound(); } } diff --git a/src/Domain/Services/User/UserPostService.php b/src/Domain/Services/User/UserPostService.php index da19d0d..ba39ebb 100644 --- a/src/Domain/Services/User/UserPostService.php +++ b/src/Domain/Services/User/UserPostService.php @@ -15,12 +15,12 @@ use PDOException; use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; /** - * @phpstan-import-type TUserSanitizedInsertInput from InputTypes * @phpstan-import-type TUserInput from InputTypes * @phpstan-import-type TValidationErrors from InputTypes */ @@ -36,6 +36,7 @@ final class UserPostService extends AbstractUserService public function __invoke(array $input): Payload { $inputObject = UserInput::new($this->sanitizer, $input); + /** @var TValidationErrors $errors */ $errors = $this->validator->validate($inputObject); /** @@ -73,11 +74,12 @@ public function __invoke(array $input): Payload /** * Get the user from the database */ + /** @var User $domainUser */ $domainUser = $this->repository->user()->findById($userId); /** * Return the user back */ - return Payload::created($domainUser->toArray()); + return Payload::created([$domainUser->id => $domainUser->toArray()]); } } diff --git a/src/Domain/Services/User/UserPutService.php b/src/Domain/Services/User/UserPutService.php index 44c7366..82871f8 100644 --- a/src/Domain/Services/User/UserPutService.php +++ b/src/Domain/Services/User/UserPutService.php @@ -15,12 +15,12 @@ use PDOException; use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; /** - * @phpstan-import-type TUserSanitizedUpdateInput from InputTypes * @phpstan-import-type TUserInput from InputTypes * @phpstan-import-type TValidationErrors from InputTypes */ @@ -36,6 +36,7 @@ final class UserPutService extends AbstractUserService public function __invoke(array $input): Payload { $inputObject = UserInput::new($this->sanitizer, $input); + /** @var TValidationErrors $errors */ $errors = $this->validator->validate($inputObject); /** @@ -48,11 +49,12 @@ public function __invoke(array $input): Payload /** * Check if the user exists, If not, return an error */ + /** @var int $userId */ $userId = $inputObject->id; $domainUser = $this->repository->user()->findById($userId); if (null === $domainUser) { - return Payload::notFound(['Record(s) not found']); + return Payload::notFound(); } /** @@ -83,11 +85,12 @@ public function __invoke(array $input): Payload /** * Get the user from the database */ + /** @var User $domainUser */ $domainUser = $this->repository->user()->findById($userId); /** * Return the user back */ - return Payload::updated($domainUser->toArray()); + return Payload::updated([$domainUser->id => $domainUser->toArray()]); } } From 4442e70b80bcee728675464be91443249210b830 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 14:21:57 -0600 Subject: [PATCH 27/74] [#.x] - phpstan corrections --- src/Action/ActionHandler.php | 4 +- src/Domain/ADR/DomainInterface.php | 4 +- src/Domain/ADR/InputTypes.php | 45 +------ .../DataSource/AbstractRepository.php | 43 +------ .../Components/DataSource/Auth/AuthInput.php | 28 ++++- .../DataSource/Auth/AuthSanitizer.php | 10 +- .../Components/DataSource/QueryRepository.php | 2 +- .../DataSource/SanitizerInterface.php | 7 +- .../Components/DataSource/User/UserMapper.php | 17 ++- .../DataSource/User/UserRepository.php | 28 +++-- .../User/UserRepositoryInterface.php | 8 +- .../DataSource/User/UserSanitizer.php | 12 +- .../Components/DataSource/User/UserTypes.php | 117 +++++++++--------- .../DataSource/User/UserValidator.php | 7 +- src/Domain/Components/Encryption/JWTToken.php | 21 ++-- .../ValidateTokenRevokedMiddleware.php | 2 +- .../ValidateTokenStructureMiddleware.php | 1 - .../ValidateTokenUserMiddleware.php | 3 - src/Domain/Components/Payload.php | 55 +++++++- .../Services/Auth/AbstractAuthService.php | 2 - src/Domain/Services/Auth/LoginPostService.php | 6 +- .../Services/Auth/LogoutPostService.php | 6 +- .../Services/Auth/RefreshPostService.php | 6 +- .../DataSource/User/UserInputTest.php | 3 +- .../DataSource/User/UserSanitizerTest.php | 4 +- .../Components/DataSource/User/UserTest.php | 36 +++--- .../Services/User/UserServiceDeleteTest.php | 8 +- .../Services/User/UserServiceDispatchTest.php | 3 +- .../Services/User/UserServiceGetTest.php | 16 ++- .../Services/User/UserServicePostTest.php | 9 +- .../Services/User/UserServicePutTest.php | 30 +++-- 31 files changed, 280 insertions(+), 263 deletions(-) diff --git a/src/Action/ActionHandler.php b/src/Action/ActionHandler.php index d169ae1..d9a8533 100644 --- a/src/Action/ActionHandler.php +++ b/src/Action/ActionHandler.php @@ -21,7 +21,7 @@ use Phalcon\Http\ResponseInterface; /** - * @phpstan-import-type TLoginInput from InputTypes + * @phpstan-import-type TAuthLoginInput from InputTypes * @phpstan-import-type TUserInput from InputTypes */ final readonly class ActionHandler implements ActionInterface @@ -37,7 +37,7 @@ public function __construct( public function __invoke(): void { $input = new Input(); - /** @var TLoginInput|TUserInput $data */ + /** @var TAuthLoginInput|TUserInput $data */ $data = $input->__invoke($this->request); $this->responder->__invoke( diff --git a/src/Domain/ADR/DomainInterface.php b/src/Domain/ADR/DomainInterface.php index 2c0a249..346a756 100644 --- a/src/Domain/ADR/DomainInterface.php +++ b/src/Domain/ADR/DomainInterface.php @@ -16,13 +16,13 @@ use Phalcon\Api\Domain\Components\Payload; /** - * @phpstan-import-type TLoginInput from InputTypes + * @phpstan-import-type TAuthLoginInput from InputTypes * @phpstan-import-type TUserInput from InputTypes */ interface DomainInterface { /** - * @param TLoginInput|TUserInput $input + * @param TAuthLoginInput|TUserInput $input * * @return Payload */ diff --git a/src/Domain/ADR/InputTypes.php b/src/Domain/ADR/InputTypes.php index b68da4d..14021fd 100644 --- a/src/Domain/ADR/InputTypes.php +++ b/src/Domain/ADR/InputTypes.php @@ -14,16 +14,18 @@ namespace Phalcon\Api\Domain\ADR; /** - * @phpstan-type TLoginInput array{ + * @phpstan-type TAuthInput TAuthLoginInput|TAuthLogoutInput + * + * @phpstan-type TAuthLoginInput array{ * email?: string, * password?: string * } * - * @phpstan-type TLogoutInput array{ + * @phpstan-type TAuthLogoutInput array{ * token?: string * } * - * @phpstan-type TRefreshInput array{ + * @phpstan-type TAuthRefreshInput array{ * token?: string * } * @@ -47,43 +49,6 @@ * updatedUserId?: int, * } * - * @phpstan-type TUserSanitizedInsertInput array{ - * status: int, - * email: string, - * password: string, - * namePrefix: string, - * nameFirst: string, - * nameMiddle: string, - * nameLast: string, - * nameSuffix: string, - * issuer: string, - * tokenPassword: string, - * tokenId: string, - * preferences: string, - * createdDate: string, - * createdUserId: int, - * updatedDate: string, - * updatedUserId: int, - * } - * - * @phpstan-type TUserSanitizedUpdateInput array{ - * id: int, - * status: int, - * email: string, - * password: string, - * namePrefix: string, - * nameFirst: string, - * nameMiddle: string, - * nameLast: string, - * nameSuffix: string, - * issuer: string, - * tokenPassword: string, - * tokenId: string, - * preferences: string, - * updatedDate: string, - * updatedUserId: int, - * } - * * @phpstan-type TRequestQuery array * * @phpstan-type TValidationErrors array> diff --git a/src/Domain/Components/DataSource/AbstractRepository.php b/src/Domain/Components/DataSource/AbstractRepository.php index c60fe7e..a5125f4 100644 --- a/src/Domain/Components/DataSource/AbstractRepository.php +++ b/src/Domain/Components/DataSource/AbstractRepository.php @@ -18,7 +18,7 @@ use Phalcon\DataMapper\Query\Delete; /** - * @phpstan-import-type TUserRecord from UserTypes + * @phpstan-import-type TCriteria from UserTypes */ abstract class AbstractRepository { @@ -37,7 +37,7 @@ public function __construct( } /** - * @param array $criteria + * @param TCriteria $criteria * * @return int */ @@ -67,43 +67,4 @@ public function deleteById(int $recordId): int ] ); } - -// /** -// * @param int $recordId -// * -// * @return TUserRecord -// */ -// public function findById(int $recordId): array -// { -// $result = []; -// if ($recordId > 0) { -// return $this->findOneBy( -// [ -// $this->idField => $recordId, -// ] -// ); -// } -// -// return $result; -// } -// -// -// /** -// * @param array $criteria -// * -// * @return TUserRecord -// */ -// public function findOneBy(array $criteria): array -// { -// $select = Select::new($this->connection); -// -// /** @var TUserRecord $result */ -// $result = $select -// ->from($this->table) -// ->whereEquals($criteria) -// ->fetchOne() -// ; -// -// return $result; -// } } diff --git a/src/Domain/Components/DataSource/Auth/AuthInput.php b/src/Domain/Components/DataSource/Auth/AuthInput.php index b4250fb..cda5ced 100644 --- a/src/Domain/Components/DataSource/Auth/AuthInput.php +++ b/src/Domain/Components/DataSource/Auth/AuthInput.php @@ -13,10 +13,19 @@ namespace Phalcon\Api\Domain\Components\DataSource\Auth; +use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; +/** + * @phpstan-import-type TAuthInput from InputTypes + */ final class AuthInput { + /** + * @param string|null $email + * @param string|null $password + * @param string|null $token + */ public function __construct( public readonly ?string $email, public readonly ?string $password, @@ -24,14 +33,23 @@ public function __construct( ) { } + /** + * @param SanitizerInterface $sanitizer + * @param TAuthInput $input + * + * @return self + */ public static function new(SanitizerInterface $sanitizer, array $input): self { $sanitized = $sanitizer->sanitize($input); - return new self( - $sanitized['email'] ?? null, - $sanitized['password'] ?? null, - $sanitized['token'] ?? null, - ); + /** @var string|null $email */ + $email = $sanitized['email'] ?? null; + /** @var string|null $password */ + $password = $sanitized['password'] ?? null; + /** @var string|null $token */ + $token = $sanitized['token'] ?? null; + + return new self($email, $password, $token); } } diff --git a/src/Domain/Components/DataSource/Auth/AuthSanitizer.php b/src/Domain/Components/DataSource/Auth/AuthSanitizer.php index 6cbc431..fdf86fd 100644 --- a/src/Domain/Components/DataSource/Auth/AuthSanitizer.php +++ b/src/Domain/Components/DataSource/Auth/AuthSanitizer.php @@ -13,9 +13,13 @@ namespace Phalcon\Api\Domain\Components\DataSource\Auth; +use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; use Phalcon\Filter\Filter; +/** + * @phpstan-import-type TAuthInput from InputTypes + */ final class AuthSanitizer implements SanitizerInterface { public function __construct( @@ -26,12 +30,13 @@ public function __construct( /** * Return a sanitized array of the input * - * @param array $input + * @param TAuthInput $input * - * @return array + * @return TAuthInput */ public function sanitize(array $input): array { + /** @var array $fields */ $fields = [ 'email' => null, 'password' => null, @@ -56,6 +61,7 @@ public function sanitize(array $input): array $sanitized[$name] = $value; } + /** @var TAuthInput $sanitized */ return $sanitized; } diff --git a/src/Domain/Components/DataSource/QueryRepository.php b/src/Domain/Components/DataSource/QueryRepository.php index 120eec3..08dce4d 100644 --- a/src/Domain/Components/DataSource/QueryRepository.php +++ b/src/Domain/Components/DataSource/QueryRepository.php @@ -32,7 +32,7 @@ public function __construct( } /** - * @return UserRepository + * @return UserRepositoryInterface */ public function user(): UserRepositoryInterface { diff --git a/src/Domain/Components/DataSource/SanitizerInterface.php b/src/Domain/Components/DataSource/SanitizerInterface.php index b4fd17b..5620f01 100644 --- a/src/Domain/Components/DataSource/SanitizerInterface.php +++ b/src/Domain/Components/DataSource/SanitizerInterface.php @@ -13,14 +13,17 @@ namespace Phalcon\Api\Domain\Components\DataSource; +/** + * @phpstan-type TInput array + */ interface SanitizerInterface { /** * Return a sanitized array of the input * - * @param array $input + * @param TInput $input * - * @return array + * @return TInput */ public function sanitize(array $input): array; } diff --git a/src/Domain/Components/DataSource/User/UserMapper.php b/src/Domain/Components/DataSource/User/UserMapper.php index 2555b2d..31eebc8 100644 --- a/src/Domain/Components/DataSource/User/UserMapper.php +++ b/src/Domain/Components/DataSource/User/UserMapper.php @@ -13,12 +13,19 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; +use Phalcon\Api\Domain\ADR\InputTypes; + +/** + * @phpstan-import-type TUser from UserTypes + * @phpstan-import-type TUserDbRecord from UserTypes + * @phpstan-import-type TUserDomainToDbRecord from UserTypes + */ final class UserMapper { /** * Map Domain User -> DB row (usr_*) * - * @return array + * @return TUserDomainToDbRecord */ public function db(User $user): array { @@ -46,7 +53,7 @@ public function db(User $user): array /** * Map DB row (usr_*) -> Domain User * - * @param array $row + * @param TUserDbRecord|array{} $row */ public function domain(array $row): User { @@ -74,15 +81,15 @@ public function domain(array $row): User /** * Map input row -> Domain User * - * @param array $row + * @param TUser $row */ public function input(array $row): User { return new User( $row['id'], $row['status'], - $row['email'], - $row['password'], + (string)$row['email'], + (string)$row['password'], $row['namePrefix'], $row['nameFirst'], $row['nameMiddle'], diff --git a/src/Domain/Components/DataSource/User/UserRepository.php b/src/Domain/Components/DataSource/User/UserRepository.php index 2b9d846..e075fce 100644 --- a/src/Domain/Components/DataSource/User/UserRepository.php +++ b/src/Domain/Components/DataSource/User/UserRepository.php @@ -24,6 +24,9 @@ use function array_filter; /** + * @phpstan-import-type TCriteria from UserTypes + * @phpstan-import-type TUserDomainToDbRecord from UserTypes + * @phpstan-import-type TUserDbRecordOptional from UserTypes * @phpstan-import-type TUserRecord from UserTypes * * The 'final' keyword was intentionally removed from this class to allow @@ -89,7 +92,7 @@ public function findById(int $recordId): ?User /** - * @param array $criteria + * @param TCriteria $criteria * * @return User|null */ @@ -132,11 +135,6 @@ public function insert(User $user): int $row['usr_updated_date'] = $now; } - /** - * Remove usr_id just in case - */ - unset($row['usr_id']); - /** * Cleanup empty fields if needed */ @@ -158,9 +156,12 @@ public function insert(User $user): int */ public function update(User $user): int { + $row = $this->mapper->db($user); $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); + /** @var int $userId */ $userId = $row['usr_id']; + /** * @todo this should not be here - the update should just add data not validate */ @@ -172,15 +173,16 @@ public function update(User $user): int } /** - * Remove createdDate and createdUserId - cannot be changed. This - * needs to be here because we don't want to touch those fields. + * Cleanup empty fields if needed */ - unset($row['usr_created_date'], $row['usr_created_usr_id']); + $columns = $this->cleanupFields($row); /** - * Cleanup empty fields if needed + * Remove createdDate and createdUserId - cannot be changed. This + * needs to be here because we don't want to touch those fields. */ - $columns = $this->cleanupFields($row); + unset($columns['usr_created_date'], $columns['usr_created_usr_id']); + $update = Update::new($this->connection); $update ->table($this->table) @@ -193,9 +195,9 @@ public function update(User $user): int } /** - * @param array $row + * @param TUserDomainToDbRecord $row * - * @return array + * @return TUserDbRecordOptional */ private function cleanupFields(array $row): array { diff --git a/src/Domain/Components/DataSource/User/UserRepositoryInterface.php b/src/Domain/Components/DataSource/User/UserRepositoryInterface.php index 4f6702a..5313ae6 100644 --- a/src/Domain/Components/DataSource/User/UserRepositoryInterface.php +++ b/src/Domain/Components/DataSource/User/UserRepositoryInterface.php @@ -14,14 +14,12 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; /** - * @phpstan-import-type TUserRecord from UserTypes - * @phpstan-import-type TUserInsert from UserTypes - * @phpstan-import-type TUserUpdate from UserTypes + * @phpstan-import-type TCriteria from UserTypes */ interface UserRepositoryInterface { /** - * @param array $criteria + * @param TCriteria $criteria * * @return int */ @@ -49,7 +47,7 @@ public function findByEmail(string $email): ?User; public function findById(int $recordId): ?User; /** - * @param array $criteria + * @param TCriteria $criteria * * @return User|null */ diff --git a/src/Domain/Components/DataSource/User/UserSanitizer.php b/src/Domain/Components/DataSource/User/UserSanitizer.php index 2a1f9c9..bfdf298 100644 --- a/src/Domain/Components/DataSource/User/UserSanitizer.php +++ b/src/Domain/Components/DataSource/User/UserSanitizer.php @@ -16,6 +16,9 @@ use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; use Phalcon\Filter\Filter; +/** + * @phpstan-import-type TUser from UserTypes + */ final class UserSanitizer implements SanitizerInterface { public function __construct( @@ -26,9 +29,9 @@ public function __construct( /** * Return a sanitized array of the input * - * @param array $input + * @param TUser $input * - * @return array + * @return TUser */ public function sanitize(array $input): array { @@ -47,9 +50,9 @@ public function sanitize(array $input): array 'tokenId' => null, 'preferences' => null, 'createdDate' => null, - 'createdUserId' => null, + 'createdUserId' => 0, 'updatedDate' => null, - 'updatedUserId' => null, + 'updatedUserId' => 0, ]; /** @@ -70,6 +73,7 @@ public function sanitize(array $input): array $sanitized[$name] = $value; } + /** @var TUser $sanitized */ return $sanitized; } diff --git a/src/Domain/Components/DataSource/User/UserTypes.php b/src/Domain/Components/DataSource/User/UserTypes.php index d42f2e4..3ccb744 100644 --- a/src/Domain/Components/DataSource/User/UserTypes.php +++ b/src/Domain/Components/DataSource/User/UserTypes.php @@ -47,71 +47,76 @@ * usr_updated_usr_id: int, * } * - * @phpstan-type TUserTokenDbRecord array{ + * @phpstan-type TUserDomainToDbRecord array{ * usr_id: int, - * usr_issuer: string, - * usr_token_password: string, - * usr_token_id: string + * usr_status_flag: int, + * usr_email: ?string, + * usr_password: ?string, + * usr_name_prefix: ?string, + * usr_name_first: ?string, + * usr_name_middle: ?string, + * usr_name_last: ?string, + * usr_name_suffix: ?string, + * usr_issuer: ?string, + * usr_token_password: ?string, + * usr_token_id: ?string, + * usr_preferences: ?string, + * usr_created_date: ?string, + * usr_created_usr_id: ?int, + * usr_updated_date: ?string, + * usr_updated_usr_id: ?int, * } * - * @phpstan-type TUserRecord array{}|TUserDbRecord + * @phpstan-type TUserDbRecordOptional array{ + * usr_id?: int, + * usr_status_flag?: int, + * usr_email?: ?string, + * usr_password?: ?string, + * usr_name_prefix?: ?string, + * usr_name_first?: ?string, + * usr_name_middle?: ?string, + * usr_name_last?: ?string, + * usr_name_suffix?: ?string, + * usr_issuer?: ?string, + * usr_token_password?: ?string, + * usr_token_id?: ?string, + * usr_preferences?: ?string, + * usr_created_date?: ?string, + * usr_created_usr_id?: ?int, + * usr_updated_date?: ?string, + * usr_updated_usr_id?: ?int, + * } * - * @phpstan-type TUserTransport array{ + * @phpstan-type TUser array{ * id: int, * status: int, - * email: string, - * password: string, - * namePrefix: string, - * nameFirst: string, - * nameMiddle: string, - * nameLast: string, - * nameSuffix: string, - * issuer: string, - * tokenPassword: string, - * tokenId: string, - * preferences: string, - * createdDate: string, - * createdUserId: int, - * updatedDate: string, - * updatedUserId: int, + * email: ?string, + * password: ?string, + * namePrefix: ?string, + * nameFirst: ?string, + * nameMiddle: ?string, + * nameLast: ?string, + * nameSuffix: ?string, + * issuer: ?string, + * tokenPassword: ?string, + * tokenId: ?string, + * preferences: ?string, + * createdDate: ?string, + * createdUserId: ?int, + * updatedDate: ?string, + * updatedUserId: ?int, * } * - * @phpstan-type TUserInsert array{ - * status: int, - * email: string, - * password: string, - * namePrefix: string, - * nameFirst: string, - * nameMiddle: string, - * nameLast: string, - * nameSuffix: string, - * issuer: string, - * tokenPassword: string, - * tokenId: string, - * preferences: string, - * createdDate: string, - * createdUserId: int, - * updatedDate: string, - * updatedUserId: int, - * } + * @phpstan-type TUserTokenDbRecord array{ + * usr_id: int, + * usr_issuer: string, + * usr_token_password: string, + * usr_token_id: string + * } + * + * @phpstan-type TUserRecord array{}|TUserDbRecord * - * @phpstan-type TUserUpdate array{ - * id: int, - * status: int, - * email: string, - * password: string, - * namePrefix: string, - * nameFirst: string, - * nameMiddle: string, - * nameLast: string, - * nameSuffix: string, - * issuer: string, - * tokenPassword: string, - * tokenId: string, - * preferences: string, - * updatedDate: string, - * updatedUserId: int, - * } + * @phpstan-type TCriteria array */ final class UserTypes { diff --git a/src/Domain/Components/DataSource/User/UserValidator.php b/src/Domain/Components/DataSource/User/UserValidator.php index 47be3f0..15f2e6b 100644 --- a/src/Domain/Components/DataSource/User/UserValidator.php +++ b/src/Domain/Components/DataSource/User/UserValidator.php @@ -13,6 +13,11 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; +use Phalcon\Api\Domain\ADR\InputTypes; + +/** + * @phpstan-import-type TValidationErrors from InputTypes + */ final class UserValidator { /** @@ -21,7 +26,7 @@ final class UserValidator * * @param UserInput $input * - * @return array + * @return TValidationErrors|array{} */ public function validate(UserInput $input): array { diff --git a/src/Domain/Components/Encryption/JWTToken.php b/src/Domain/Components/Encryption/JWTToken.php index 47908f6..b1deaf2 100644 --- a/src/Domain/Components/Encryption/JWTToken.php +++ b/src/Domain/Components/Encryption/JWTToken.php @@ -31,8 +31,6 @@ use Phalcon\Support\Helper\Json\Decode; /** - * @phpstan-import-type TUserRecord from UserTypes - * @phpstan-import-type TUserTokenDbRecord from UserTypes * @phpstan-type TValidatorErrors array{}|array * * Removed the final declaration so that this class can be mocked. This @@ -139,15 +137,24 @@ public function validate( $signer = new Hmac(); $now = new DateTimeImmutable(); + /** @var string $tokenId */ + $tokenId = $user->tokenId; + /** @var string $issuer */ + $issuer = $user->issuer; + /** @var string $tokenPassword */ + $tokenPassword = $user->tokenPassword; + /** @var int $userId */ + $userId = $user->id; + $validator - ->validateId($user->tokenId) + ->validateId($tokenId) ->validateAudience($this->getTokenAudience()) - ->validateIssuer($user->issuer) + ->validateIssuer($issuer) ->validateNotBefore($now->getTimestamp()) ->validateIssuedAt($now->getTimestamp()) ->validateExpiration($now->getTimestamp()) - ->validateSignature($signer, $user->tokenPassword) - ->validateClaim(JWTEnum::UserId->value, $user->id) + ->validateSignature($signer, $tokenPassword) + ->validateClaim(JWTEnum::UserId->value, $userId) ; /** @var TValidatorErrors $errors */ @@ -189,7 +196,7 @@ private function generateTokenForUser( $tokenPassword = $user->tokenPassword; /** @var string $tokenId */ $tokenId = $user->tokenId; - /** @var string $userId */ + /** @var int $userId */ $userId = $user->id; $tokenObject = $tokenBuilder diff --git a/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php index ea9be43..399adb0 100644 --- a/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php @@ -40,7 +40,7 @@ public function call(Micro $application): bool /** @var Registry $registry */ $registry = $application->getSharedService(Container::REGISTRY); - /** @var User $user */ + /** @var User $domainUser */ $domainUser = $registry->get('user'); /** diff --git a/src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php index eed5801..57b8b9b 100644 --- a/src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php @@ -14,7 +14,6 @@ namespace Phalcon\Api\Domain\Components\Middleware; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Env\EnvManager; diff --git a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php index bc1b12f..ccb9bf4 100644 --- a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php @@ -15,7 +15,6 @@ use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; @@ -26,8 +25,6 @@ use Phalcon\Support\Registry; /** - * @phpstan-import-type TUserDbRecord from UserTypes - * @phpstan-import-type TUserRecord from UserTypes */ final class ValidateTokenUserMiddleware extends AbstractMiddleware { diff --git a/src/Domain/Components/Payload.php b/src/Domain/Components/Payload.php index 203e8d3..860a218 100644 --- a/src/Domain/Components/Payload.php +++ b/src/Domain/Components/Payload.php @@ -14,10 +14,20 @@ namespace Phalcon\Api\Domain\Components; use PayloadInterop\DomainStatus; +use Phalcon\Api\Responder\ResponderTypes; use Phalcon\Domain\Payload as PhalconPayload; +/** + * @phpstan-import-type TData from ResponderTypes + * @phpstan-import-type TErrors from ResponderTypes + */ final class Payload extends PhalconPayload { + /** + * @param string $status + * @param TData $data + * @param TErrors $errors + */ private function __construct( string $status, array $data = [], @@ -40,41 +50,82 @@ private function __construct( parent::__construct($status, $result); } + /** + * @param TData $data + * + * @return self + */ public static function created(array $data): self { return new self(DomainStatus::CREATED, $data); } + /** + * @param TData $data + * + * @return self + */ public static function deleted(array $data): self { return new self(DomainStatus::DELETED, $data); } + /** + * @param TErrors $errors + * + * @return self + */ public static function error(array $errors): self { return new self(status: DomainStatus::ERROR, errors: $errors); } + /** + * @param TErrors $errors + * + * @return self + */ public static function invalid(array $errors): self { return new self(status: DomainStatus::INVALID, errors: $errors); } - public static function notFound(array $errors): self + /** + * @return self + */ + public static function notFound(): self { - return new self(status: DomainStatus::NOT_FOUND, errors: $errors); + return new self( + status: DomainStatus::NOT_FOUND, + errors: [['Record(s) not found']] + ); } + /** + * @param TData $data + * + * @return self + */ public static function success(array $data): self { return new self(DomainStatus::SUCCESS, $data); } + /** + * @param TErrors $errors + * + * @return self + */ public static function unauthorized(array $errors): self { return new self(status: DomainStatus::UNAUTHORIZED, errors: $errors); } + /** + * @param TData $data + * + * @return self + */ public static function updated(array $data): self { return new self(DomainStatus::UPDATED, $data); diff --git a/src/Domain/Services/Auth/AbstractAuthService.php b/src/Domain/Services/Auth/AbstractAuthService.php index 446b8c8..d55c1ff 100644 --- a/src/Domain/Services/Auth/AbstractAuthService.php +++ b/src/Domain/Services/Auth/AbstractAuthService.php @@ -24,8 +24,6 @@ use Phalcon\Api\Domain\Components\Env\EnvManager; /** - * @phpstan-import-type TUserDbRecord from UserTypes - * @phpstan-import-type TLoginInput from InputTypes * @phpstan-import-type TValidationErrors from InputTypes */ abstract class AbstractAuthService implements DomainInterface diff --git a/src/Domain/Services/Auth/LoginPostService.php b/src/Domain/Services/Auth/LoginPostService.php index fe00570..50730c1 100644 --- a/src/Domain/Services/Auth/LoginPostService.php +++ b/src/Domain/Services/Auth/LoginPostService.php @@ -15,19 +15,17 @@ use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; -use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; /** - * @phpstan-import-type TUserDbRecord from UserTypes - * @phpstan-import-type TLoginInput from InputTypes + * @phpstan-import-type TAuthLoginInput from InputTypes */ final class LoginPostService extends AbstractAuthService { /** - * @param TLoginInput $input + * @param TAuthLoginInput $input * * @return Payload */ diff --git a/src/Domain/Services/Auth/LogoutPostService.php b/src/Domain/Services/Auth/LogoutPostService.php index 0a50524..e217816 100644 --- a/src/Domain/Services/Auth/LogoutPostService.php +++ b/src/Domain/Services/Auth/LogoutPostService.php @@ -15,20 +15,18 @@ use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; -use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; /** - * @phpstan-import-type TUserDbRecord from UserTypes - * @phpstan-import-type TLogoutInput from InputTypes + * @phpstan-import-type TAuthLogoutInput from InputTypes * @phpstan-import-type TValidationErrors from InputTypes */ final class LogoutPostService extends AbstractAuthService { /** - * @param TLogoutInput $input + * @param TAuthLogoutInput $input * * @return Payload */ diff --git a/src/Domain/Services/Auth/RefreshPostService.php b/src/Domain/Services/Auth/RefreshPostService.php index 63b978f..9fce9d0 100644 --- a/src/Domain/Services/Auth/RefreshPostService.php +++ b/src/Domain/Services/Auth/RefreshPostService.php @@ -15,20 +15,18 @@ use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; -use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; /** - * @phpstan-import-type TUserDbRecord from UserTypes - * @phpstan-import-type TRefreshInput from InputTypes + * @phpstan-import-type TAuthRefreshInput from InputTypes * @phpstan-import-type TValidationErrors from InputTypes */ final class RefreshPostService extends AbstractAuthService { /** - * @param TRefreshInput $input + * @param TAuthRefreshInput $input * * @return Payload */ diff --git a/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php b/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php index 2424f60..0c99a1c 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php +++ b/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php @@ -122,8 +122,7 @@ public function testToArray(): void $actual = $userInput->updatedUserId; $this->assertSame($expected, $actual); - // Verify toArray behaviour: key is the id value per implementation - $expected = [$userInput->id => get_object_vars($userInput)]; + $expected =get_object_vars($userInput); $actual = $userInput->toArray(); $this->assertSame($expected, $actual); } diff --git a/tests/Unit/Domain/Components/DataSource/User/UserSanitizerTest.php b/tests/Unit/Domain/Components/DataSource/User/UserSanitizerTest.php index 8b6ef93..481651f 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserSanitizerTest.php +++ b/tests/Unit/Domain/Components/DataSource/User/UserSanitizerTest.php @@ -41,9 +41,9 @@ public function testEmpty(): void 'tokenId' => null, 'preferences' => null, 'createdDate' => null, - 'createdUserId' => null, + 'createdUserId' => 0, 'updatedDate' => null, - 'updatedUserId' => null, + 'updatedUserId' => 0, ]; $actual = $sanitizer->sanitize([]); $this->assertSame($expected, $actual); diff --git a/tests/Unit/Domain/Components/DataSource/User/UserTest.php b/tests/Unit/Domain/Components/DataSource/User/UserTest.php index 0ce4a01..9d35178 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserTest.php +++ b/tests/Unit/Domain/Components/DataSource/User/UserTest.php @@ -54,25 +54,23 @@ public function testObject(): void $this->assertSame($expected, $actual); $expected = [ - $userData['usr_id'] => [ - 'id' => $userData['usr_id'], - 'status' => $userData['usr_status_flag'], - 'email' => $userData['usr_email'], - 'password' => $userData['usr_password'], - 'namePrefix' => $userData['usr_name_prefix'], - 'nameFirst' => $userData['usr_name_first'], - 'nameMiddle' => $userData['usr_name_middle'], - 'nameLast' => $userData['usr_name_last'], - 'nameSuffix' => $userData['usr_name_suffix'], - 'issuer' => $userData['usr_issuer'], - 'tokenPassword' => $userData['usr_token_password'], - 'tokenId' => $userData['usr_token_id'], - 'preferences' => $userData['usr_preferences'], - 'createdDate' => $userData['usr_created_date'], - 'createdUserId' => $userData['usr_created_usr_id'], - 'updatedDate' => $userData['usr_updated_date'], - 'updatedUserId' => $userData['usr_updated_usr_id'], - ], + 'id' => $userData['usr_id'], + 'status' => $userData['usr_status_flag'], + 'email' => $userData['usr_email'], + 'password' => $userData['usr_password'], + 'namePrefix' => $userData['usr_name_prefix'], + 'nameFirst' => $userData['usr_name_first'], + 'nameMiddle' => $userData['usr_name_middle'], + 'nameLast' => $userData['usr_name_last'], + 'nameSuffix' => $userData['usr_name_suffix'], + 'issuer' => $userData['usr_issuer'], + 'tokenPassword' => $userData['usr_token_password'], + 'tokenId' => $userData['usr_token_id'], + 'preferences' => $userData['usr_preferences'], + 'createdDate' => $userData['usr_created_date'], + 'createdUserId' => $userData['usr_created_usr_id'], + 'updatedDate' => $userData['usr_updated_date'], + 'updatedUserId' => $userData['usr_updated_usr_id'], ]; $actual = $user->toArray(); $this->assertSame($expected, $actual); diff --git a/tests/Unit/Domain/Services/User/UserServiceDeleteTest.php b/tests/Unit/Domain/Services/User/UserServiceDeleteTest.php index 05807f5..eb3fa4d 100644 --- a/tests/Unit/Domain/Services/User/UserServiceDeleteTest.php +++ b/tests/Unit/Domain/Services/User/UserServiceDeleteTest.php @@ -76,9 +76,7 @@ public function testServiceWithUserId(): void $errors = $actual['errors']; - $expected = [ - 'Record(s) not found', - ]; + $expected = [['Record(s) not found']]; $actual = $errors; $this->assertSame($expected, $actual); } @@ -103,9 +101,7 @@ public function testServiceZeroUserId(): void $errors = $actual['errors']; - $expected = [ - 'Record(s) not found', - ]; + $expected = [['Record(s) not found']]; $actual = $errors; $this->assertSame($expected, $actual); } diff --git a/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php b/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php index 0167686..d7fe1a6 100644 --- a/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php +++ b/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php @@ -15,7 +15,6 @@ use Phalcon\Api\Domain\Components\Cache\Cache; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\TransportRepository; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\Enums\Http\RoutesEnum; use Phalcon\Api\Domain\Components\Env\EnvManager; @@ -77,7 +76,7 @@ public function testDispatchGet(): void $this->assertSame($expected, $actual); $user = $userMapper->domain($dbUser); - $expected = $user->toArray(); + $expected = [$userId => $user->toArray()]; $actual = $data; $this->assertSame($expected, $actual); } diff --git a/tests/Unit/Domain/Services/User/UserServiceGetTest.php b/tests/Unit/Domain/Services/User/UserServiceGetTest.php index d84e820..d062474 100644 --- a/tests/Unit/Domain/Services/User/UserServiceGetTest.php +++ b/tests/Unit/Domain/Services/User/UserServiceGetTest.php @@ -37,9 +37,11 @@ public function testServiceEmptyUserId(): void $actual = $payload->getResult(); $this->assertArrayHasKey('errors', $actual); - $expected = 'Record(s) not found'; - $actual = $actual['errors'][0]; - $this->assertStringContainsString($expected, $actual); + $errors = $actual['errors']; + + $expected = [['Record(s) not found']]; + $actual = $errors; + $this->assertSame($expected, $actual); } public function testServiceWithUserId(): void @@ -155,8 +157,10 @@ public function testServiceWrongUserId(): void $actual = $payload->getResult(); $this->assertArrayHasKey('errors', $actual); - $expected = 'Record(s) not found'; - $actual = $actual['errors'][0]; - $this->assertStringContainsString($expected, $actual); + $errors = $actual['errors']; + + $expected = [['Record(s) not found']]; + $actual = $errors; + $this->assertSame($expected, $actual); } } diff --git a/tests/Unit/Domain/Services/User/UserServicePostTest.php b/tests/Unit/Domain/Services/User/UserServicePostTest.php index 653de11..a4082ca 100644 --- a/tests/Unit/Domain/Services/User/UserServicePostTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePostTest.php @@ -71,7 +71,6 @@ public function testServiceFailureNoIdReturned(): void */ $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = array_shift($domainData); $payload = $service->__invoke($domainData); @@ -84,7 +83,7 @@ public function testServiceFailureNoIdReturned(): void $errors = $actual['errors']; - $expected = ['Cannot create database record: No id returned']; + $expected = [['Cannot create database record: No id returned']]; $actual = $errors; $this->assertSame($expected, $actual); } @@ -136,7 +135,6 @@ public function testServiceFailurePdoError(): void */ $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = array_shift($domainData); $payload = $service->__invoke($domainData); @@ -149,7 +147,7 @@ public function testServiceFailurePdoError(): void $errors = $actual['errors']; - $expected = ['Cannot create database record: abcde']; + $expected = [['Cannot create database record: abcde']]; $actual = $errors; $this->assertSame($expected, $actual); } @@ -176,7 +174,6 @@ public function testServiceFailureValidation(): void */ $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = $domainData[0]; $payload = $service->__invoke($domainData); @@ -216,7 +213,6 @@ public function testServiceSuccess(): void */ $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = array_shift($domainData); $payload = $service->__invoke($domainData); @@ -318,7 +314,6 @@ public function testServiceSuccessEmptyDates(): void */ $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = $domainData[0]; $payload = $service->__invoke($domainData); diff --git a/tests/Unit/Domain/Services/User/UserServicePutTest.php b/tests/Unit/Domain/Services/User/UserServicePutTest.php index 1f6a945..976afd2 100644 --- a/tests/Unit/Domain/Services/User/UserServicePutTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePutTest.php @@ -77,7 +77,6 @@ public function testServiceFailureRecordNotFound(): void $updateUser = $userMapper->domain($userData); $updateUser = $updateUser->toArray(); - $updateUser = array_shift($updateUser); $payload = $service->__invoke($updateUser); @@ -90,7 +89,7 @@ public function testServiceFailureRecordNotFound(): void $errors = $actual['errors']; - $expected = ['Record(s) not found']; + $expected = [['Record(s) not found']]; $actual = $errors; $this->assertSame($expected, $actual); } @@ -144,7 +143,6 @@ public function testServiceFailureNoIdReturned(): void $updateUser = $userMapper->domain($userData); $updateUser = $updateUser->toArray(); - $updateUser = array_shift($updateUser); $payload = $service->__invoke($updateUser); @@ -157,7 +155,7 @@ public function testServiceFailureNoIdReturned(): void $errors = $actual['errors']; - $expected = ['Cannot update database record: No id returned']; + $expected = [['Cannot update database record: No id returned']]; $actual = $errors; $this->assertSame($expected, $actual); } @@ -216,7 +214,6 @@ public function testServiceFailurePdoError(): void */ $updateUser = $userMapper->domain($userData); $updateUser = $updateUser->toArray(); - $updateUser = array_shift($updateUser); $payload = $service->__invoke($updateUser); @@ -229,7 +226,7 @@ public function testServiceFailurePdoError(): void $errors = $actual['errors']; - $expected = ['Cannot update database record: abcde']; + $expected = [['Cannot update database record: abcde']]; $actual = $errors; $this->assertSame($expected, $actual); } @@ -253,11 +250,10 @@ public function testServiceFailureValidation(): void /** * $userData is a db record. We need a domain object here */ - $domainUser = $userMapper->domain($userData); - $domainData = $domainUser->toArray(); - $domainData = $domainData[0]; + $updateUser = $userMapper->domain($userData); + $updateUser = $updateUser->toArray(); - $payload = $service->__invoke($domainData); + $payload = $service->__invoke($updateUser); $expected = DomainStatus::INVALID; $actual = $payload->getStatus(); @@ -304,7 +300,6 @@ public function testServiceSuccess(): void */ $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = array_shift($domainData); $payload = $service->__invoke($domainData); @@ -368,10 +363,16 @@ public function testServiceSuccess(): void $actual = $data['preferences']; $this->assertSame($expected, $actual); + /** + * These have to be the same on an update + */ $expected = $dbUser['usr_created_date']; $actual = $data['createdDate']; $this->assertSame($expected, $actual); + /** + * These have to be the same on an update + */ $expected = 0; $actual = $data['createdUserId']; $this->assertSame($expected, $actual); @@ -414,7 +415,6 @@ public function testServiceSuccessEmptyDates(): void */ $domainUser = $userMapper->domain($userData); $domainData = $domainUser->toArray(); - $domainData = array_shift($domainData); $payload = $service->__invoke($domainData); @@ -478,10 +478,16 @@ public function testServiceSuccessEmptyDates(): void $actual = $data['preferences']; $this->assertSame($expected, $actual); + /** + * These have to be the same on an update + */ $expected = $dbUser['usr_created_date']; $actual = $data['createdDate']; $this->assertSame($expected, $actual); + /** + * These have to be the same on an update + */ $expected = 0; $actual = $data['createdUserId']; $this->assertSame($expected, $actual); From cd2ddc9f45c8f1f445441f660ae3c1a2d552291c Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Fri, 7 Nov 2025 14:31:07 -0600 Subject: [PATCH 28/74] [#.x] - phpcs --- src/Action/ActionHandler.php | 2 +- src/Domain/Components/Container.php | 43 +++++----- .../Components/DataSource/Auth/AuthInput.php | 4 +- .../Components/DataSource/User/UserMapper.php | 2 - .../DataSource/User/UserRepository.php | 7 +- src/Domain/Components/Encryption/JWTToken.php | 3 +- .../Components/Enums/Http/RoutesEnum.php | 6 +- .../ValidateTokenUserMiddleware.php | 1 - .../Services/Auth/AbstractAuthService.php | 1 - src/Domain/Services/Auth/LoginPostService.php | 1 - .../Services/User/AbstractUserService.php | 3 - src/Domain/Services/User/UserPostService.php | 2 +- src/Domain/Services/User/UserPutService.php | 2 +- .../DataSource/User/UserInputTest.php | 2 +- .../Components/Encryption/JWTTokenTest.php | 22 +++--- .../ValidateTokenClaimsMiddlewareTest.php | 10 +-- .../ValidateTokenRevokedMiddlewareTest.php | 12 +-- .../ValidateTokenStructureMiddlewareTest.php | 4 +- .../ValidateTokenUserMiddlewareTest.php | 4 +- .../Services/Auth/LoginPostServiceTest.php | 26 +++---- .../Services/User/UserServicePostTest.php | 1 - .../Services/User/UserServicePutTest.php | 78 +++++++++---------- 22 files changed, 111 insertions(+), 125 deletions(-) diff --git a/src/Action/ActionHandler.php b/src/Action/ActionHandler.php index d9a8533..ffeefe8 100644 --- a/src/Action/ActionHandler.php +++ b/src/Action/ActionHandler.php @@ -38,7 +38,7 @@ public function __invoke(): void { $input = new Input(); /** @var TAuthLoginInput|TUserInput $data */ - $data = $input->__invoke($this->request); + $data = $input->__invoke($this->request); $this->responder->__invoke( $this->response, diff --git a/src/Domain/Components/Container.php b/src/Domain/Components/Container.php index 861fee6..0df1641 100644 --- a/src/Domain/Components/Container.php +++ b/src/Domain/Components/Container.php @@ -107,8 +107,9 @@ class Container extends Di public const REPOSITORY = 'repository'; public const AUTH_SANITIZER = 'auth.sanitizer'; public const USER_MAPPER = UserMapper::class; - public const USER_VALIDATOR = UserValidator::class; public const USER_SANITIZER = 'user.sanitizer'; + public const USER_VALIDATOR = UserValidator::class; + /** * Responders */ @@ -402,7 +403,7 @@ private function getServiceRouter(): Service * * @return Service */ - private function getServiceUser(string $className): Service + private function getServiceSanitizer(string $className): Service { return new Service( [ @@ -410,23 +411,7 @@ private function getServiceUser(string $className): Service 'arguments' => [ [ 'type' => 'service', - 'name' => self::REPOSITORY, - ], - [ - 'type' => 'service', - 'name' => self::USER_MAPPER, - ], - [ - 'type' => 'service', - 'name' => self::USER_VALIDATOR, - ], - [ - 'type' => 'service', - 'name' => self::USER_SANITIZER, - ], - [ - 'type' => 'service', - 'name' => self::SECURITY, + 'name' => self::FILTER, ], ], ] @@ -438,7 +423,7 @@ private function getServiceUser(string $className): Service * * @return Service */ - private function getServiceSanitizer(string $className): Service + private function getServiceUser(string $className): Service { return new Service( [ @@ -446,7 +431,23 @@ private function getServiceSanitizer(string $className): Service 'arguments' => [ [ 'type' => 'service', - 'name' => self::FILTER, + 'name' => self::REPOSITORY, + ], + [ + 'type' => 'service', + 'name' => self::USER_MAPPER, + ], + [ + 'type' => 'service', + 'name' => self::USER_VALIDATOR, + ], + [ + 'type' => 'service', + 'name' => self::USER_SANITIZER, + ], + [ + 'type' => 'service', + 'name' => self::SECURITY, ], ], ] diff --git a/src/Domain/Components/DataSource/Auth/AuthInput.php b/src/Domain/Components/DataSource/Auth/AuthInput.php index cda5ced..ea53cec 100644 --- a/src/Domain/Components/DataSource/Auth/AuthInput.php +++ b/src/Domain/Components/DataSource/Auth/AuthInput.php @@ -44,11 +44,11 @@ public static function new(SanitizerInterface $sanitizer, array $input): self $sanitized = $sanitizer->sanitize($input); /** @var string|null $email */ - $email = $sanitized['email'] ?? null; + $email = $sanitized['email'] ?? null; /** @var string|null $password */ $password = $sanitized['password'] ?? null; /** @var string|null $token */ - $token = $sanitized['token'] ?? null; + $token = $sanitized['token'] ?? null; return new self($email, $password, $token); } diff --git a/src/Domain/Components/DataSource/User/UserMapper.php b/src/Domain/Components/DataSource/User/UserMapper.php index 31eebc8..7f16302 100644 --- a/src/Domain/Components/DataSource/User/UserMapper.php +++ b/src/Domain/Components/DataSource/User/UserMapper.php @@ -13,8 +13,6 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; -use Phalcon\Api\Domain\ADR\InputTypes; - /** * @phpstan-import-type TUser from UserTypes * @phpstan-import-type TUserDbRecord from UserTypes diff --git a/src/Domain/Components/DataSource/User/UserRepository.php b/src/Domain/Components/DataSource/User/UserRepository.php index e075fce..7a525f5 100644 --- a/src/Domain/Components/DataSource/User/UserRepository.php +++ b/src/Domain/Components/DataSource/User/UserRepository.php @@ -156,9 +156,8 @@ public function insert(User $user): int */ public function update(User $user): int { - - $row = $this->mapper->db($user); - $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); + $row = $this->mapper->db($user); + $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); /** @var int $userId */ $userId = $row['usr_id']; @@ -183,7 +182,7 @@ public function update(User $user): int */ unset($columns['usr_created_date'], $columns['usr_created_usr_id']); - $update = Update::new($this->connection); + $update = Update::new($this->connection); $update ->table($this->table) ->columns($columns) diff --git a/src/Domain/Components/Encryption/JWTToken.php b/src/Domain/Components/Encryption/JWTToken.php index b1deaf2..ab2dbfc 100644 --- a/src/Domain/Components/Encryption/JWTToken.php +++ b/src/Domain/Components/Encryption/JWTToken.php @@ -18,7 +18,6 @@ use Phalcon\Api\Domain\Components\Cache\Cache; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Enums\Common\FlagsEnum; use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; use Phalcon\Api\Domain\Components\Env\EnvManager; @@ -140,7 +139,7 @@ public function validate( /** @var string $tokenId */ $tokenId = $user->tokenId; /** @var string $issuer */ - $issuer = $user->issuer; + $issuer = $user->issuer; /** @var string $tokenPassword */ $tokenPassword = $user->tokenPassword; /** @var int $userId */ diff --git a/src/Domain/Components/Enums/Http/RoutesEnum.php b/src/Domain/Components/Enums/Http/RoutesEnum.php index e28552c..f703473 100644 --- a/src/Domain/Components/Enums/Http/RoutesEnum.php +++ b/src/Domain/Components/Enums/Http/RoutesEnum.php @@ -31,9 +31,9 @@ enum RoutesEnum: int */ public const EVENT_BEFORE = 'before'; public const EVENT_FINISH = 'finish'; - public const GET = 'get'; - public const POST = 'post'; - public const PUT = 'put'; + public const GET = 'get'; + public const POST = 'post'; + public const PUT = 'put'; case authLoginPost = 11; case authLogoutPost = 12; diff --git a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php index ccb9bf4..53ba2e9 100644 --- a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php @@ -15,7 +15,6 @@ use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; -use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Encryption\Security\JWT\Token\Token; diff --git a/src/Domain/Services/Auth/AbstractAuthService.php b/src/Domain/Services/Auth/AbstractAuthService.php index d55c1ff..e5ecca6 100644 --- a/src/Domain/Services/Auth/AbstractAuthService.php +++ b/src/Domain/Services/Auth/AbstractAuthService.php @@ -18,7 +18,6 @@ use Phalcon\Api\Domain\Components\Cache\Cache; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; -use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Encryption\Security; use Phalcon\Api\Domain\Components\Env\EnvManager; diff --git a/src/Domain/Services/Auth/LoginPostService.php b/src/Domain/Services/Auth/LoginPostService.php index 50730c1..9e26a68 100644 --- a/src/Domain/Services/Auth/LoginPostService.php +++ b/src/Domain/Services/Auth/LoginPostService.php @@ -15,7 +15,6 @@ use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; -use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; diff --git a/src/Domain/Services/User/AbstractUserService.php b/src/Domain/Services/User/AbstractUserService.php index a907c59..6a69945 100644 --- a/src/Domain/Services/User/AbstractUserService.php +++ b/src/Domain/Services/User/AbstractUserService.php @@ -25,8 +25,6 @@ use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; -use function array_shift; - /** * @phpstan-import-type TUser from UserTypes */ @@ -47,7 +45,6 @@ public function __construct( protected readonly UserValidator $validator, protected readonly SanitizerInterface $sanitizer, protected readonly Security $security, - ) { } diff --git a/src/Domain/Services/User/UserPostService.php b/src/Domain/Services/User/UserPostService.php index ba39ebb..f29d544 100644 --- a/src/Domain/Services/User/UserPostService.php +++ b/src/Domain/Services/User/UserPostService.php @@ -37,7 +37,7 @@ public function __invoke(array $input): Payload { $inputObject = UserInput::new($this->sanitizer, $input); /** @var TValidationErrors $errors */ - $errors = $this->validator->validate($inputObject); + $errors = $this->validator->validate($inputObject); /** * Errors exist - return early diff --git a/src/Domain/Services/User/UserPutService.php b/src/Domain/Services/User/UserPutService.php index 82871f8..3f189c0 100644 --- a/src/Domain/Services/User/UserPutService.php +++ b/src/Domain/Services/User/UserPutService.php @@ -37,7 +37,7 @@ public function __invoke(array $input): Payload { $inputObject = UserInput::new($this->sanitizer, $input); /** @var TValidationErrors $errors */ - $errors = $this->validator->validate($inputObject); + $errors = $this->validator->validate($inputObject); /** * Errors exist - return early diff --git a/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php b/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php index 0c99a1c..5ee9857 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php +++ b/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php @@ -122,7 +122,7 @@ public function testToArray(): void $actual = $userInput->updatedUserId; $this->assertSame($expected, $actual); - $expected =get_object_vars($userInput); + $expected = get_object_vars($userInput); $actual = $userInput->toArray(); $this->assertSame($expected, $actual); } diff --git a/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php b/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php index 17b517e..f7a24fa 100644 --- a/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php +++ b/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php @@ -22,8 +22,6 @@ use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Encryption\Security\JWT\Token\Token; -use function sleep; - final class JWTTokenTest extends AbstractUnitTestCase { private JWTToken $jwtToken; @@ -39,8 +37,8 @@ public function testGetForUserReturnsTokenString(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getUserData(); - $domainUser = $userMapper->domain($userData); + $userData = $this->getUserData(); + $domainUser = $userMapper->domain($userData); $token = $this->jwtToken->getForUser($domainUser); $this->assertIsString($token); @@ -51,8 +49,8 @@ public function testGetObjectReturnsPlainToken(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getUserData(); - $domainUser = $userMapper->domain($userData); + $userData = $this->getUserData(); + $domainUser = $userMapper->domain($userData); $tokenString = $this->jwtToken->getForUser($domainUser); @@ -81,8 +79,8 @@ public function testGetUserReturnsUserArray(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getUserData(); - $domainUser = $userMapper->domain($userData); + $userData = $this->getUserData(); + $domainUser = $userMapper->domain($userData); $tokenString = $this->jwtToken->getForUser($domainUser); $plain = $this->jwtToken->getObject($tokenString); @@ -126,11 +124,11 @@ public function testValidateSuccess(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getUserData(); - $domainUser = $userMapper->domain($userData); + $userData = $this->getUserData(); + $domainUser = $userMapper->domain($userData); - $tokenString = $this->jwtToken->getForUser($domainUser); - $plain = $this->jwtToken->getObject($tokenString); + $tokenString = $this->jwtToken->getForUser($domainUser); + $plain = $this->jwtToken->getObject($tokenString); $actual = $this->jwtToken->validate($plain, $domainUser); diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenClaimsMiddlewareTest.php b/tests/Unit/Domain/Components/Middleware/ValidateTokenClaimsMiddlewareTest.php index bcbfec2..0489971 100644 --- a/tests/Unit/Domain/Components/Middleware/ValidateTokenClaimsMiddlewareTest.php +++ b/tests/Unit/Domain/Components/Middleware/ValidateTokenClaimsMiddlewareTest.php @@ -70,8 +70,8 @@ public function testValidateTokenClaimsFailure( ): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $migration = new UsersMigration($this->getConnection()); - $user = $this->getNewUser($migration); + $migration = new UsersMigration($this->getConnection()); + $user = $this->getNewUser($migration); [$micro, $middleware, $jwtToken] = $this->setupTest(); @@ -118,9 +118,9 @@ public function testValidateTokenClaimsSuccess(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $migration = new UsersMigration($this->getConnection()); - $user = $this->getNewUser($migration); - $tokenUser = $userMapper->domain($user); + $migration = new UsersMigration($this->getConnection()); + $user = $this->getNewUser($migration); + $tokenUser = $userMapper->domain($user); [$micro, $middleware, $jwtToken] = $this->setupTest(); diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php b/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php index 8ce166e..ec74105 100644 --- a/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php +++ b/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php @@ -30,9 +30,9 @@ public function testValidateTokenRevokedFailureInvalidToken(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $migration = new UsersMigration($this->getConnection()); - $user = $this->getNewUser($migration); - $tokenUser = $userMapper->domain($user); + $migration = new UsersMigration($this->getConnection()); + $user = $this->getNewUser($migration); + $tokenUser = $userMapper->domain($user); [$micro, $middleware] = $this->setupTest(); @@ -73,9 +73,9 @@ public function testValidateTokenRevokedSuccess(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $migration = new UsersMigration($this->getConnection()); - $user = $this->getNewUser($migration); - $tokenUser = $userMapper->domain($user); + $migration = new UsersMigration($this->getConnection()); + $user = $this->getNewUser($migration); + $tokenUser = $userMapper->domain($user); [$micro, $middleware] = $this->setupTest(); diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenStructureMiddlewareTest.php b/tests/Unit/Domain/Components/Middleware/ValidateTokenStructureMiddlewareTest.php index 4a7ebbf..f7f5f99 100644 --- a/tests/Unit/Domain/Components/Middleware/ValidateTokenStructureMiddlewareTest.php +++ b/tests/Unit/Domain/Components/Middleware/ValidateTokenStructureMiddlewareTest.php @@ -85,8 +85,8 @@ public function testValidateTokenStructureSuccess(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getNewUserData(); - $domainUser = $userMapper->domain($userData); + $userData = $this->getNewUserData(); + $domainUser = $userMapper->domain($userData); [$micro, $middleware] = $this->setupTest(); /** @var JWTToken $jwtToken */ diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenUserMiddlewareTest.php b/tests/Unit/Domain/Components/Middleware/ValidateTokenUserMiddlewareTest.php index a207a99..c3459e0 100644 --- a/tests/Unit/Domain/Components/Middleware/ValidateTokenUserMiddlewareTest.php +++ b/tests/Unit/Domain/Components/Middleware/ValidateTokenUserMiddlewareTest.php @@ -63,8 +63,8 @@ public function testValidateTokenUserSuccess(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $migration = new UsersMigration($this->getConnection()); - $user = $this->getNewUser($migration); + $migration = new UsersMigration($this->getConnection()); + $user = $this->getNewUser($migration); $domainUser = $userMapper->domain($user); [$micro, $middleware, $jwtToken] = $this->setupTest(); diff --git a/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php b/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php index b3d60be..fe69a9a 100644 --- a/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php +++ b/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php @@ -110,22 +110,18 @@ public function testServiceWithCredentials(): void $this->assertNotEmpty($jwt['refreshToken']); } - public function testServiceWrongCredentialsForUser(): void + public function testServiceWrongCredentials(): void { + $faker = Factory::create(); /** @var LoginPostService $service */ - $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); - $migration = new UsersMigration($this->getConnection()); - - $password = 'password'; + $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); /** * Issue a wrong password */ - $dbUser = $this->getNewUser($migration, ['usr_password' => $password]); - $email = $dbUser['usr_email']; $payload = [ - 'email' => $email, - 'password' => $password . '2', + 'email' => $faker->email(), + 'password' => $faker->password(), ]; $payload = $service->__invoke($payload); @@ -142,18 +138,22 @@ public function testServiceWrongCredentialsForUser(): void $this->assertSame($expected, $actual); } - public function testServiceWrongCredentials(): void + public function testServiceWrongCredentialsForUser(): void { - $faker = Factory::create(); /** @var LoginPostService $service */ $service = $this->container->get(Container::AUTH_LOGIN_POST_SERVICE); + $migration = new UsersMigration($this->getConnection()); + + $password = 'password'; /** * Issue a wrong password */ + $dbUser = $this->getNewUser($migration, ['usr_password' => $password]); + $email = $dbUser['usr_email']; $payload = [ - 'email' => $faker->email(), - 'password' => $faker->password(), + 'email' => $email, + 'password' => $password . '2', ]; $payload = $service->__invoke($payload); diff --git a/tests/Unit/Domain/Services/User/UserServicePostTest.php b/tests/Unit/Domain/Services/User/UserServicePostTest.php index a4082ca..231523b 100644 --- a/tests/Unit/Domain/Services/User/UserServicePostTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePostTest.php @@ -24,7 +24,6 @@ use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\BackupGlobals; -use function array_shift; use function htmlspecialchars; #[BackupGlobals(true)] diff --git a/tests/Unit/Domain/Services/User/UserServicePutTest.php b/tests/Unit/Domain/Services/User/UserServicePutTest.php index 976afd2..34fab9a 100644 --- a/tests/Unit/Domain/Services/User/UserServicePutTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePutTest.php @@ -25,16 +25,14 @@ use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use PHPUnit\Framework\Attributes\BackupGlobals; -use function array_shift; - #[BackupGlobals(true)] final class UserServicePutTest extends AbstractUnitTestCase { - public function testServiceFailureRecordNotFound(): void + public function testServiceFailureNoIdReturned(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getNewUserData(); + $userData = $this->getNewUserData(); $userData['usr_id'] = 1; @@ -45,12 +43,14 @@ public function testServiceFailureRecordNotFound(): void ->disableOriginalConstructor() ->onlyMethods( [ - 'findById' + 'update', + 'findById', ] ) ->getMock() ; - $userRepository->method('findById')->willReturn(null); + $userRepository->method('update')->willReturn(0); + $userRepository->method('findById')->willReturn($findByUser); $repository = $this ->getMockBuilder(QueryRepository::class) @@ -80,7 +80,7 @@ public function testServiceFailureRecordNotFound(): void $payload = $service->__invoke($updateUser); - $expected = DomainStatus::NOT_FOUND; + $expected = DomainStatus::ERROR; $actual = $payload->getStatus(); $this->assertSame($expected, $actual); @@ -89,16 +89,16 @@ public function testServiceFailureRecordNotFound(): void $errors = $actual['errors']; - $expected = [['Record(s) not found']]; + $expected = [['Cannot update database record: No id returned']]; $actual = $errors; $this->assertSame($expected, $actual); } - public function testServiceFailureNoIdReturned(): void + public function testServiceFailurePdoError(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getNewUserData(); + $userData = $this->getNewUserData(); $userData['usr_id'] = 1; @@ -110,13 +110,16 @@ public function testServiceFailureNoIdReturned(): void ->onlyMethods( [ 'update', - 'findById' + 'findById', ] ) ->getMock() ; - $userRepository->method('update')->willReturn(0); $userRepository->method('findById')->willReturn($findByUser); + $userRepository + ->method('update') + ->willThrowException(new PDOException('abcde')) + ; $repository = $this ->getMockBuilder(QueryRepository::class) @@ -128,19 +131,21 @@ public function testServiceFailureNoIdReturned(): void ) ->getMock() ; - $repository->method('user')->willReturn($userRepository); - - $this->container->setShared(Container::REPOSITORY, $repository); + $repository + ->method('user') + ->willReturn($userRepository) + ; + $this->container->set(Container::REPOSITORY, $repository); /** @var UserPutService $service */ $service = $this->container->get(Container::USER_PUT_SERVICE); - /** - * Update user - */ $userData = $this->getNewUserData(); $userData['usr_id'] = 1; + /** + * $userData is a db record. We need a domain object here + */ $updateUser = $userMapper->domain($userData); $updateUser = $updateUser->toArray(); @@ -155,16 +160,16 @@ public function testServiceFailureNoIdReturned(): void $errors = $actual['errors']; - $expected = [['Cannot update database record: No id returned']]; + $expected = [['Cannot update database record: abcde']]; $actual = $errors; $this->assertSame($expected, $actual); } - public function testServiceFailurePdoError(): void + public function testServiceFailureRecordNotFound(): void { /** @var UserMapper $userMapper */ $userMapper = $this->container->get(Container::USER_MAPPER); - $userData = $this->getNewUserData(); + $userData = $this->getNewUserData(); $userData['usr_id'] = 1; @@ -175,17 +180,12 @@ public function testServiceFailurePdoError(): void ->disableOriginalConstructor() ->onlyMethods( [ - 'update', - 'findById' + 'findById', ] ) ->getMock() ; - $userRepository->method('findById')->willReturn($findByUser); - $userRepository - ->method('update') - ->willThrowException(new PDOException('abcde')) - ; + $userRepository->method('findById')->willReturn(null); $repository = $this ->getMockBuilder(QueryRepository::class) @@ -197,27 +197,25 @@ public function testServiceFailurePdoError(): void ) ->getMock() ; - $repository - ->method('user') - ->willReturn($userRepository) - ; + $repository->method('user')->willReturn($userRepository); + + $this->container->setShared(Container::REPOSITORY, $repository); - $this->container->set(Container::REPOSITORY, $repository); /** @var UserPutService $service */ $service = $this->container->get(Container::USER_PUT_SERVICE); + /** + * Update user + */ $userData = $this->getNewUserData(); $userData['usr_id'] = 1; - /** - * $userData is a db record. We need a domain object here - */ $updateUser = $userMapper->domain($userData); $updateUser = $updateUser->toArray(); $payload = $service->__invoke($updateUser); - $expected = DomainStatus::ERROR; + $expected = DomainStatus::NOT_FOUND; $actual = $payload->getStatus(); $this->assertSame($expected, $actual); @@ -226,7 +224,7 @@ public function testServiceFailurePdoError(): void $errors = $actual['errors']; - $expected = [['Cannot update database record: abcde']]; + $expected = [['Record(s) not found']]; $actual = $errors; $this->assertSame($expected, $actual); } @@ -492,8 +490,8 @@ public function testServiceSuccessEmptyDates(): void $actual = $data['createdUserId']; $this->assertSame($expected, $actual); - $today = date('Y-m-d '); - $actual = $data['updatedDate']; + $today = date('Y-m-d '); + $actual = $data['updatedDate']; $this->assertStringContainsString($today, $actual); $expected = $dbUser['usr_updated_usr_id']; From e2bd5ef9a2210eb35580a18b37cf883c1deea40a Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:48:35 -0600 Subject: [PATCH 29/74] [#.x] - removed cache and query repository - major refactoring --- src/Domain/Components/Cache/Cache.php | 134 ------------------ .../Components/DataSource/QueryRepository.php | 45 ------ 2 files changed, 179 deletions(-) delete mode 100644 src/Domain/Components/Cache/Cache.php delete mode 100644 src/Domain/Components/DataSource/QueryRepository.php diff --git a/src/Domain/Components/Cache/Cache.php b/src/Domain/Components/Cache/Cache.php deleted file mode 100644 index e1f38ef..0000000 --- a/src/Domain/Components/Cache/Cache.php +++ /dev/null @@ -1,134 +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\Domain\Components\Cache; - -use DateTimeImmutable; -use Phalcon\Api\Domain\Components\Constants\Dates; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\Env\EnvManager; -use Phalcon\Cache\Adapter\Redis; -use Phalcon\Cache\Cache as PhalconCache; -use Psr\SimpleCache\InvalidArgumentException; - -use function sha1; - -class Cache extends PhalconCache -{ - /** @var int */ - public const CACHE_LIFETIME_DAY = 86400; - /** @var int */ - public const CACHE_LIFETIME_HOUR = 3600; - /** - * Cache Timeouts - */ - /** @var int */ - public const CACHE_LIFETIME_MINUTE = 60; - /** @var int */ - public const CACHE_LIFETIME_MONTH = 2592000; - /** - * Default token expiry - 4 hours - */ - /** @var int */ - public const CACHE_TOKEN_EXPIRY = 14400; - /** - * Cache masks - */ - /** @var string */ - private const MASK_TOKEN_USER = 'tk-%s-%s'; - - /** - * @param User $domainUser - * @param string $token - * - * @return string - */ - public function getCacheTokenKey(User $domainUser, string $token): string - { - $tokenString = ''; - if (true !== empty($token)) { - $tokenString = sha1($token); - } - - return sprintf( - self::MASK_TOKEN_USER, - $domainUser->id, - $tokenString - ); - } - - /** - * @param EnvManager $env - * @param User $domainUser - * - * @return bool - * @throws InvalidArgumentException - */ - public function invalidateForUser( - EnvManager $env, - User $domainUser - ): bool { - /** - * We could store the tokens in the database but this way is faster - * and Redis also has a TTL which auto expires elements. - * - * To get all the keys for a user, we use the underlying adapter - * of the cache which is Redis and call the `getKeys()` on it. The - * keys will come back with the prefix defined in the adapter. In order - * to delete them, we need to remove the prefix because `delete()` will - * automatically prepend each key with it. - */ - /** @var Redis $redis */ - $redis = $this->getAdapter(); - $pattern = $this->getCacheTokenKey($domainUser, ''); - $keys = $redis->getKeys($pattern); - /** @var string $prefix */ - $prefix = $env->get('CACHE_PREFIX', '-rest-', 'string'); - $newKeys = []; - /** @var string $key */ - foreach ($keys as $key) { - $newKeys[] = str_replace($prefix, '', $key); - } - - return $this->deleteMultiple($newKeys); - } - - /** - * @param EnvManager $env - * @param User $domainUser - * @param string $token - * - * @return bool - * @throws InvalidArgumentException - */ - public function storeTokenInCache( - EnvManager $env, - User $domainUser, - string $token - ): bool { - $cacheKey = $this->getCacheTokenKey($domainUser, $token); - /** @var int $expiration */ - $expiration = $env->get('TOKEN_EXPIRATION', self::CACHE_TOKEN_EXPIRY, 'int'); - $expirationDate = (new DateTimeImmutable()) - ->modify('+' . $expiration . ' seconds') - ->format(Dates::DATE_TIME_FORMAT) - ; - - $payload = [ - 'token' => $token, - 'expiry' => $expirationDate, - ]; - - return $this->set($cacheKey, $payload, $expiration); - } -} diff --git a/src/Domain/Components/DataSource/QueryRepository.php b/src/Domain/Components/DataSource/QueryRepository.php deleted file mode 100644 index 08dce4d..0000000 --- a/src/Domain/Components/DataSource/QueryRepository.php +++ /dev/null @@ -1,45 +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\Domain\Components\DataSource; - -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; -use Phalcon\DataMapper\Pdo\Connection; - -class QueryRepository -{ - private ?UserRepositoryInterface $user = null; - - /** - * @param Connection $connection - */ - public function __construct( - private readonly Connection $connection, - private readonly UserMapper $userMapper, - ) { - } - - /** - * @return UserRepositoryInterface - */ - public function user(): UserRepositoryInterface - { - if (null === $this->user) { - $this->user = new UserRepository($this->connection, $this->userMapper); - } - - return $this->user; - } -} From 82126b05ef08eb6701fe364476155a4e08c7447a Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:49:12 -0600 Subject: [PATCH 30/74] [#.x] - refactored input classes --- .../Components/DataSource/AbstractInput.php | 55 +++++++++++++ .../Components/DataSource/Auth/AuthInput.php | 22 +++--- .../Components/DataSource/User/UserInput.php | 78 ++++++++++--------- 3 files changed, 105 insertions(+), 50 deletions(-) create mode 100644 src/Domain/Components/DataSource/AbstractInput.php diff --git a/src/Domain/Components/DataSource/AbstractInput.php b/src/Domain/Components/DataSource/AbstractInput.php new file mode 100644 index 0000000..16993b7 --- /dev/null +++ b/src/Domain/Components/DataSource/AbstractInput.php @@ -0,0 +1,55 @@ + + * + * 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\Components\DataSource; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; + +/** + * Base factory for input DTOs. + * + * Concrete DTOs must implement protected static function + * + * `fromArray(array $sanitized): static` + * + * and keep themselves immutable/read\-only. + * + * @phpstan-import-type TInputSanitize from InputTypes + */ +abstract class AbstractInput +{ + /** + * Factory that accepts a SanitizerInterface and returns the concrete DTO. + * + * @param SanitizerInterface $sanitizer + * @param TInputSanitize $input + * + * @return static + */ + public static function new(SanitizerInterface $sanitizer, array $input): static + { + $sanitized = $sanitizer->sanitize($input); + + return static::fromArray($sanitized); + } + + /** + * Build the concrete DTO from a sanitized array. + * + * @param TInputSanitize $sanitized + * + * @return static + */ + abstract protected static function fromArray(array $sanitized): static; +} diff --git a/src/Domain/Components/DataSource/Auth/AuthInput.php b/src/Domain/Components/DataSource/Auth/AuthInput.php index ea53cec..3fe35d6 100644 --- a/src/Domain/Components/DataSource/Auth/AuthInput.php +++ b/src/Domain/Components/DataSource/Auth/AuthInput.php @@ -14,12 +14,12 @@ namespace Phalcon\Api\Domain\Components\DataSource\Auth; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; +use Phalcon\Api\Domain\Components\DataSource\AbstractInput; /** * @phpstan-import-type TAuthInput from InputTypes */ -final class AuthInput +final class AuthInput extends AbstractInput { /** * @param string|null $email @@ -34,21 +34,17 @@ public function __construct( } /** - * @param SanitizerInterface $sanitizer - * @param TAuthInput $input + * Build the concrete DTO from a sanitized array. * - * @return self + * @param TAuthInput $sanitized + * + * @return static */ - public static function new(SanitizerInterface $sanitizer, array $input): self + protected static function fromArray(array $sanitized): static { - $sanitized = $sanitizer->sanitize($input); - - /** @var string|null $email */ - $email = $sanitized['email'] ?? null; - /** @var string|null $password */ + $email = $sanitized['email'] ?? null; $password = $sanitized['password'] ?? null; - /** @var string|null $token */ - $token = $sanitized['token'] ?? null; + $token = $sanitized['token'] ?? null; return new self($email, $password, $token); } diff --git a/src/Domain/Components/DataSource/User/UserInput.php b/src/Domain/Components/DataSource/User/UserInput.php index e983dc7..de0c63a 100644 --- a/src/Domain/Components/DataSource/User/UserInput.php +++ b/src/Domain/Components/DataSource/User/UserInput.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; +use Phalcon\Api\Domain\Components\DataSource\AbstractInput; use function get_object_vars; @@ -22,11 +22,11 @@ * @phpstan-import-type TUserInput from InputTypes * @phpstan-import-type TUser from UserTypes */ -final class UserInput +final class UserInput extends AbstractInput { /** - * @param int|null $id - * @param int|null $status + * @param int $id + * @param int $status * @param string|null $email * @param string|null $password * @param string|null $namePrefix @@ -44,8 +44,8 @@ final class UserInput * @param int|null $updatedUserId */ public function __construct( - public readonly ?int $id, - public readonly ?int $status, + public readonly int $id, + public readonly int $status, public readonly ?string $email, public readonly ?string $password, public readonly ?string $namePrefix, @@ -65,45 +65,49 @@ public function __construct( } /** - * @param SanitizerInterface $sanitizer - * @param TUserInput $input - * - * @return self + * @return TUser */ - public static function new(SanitizerInterface $sanitizer, array $input): self + public function toArray(): array { - /** @var TUser $sanitized */ - $sanitized = $sanitizer->sanitize($input); + /** @var TUser $vars */ + $vars = get_object_vars($this); - return new self( - $sanitized['id'], - $sanitized['status'], - $sanitized['email'], - $sanitized['password'], - $sanitized['namePrefix'], - $sanitized['nameFirst'], - $sanitized['nameMiddle'], - $sanitized['nameLast'], - $sanitized['nameSuffix'], - $sanitized['issuer'], - $sanitized['tokenPassword'], - $sanitized['tokenId'], - $sanitized['preferences'], - $sanitized['createdDate'], - $sanitized['createdUserId'], - $sanitized['updatedDate'], - $sanitized['updatedUserId'] - ); + return $vars; } /** - * @return TUser + * Build the concrete DTO from a sanitized array. + * + * @param TUserInput $sanitized + * + * @return static */ - public function toArray(): array + protected static function fromArray(array $sanitized): static { - /** @var TUser $vars */ - $vars = get_object_vars($this); + $id = isset($sanitized['id']) ? (int)$sanitized['id'] : 0; + $status = isset($sanitized['status']) ? (int)$sanitized['status'] : 0; - return $vars; + $createdUserId = isset($sanitized['createdUserId']) ? (int)$sanitized['createdUserId'] : 0; + $updatedUserId = isset($sanitized['updatedUserId']) ? (int)$sanitized['updatedUserId'] : 0; + + return new self( + $id, + $status, + isset($sanitized['email']) ? (string)$sanitized['email'] : null, + isset($sanitized['password']) ? (string)$sanitized['password'] : null, + isset($sanitized['namePrefix']) ? (string)$sanitized['namePrefix'] : null, + isset($sanitized['nameFirst']) ? (string)$sanitized['nameFirst'] : null, + isset($sanitized['nameMiddle']) ? (string)$sanitized['nameMiddle'] : null, + isset($sanitized['nameLast']) ? (string)$sanitized['nameLast'] : null, + isset($sanitized['nameSuffix']) ? (string)$sanitized['nameSuffix'] : null, + isset($sanitized['issuer']) ? (string)$sanitized['issuer'] : null, + isset($sanitized['tokenPassword']) ? (string)$sanitized['tokenPassword'] : null, + isset($sanitized['tokenId']) ? (string)$sanitized['tokenId'] : null, + isset($sanitized['preferences']) ? (string)$sanitized['preferences'] : null, + isset($sanitized['createdDate']) ? (string)$sanitized['createdDate'] : null, + $createdUserId, + isset($sanitized['updatedDate']) ? (string)$sanitized['updatedDate'] : null, + $updatedUserId + ); } } From 9bb15818fab3227fd053e9c0e2b69cf147887429 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:49:37 -0600 Subject: [PATCH 31/74] [#.x] - adjusted sanitizers and introduced interface --- .../Components/DataSource/Auth/AuthSanitizer.php | 5 +++-- .../{ => Interfaces}/SanitizerInterface.php | 10 ++++++---- .../Components/DataSource/User/UserSanitizer.php | 14 ++++++++------ 3 files changed, 17 insertions(+), 12 deletions(-) rename src/Domain/Components/DataSource/{ => Interfaces}/SanitizerInterface.php (65%) diff --git a/src/Domain/Components/DataSource/Auth/AuthSanitizer.php b/src/Domain/Components/DataSource/Auth/AuthSanitizer.php index fdf86fd..3fa50de 100644 --- a/src/Domain/Components/DataSource/Auth/AuthSanitizer.php +++ b/src/Domain/Components/DataSource/Auth/AuthSanitizer.php @@ -14,8 +14,9 @@ namespace Phalcon\Api\Domain\Components\DataSource\Auth; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; +use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; use Phalcon\Filter\Filter; +use Phalcon\Filter\FilterInterface; /** * @phpstan-import-type TAuthInput from InputTypes @@ -23,7 +24,7 @@ final class AuthSanitizer implements SanitizerInterface { public function __construct( - private readonly Filter $filter, + private readonly FilterInterface $filter, ) { } diff --git a/src/Domain/Components/DataSource/SanitizerInterface.php b/src/Domain/Components/DataSource/Interfaces/SanitizerInterface.php similarity index 65% rename from src/Domain/Components/DataSource/SanitizerInterface.php rename to src/Domain/Components/DataSource/Interfaces/SanitizerInterface.php index 5620f01..bf184b1 100644 --- a/src/Domain/Components/DataSource/SanitizerInterface.php +++ b/src/Domain/Components/DataSource/Interfaces/SanitizerInterface.php @@ -11,19 +11,21 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource; +namespace Phalcon\Api\Domain\Components\DataSource\Interfaces; + +use Phalcon\Api\Domain\ADR\InputTypes; /** - * @phpstan-type TInput array + * @phpstan-import-type TInputSanitize from InputTypes */ interface SanitizerInterface { /** * Return a sanitized array of the input * - * @param TInput $input + * @param TInputSanitize $input * - * @return TInput + * @return TInputSanitize */ public function sanitize(array $input): array; } diff --git a/src/Domain/Components/DataSource/User/UserSanitizer.php b/src/Domain/Components/DataSource/User/UserSanitizer.php index bfdf298..98c073d 100644 --- a/src/Domain/Components/DataSource/User/UserSanitizer.php +++ b/src/Domain/Components/DataSource/User/UserSanitizer.php @@ -13,25 +13,27 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; -use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; use Phalcon\Filter\Filter; +use Phalcon\Filter\FilterInterface; /** - * @phpstan-import-type TUser from UserTypes + * @phpstan-import-type TUserInput from InputTypes */ final class UserSanitizer implements SanitizerInterface { public function __construct( - private readonly Filter $filter, + private readonly FilterInterface $filter, ) { } /** * Return a sanitized array of the input * - * @param TUser $input + * @param TUserInput $input * - * @return TUser + * @return TUserInput */ public function sanitize(array $input): array { @@ -73,7 +75,7 @@ public function sanitize(array $input): array $sanitized[$name] = $value; } - /** @var TUser $sanitized */ + /** @var TUserInput $sanitized */ return $sanitized; } From 2ca3e12910caeb0b80579899de4a808f81fa61ce Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:50:25 -0600 Subject: [PATCH 32/74] [#.x] - validator interface, validator classes for endpoints and tests --- .../DataSource/Auth/AuthLoginValidator.php | 41 +++++++++ .../DataSource/Auth/AuthTokenValidator.php | 90 +++++++++++++++++++ .../DataSource/User/UserValidator.php | 13 ++- .../Validation/ValidatorInterface.php | 29 ++++++ .../DataSource/User/UserValidatorTest.php | 6 +- 5 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 src/Domain/Components/DataSource/Auth/AuthLoginValidator.php create mode 100644 src/Domain/Components/DataSource/Auth/AuthTokenValidator.php create mode 100644 src/Domain/Components/DataSource/Validation/ValidatorInterface.php diff --git a/src/Domain/Components/DataSource/Auth/AuthLoginValidator.php b/src/Domain/Components/DataSource/Auth/AuthLoginValidator.php new file mode 100644 index 0000000..c2a4d78 --- /dev/null +++ b/src/Domain/Components/DataSource/Auth/AuthLoginValidator.php @@ -0,0 +1,41 @@ + + * + * 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\Components\DataSource\Auth; + +use Phalcon\Api\Domain\Components\DataSource\Validation\Result; +use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; + +final class AuthLoginValidator implements ValidatorInterface +{ + /** + * Validate a AuthInput and return an array of errors. + * Empty array means valid. + * + * @param AuthInput $input + * + * @return Result + */ + public function validate(mixed $input): Result + { + /** @var AuthInput $input */ + if (true === empty($input->email) || true === empty($input->password)) { + return Result::error( + [HttpCodesEnum::AppIncorrectCredentials->error()] + ); + } + + return Result::success(); + } +} diff --git a/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php b/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php new file mode 100644 index 0000000..8b8e1d6 --- /dev/null +++ b/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php @@ -0,0 +1,90 @@ + + * + * 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\Components\DataSource\Auth; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\DataSource\User\UserInput; +use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; +use Phalcon\Api\Domain\Components\DataSource\Validation\Result; +use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Components\Encryption\TokenManager; +use Phalcon\Api\Domain\Components\Encryption\TokenManagerInterface; +use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; +use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Components\Payload; +use Phalcon\Encryption\Security\JWT\Token\Token; + +final class AuthTokenValidator implements ValidatorInterface +{ + public function __construct( + private TokenManagerInterface $tokenManager, + private UserRepositoryInterface $userRepository, + ) { + } + + /** + * Validate a AuthInput and return an array of errors. + * Empty array means valid. + * + * @param AuthInput $input + * + * @return Result + */ + public function validate(mixed $input): Result + { + /** @var AuthInput $input */ + if (true === empty($input->token)) { + return Result::error([HttpCodesEnum::AppTokenNotPresent->error()]); + } + + $token = $input->token; + $tokenObject = $this->tokenManager->getObject($token); + if (null === $tokenObject) { + return Result::error([HttpCodesEnum::AppTokenNotValid->error()]); + } + + if ($this->tokenIsNotRefresh($tokenObject)) { + return Result::error([HttpCodesEnum::AppTokenNotValid->error()]); + } + + $domainUser = $this->tokenManager->getUser($this->userRepository, $tokenObject); + if (null === $domainUser) { + return Result::error([HttpCodesEnum::AppTokenInvalidUser->error()]); + } + + $errors = $this->tokenManager->validate($tokenObject, $domainUser); + if (!empty($errors)) { + return Result::error($errors); + } + + $result = Result::success(); + $result->setMeta('user', $domainUser); + + return $result; + } + + /** + * Return if the token is a refresh one or not + * + * @param Token $tokenObject + * + * @return bool + */ + private function tokenIsNotRefresh(Token $tokenObject): bool + { + $isRefresh = $tokenObject->getClaims()->get(JWTEnum::Refresh->value); + + return false === $isRefresh; + } +} diff --git a/src/Domain/Components/DataSource/User/UserValidator.php b/src/Domain/Components/DataSource/User/UserValidator.php index 15f2e6b..4aae41b 100644 --- a/src/Domain/Components/DataSource/User/UserValidator.php +++ b/src/Domain/Components/DataSource/User/UserValidator.php @@ -14,11 +14,10 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\DataSource\Validation\Result; +use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; -/** - * @phpstan-import-type TValidationErrors from InputTypes - */ -final class UserValidator +final class UserValidator implements ValidatorInterface { /** * Validate a UserInput and return an array of errors. @@ -26,9 +25,9 @@ final class UserValidator * * @param UserInput $input * - * @return TValidationErrors|array{} + * @return Result */ - public function validate(UserInput $input): array + public function validate(mixed $input): Result { $errors = []; $required = [ @@ -50,6 +49,6 @@ public function validate(UserInput $input): array * @todo add validators */ - return $errors; + return new Result($errors); } } diff --git a/src/Domain/Components/DataSource/Validation/ValidatorInterface.php b/src/Domain/Components/DataSource/Validation/ValidatorInterface.php new file mode 100644 index 0000000..72979d2 --- /dev/null +++ b/src/Domain/Components/DataSource/Validation/ValidatorInterface.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\Components\DataSource\Validation; + +/** + * Validator contract. Accepts a DTO or input and returns a Result. + */ +interface ValidatorInterface +{ + /** + * Validate a DTO or input structure. + * + * @param mixed $input DTO or array + * + * @return Result + */ + public function validate(mixed $input): Result; +} diff --git a/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php b/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php index a71e7b2..f1ea417 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php +++ b/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php @@ -31,7 +31,8 @@ public function testError(): void $userInput = UserInput::new($sanitizer, $input); $validator = new UserValidator(); - $actual = $validator->validate($userInput); + $result = $validator->validate($userInput); + $actual = $result->getErrors(); $expected = [ ['Field email cannot be empty.'], @@ -61,7 +62,8 @@ public function testSuccess(): void $userInput = UserInput::new($sanitizer, $input); $validator = new UserValidator(); - $actual = $validator->validate($userInput); + $result = $validator->validate($userInput); + $actual = $result->getErrors(); $expected = []; $this->assertSame($expected, $actual); From 78064662f3e5a628e2fd9488ca9208b0fffb4407 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:50:53 -0600 Subject: [PATCH 33/74] [#.x] - introducing token manager and token cache for token operations + interfaces --- src/Domain/Components/Encryption/JWTToken.php | 15 +- .../Components/Encryption/TokenCache.php | 114 ++++++++++++++ .../Encryption/TokenCacheInterface.php | 47 ++++++ .../Components/Encryption/TokenManager.php | 140 ++++++++++++++++++ .../Encryption/TokenManagerInterface.php | 89 +++++++++++ .../Components/Encryption/JWTTokenTest.php | 18 +-- 6 files changed, 399 insertions(+), 24 deletions(-) create mode 100644 src/Domain/Components/Encryption/TokenCache.php create mode 100644 src/Domain/Components/Encryption/TokenCacheInterface.php create mode 100644 src/Domain/Components/Encryption/TokenManager.php create mode 100644 src/Domain/Components/Encryption/TokenManagerInterface.php diff --git a/src/Domain/Components/Encryption/JWTToken.php b/src/Domain/Components/Encryption/JWTToken.php index ab2dbfc..749b4e3 100644 --- a/src/Domain/Components/Encryption/JWTToken.php +++ b/src/Domain/Components/Encryption/JWTToken.php @@ -15,9 +15,10 @@ use DateTimeImmutable; use InvalidArgumentException; -use Phalcon\Api\Domain\Components\Cache\Cache; -use Phalcon\Api\Domain\Components\DataSource\QueryRepository; +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; use Phalcon\Api\Domain\Components\Enums\Common\FlagsEnum; use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; use Phalcon\Api\Domain\Components\Env\EnvManager; @@ -30,7 +31,7 @@ use Phalcon\Support\Helper\Json\Decode; /** - * @phpstan-type TValidatorErrors array{}|array + * @phpstan-import-type TValidatorErrors from InputTypes * * Removed the final declaration so that this class can be mocked. This * class should not be extended @@ -94,13 +95,13 @@ public function getRefreshForUser(User $user): string } /** - * @param QueryRepository $repository + * @param UserRepositoryInterface $repository * @param Token $token * * @return User|null */ public function getUser( - QueryRepository $repository, + UserRepositoryInterface $repository, Token $token, ): ?User { /** @var string $issuer */ @@ -117,7 +118,7 @@ public function getUser( 'usr_token_id' => $tokenId, ]; - return $repository->user()->findOneBy($criteria); + return $repository->findOneBy($criteria); } /** @@ -177,7 +178,7 @@ private function generateTokenForUser( /** @var int $expiration */ $expiration = $this->env->get( 'TOKEN_EXPIRATION', - Cache::CACHE_TOKEN_EXPIRY, + CacheConstants::CACHE_TOKEN_EXPIRY, 'int' ); diff --git a/src/Domain/Components/Encryption/TokenCache.php b/src/Domain/Components/Encryption/TokenCache.php new file mode 100644 index 0000000..fc06d9b --- /dev/null +++ b/src/Domain/Components/Encryption/TokenCache.php @@ -0,0 +1,114 @@ + + * + * 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\Components\Encryption; + +use DateTimeImmutable; +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Components\Constants\Dates; +use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; +use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Cache\Adapter\Redis; +use Phalcon\Cache\Cache; +use Phalcon\Encryption\Security\JWT\Token\Token; +use Psr\SimpleCache\CacheInterface; +use Psr\SimpleCache\InvalidArgumentException; +use Throwable; + +use function str_replace; + +/** + * Small component to issue/rotate/revoke tokens and + * interact with cache. + * + * @phpstan-import-type TTokenIssue from TokenManagerInterface + * @phpstan-import-type TValidatorErrors from InputTypes + */ +final class TokenCache implements TokenCacheInterface +{ + public function __construct( + private readonly Cache $cache, + ) { + } + + /** + * @param EnvManager $env + * @param User $domainUser + * + * @return bool + * @throws InvalidArgumentException + */ + public function invalidateForUser( + EnvManager $env, + User $domainUser + ): bool { + /** + * We could store the tokens in the database but this way is faster + * and Redis also has a TTL which auto expires elements. + * + * To get all the keys for a user, we use the underlying adapter + * of the cache which is Redis and call the `getKeys()` on it. The + * keys will come back with the prefix defined in the adapter. In order + * to delete them, we need to remove the prefix because `delete()` will + * automatically prepend each key with it. + */ + /** @var Redis $redis */ + $redis = $this->cache->getAdapter(); + $pattern = CacheConstants::getCacheTokenKey($domainUser, ''); + $keys = $redis->getKeys($pattern); + /** @var string $prefix */ + $prefix = $env->get('CACHE_PREFIX', '-rest-', 'string'); + $newKeys = []; + /** @var string $key */ + foreach ($keys as $key) { + $newKeys[] = str_replace($prefix, '', $key); + } + + return $this->cache->deleteMultiple($newKeys); + } + + /** + * @param EnvManager $env + * @param User $domainUser + * @param string $token + * + * @return bool + * @throws InvalidArgumentException + */ + public function storeTokenInCache( + EnvManager $env, + User $domainUser, + string $token + ): bool { + $cacheKey = CacheConstants::getCacheTokenKey($domainUser, $token); + /** @var int $expiration */ + $expiration = $env->get( + 'TOKEN_EXPIRATION', + CacheConstants::CACHE_TOKEN_EXPIRY, + 'int' + ); + $expirationDate = (new DateTimeImmutable()) + ->modify('+' . $expiration . ' seconds') + ->format(Dates::DATE_TIME_FORMAT) + ; + + $payload = [ + 'token' => $token, + 'expiry' => $expirationDate, + ]; + + return $this->cache->set($cacheKey, $payload, $expiration); + } +} diff --git a/src/Domain/Components/Encryption/TokenCacheInterface.php b/src/Domain/Components/Encryption/TokenCacheInterface.php new file mode 100644 index 0000000..d73fffd --- /dev/null +++ b/src/Domain/Components/Encryption/TokenCacheInterface.php @@ -0,0 +1,47 @@ + + * + * 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\Components\Encryption; + +use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\Env\EnvManager; +use Psr\SimpleCache\InvalidArgumentException; + +interface TokenCacheInterface +{ + /** + * @param EnvManager $env + * @param User $domainUser + * + * @return bool + * @throws InvalidArgumentException + */ + public function invalidateForUser( + EnvManager $env, + User $domainUser + ): bool; + + /** + * @param EnvManager $env + * @param User $domainUser + * @param string $token + * + * @return bool + * @throws InvalidArgumentException + */ + public function storeTokenInCache( + EnvManager $env, + User $domainUser, + string $token + ): bool; +} diff --git a/src/Domain/Components/Encryption/TokenManager.php b/src/Domain/Components/Encryption/TokenManager.php new file mode 100644 index 0000000..e5afad7 --- /dev/null +++ b/src/Domain/Components/Encryption/TokenManager.php @@ -0,0 +1,140 @@ + + * + * 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\Components\Encryption; + +use DateTimeImmutable; +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Components\Constants\Dates; +use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; +use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Cache\Adapter\Redis; +use Phalcon\Encryption\Security\JWT\Token\Token; +use Psr\SimpleCache\InvalidArgumentException; +use Throwable; + +use function str_replace; + +/** + * Small component to issue/rotate/revoke tokens and + * interact with cache. + * + * @phpstan-import-type TTokenIssue from TokenManagerInterface + * @phpstan-import-type TValidatorErrors from InputTypes + */ +final class TokenManager implements TokenManagerInterface +{ + public function __construct( + private readonly TokenCacheInterface $tokenCache, + private readonly EnvManager $env, + private readonly JWTToken $jwtToken, + ) { + } + + /** + * Parse a token and return either null or a Token object + * + * @param string|null $token + * + * @return Token|null + */ + public function getObject(?string $token): ?Token + { + if (true === empty($token)) { + return null; + } + + try { + return $this->jwtToken->getObject($token); + } catch (Throwable) { + return null; + } + } + + /** + * Return the domain user from the database + * + * @param UserRepositoryInterface $repository + * @param Token $tokenObject + * + * @return User|null + */ + public function getUser( + UserRepositoryInterface $repository, + Token $tokenObject + ): ?User { + return $this->jwtToken->getUser($repository, $tokenObject); + } + + /** + * Issue new tokens for the user (token, refreshToken) + * + * @param User $domainUser + * + * @return TTokenIssue + */ + public function issue(User $domainUser): array + { + $token = $this->jwtToken->getForUser($domainUser); + $refresh = $this->jwtToken->getRefreshForUser($domainUser); + + $this->tokenCache->storeTokenInCache($this->env, $domainUser, $token); + $this->tokenCache->storeTokenInCache($this->env, $domainUser, $refresh); + + return [ + 'token' => $token, + 'refreshToken' => $refresh, + ]; + } + + /** + * Revoke old tokens and issue new ones. + * + * @param User $domainUser + * + * @return TTokenIssue + */ + public function refresh(User $domainUser): array + { + $this->revoke($domainUser); + + return $this->issue($domainUser); + } + + /** + * Revoke cached tokens for a user. + * + * @param User $domainUser + * + * @return void + */ + public function revoke(User $domainUser): void + { + $this->tokenCache->invalidateForUser($this->env, $domainUser); + } + + /** + * Validate token claims + * + * @param Token $tokenObject + * @param User $user + * + * @return TValidatorErrors + */ + public function validate(Token $tokenObject, User $user): array + { + return $this->jwtToken->validate($tokenObject, $user); + } +} diff --git a/src/Domain/Components/Encryption/TokenManagerInterface.php b/src/Domain/Components/Encryption/TokenManagerInterface.php new file mode 100644 index 0000000..d3f4c8a --- /dev/null +++ b/src/Domain/Components/Encryption/TokenManagerInterface.php @@ -0,0 +1,89 @@ + + * + * 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\Components\Encryption; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; +use Phalcon\Encryption\Security\JWT\Token\Token; + +/** + * @phpstan-type TTokenIssue array{ + * token: string, + * refreshToken: string + * } + * + * @phpstan-import-type TValidatorErrors from InputTypes + */ +interface TokenManagerInterface +{ + /** + * Return the domain user from the database + * + * @param UserRepositoryInterface $repository + * @param Token $tokenObject + * + * @return User|null + */ + public function getUser( + UserRepositoryInterface $repository, + Token $tokenObject + ): ?User; + + /** + * Parse a token and return either null or a Token object + * + * @param string $token + * + * @return Token|null + */ + public function getObject(string $token): ?Token; + + /** + * Issue new tokens for the user (token, refreshToken) + * + * @param User $domainUser + * + * @return TTokenIssue + */ + public function issue(User $domainUser): array; + + /** + * Revoke old tokens and issue new ones. + * + * @param User $domainUser + * + * @return TTokenIssue + */ + public function refresh(User $domainUser): array; + + /** + * Revoke cached tokens for a user. + * + * @param User $domainUser + * + * @return void + */ + public function revoke(User $domainUser): void; + + /** + * Validate token claims + * + * @param Token $tokenObject + * @param User $user + * + * @return TValidatorErrors + */ + public function validate(Token $tokenObject, User $user): array; +} diff --git a/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php b/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php index f7a24fa..5d93a4c 100644 --- a/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php +++ b/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php @@ -14,7 +14,6 @@ namespace Phalcon\Api\Tests\Unit\Domain\Components\Encryption; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\QueryRepository; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; use Phalcon\Api\Domain\Components\Encryption\JWTToken; @@ -101,22 +100,7 @@ public function testGetUserReturnsUserArray(): void ->willReturn($domainUser) ; - $mockRepository = $this - ->getMockBuilder(QueryRepository::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'user', - ] - ) - ->getMock() - ; - $mockRepository->expects($this->once()) - ->method('user') - ->willReturn($userRepository) - ; - - $result = $this->jwtToken->getUser($mockRepository, $plain); + $result = $this->jwtToken->getUser($userRepository, $plain); $this->assertEquals($domainUser, $result); } From 0e834e159ca6cf7716dc9360cdb332dcf6057682 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:51:08 -0600 Subject: [PATCH 34/74] [#.x] - new cache constants --- src/Domain/Components/Constants/Cache.php | 63 +++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/Domain/Components/Constants/Cache.php diff --git a/src/Domain/Components/Constants/Cache.php b/src/Domain/Components/Constants/Cache.php new file mode 100644 index 0000000..be360a0 --- /dev/null +++ b/src/Domain/Components/Constants/Cache.php @@ -0,0 +1,63 @@ + + * + * 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\Components\Constants; + +use Phalcon\Api\Domain\Components\DataSource\User\User; + +use function sha1; + +class Cache +{ + /** @var int */ + public const CACHE_LIFETIME_DAY = 86400; + /** @var int */ + public const CACHE_LIFETIME_HOUR = 3600; + /** + * Cache Timeouts + */ + /** @var int */ + public const CACHE_LIFETIME_MINUTE = 60; + /** @var int */ + public const CACHE_LIFETIME_MONTH = 2592000; + /** + * Default token expiry - 4 hours + */ + /** @var int */ + public const CACHE_TOKEN_EXPIRY = 14400; + /** + * Cache masks + */ + /** @var string */ + private const MASK_TOKEN_USER = 'tk-%s-%s'; + + /** + * @param User $domainUser + * @param string $token + * + * @return string + */ + public static function getCacheTokenKey(User $domainUser, string $token): string + { + $tokenString = ''; + if (true !== empty($token)) { + $tokenString = sha1($token); + } + + return sprintf( + self::MASK_TOKEN_USER, + $domainUser->id, + $tokenString + ); + } +} From 71c9d39d6bc56e0925706751ee371ffa2ef56dce Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:51:22 -0600 Subject: [PATCH 35/74] [#.x] - added validation results class --- .../DataSource/Validation/Result.php | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/Domain/Components/DataSource/Validation/Result.php diff --git a/src/Domain/Components/DataSource/Validation/Result.php b/src/Domain/Components/DataSource/Validation/Result.php new file mode 100644 index 0000000..7fe3223 --- /dev/null +++ b/src/Domain/Components/DataSource/Validation/Result.php @@ -0,0 +1,91 @@ + + * + * 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\Components\DataSource\Validation; + +use Phalcon\Api\Domain\ADR\InputTypes; + +/** + * @phpstan-import-type TValidatorErrors from InputTypes + * @phpstan-type TResultMeta array + */ +final class Result +{ + /** + * @param TValidatorErrors $errors + * @param TResultMeta $meta + */ + public function __construct( + private array $errors = [], + private array $meta = [], + ) { + } + + /** + * Create a failure result. + * + * @param TValidatorErrors $errors + * + * @return self + */ + public static function error(array $errors): self + { + return new self($errors); + } + + /** + * @return TValidatorErrors + */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @param string $key + * @param mixed|null $defaultValue + * + * @return mixed + */ + public function getMeta(string $key, mixed $defaultValue = null): mixed + { + return $this->meta[$key] ?? $defaultValue; + } + + /** + * @param string $key + * @param mixed $value + * + * @return void + */ + public function setMeta(string $key, mixed $value): void + { + $this->meta[$key] = $value; + } + + /** + * @return bool + */ + public function isValid(): bool + { + return $this->errors === []; + } + + /** + * @return self + */ + public static function success(): self + { + return new self([]); + } +} From 79322494e4b68e2f179ef28c5a15e75232b1dd9d Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:51:40 -0600 Subject: [PATCH 36/74] [#.x] - new mapper interface and adjustments --- .../DataSource/Interfaces/MapperInterface.php | 49 +++++++++++++++++++ .../Components/DataSource/User/UserMapper.php | 6 ++- .../DataSource/User/UserMapperTest.php | 3 +- 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 src/Domain/Components/DataSource/Interfaces/MapperInterface.php diff --git a/src/Domain/Components/DataSource/Interfaces/MapperInterface.php b/src/Domain/Components/DataSource/Interfaces/MapperInterface.php new file mode 100644 index 0000000..9b1c7c7 --- /dev/null +++ b/src/Domain/Components/DataSource/Interfaces/MapperInterface.php @@ -0,0 +1,49 @@ + + * + * 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\Components\DataSource\Interfaces; + +use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\DataSource\User\UserInput; +use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; + +/** + * Contract for mapping between domain DTO/objects and persistence arrays. + * + * @phpstan-import-type TUser from UserTypes + * @phpstan-import-type TUserDbRecord from UserTypes + * @phpstan-import-type TUserDomainToDbRecord from UserTypes + */ +interface MapperInterface +{ + /** + * Map Domain User -> DB row (usr_*) + * + * @return TUserDomainToDbRecord + */ + public function db(UserInput $user): array; + + /** + * Map DB row (usr_*) -> Domain User + * + * @param TUserDbRecord|array{} $row + */ + public function domain(array $row): User; + + /** + * Map input row -> Domain User + * + * @param TUser $row + */ + public function input(array $row): User; +} diff --git a/src/Domain/Components/DataSource/User/UserMapper.php b/src/Domain/Components/DataSource/User/UserMapper.php index 7f16302..b996be1 100644 --- a/src/Domain/Components/DataSource/User/UserMapper.php +++ b/src/Domain/Components/DataSource/User/UserMapper.php @@ -13,19 +13,21 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; +use Phalcon\Api\Domain\Components\DataSource\Interfaces\MapperInterface; + /** * @phpstan-import-type TUser from UserTypes * @phpstan-import-type TUserDbRecord from UserTypes * @phpstan-import-type TUserDomainToDbRecord from UserTypes */ -final class UserMapper +final class UserMapper implements MapperInterface { /** * Map Domain User -> DB row (usr_*) * * @return TUserDomainToDbRecord */ - public function db(User $user): array + public function db(UserInput $user): array { return [ 'usr_id' => $user->id, diff --git a/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php b/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php index a44e353..5511a9c 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php +++ b/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php @@ -16,6 +16,7 @@ use Faker\Factory as FakerFactory; use Phalcon\Api\Domain\Components\Constants\Dates; use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Tests\AbstractUnitTestCase; @@ -36,7 +37,7 @@ public function testDb(): void $createdDate = $faker->date(Dates::DATE_TIME_FORMAT); $updatedDate = $faker->date(Dates::DATE_TIME_FORMAT); - $user = new User( + $user = new UserInput( $faker->numberBetween(1, 1000), $faker->numberBetween(0, 9), $faker->safeEmail(), From 91d76f1bb5a8ca1fa0563f02acbd0cd4d9a92698 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:52:15 -0600 Subject: [PATCH 37/74] [#.x] - added auth and user facades for orchestration --- .../Components/DataSource/Auth/AuthFacade.php | 193 ++++++++++ .../Components/DataSource/User/UserFacade.php | 346 ++++++++++++++++++ 2 files changed, 539 insertions(+) create mode 100644 src/Domain/Components/DataSource/Auth/AuthFacade.php create mode 100644 src/Domain/Components/DataSource/User/UserFacade.php diff --git a/src/Domain/Components/DataSource/Auth/AuthFacade.php b/src/Domain/Components/DataSource/Auth/AuthFacade.php new file mode 100644 index 0000000..6460a73 --- /dev/null +++ b/src/Domain/Components/DataSource/Auth/AuthFacade.php @@ -0,0 +1,193 @@ + + * + * 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\Components\DataSource\Auth; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; +use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; +use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Components\Encryption\Security; +use Phalcon\Api\Domain\Components\Encryption\TokenManagerInterface; +use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Components\Payload; + +/** + * @phpstan-import-type TAuthLoginInput from InputTypes + * @phpstan-import-type TAuthLogoutInput from InputTypes + * @phpstan-import-type TAuthRefreshInput from InputTypes + */ +final class AuthFacade +{ + /** + * @param UserRepositoryInterface $repository + * @param SanitizerInterface $sanitizer + * @param TokenManagerInterface $tokenManager + * @param Security $security + */ + public function __construct( + private readonly UserRepositoryInterface $repository, + private readonly SanitizerInterface $sanitizer, + private readonly TokenManagerInterface $tokenManager, + private readonly Security $security, + ) { + } + + /** + * Authenticates users (login) + * + * @param TAuthLoginInput $input + * @param ValidatorInterface $validator + * + * @return Payload + */ + public function authenticate( + array $input, + ValidatorInterface $validator + ): Payload { + /** + * Data Transfer Object + */ + $dto = AuthInput::new($this->sanitizer, $input); + + /** + * Validate + */ + $validation = $validator->validate($dto); + if (!$validation->isValid()) { + return Payload::unauthorized($validation->getErrors()); + } + + /** + * Find the user by email + */ + /** @var string $email */ + $email = $dto->email; + $domainUser = $this->repository->findByEmail($email); + if (null === $domainUser) { + return Payload::unauthorized([HttpCodesEnum::AppIncorrectCredentials->error()]); + } + + /** + * Verify the password + */ + /** @var string $suppliedPassword */ + $suppliedPassword = $dto->password; + /** @var string $dbPassword */ + $dbPassword = $domainUser->password; + if (true !== $this->security->verify($suppliedPassword, $dbPassword)) { + return Payload::unauthorized([HttpCodesEnum::AppIncorrectCredentials->error()]); + } + + /** + * Issue a new set of tokens + */ + $tokens = $this->tokenManager->issue($domainUser); + + /** + * Construct the response + */ + $results = [ + 'authenticated' => true, + 'user' => [ + 'id' => $domainUser->id, + 'name' => $domainUser->fullName(), + 'email' => $domainUser->email, + ], + 'jwt' => [ + 'token' => $tokens['token'], + 'refreshToken' => $tokens['refreshToken'], + ], + ]; + + return Payload::success($results); + } + + /** + * Logout: revoke refresh token after parsing/validation. + * + * @param TAuthLogoutInput $input + * @param ValidatorInterface $validator + * + * @return Payload + */ + public function logout( + array $input, + ValidatorInterface $validator + ): Payload { + /** + * Data Transfer Object + */ + $dto = AuthInput::new($this->sanitizer, $input); + + /** + * Validate + */ + $validation = $validator->validate($dto); + if (!$validation->isValid()) { + return Payload::unauthorized($validation->getErrors()); + } + + /** + * If we are here validation has passed and the Result object + * has the user in the meta store + */ + /** @var User $domainUser */ + $domainUser = $validation->getMeta('user'); + + $this->tokenManager->revoke($domainUser); + + return Payload::success(['authenticated' => false]); + } + + /** + * Refresh: validate refresh token, issue new tokens via TokenManager. + * + * @param TAuthLogoutInput $input + * @param ValidatorInterface $validator + * + * @return Payload + */ + public function refresh( + array $input, + ValidatorInterface $validator + ): Payload { + /** + * Data Transfer Object + */ + $dto = AuthInput::new($this->sanitizer, $input); + + /** + * Validate + */ + $validation = $validator->validate($dto); + if (!$validation->isValid()) { + return Payload::unauthorized($validation->getErrors()); + } + + /** + * If we are here validation has passed and the Result object + * has the user in the meta store + */ + /** @var User $domainUser */ + $domainUser = $validation->getMeta('user'); + + $tokens = $this->tokenManager->refresh($domainUser); + + return Payload::success([ + 'token' => $tokens['token'], + 'refreshToken' => $tokens['refreshToken'], + ]); + } +} diff --git a/src/Domain/Components/DataSource/User/UserFacade.php b/src/Domain/Components/DataSource/User/UserFacade.php new file mode 100644 index 0000000..1b36969 --- /dev/null +++ b/src/Domain/Components/DataSource/User/UserFacade.php @@ -0,0 +1,346 @@ + + * + * 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\Components\DataSource\User; + +use PDOException; +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Components\Constants\Dates; +use Phalcon\Api\Domain\Components\DataSource\Interfaces\MapperInterface; +use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; +use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Components\Encryption\Security; +use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Components\Payload; + +use function array_filter; + +/** + * Orchestration for workflow + * + * - Sanitization + * - DTO creation + * - Validation + * - Pre-operation checks (when necessary) + * - Repository operation + * + * @phpstan-import-type TUserInput from InputTypes + * @phpstan-import-type TUserDomainToDbRecord from UserTypes + * @phpstan-import-type TUserDbRecordOptional from UserTypes + */ +final class UserFacade +{ + /** + * @param SanitizerInterface $sanitizer + * @param ValidatorInterface $validator + * @param MapperInterface $mapper + * @param UserRepositoryInterface $repository + * @param Security $security + */ + public function __construct( + private readonly SanitizerInterface $sanitizer, + private readonly ValidatorInterface $validator, + private readonly MapperInterface $mapper, + private readonly UserRepositoryInterface $repository, + private readonly Security $security, + ) { + } + + /** + * Delete a user. + * + * @param TUserInput $input + * + * @return Payload + */ + public function delete(array $input): Payload + { + $dto = UserInput::new($this->sanitizer, $input); + $userId = $dto->id; + + /** + * Success + */ + if ($userId > 0) { + $rowCount = $this->repository->deleteById($userId); + + if (0 !== $rowCount) { + return Payload::deleted( + [ + 'Record deleted successfully [#' . $userId . '].', + ], + ); + } + } + + /** + * 404 + */ + return Payload::notFound(); + } + + /** + * Get a user. + * + * @param TUserInput $input + * + * @return Payload + */ + public function get(array $input): Payload + { + $dto = UserInput::new($this->sanitizer, $input); + $userId = $dto->id; + + /** + * Success + */ + if ($userId > 0) { + $user = $this->repository->findById($userId); + + if (null !== $user) { + return Payload::success([$user->id => $user->toArray()]); + } + } + + /** + * 404 + */ + return Payload::notFound(); + } + + /** + * Create a user. + * + * @param TUserInput $input + * + * @return Payload + */ + public function insert(array $input): Payload + { + $dto = UserInput::new($this->sanitizer, $input); + + $validation = $this->validator->validate($dto); + if (!$validation->isValid()) { + return Payload::invalid($validation->getErrors()); + } + + /** + * Array for inserting + */ + $user = $this->mapper->db($dto); + + /** + * Pre-insert checks and manipulations + */ + $user = $this->preInsert($user); + + /** + * Insert the record + */ + try { + $userId = $this->repository->insert($user); + } catch (PDOException $ex) { + /** + * @todo send generic response and log the error + */ + return $this->getErrorPayload( + HttpCodesEnum::AppCannotCreateDatabaseRecord, + $ex->getMessage() + ); + } + + if ($userId < 1) { + return $this->getErrorPayload( + HttpCodesEnum::AppCannotCreateDatabaseRecord, + 'No id returned' + ); + } + + /** + * Get the user from the database + */ + /** @var User $domainUser */ + $domainUser = $this->repository->findById($userId); + + /** + * Return the user back + */ + return Payload::created([$domainUser->id => $domainUser->toArray()]); + } + + /** + * Create a user. + * + * @param TUserInput $input + * + * @return Payload + */ + public function update(array $input): Payload + { + $dto = UserInput::new($this->sanitizer, $input); + + $validation = $this->validator->validate($dto); + if (!$validation->isValid()) { + return Payload::invalid($validation->getErrors()); + } + + /** + * Check if the user exists, If not, return an error + */ + /** @var int $userId */ + $userId = $dto->id; + $domainUser = $this->repository->findById($userId); + + if (null === $domainUser) { + return Payload::notFound(); + } + + /** + * Array for updating + */ + $user = $this->mapper->db($dto); + + /** + * Pre-update checks and manipulations + */ + $user = $this->preUpdate($user); + + /** + * Update the record + */ + try { + $userId = $this->repository->update($userId, $user); + } catch (PDOException $ex) { + /** + * @todo send generic response and log the error + */ + return $this->getErrorPayload( + HttpCodesEnum::AppCannotUpdateDatabaseRecord, + $ex->getMessage() + ); + } + + if ($userId < 1) { + return $this->getErrorPayload( + HttpCodesEnum::AppCannotUpdateDatabaseRecord, + 'No id returned' + ); + } + + /** + * Get the user from the database + */ + /** @var User $domainUser */ + $domainUser = $this->repository->findById($userId); + + /** + * Return the user back + */ + return Payload::updated([$domainUser->id => $domainUser->toArray()]); + } + + /** + * @param string $message + * + * @return Payload + */ + private function getErrorPayload( + HttpCodesEnum $item, + string $message): Payload + { + return Payload::error([[$item->text() . $message]]); + } + + /** + * @param TUserDomainToDbRecord $input + * + * @return TUserDbRecordOptional + */ + private function preInsert(array $input): array + { + $result = $this->processPassword($input); + $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); + + /** + * Set the created/updated dates if need be + */ + if (true === empty($result['usr_created_date'])) { + $result['usr_created_date'] = $now; + } + if (true === empty($result['usr_updated_date'])) { + $result['usr_updated_date'] = $now; + } + + /** @var TUserDbRecordOptional $result */ + return $this->cleanupFields($result); + } + + /** + * @param TUserDomainToDbRecord $input + * + * @return TUserDbRecordOptional + */ + private function preUpdate(array $input): array + { + $result = $this->processPassword($input); + $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); + + /** + * Set updated date to now if it has not been set + */ + if (true === empty($result['usr_updated_date'])) { + $result['usr_updated_date'] = $now; + } + + /** + * Remove createdDate and createdUserId - cannot be changed. This + * needs to be here because we don't want to touch those fields. + */ + unset($result['usr_created_date'], $result['usr_created_usr_id']); + + + return $this->cleanupFields($result); + } + + /** + * @param TUserDomainToDbRecord $input + * + * @return TUserDomainToDbRecord + */ + private function processPassword(array $input): array + { + if (null !== $input['usr_password']) { + $plain = $input['usr_password']; + $hashed = $this->security->hash($plain); + + $input['usr_password'] = $hashed; + } + + return $input; + } + + /** + * @param TUserDbRecordOptional $row + * + * @return TUserDbRecordOptional + */ + private function cleanupFields(array $row): array + { + unset($row['usr_id']); + + return array_filter( + $row, + static fn($v) => $v !== null && $v !== '' + ); + } +} From 6abfde8038079d3d75c90b4327c3657037bdafef Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:52:49 -0600 Subject: [PATCH 38/74] [#.x] - correcting references and phpstan after refactoring --- .../Components/Middleware/ValidateTokenClaimsMiddleware.php | 1 + .../Middleware/ValidateTokenRevokedMiddleware.php | 5 +++-- .../Components/Middleware/ValidateTokenUserMiddleware.php | 6 ++++-- .../Middleware/ValidateTokenRevokedMiddlewareTest.php | 5 +++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php index bb72854..4580e87 100644 --- a/src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php @@ -60,6 +60,7 @@ public function call(Micro $application): bool * claims of the token, we will still check the claims against the * user stored in the session */ + /** @var array $errors */ $errors = $jwtToken->validate($tokenObject, $sessionUser); if (true !== empty($errors)) { diff --git a/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php index 399adb0..cd6e782 100644 --- a/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php @@ -13,11 +13,12 @@ namespace Phalcon\Api\Domain\Components\Middleware; -use Phalcon\Api\Domain\Components\Cache\Cache; +use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Cache\Cache; use Phalcon\Http\RequestInterface; use Phalcon\Mvc\Micro; use Phalcon\Support\Registry; @@ -47,7 +48,7 @@ public function call(Micro $application): bool * Get the token object */ $token = $this->getBearerTokenFromHeader($request, $env); - $cacheKey = $cache->getCacheTokenKey($domainUser, $token); + $cacheKey = CacheConstants::getCacheTokenKey($domainUser, $token); $exists = $cache->has($cacheKey); if (true !== $exists) { diff --git a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php index 53ba2e9..97c2fe2 100644 --- a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php @@ -15,6 +15,8 @@ use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\QueryRepository; +use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Encryption\Security\JWT\Token\Token; @@ -38,8 +40,8 @@ public function call(Micro $application): bool { /** @var JWTToken $jwtToken */ $jwtToken = $application->getSharedService(Container::JWT_TOKEN); - /** @var QueryRepository $repository */ - $repository = $application->getSharedService(Container::REPOSITORY); + /** @var UserRepository $repository */ + $repository = $application->getSharedService(Container::USER_REPOSITORY); /** @var Registry $registry */ $registry = $application->getSharedService(Container::REGISTRY); diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php b/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php index ec74105..5f2de88 100644 --- a/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php +++ b/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php @@ -13,12 +13,13 @@ namespace Phalcon\Api\Tests\Unit\Domain\Components\Middleware; -use Phalcon\Api\Domain\Components\Cache\Cache; +use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\Cache\Cache; use Phalcon\Mvc\Micro; use Phalcon\Support\Registry; use PHPUnit\Framework\Attributes\BackupGlobals; @@ -91,7 +92,7 @@ public function testValidateTokenRevokedSuccess(): void /** @var Cache $cache */ $cache = $micro->getSharedService(Container::CACHE); $sessionUser = $registry->get('user'); - $cacheKey = $cache->getCacheTokenKey($sessionUser, $token); + $cacheKey = CacheConstants::getCacheTokenKey($sessionUser, $token); $payload = [ 'token' => $token, ]; From 460db6b1a98fb92b22b9af19c76d1294c90d95ae Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:53:09 -0600 Subject: [PATCH 39/74] [#.x] - correcting references and phpstan after refactoring --- .../Services/Auth/AbstractAuthService.php | 16 +--- src/Domain/Services/Auth/LoginPostService.php | 71 +----------------- .../Services/Auth/LogoutPostService.php | 71 +----------------- .../Services/Auth/RefreshPostService.php | 73 +------------------ .../Services/User/AbstractUserService.php | 62 +--------------- .../Services/User/UserDeleteService.php | 24 +----- src/Domain/Services/User/UserGetService.php | 20 +---- src/Domain/Services/User/UserPostService.php | 54 +------------- src/Domain/Services/User/UserPutService.php | 64 +--------------- .../Services/Auth/LogoutPostServiceTest.php | 7 +- 10 files changed, 18 insertions(+), 444 deletions(-) diff --git a/src/Domain/Services/Auth/AbstractAuthService.php b/src/Domain/Services/Auth/AbstractAuthService.php index e5ecca6..645f629 100644 --- a/src/Domain/Services/Auth/AbstractAuthService.php +++ b/src/Domain/Services/Auth/AbstractAuthService.php @@ -16,24 +16,16 @@ use Phalcon\Api\Domain\ADR\DomainInterface; use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Components\Cache\Cache; -use Phalcon\Api\Domain\Components\DataSource\QueryRepository; -use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthFacade; +use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; use Phalcon\Api\Domain\Components\Encryption\JWTToken; -use Phalcon\Api\Domain\Components\Encryption\Security; use Phalcon\Api\Domain\Components\Env\EnvManager; -/** - * @phpstan-import-type TValidationErrors from InputTypes - */ abstract class AbstractAuthService implements DomainInterface { public function __construct( - protected readonly QueryRepository $repository, - protected readonly Cache $cache, - protected readonly EnvManager $env, - protected readonly JWTToken $jwtToken, - protected readonly SanitizerInterface $sanitizer, - protected readonly Security $security, + protected readonly AuthFacade $facade, + protected readonly ValidatorInterface $validator ) { } } diff --git a/src/Domain/Services/Auth/LoginPostService.php b/src/Domain/Services/Auth/LoginPostService.php index 9e26a68..50749a3 100644 --- a/src/Domain/Services/Auth/LoginPostService.php +++ b/src/Domain/Services/Auth/LoginPostService.php @@ -14,8 +14,6 @@ namespace Phalcon\Api\Domain\Services\Auth; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; /** @@ -30,73 +28,6 @@ final class LoginPostService extends AbstractAuthService */ public function __invoke(array $input): Payload { - /** - * Get email and password from the input and sanitize them - */ - $inputObject = AuthInput::new($this->sanitizer, $input); - $email = $inputObject->email; - $password = $inputObject->password; - - /** - * Check if email or password are empty - */ - if (true === empty($email) || true === empty($password)) { - return Payload::unauthorized( - [HttpCodesEnum::AppIncorrectCredentials->error()] - ); - } - - /** - * Find the user in the database - */ - $domainUser = $this->repository->user()->findByEmail($email); - - /** - * Check if the user exists - */ - if (null === $domainUser) { - return Payload::unauthorized( - [HttpCodesEnum::AppIncorrectCredentials->error()] - ); - } - - /** - * Check if the password matches - */ - if (true !== $this->security->verify($password, $domainUser->password)) { - return Payload::unauthorized( - [HttpCodesEnum::AppIncorrectCredentials->error()] - ); - } - - /** - * Get a new token for this user - */ - $token = $this->jwtToken->getForUser($domainUser); - $refreshToken = $this->jwtToken->getRefreshForUser($domainUser); - - /** - * Store the token in cache - */ - $this->cache->storeTokenInCache($this->env, $domainUser, $token); - $this->cache->storeTokenInCache($this->env, $domainUser, $refreshToken); - - /** - * Send the payload back - */ - $results = [ - 'authenticated' => true, - 'user' => [ - 'id' => $domainUser->id, - 'name' => $domainUser->fullName(), - 'email' => $domainUser->email, - ], - 'jwt' => [ - 'token' => $token, - 'refreshToken' => $refreshToken, - ], - ]; - - return Payload::success($results); + return $this->facade->authenticate($input, $this->validator); } } diff --git a/src/Domain/Services/Auth/LogoutPostService.php b/src/Domain/Services/Auth/LogoutPostService.php index e217816..90ad2c1 100644 --- a/src/Domain/Services/Auth/LogoutPostService.php +++ b/src/Domain/Services/Auth/LogoutPostService.php @@ -14,14 +14,10 @@ namespace Phalcon\Api\Domain\Services\Auth; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; -use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TAuthLogoutInput from InputTypes - * @phpstan-import-type TValidationErrors from InputTypes */ final class LogoutPostService extends AbstractAuthService { @@ -32,71 +28,6 @@ final class LogoutPostService extends AbstractAuthService */ public function __invoke(array $input): Payload { - /** - * @todo common code with refresh - */ - /** - * Get the token - */ - $inputObject = AuthInput::new($this->sanitizer, $input); - $token = $inputObject->token; - - /** - * Validation - * - * Empty token - */ - if (true === empty($token)) { - return Payload::unauthorized( - [HttpCodesEnum::AppTokenNotPresent->error()] - ); - } - - /** - * @todo catch any exceptions here - * - * Is this the refresh token - */ - $tokenObject = $this->jwtToken->getObject($token); - $isRefresh = $tokenObject->getClaims()->get(JWTEnum::Refresh->value); - if (false === $isRefresh) { - return Payload::unauthorized( - [HttpCodesEnum::AppTokenNotValid->error()] - ); - } - - /** - * Get the user - if empty return error - */ - $domainUser = $this - ->jwtToken - ->getUser($this->repository, $tokenObject) - ; - - if (null === $domainUser) { - return Payload::unauthorized( - [HttpCodesEnum::AppTokenInvalidUser->error()] - ); - } - - /** @var TValidationErrors $errors */ - $errors = $this->jwtToken->validate($tokenObject, $domainUser); - if (true !== empty($errors)) { - return Payload::unauthorized($errors); - } - - /** - * Invalidate old tokens - */ - $this->cache->invalidateForUser($this->env, $domainUser); - - /** - * Send the payload back - */ - return Payload::success( - [ - 'authenticated' => false, - ], - ); + return $this->facade->logout($input, $this->validator); } } diff --git a/src/Domain/Services/Auth/RefreshPostService.php b/src/Domain/Services/Auth/RefreshPostService.php index 9fce9d0..d30a53f 100644 --- a/src/Domain/Services/Auth/RefreshPostService.php +++ b/src/Domain/Services/Auth/RefreshPostService.php @@ -14,14 +14,10 @@ namespace Phalcon\Api\Domain\Services\Auth; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; -use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TAuthRefreshInput from InputTypes - * @phpstan-import-type TValidationErrors from InputTypes */ final class RefreshPostService extends AbstractAuthService { @@ -32,73 +28,6 @@ final class RefreshPostService extends AbstractAuthService */ public function __invoke(array $input): Payload { - /** - * Get email and password from the input and sanitize them - */ - $inputObject = AuthInput::new($this->sanitizer, $input); - $token = $inputObject->token; - - /** - * Validation - * - * Empty token - */ - if (true === empty($token)) { - return Payload::unauthorized( - [HttpCodesEnum::AppTokenNotPresent->error()] - ); - } - - /** - * @todo catch any exceptions here - * - * Is this the refresh token - */ - $tokenObject = $this->jwtToken->getObject($token); - $isRefresh = $tokenObject->getClaims()->get(JWTEnum::Refresh->value); - if (false === $isRefresh) { - return Payload::unauthorized( - [HttpCodesEnum::AppTokenNotValid->error()] - ); - } - - /** - * Get the user - if empty return error - */ - $domainUser = $this - ->jwtToken - ->getUser($this->repository, $tokenObject) - ; - if (null === $domainUser) { - return Payload::unauthorized( - [HttpCodesEnum::AppTokenInvalidUser->error()] - ); - } - - /** @var TValidationErrors $errors */ - $errors = $this->jwtToken->validate($tokenObject, $domainUser); - if (true !== empty($errors)) { - return Payload::unauthorized($errors); - } - - $newToken = $this->jwtToken->getForUser($domainUser); - $newRefreshToken = $this->jwtToken->getRefreshForUser($domainUser); - - /** - * Invalidate old tokens, store new tokens in cache - */ - $this->cache->invalidateForUser($this->env, $domainUser); - $this->cache->storeTokenInCache($this->env, $domainUser, $newToken); - $this->cache->storeTokenInCache($this->env, $domainUser, $newRefreshToken); - - /** - * Send the payload back - */ - return Payload::success( - [ - 'token' => $newToken, - 'refreshToken' => $newRefreshToken, - ], - ); + return $this->facade->refresh($input, $this->validator); } } diff --git a/src/Domain/Services/User/AbstractUserService.php b/src/Domain/Services/User/AbstractUserService.php index 6a69945..c3368c4 100644 --- a/src/Domain/Services/User/AbstractUserService.php +++ b/src/Domain/Services/User/AbstractUserService.php @@ -14,71 +14,15 @@ namespace Phalcon\Api\Domain\Services\User; use Phalcon\Api\Domain\ADR\DomainInterface; -use Phalcon\Api\Domain\Components\DataSource\QueryRepository; -use Phalcon\Api\Domain\Components\DataSource\SanitizerInterface; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\DataSource\User\UserInput; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; -use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; -use Phalcon\Api\Domain\Components\Encryption\Security; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Components\Payload; +use Phalcon\Api\Domain\Components\DataSource\User\UserFacade; -/** - * @phpstan-import-type TUser from UserTypes - */ abstract class AbstractUserService implements DomainInterface { - protected HttpCodesEnum $errorMessage; - /** - * @param QueryRepository $repository - * @param UserMapper $mapper - * @param UserValidator $validator - * @param SanitizerInterface $sanitizer - * @param Security $security + * @param UserFacade $facade */ public function __construct( - protected readonly QueryRepository $repository, - protected readonly UserMapper $mapper, - protected readonly UserValidator $validator, - protected readonly SanitizerInterface $sanitizer, - protected readonly Security $security, + protected readonly UserFacade $facade, ) { } - - /** - * @param string $message - * - * @return Payload - */ - protected function getErrorPayload(string $message): Payload - { - return Payload::error( - [ - [$this->errorMessage->text() . $message], - ] - ); - } - - /** - * @param UserInput $inputObject - * - * @return User - */ - protected function processPassword(UserInput $inputObject): User - { - /** @var TUser $inputData */ - $inputData = $inputObject->toArray(); - - if (null !== $inputData['password']) { - $plain = $inputData['password']; - $hashed = $this->security->hash($plain); - - $inputData['password'] = $hashed; - } - - return $this->mapper->input($inputData); - } } diff --git a/src/Domain/Services/User/UserDeleteService.php b/src/Domain/Services/User/UserDeleteService.php index b380eff..27e7f00 100644 --- a/src/Domain/Services/User/UserDeleteService.php +++ b/src/Domain/Services/User/UserDeleteService.php @@ -14,7 +14,6 @@ namespace Phalcon\Api\Domain\Services\User; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\Payload; /** @@ -29,27 +28,6 @@ final class UserDeleteService extends AbstractUserService */ public function __invoke(array $input): Payload { - $inputObject = UserInput::new($this->sanitizer, $input); - $userId = $inputObject->id; - - /** - * Success - */ - if ($userId > 0) { - $rows = $this->repository->user()->deleteById($userId); - - if ($rows > 0) { - return Payload::deleted( - [ - 'Record deleted successfully [#' . $userId . '].', - ], - ); - } - } - - /** - * 404 - */ - return Payload::notFound(); + return $this->facade->delete($input); } } diff --git a/src/Domain/Services/User/UserGetService.php b/src/Domain/Services/User/UserGetService.php index 9950ec6..6fe3716 100644 --- a/src/Domain/Services/User/UserGetService.php +++ b/src/Domain/Services/User/UserGetService.php @@ -14,7 +14,6 @@ namespace Phalcon\Api\Domain\Services\User; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\Payload; /** @@ -29,23 +28,6 @@ final class UserGetService extends AbstractUserService */ public function __invoke(array $input): Payload { - $inputObject = UserInput::new($this->sanitizer, $input); - $userId = $inputObject->id; - - /** - * Success - */ - if ($userId > 0) { - $user = $this->repository->user()->findById($userId); - - if (null !== $user) { - return Payload::success([$user->id => $user->toArray()]); - } - } - - /** - * 404 - */ - return Payload::notFound(); + return $this->facade->get($input); } } diff --git a/src/Domain/Services/User/UserPostService.php b/src/Domain/Services/User/UserPostService.php index f29d544..ea77145 100644 --- a/src/Domain/Services/User/UserPostService.php +++ b/src/Domain/Services/User/UserPostService.php @@ -13,21 +13,14 @@ namespace Phalcon\Api\Domain\Services\User; -use PDOException; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\DataSource\User\UserInput; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TUserInput from InputTypes - * @phpstan-import-type TValidationErrors from InputTypes */ final class UserPostService extends AbstractUserService { - protected HttpCodesEnum $errorMessage = HttpCodesEnum::AppCannotCreateDatabaseRecord; - /** * @param TUserInput $input * @@ -35,51 +28,6 @@ final class UserPostService extends AbstractUserService */ public function __invoke(array $input): Payload { - $inputObject = UserInput::new($this->sanitizer, $input); - /** @var TValidationErrors $errors */ - $errors = $this->validator->validate($inputObject); - - /** - * Errors exist - return early - */ - if (true !== empty($errors)) { - return Payload::invalid($errors); - } - - /** - * The password needs to be hashed - */ - $domainUser = $this->processPassword($inputObject); - - /** - * Insert the record - */ - try { - $userId = $this - ->repository - ->user() - ->insert($domainUser) - ; - } catch (PDOException $ex) { - /** - * @todo send generic response and log the error - */ - return $this->getErrorPayload($ex->getMessage()); - } - - if ($userId < 1) { - return $this->getErrorPayload('No id returned'); - } - - /** - * Get the user from the database - */ - /** @var User $domainUser */ - $domainUser = $this->repository->user()->findById($userId); - - /** - * Return the user back - */ - return Payload::created([$domainUser->id => $domainUser->toArray()]); + return $this->facade->insert($input); } } diff --git a/src/Domain/Services/User/UserPutService.php b/src/Domain/Services/User/UserPutService.php index 3f189c0..ad596a8 100644 --- a/src/Domain/Services/User/UserPutService.php +++ b/src/Domain/Services/User/UserPutService.php @@ -13,21 +13,15 @@ namespace Phalcon\Api\Domain\Services\User; -use PDOException; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TUserInput from InputTypes - * @phpstan-import-type TValidationErrors from InputTypes */ final class UserPutService extends AbstractUserService { - protected HttpCodesEnum $errorMessage = HttpCodesEnum::AppCannotUpdateDatabaseRecord; - /** * @param TUserInput $input * @@ -35,62 +29,6 @@ final class UserPutService extends AbstractUserService */ public function __invoke(array $input): Payload { - $inputObject = UserInput::new($this->sanitizer, $input); - /** @var TValidationErrors $errors */ - $errors = $this->validator->validate($inputObject); - - /** - * Errors exist - return early - */ - if (true !== empty($errors)) { - return Payload::invalid($errors); - } - - /** - * Check if the user exists, If not, return an error - */ - /** @var int $userId */ - $userId = $inputObject->id; - $domainUser = $this->repository->user()->findById($userId); - - if (null === $domainUser) { - return Payload::notFound(); - } - - /** - * The password needs to be hashed - */ - $domainUser = $this->processPassword($inputObject); - - /** - * Update the record - */ - try { - $userId = $this - ->repository - ->user() - ->update($domainUser) - ; - } catch (PDOException $ex) { - /** - * @todo send generic response and log the error - */ - return $this->getErrorPayload($ex->getMessage()); - } - - if ($userId < 1) { - return $this->getErrorPayload('No id returned'); - } - - /** - * Get the user from the database - */ - /** @var User $domainUser */ - $domainUser = $this->repository->user()->findById($userId); - - /** - * Return the user back - */ - return Payload::updated([$domainUser->id => $domainUser->toArray()]); + return $this->facade->update($input); } } diff --git a/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php b/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php index 57b9be8..cf2e648 100644 --- a/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php +++ b/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\Auth; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Components\Cache\Cache; +use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\Encryption\JWTToken; @@ -23,6 +23,7 @@ use Phalcon\Api\Domain\Services\Auth\LogoutPostService; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\Cache\Cache; use Phalcon\Encryption\Security\JWT\Token\Item; use Phalcon\Encryption\Security\JWT\Token\Token; use PHPUnit\Framework\Attributes\BackupGlobals; @@ -237,13 +238,13 @@ public function testServiceSuccess(): void $token = $data['jwt']['token']; $domainUser = $userMapper->domain($dbUser); - $tokenKey = $cache->getCacheTokenKey($domainUser, $token); + $tokenKey = CacheConstants::getCacheTokenKey($domainUser, $token); $actual = $cache->has($tokenKey); $this->assertTrue($actual); $refreshToken = $data['jwt']['refreshToken']; - $tokenKey = $cache->getCacheTokenKey($domainUser, $refreshToken); + $tokenKey = CacheConstants::getCacheTokenKey($domainUser, $refreshToken); $actual = $cache->has($tokenKey); $this->assertTrue($actual); From 51b037117c43bfaf77c649f42348ff4917378300 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:53:23 -0600 Subject: [PATCH 40/74] [#.x] - adjusting tests --- .../Providers/CacheDataProviderTest.php | 2 +- .../Services/User/UserServiceDispatchTest.php | 24 +++++++++- .../Services/User/UserServicePostTest.php | 32 +------------ .../Services/User/UserServicePutTest.php | 45 ++----------------- 4 files changed, 28 insertions(+), 75 deletions(-) diff --git a/tests/Unit/Domain/Components/Providers/CacheDataProviderTest.php b/tests/Unit/Domain/Components/Providers/CacheDataProviderTest.php index 041baea..a7b8887 100644 --- a/tests/Unit/Domain/Components/Providers/CacheDataProviderTest.php +++ b/tests/Unit/Domain/Components/Providers/CacheDataProviderTest.php @@ -13,9 +13,9 @@ namespace Phalcon\Api\Tests\Unit\Domain\Components\Providers; -use Phalcon\Api\Domain\Components\Cache\Cache; use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Cache\Cache; final class CacheDataProviderTest extends AbstractUnitTestCase { diff --git a/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php b/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php index d7fe1a6..9741943 100644 --- a/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php +++ b/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php @@ -13,13 +13,16 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\User; -use Phalcon\Api\Domain\Components\Cache\Cache; +use DateTimeImmutable; +use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Components\Constants\Dates; use Phalcon\Api\Domain\Components\Container; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\Enums\Http\RoutesEnum; use Phalcon\Api\Domain\Components\Env\EnvManager; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\Cache\Cache; use PHPUnit\Framework\Attributes\BackupGlobals; #[BackupGlobals(true)] @@ -43,7 +46,24 @@ public function testDispatchGet(): void /** * Store the token in the cache */ - $cache->storeTokenInCache($env, $domainUser, $token); + $cacheKey = CacheConstants::getCacheTokenKey($domainUser, $token); + /** @var int $expiration */ + $expiration = $env->get( + 'TOKEN_EXPIRATION', + CacheConstants::CACHE_TOKEN_EXPIRY, + 'int' + ); + $expirationDate = (new DateTimeImmutable()) + ->modify('+' . $expiration . ' seconds') + ->format(Dates::DATE_TIME_FORMAT) + ; + + $payload = [ + 'token' => $token, + 'expiry' => $expirationDate, + ]; + + $cache->set($cacheKey, $payload, $expiration); $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); $_SERVER = [ diff --git a/tests/Unit/Domain/Services/User/UserServicePostTest.php b/tests/Unit/Domain/Services/User/UserServicePostTest.php index 231523b..006c795 100644 --- a/tests/Unit/Domain/Services/User/UserServicePostTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePostTest.php @@ -17,7 +17,6 @@ use PayloadInterop\DomainStatus; use PDOException; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\QueryRepository; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; use Phalcon\Api\Domain\Services\User\UserPostService; @@ -43,19 +42,7 @@ public function testServiceFailureNoIdReturned(): void ; $userRepository->method('insert')->willReturn(0); - $repository = $this - ->getMockBuilder(QueryRepository::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'user', - ] - ) - ->getMock() - ; - $repository->method('user')->willReturn($userRepository); - - $this->container->setShared(Container::REPOSITORY, $repository); + $this->container->setShared(Container::USER_REPOSITORY, $userRepository); /** @var UserPostService $service */ $service = $this->container->get(Container::USER_POST_SERVICE); @@ -104,22 +91,7 @@ public function testServiceFailurePdoError(): void ->willThrowException(new PDOException('abcde')) ; - $repository = $this - ->getMockBuilder(QueryRepository::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'user', - ] - ) - ->getMock() - ; - $repository - ->method('user') - ->willReturn($userRepository) - ; - - $this->container->set(Container::REPOSITORY, $repository); + $this->container->setShared(Container::USER_REPOSITORY, $userRepository); /** @var UserPostService $service */ $service = $this->container->get(Container::USER_POST_SERVICE); diff --git a/tests/Unit/Domain/Services/User/UserServicePutTest.php b/tests/Unit/Domain/Services/User/UserServicePutTest.php index 34fab9a..0aa5de6 100644 --- a/tests/Unit/Domain/Services/User/UserServicePutTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePutTest.php @@ -17,7 +17,6 @@ use PayloadInterop\DomainStatus; use PDOException; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\QueryRepository; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; use Phalcon\Api\Domain\Services\User\UserPutService; @@ -52,19 +51,7 @@ public function testServiceFailureNoIdReturned(): void $userRepository->method('update')->willReturn(0); $userRepository->method('findById')->willReturn($findByUser); - $repository = $this - ->getMockBuilder(QueryRepository::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'user', - ] - ) - ->getMock() - ; - $repository->method('user')->willReturn($userRepository); - - $this->container->setShared(Container::REPOSITORY, $repository); + $this->container->setShared(Container::USER_REPOSITORY, $userRepository); /** @var UserPutService $service */ $service = $this->container->get(Container::USER_PUT_SERVICE); @@ -121,22 +108,8 @@ public function testServiceFailurePdoError(): void ->willThrowException(new PDOException('abcde')) ; - $repository = $this - ->getMockBuilder(QueryRepository::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'user', - ] - ) - ->getMock() - ; - $repository - ->method('user') - ->willReturn($userRepository) - ; + $this->container->setShared(Container::USER_REPOSITORY, $userRepository); - $this->container->set(Container::REPOSITORY, $repository); /** @var UserPutService $service */ $service = $this->container->get(Container::USER_PUT_SERVICE); @@ -187,19 +160,7 @@ public function testServiceFailureRecordNotFound(): void ; $userRepository->method('findById')->willReturn(null); - $repository = $this - ->getMockBuilder(QueryRepository::class) - ->disableOriginalConstructor() - ->onlyMethods( - [ - 'user', - ] - ) - ->getMock() - ; - $repository->method('user')->willReturn($userRepository); - - $this->container->setShared(Container::REPOSITORY, $repository); + $this->container->setShared(Container::USER_REPOSITORY, $userRepository); /** @var UserPutService $service */ $service = $this->container->get(Container::USER_PUT_SERVICE); From 02461590608ae14b5046eca346285ab3f45332d5 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:53:48 -0600 Subject: [PATCH 41/74] [#.x] - user repository cleanup and added interface --- .../DataSource/User/UserRepository.php | 85 ++----------------- .../User/UserRepositoryInterface.php | 10 ++- .../DataSource/User/UserRepositoryTest.php | 18 ++-- 3 files changed, 24 insertions(+), 89 deletions(-) diff --git a/src/Domain/Components/DataSource/User/UserRepository.php b/src/Domain/Components/DataSource/User/UserRepository.php index 7a525f5..11c5bf9 100644 --- a/src/Domain/Components/DataSource/User/UserRepository.php +++ b/src/Domain/Components/DataSource/User/UserRepository.php @@ -13,7 +13,6 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; -use Phalcon\Api\Domain\Components\Constants\Dates; use Phalcon\Api\Domain\Components\DataSource\AbstractRepository; use Phalcon\Api\Domain\Components\Enums\Common\FlagsEnum; use Phalcon\DataMapper\Pdo\Connection; @@ -21,18 +20,10 @@ use Phalcon\DataMapper\Query\Select; use Phalcon\DataMapper\Query\Update; -use function array_filter; - /** * @phpstan-import-type TCriteria from UserTypes - * @phpstan-import-type TUserDomainToDbRecord from UserTypes - * @phpstan-import-type TUserDbRecordOptional from UserTypes * @phpstan-import-type TUserRecord from UserTypes - * - * The 'final' keyword was intentionally removed from this class to allow - * extension for testing purposes (e.g., mocking in unit tests). - * - * Please avoid extending this class in production code unless absolutely necessary. + * @phpstan-import-type TUserDbRecordOptional from UserTypes */ class UserRepository extends AbstractRepository implements UserRepositoryInterface { @@ -63,7 +54,7 @@ public function findByEmail(string $email): ?User if (true !== empty($email)) { return $this->findOneBy( [ - 'usr_email' => $email, + 'usr_email' => $email, 'usr_status_flag' => FlagsEnum::Active->value, ] ); @@ -116,33 +107,16 @@ public function findOneBy(array $criteria): ?User /** - * @param User $user + * @param TUserDbRecordOptional $user * * @return int */ - public function insert(User $user): int + public function insert(array $user): int { - $row = $this->mapper->db($user); - $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); - - /** - * @todo this should not be here - the insert should just add data not validate - */ - if (true === empty($row['usr_created_date'])) { - $row['usr_created_date'] = $now; - } - if (true === empty($row['usr_updated_date'])) { - $row['usr_updated_date'] = $now; - } - - /** - * Cleanup empty fields if needed - */ - $columns = $this->cleanupFields($row); - $insert = Insert::new($this->connection); + $insert = Insert::new($this->connection); $insert ->into($this->table) - ->columns($columns) + ->columns($user) ->perform() ; @@ -150,61 +124,20 @@ public function insert(User $user): int } /** - * @param User $user + * @param TUserDbRecordOptional $user * * @return int */ - public function update(User $user): int + public function update(int $userId, array $user): int { - $row = $this->mapper->db($user); - $now = Dates::toUTC(format: Dates::DATE_TIME_FORMAT); - /** @var int $userId */ - $userId = $row['usr_id']; - - /** - * @todo this should not be here - the update should just add data not validate - */ - /** - * Set updated date to now if it has not been set - */ - if (true === empty($row['usr_updated_date'])) { - $row['usr_updated_date'] = $now; - } - - /** - * Cleanup empty fields if needed - */ - $columns = $this->cleanupFields($row); - - /** - * Remove createdDate and createdUserId - cannot be changed. This - * needs to be here because we don't want to touch those fields. - */ - unset($columns['usr_created_date'], $columns['usr_created_usr_id']); - $update = Update::new($this->connection); $update ->table($this->table) - ->columns($columns) + ->columns($user) ->where('usr_id = ', $userId) ->perform() ; return $userId; } - - /** - * @param TUserDomainToDbRecord $row - * - * @return TUserDbRecordOptional - */ - private function cleanupFields(array $row): array - { - unset($row['usr_id']); - - return array_filter( - $row, - static fn($v) => $v !== null && $v !== '' - ); - } } diff --git a/src/Domain/Components/DataSource/User/UserRepositoryInterface.php b/src/Domain/Components/DataSource/User/UserRepositoryInterface.php index 5313ae6..5afb71b 100644 --- a/src/Domain/Components/DataSource/User/UserRepositoryInterface.php +++ b/src/Domain/Components/DataSource/User/UserRepositoryInterface.php @@ -15,6 +15,7 @@ /** * @phpstan-import-type TCriteria from UserTypes + * @phpstan-import-type TUserDbRecordOptional from UserTypes */ interface UserRepositoryInterface { @@ -54,16 +55,17 @@ public function findById(int $recordId): ?User; public function findOneBy(array $criteria): ?User; /** - * @param User $user + * @param TUserDbRecordOptional $user * * @return int */ - public function insert(User $user): int; + public function insert(array $user): int; /** - * @param User $user + * @param int $userId + * @param TUserDbRecordOptional $user * * @return int */ - public function update(User $user): int; + public function update(int $userId, array $user): int; } diff --git a/tests/Unit/Domain/Components/DataSource/User/UserRepositoryTest.php b/tests/Unit/Domain/Components/DataSource/User/UserRepositoryTest.php index 569a63c..6c377bc 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserRepositoryTest.php +++ b/tests/Unit/Domain/Components/DataSource/User/UserRepositoryTest.php @@ -14,8 +14,8 @@ namespace Phalcon\Api\Tests\Unit\Domain\Components\DataSource\User; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\QueryRepository; use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; @@ -23,36 +23,36 @@ final class UserRepositoryTest extends AbstractUnitTestCase { public function testFindByEmail(): void { - /** @var QueryRepository $repository */ - $repository = $this->container->get(Container::REPOSITORY); + /** @var UserRepository $repository */ + $repository = $this->container->get(Container::USER_REPOSITORY); $migration = new UsersMigration($this->getConnection()); - $repositoryUser = $repository->user()->findByEmail(''); + $repositoryUser = $repository->findByEmail(''); $this->assertEmpty($repositoryUser); $migrationUser = $this->getNewUser($migration); $email = $migrationUser['usr_email']; - $repositoryUser = $repository->user()->findByEmail($email); + $repositoryUser = $repository->findByEmail($email); $this->runAssertions($migrationUser, $repositoryUser); } public function testFindById(): void { - /** @var QueryRepository $repository */ - $repository = $this->container->get(Container::REPOSITORY); + /** @var UserRepository $repository */ + $repository = $this->container->get(Container::USER_REPOSITORY); $migration = new UsersMigration($this->getConnection()); - $repositoryUser = $repository->user()->findById(0); + $repositoryUser = $repository->findById(0); $this->assertEmpty($repositoryUser); $migrationUser = $this->getNewUser($migration); $userId = $migrationUser['usr_id']; - $repositoryUser = $repository->user()->findById($userId); + $repositoryUser = $repository->findById($userId); $this->runAssertions($migrationUser, $repositoryUser); } From 0db320aaed3c165915b701c9d31434245d4ef312 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:54:05 -0600 Subject: [PATCH 42/74] [#.x] - new array shapes for phpstan --- src/Domain/ADR/InputTypes.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Domain/ADR/InputTypes.php b/src/Domain/ADR/InputTypes.php index 14021fd..43a1471 100644 --- a/src/Domain/ADR/InputTypes.php +++ b/src/Domain/ADR/InputTypes.php @@ -50,8 +50,8 @@ * } * * @phpstan-type TRequestQuery array - * - * @phpstan-type TValidationErrors array> + * @phpstan-type TValidatorErrors array{}|array> + * @phpstan-type TInputSanitize TUserInput|TAuthInput */ final class InputTypes { From e87f2da7480cf80a2b06c74e1749f8c8483bebe0 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 11:54:13 -0600 Subject: [PATCH 43/74] [#.x] - more registrations --- src/Domain/Components/Container.php | 266 ++++++++++++++++++++++------ 1 file changed, 211 insertions(+), 55 deletions(-) diff --git a/src/Domain/Components/Container.php b/src/Domain/Components/Container.php index 0df1641..cca1e4a 100644 --- a/src/Domain/Components/Container.php +++ b/src/Domain/Components/Container.php @@ -13,14 +13,20 @@ namespace Phalcon\Api\Domain\Components; -use Phalcon\Api\Domain\Components\Cache\Cache; +use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthFacade; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthLoginValidator; use Phalcon\Api\Domain\Components\DataSource\Auth\AuthSanitizer; -use Phalcon\Api\Domain\Components\DataSource\QueryRepository; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthTokenValidator; +use Phalcon\Api\Domain\Components\DataSource\User\UserFacade; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; +use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Encryption\Security; +use Phalcon\Api\Domain\Components\Encryption\TokenCache; +use Phalcon\Api\Domain\Components\Encryption\TokenManager; use Phalcon\Api\Domain\Components\Env\EnvManager; use Phalcon\Api\Domain\Components\Middleware\HealthMiddleware; use Phalcon\Api\Domain\Components\Middleware\NotFoundMiddleware; @@ -38,6 +44,7 @@ use Phalcon\Api\Domain\Services\User\UserPutService; use Phalcon\Api\Responder\JsonResponder; use Phalcon\Cache\AdapterFactory; +use Phalcon\Cache\Cache; use Phalcon\DataMapper\Pdo\Connection; use Phalcon\Di\Di; use Phalcon\Di\Service; @@ -68,6 +75,10 @@ class Container extends Di /** @var string */ public const JWT_TOKEN = 'jwt.token'; /** @var string */ + public const JWT_TOKEN_CACHE = 'jwt.token.cache'; + /** @var string */ + public const JWT_TOKEN_MANAGER = 'jwt.token.manager'; + /** @var string */ public const LOGGER = 'logger'; /** @var string */ public const REGISTRY = 'registry'; @@ -101,14 +112,32 @@ class Container extends Di public const MIDDLEWARE_VALIDATE_TOKEN_REVOKED = ValidateTokenRevokedMiddleware::class; public const MIDDLEWARE_VALIDATE_TOKEN_STRUCTURE = ValidateTokenStructureMiddleware::class; public const MIDDLEWARE_VALIDATE_TOKEN_USER = ValidateTokenUserMiddleware::class; + + /** + * Facades + */ + public const AUTH_FACADE = 'auth.facade'; + public const USER_FACADE = 'user.facade'; + /** + * Mappers + */ + public const USER_MAPPER = UserMapper::class; + /** + * Repositories + */ + public const USER_REPOSITORY = 'user.repository'; + /** + * Sanitizers + */ + public const AUTH_SANITIZER = 'auth.sanitizer'; + public const USER_SANITIZER = 'user.sanitizer'; + /** - * Repositories/Sanitizers/Validators + * Validators */ - public const REPOSITORY = 'repository'; - public const AUTH_SANITIZER = 'auth.sanitizer'; - public const USER_MAPPER = UserMapper::class; - public const USER_SANITIZER = 'user.sanitizer'; - public const USER_VALIDATOR = UserValidator::class; + public const AUTH_LOGIN_VALIDATOR = AuthLoginValidator::class; + public const AUTH_TOKEN_VALIDATOR = 'auth.validator.token'; + public const USER_VALIDATOR = UserValidator::class; /** * Responders @@ -118,25 +147,32 @@ class Container extends Di public function __construct() { $this->services = [ - self::CACHE => $this->getServiceCache(), - self::CONNECTION => $this->getServiceConnection(), - self::ENV => $this->getServiceEnv(), - self::EVENTS_MANAGER => $this->getServiceEventsManger(), - self::FILTER => $this->getServiceFilter(), - self::JWT_TOKEN => $this->getServiceJWTToken(), - self::LOGGER => $this->getServiceLogger(), - self::REGISTRY => new Service(Registry::class, true), - self::REQUEST => new Service(Request::class, true), - self::RESPONSE => new Service(Response::class, true), - self::ROUTER => $this->getServiceRouter(), - - self::REPOSITORY => $this->getServiceRepository(), + self::CACHE => $this->getServiceCache(), + self::CONNECTION => $this->getServiceConnection(), + self::ENV => $this->getServiceEnv(), + self::EVENTS_MANAGER => $this->getServiceEventsManger(), + self::FILTER => $this->getServiceFilter(), + self::JWT_TOKEN => $this->getServiceJWTToken(), + self::JWT_TOKEN_CACHE => $this->getServiceJWTTokenCache(), + self::JWT_TOKEN_MANAGER => $this->getServiceJWTTokenManager(), + self::LOGGER => $this->getServiceLogger(), + self::REGISTRY => new Service(Registry::class, true), + self::REQUEST => new Service(Request::class, true), + self::RESPONSE => new Service(Response::class, true), + self::ROUTER => $this->getServiceRouter(), + + self::AUTH_FACADE => $this->getServiceFacadeAuth(), + self::USER_FACADE => $this->getServiceFacadeUser(), + self::USER_REPOSITORY => $this->getServiceRepositoryUser(), + self::AUTH_SANITIZER => $this->getServiceSanitizer(AuthSanitizer::class), self::USER_SANITIZER => $this->getServiceSanitizer(UserSanitizer::class), - self::AUTH_LOGIN_POST_SERVICE => $this->getServiceAuthPost(LoginPostService::class), - self::AUTH_LOGOUT_POST_SERVICE => $this->getServiceAuthPost(LogoutPostService::class), - self::AUTH_REFRESH_POST_SERVICE => $this->getServiceAuthPost(RefreshPostService::class), + self::AUTH_TOKEN_VALIDATOR => $this->getServiceValidatorAuthToken(), + + self::AUTH_LOGIN_POST_SERVICE => $this->getServiceAuthLoginPost(), + self::AUTH_LOGOUT_POST_SERVICE => $this->getServiceAuthTokenPost(LogoutPostService::class), + self::AUTH_REFRESH_POST_SERVICE => $this->getServiceAuthTokenPost(RefreshPostService::class), self::USER_DELETE_SERVICE => $this->getServiceUser(UserDeleteService::class), self::USER_GET_SERVICE => $this->getServiceUser(UserGetService::class), self::USER_POST_SERVICE => $this->getServiceUser(UserPostService::class), @@ -147,39 +183,45 @@ public function __construct() } /** - * @param class-string $className - * * @return Service */ - private function getServiceAuthPost(string $className): Service + private function getServiceAuthLoginPost(): Service { return new Service( [ - 'className' => $className, + 'className' => LoginPostService::class, 'arguments' => [ [ 'type' => 'service', - 'name' => self::REPOSITORY, - ], - [ - 'type' => 'service', - 'name' => self::CACHE, - ], - [ - 'type' => 'service', - 'name' => self::ENV, + 'name' => self::AUTH_FACADE, ], [ 'type' => 'service', - 'name' => self::JWT_TOKEN, + 'name' => self::AUTH_LOGIN_VALIDATOR, ], + ], + ] + ); + } + + /** + * @param class-string $className + * + * @return Service + */ + private function getServiceAuthTokenPost(string $className): Service + { + return new Service( + [ + 'className' => $className, + 'arguments' => [ [ 'type' => 'service', - 'name' => self::AUTH_SANITIZER, + 'name' => self::AUTH_FACADE, ], [ 'type' => 'service', - 'name' => self::SECURITY, + 'name' => self::AUTH_TOKEN_VALIDATOR, ], ], ] @@ -201,7 +243,7 @@ function () { /** @var string $host */ $host = $env->get('CACHE_HOST', 'localhost'); /** @var int $lifetime */ - $lifetime = $env->get('CACHE_LIFETIME', Cache::CACHE_LIFETIME_DAY, 'int'); + $lifetime = $env->get('CACHE_LIFETIME', CacheConstants::CACHE_LIFETIME_DAY, 'int'); /** @var int $index */ $index = $env->get('CACHE_INDEX', 0, 'int'); /** @var int $port */ @@ -301,6 +343,70 @@ function () { ); } + /** + * @return Service + */ + private function getServiceFacadeAuth(): Service + { + return new Service( + [ + 'className' => AuthFacade::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::USER_REPOSITORY, + ], + [ + 'type' => 'service', + 'name' => self::AUTH_SANITIZER, + ], + [ + 'type' => 'service', + 'name' => self::JWT_TOKEN_MANAGER, + ], + [ + 'type' => 'service', + 'name' => self::SECURITY, + ], + ] + ] + ); + } + + /** + * @return Service + */ + private function getServiceFacadeUser(): Service + { + return new Service( + [ + 'className' => UserFacade::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::USER_SANITIZER, + ], + [ + 'type' => 'service', + 'name' => self::USER_VALIDATOR, + ], + [ + 'type' => 'service', + 'name' => self::USER_MAPPER, + ], + [ + 'type' => 'service', + 'name' => self::USER_REPOSITORY, + ], + [ + 'type' => 'service', + 'name' => self::SECURITY, + ], + ] + ] + ); + } + /** * @return Service */ @@ -332,6 +438,50 @@ private function getServiceJWTToken(): Service ); } + /** + * @return Service + */ + private function getServiceJWTTokenCache(): Service + { + return new Service( + [ + 'className' => TokenCache::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::CACHE, + ], + ], + ] + ); + } + + /** + * @return Service + */ + private function getServiceJWTTokenManager(): Service + { + return new Service( + [ + 'className' => TokenManager::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::JWT_TOKEN_CACHE, + ], + [ + 'type' => 'service', + 'name' => self::ENV, + ], + [ + 'type' => 'service', + 'name' => self::JWT_TOKEN, + ], + ], + ] + ); + } + /** * @return Service */ @@ -361,11 +511,11 @@ function () { /** * @return Service */ - private function getServiceRepository(): Service + private function getServiceRepositoryUser(): Service { return new Service( [ - 'className' => QueryRepository::class, + 'className' => UserRepository::class, 'arguments' => [ [ 'type' => 'service', @@ -375,7 +525,7 @@ private function getServiceRepository(): Service 'type' => 'service', 'name' => self::USER_MAPPER, ], - ], + ] ] ); } @@ -431,23 +581,29 @@ private function getServiceUser(string $className): Service 'arguments' => [ [ 'type' => 'service', - 'name' => self::REPOSITORY, - ], - [ - 'type' => 'service', - 'name' => self::USER_MAPPER, - ], - [ - 'type' => 'service', - 'name' => self::USER_VALIDATOR, + 'name' => self::USER_FACADE, ], + ], + ] + ); + } + + /** + * @return Service + */ + private function getServiceValidatorAuthToken(): Service + { + return new Service( + [ + 'className' => AuthTokenValidator::class, + 'arguments' => [ [ 'type' => 'service', - 'name' => self::USER_SANITIZER, + 'name' => self::JWT_TOKEN_MANAGER, ], [ 'type' => 'service', - 'name' => self::SECURITY, + 'name' => self::USER_REPOSITORY, ], ], ] From ca09a44f3c979aabd6cbd5ff839776ca0c06dfd3 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 12:16:59 -0600 Subject: [PATCH 44/74] [#.x] - phpcs --- src/Domain/Components/Container.php | 53 +++++++++---------- .../Components/DataSource/Auth/AuthFacade.php | 2 +- .../DataSource/Auth/AuthTokenValidator.php | 6 +-- .../Components/DataSource/User/UserFacade.php | 34 ++++++------ .../DataSource/User/UserValidator.php | 1 - .../DataSource/Validation/Result.php | 16 +++--- src/Domain/Components/Encryption/JWTToken.php | 2 +- .../Components/Encryption/TokenCache.php | 4 -- .../Components/Encryption/TokenManager.php | 7 --- .../Encryption/TokenManagerInterface.php | 18 +++---- .../ValidateTokenUserMiddleware.php | 2 - .../Services/Auth/AbstractAuthService.php | 4 -- src/Domain/Services/User/UserPutService.php | 1 - .../DataSource/User/UserMapperTest.php | 1 - 14 files changed, 62 insertions(+), 89 deletions(-) diff --git a/src/Domain/Components/Container.php b/src/Domain/Components/Container.php index cca1e4a..7ced6c9 100644 --- a/src/Domain/Components/Container.php +++ b/src/Domain/Components/Container.php @@ -92,16 +92,6 @@ class Container extends Di public const SECURITY = Security::class; /** @var string */ public const TIME = 'time'; - /** - * Services - */ - public const AUTH_LOGIN_POST_SERVICE = 'service.auth.login.post'; - public const AUTH_LOGOUT_POST_SERVICE = 'service.auth.logout.post'; - public const AUTH_REFRESH_POST_SERVICE = 'service.auth.refresh.post'; - public const USER_DELETE_SERVICE = 'service.user.delete'; - public const USER_GET_SERVICE = 'service.user.get'; - public const USER_POST_SERVICE = 'service.user.post'; - public const USER_PUT_SERVICE = 'service.user.put'; /** * Middleware */ @@ -112,16 +102,29 @@ class Container extends Di public const MIDDLEWARE_VALIDATE_TOKEN_REVOKED = ValidateTokenRevokedMiddleware::class; public const MIDDLEWARE_VALIDATE_TOKEN_STRUCTURE = ValidateTokenStructureMiddleware::class; public const MIDDLEWARE_VALIDATE_TOKEN_USER = ValidateTokenUserMiddleware::class; - /** * Facades */ - public const AUTH_FACADE = 'auth.facade'; - public const USER_FACADE = 'user.facade'; + public const AUTH_FACADE = 'auth.facade'; + public const USER_FACADE = 'user.facade'; + /** + * Services + */ + public const AUTH_LOGIN_POST_SERVICE = 'service.auth.login.post'; + public const AUTH_LOGOUT_POST_SERVICE = 'service.auth.logout.post'; + public const AUTH_REFRESH_POST_SERVICE = 'service.auth.refresh.post'; + public const USER_DELETE_SERVICE = 'service.user.delete'; + public const USER_GET_SERVICE = 'service.user.get'; + public const USER_POST_SERVICE = 'service.user.post'; + public const USER_PUT_SERVICE = 'service.user.put'; /** * Mappers */ - public const USER_MAPPER = UserMapper::class; + public const USER_MAPPER = UserMapper::class; + /** + * Responders + */ + public const RESPONDER_JSON = JsonResponder::class; /** * Repositories */ @@ -129,20 +132,14 @@ class Container extends Di /** * Sanitizers */ - public const AUTH_SANITIZER = 'auth.sanitizer'; - public const USER_SANITIZER = 'user.sanitizer'; - + public const AUTH_SANITIZER = 'auth.sanitizer'; + public const USER_SANITIZER = 'user.sanitizer'; /** * Validators */ - public const AUTH_LOGIN_VALIDATOR = AuthLoginValidator::class; - public const AUTH_TOKEN_VALIDATOR = 'auth.validator.token'; - public const USER_VALIDATOR = UserValidator::class; - - /** - * Responders - */ - public const RESPONDER_JSON = JsonResponder::class; + public const AUTH_LOGIN_VALIDATOR = AuthLoginValidator::class; + public const AUTH_TOKEN_VALIDATOR = 'auth.validator.token'; + public const USER_VALIDATOR = UserValidator::class; public function __construct() { @@ -368,7 +365,7 @@ private function getServiceFacadeAuth(): Service 'type' => 'service', 'name' => self::SECURITY, ], - ] + ], ] ); } @@ -402,7 +399,7 @@ private function getServiceFacadeUser(): Service 'type' => 'service', 'name' => self::SECURITY, ], - ] + ], ] ); } @@ -525,7 +522,7 @@ private function getServiceRepositoryUser(): Service 'type' => 'service', 'name' => self::USER_MAPPER, ], - ] + ], ] ); } diff --git a/src/Domain/Components/DataSource/Auth/AuthFacade.php b/src/Domain/Components/DataSource/Auth/AuthFacade.php index 6460a73..60997c0 100644 --- a/src/Domain/Components/DataSource/Auth/AuthFacade.php +++ b/src/Domain/Components/DataSource/Auth/AuthFacade.php @@ -85,7 +85,7 @@ public function authenticate( /** @var string $suppliedPassword */ $suppliedPassword = $dto->password; /** @var string $dbPassword */ - $dbPassword = $domainUser->password; + $dbPassword = $domainUser->password; if (true !== $this->security->verify($suppliedPassword, $dbPassword)) { return Payload::unauthorized([HttpCodesEnum::AppIncorrectCredentials->error()]); } diff --git a/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php b/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php index 8b8e1d6..7cb7257 100644 --- a/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php +++ b/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php @@ -13,16 +13,12 @@ namespace Phalcon\Api\Domain\Components\DataSource\Auth; -use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; use Phalcon\Api\Domain\Components\DataSource\Validation\Result; use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; -use Phalcon\Api\Domain\Components\Encryption\TokenManager; use Phalcon\Api\Domain\Components\Encryption\TokenManagerInterface; use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Components\Payload; use Phalcon\Encryption\Security\JWT\Token\Token; final class AuthTokenValidator implements ValidatorInterface @@ -48,7 +44,7 @@ public function validate(mixed $input): Result return Result::error([HttpCodesEnum::AppTokenNotPresent->error()]); } - $token = $input->token; + $token = $input->token; $tokenObject = $this->tokenManager->getObject($token); if (null === $tokenObject) { return Result::error([HttpCodesEnum::AppTokenNotValid->error()]); diff --git a/src/Domain/Components/DataSource/User/UserFacade.php b/src/Domain/Components/DataSource/User/UserFacade.php index 1b36969..03af5c3 100644 --- a/src/Domain/Components/DataSource/User/UserFacade.php +++ b/src/Domain/Components/DataSource/User/UserFacade.php @@ -249,6 +249,21 @@ public function update(array $input): Payload return Payload::updated([$domainUser->id => $domainUser->toArray()]); } + /** + * @param TUserDbRecordOptional $row + * + * @return TUserDbRecordOptional + */ + private function cleanupFields(array $row): array + { + unset($row['usr_id']); + + return array_filter( + $row, + static fn($v) => $v !== null && $v !== '' + ); + } + /** * @param string $message * @@ -256,8 +271,8 @@ public function update(array $input): Payload */ private function getErrorPayload( HttpCodesEnum $item, - string $message): Payload - { + string $message + ): Payload { return Payload::error([[$item->text() . $message]]); } @@ -328,19 +343,4 @@ private function processPassword(array $input): array return $input; } - - /** - * @param TUserDbRecordOptional $row - * - * @return TUserDbRecordOptional - */ - private function cleanupFields(array $row): array - { - unset($row['usr_id']); - - return array_filter( - $row, - static fn($v) => $v !== null && $v !== '' - ); - } } diff --git a/src/Domain/Components/DataSource/User/UserValidator.php b/src/Domain/Components/DataSource/User/UserValidator.php index 4aae41b..3ee0bd9 100644 --- a/src/Domain/Components/DataSource/User/UserValidator.php +++ b/src/Domain/Components/DataSource/User/UserValidator.php @@ -13,7 +13,6 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; -use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\Components\DataSource\Validation\Result; use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; diff --git a/src/Domain/Components/DataSource/Validation/Result.php b/src/Domain/Components/DataSource/Validation/Result.php index 7fe3223..4a81296 100644 --- a/src/Domain/Components/DataSource/Validation/Result.php +++ b/src/Domain/Components/DataSource/Validation/Result.php @@ -62,6 +62,14 @@ public function getMeta(string $key, mixed $defaultValue = null): mixed return $this->meta[$key] ?? $defaultValue; } + /** + * @return bool + */ + public function isValid(): bool + { + return $this->errors === []; + } + /** * @param string $key * @param mixed $value @@ -73,14 +81,6 @@ public function setMeta(string $key, mixed $value): void $this->meta[$key] = $value; } - /** - * @return bool - */ - public function isValid(): bool - { - return $this->errors === []; - } - /** * @return self */ diff --git a/src/Domain/Components/Encryption/JWTToken.php b/src/Domain/Components/Encryption/JWTToken.php index 749b4e3..dc7be76 100644 --- a/src/Domain/Components/Encryption/JWTToken.php +++ b/src/Domain/Components/Encryption/JWTToken.php @@ -96,7 +96,7 @@ public function getRefreshForUser(User $user): string /** * @param UserRepositoryInterface $repository - * @param Token $token + * @param Token $token * * @return User|null */ diff --git a/src/Domain/Components/Encryption/TokenCache.php b/src/Domain/Components/Encryption/TokenCache.php index fc06d9b..b384f14 100644 --- a/src/Domain/Components/Encryption/TokenCache.php +++ b/src/Domain/Components/Encryption/TokenCache.php @@ -18,14 +18,10 @@ use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; use Phalcon\Api\Domain\Components\Constants\Dates; use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; use Phalcon\Api\Domain\Components\Env\EnvManager; use Phalcon\Cache\Adapter\Redis; use Phalcon\Cache\Cache; -use Phalcon\Encryption\Security\JWT\Token\Token; -use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\InvalidArgumentException; -use Throwable; use function str_replace; diff --git a/src/Domain/Components/Encryption/TokenManager.php b/src/Domain/Components/Encryption/TokenManager.php index e5afad7..50ef6c0 100644 --- a/src/Domain/Components/Encryption/TokenManager.php +++ b/src/Domain/Components/Encryption/TokenManager.php @@ -13,20 +13,13 @@ namespace Phalcon\Api\Domain\Components\Encryption; -use DateTimeImmutable; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Components\Constants\Dates; use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; use Phalcon\Api\Domain\Components\Env\EnvManager; -use Phalcon\Cache\Adapter\Redis; use Phalcon\Encryption\Security\JWT\Token\Token; -use Psr\SimpleCache\InvalidArgumentException; use Throwable; -use function str_replace; - /** * Small component to issue/rotate/revoke tokens and * interact with cache. diff --git a/src/Domain/Components/Encryption/TokenManagerInterface.php b/src/Domain/Components/Encryption/TokenManagerInterface.php index d3f4c8a..589dead 100644 --- a/src/Domain/Components/Encryption/TokenManagerInterface.php +++ b/src/Domain/Components/Encryption/TokenManagerInterface.php @@ -28,6 +28,15 @@ */ interface TokenManagerInterface { + /** + * Parse a token and return either null or a Token object + * + * @param string $token + * + * @return Token|null + */ + public function getObject(string $token): ?Token; + /** * Return the domain user from the database * @@ -41,15 +50,6 @@ public function getUser( Token $tokenObject ): ?User; - /** - * Parse a token and return either null or a Token object - * - * @param string $token - * - * @return Token|null - */ - public function getObject(string $token): ?Token; - /** * Issue new tokens for the user (token, refreshToken) * diff --git a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php index 97c2fe2..04503ee 100644 --- a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php @@ -14,8 +14,6 @@ namespace Phalcon\Api\Domain\Components\Middleware; use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\QueryRepository; -use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; diff --git a/src/Domain/Services/Auth/AbstractAuthService.php b/src/Domain/Services/Auth/AbstractAuthService.php index 645f629..ac877dd 100644 --- a/src/Domain/Services/Auth/AbstractAuthService.php +++ b/src/Domain/Services/Auth/AbstractAuthService.php @@ -14,12 +14,8 @@ namespace Phalcon\Api\Domain\Services\Auth; use Phalcon\Api\Domain\ADR\DomainInterface; -use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Cache\Cache; use Phalcon\Api\Domain\Components\DataSource\Auth\AuthFacade; use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; -use Phalcon\Api\Domain\Components\Encryption\JWTToken; -use Phalcon\Api\Domain\Components\Env\EnvManager; abstract class AbstractAuthService implements DomainInterface { diff --git a/src/Domain/Services/User/UserPutService.php b/src/Domain/Services/User/UserPutService.php index ad596a8..d1a5691 100644 --- a/src/Domain/Services/User/UserPutService.php +++ b/src/Domain/Services/User/UserPutService.php @@ -14,7 +14,6 @@ namespace Phalcon\Api\Domain\Services\User; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Payload; /** diff --git a/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php b/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php index 5511a9c..6052b43 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php +++ b/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php @@ -15,7 +15,6 @@ use Faker\Factory as FakerFactory; use Phalcon\Api\Domain\Components\Constants\Dates; -use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\DataSource\User\UserInput; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; use Phalcon\Api\Tests\AbstractUnitTestCase; From 9a6622345f52f607c9c99bd7b2e4df9fdb601bfe Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 13:10:58 -0600 Subject: [PATCH 45/74] [#.x] - adjusted the readme --- README.md | 153 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 122 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 2f77ef5..ac5552a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,41 @@ # REST API with Phalcon v6 -A REST API developed with Phalcon v6 +A REST API developed with Phalcon v6. This document explains how the project is organised, how the main components interact, and the important design decisions to keep in mind when extending the codebase. + +## Introduction + +Our goal is to build a REST API that has: +- Slim/efficient design +- Middleware +- JSON (or other) responses +- Action/Domain/Responder implementation +- JWT token authentication + +> This is not **THE** way to build a REST API with Phalcon. It is simply **A** way to do that. You can adopt this implementation if you wish, parts of it or none of it. + +This application has evolved significantly with every video release. Several areas were implemented in one way and later refactored to demonstrate the design trade-offs and how earlier choices affect the codebase. + +The main takeaways that we want to convey to developers are: +- The code has to be easy to read and understand +- Each component must do one thing and one thing only +- Components can be swapped out with others so the use of interfaces is essential +- Static analysis tools (PHPStan) must not produce errors +- Code coverage for tests must be at 100% + +Part 1 https://youtu.be/f3wP_M_NFKc +Part 2 https://youtu.be/VEZvUf_PdSY +Part 3 https://youtu.be/LP64Doh0t4g +Part 4 https://youtu.be/jCEZ2WMil8Q +Part 5 https://youtu.be/syU_3cIXFMM +Part 6 https://youtu.be/AgCbqW-leCM +Part 7 https://youtu.be/tGV4pSyVLdI +Part 8 https://youtu.be/GaJhNnw_1cE +Part 9 https://youtu.be/CWofDyTdToI + +## Directory Structure The directory structure for this projects follows the recommendations of [pds/skeleton][pds_skeleton] -# Folders The folders contain: - `bin`: empty for now, we might use it later on @@ -16,23 +47,37 @@ The folders contain: - `storage`: various storage data such as application logs - `tests`: tests -# Code organization +## High-level architecture + The application follows the [ADR pattern][adr_pattern] where the application is split into an `Action` layer, the `Domain` layer and a `Responder` layer. -## Action -The folder (under `src/`) contains the handler that is responsible for receiving data from the Domain and injecting it to the Responder so that the response can be generated. It also collects all the Input supplied by the user and injects it into the Domain for further processing. +- `Action` — receives HTTP input, collects and sanitizes request data, and calls a Domain service. +- `Domain` — contains the application logic. Implements small components, services that map to endpoints, validators, repositories and helpers. +- `Responder` — builds and emits the HTTP response from a `Payload`. + +Core files live under `src/` and are registered in the DI container in `src/Domain/Components/Container.php`. + +## Main components + +### `Action` layer -## Domain +Contains a handler that translate HTTP requests into Domain calls. For example, actions route requests to `LoginPostService`, `LogoutPostService`, `RefreshPostService` etc. -The Domain is organized in folders based on their use. The `Components` folder contains components that are essential to the operation of the application, while the `Services` folder contains classes that map to endpoints of the application +### `Domain` layer -### Container +#### `Components` + +##### `Container` The application uses the `Phalcon\Di\Di` container with minimal components lazy loaded. Each non "core" component is also registered there (i.e. domain services, responder etc.) and all necessary dependencies are injected based on the service definitions. Additionally there are two `Providers` that are also registered in the DI container for further functionality. The `ErrorHandlerProvider` which caters for the starting up/shut down of the application and error logging, and the very important `RoutesProvider` which handles registering all the routes that the application serves. -### Enums +##### `Payload` + +A uniform result object used across Domain → Responder. + +##### `Enums` There are several enumerations present in the application. Those help with common values for tasks. For example the `FlagsEnum` holds the values for the `co_users.usr_status_flag` field. We could certainly introduce a lookup table in the database for "status" and hold the values there, joining it to the `co_users` table with a lookup table. However, this will introduce an extra join in our query which will inevitable reduce performance. Since the `FlagsEnum` can keep the various statuses, we keep everything in code instead of the database. Thorough tests for enumerations ensure that if a change is made in the future, tests will fail, so that database integrity can be kept. @@ -40,9 +85,74 @@ The `RoutesEnum` holds the various routes of the application. Every route is rep Finally, the `RoutesEnum` also holds the middleware array, which defines their execution and the "hook" they will execute in (before/after). -### Middleware +##### `Env` + +The environment manager and adapters. It reads environment variables using [DotEnv][dotenv] as the main adapter but can be extended if necessary. + +##### `Validation` + +Contains the `ValidatorInterface` for all validators and a `Result` object for returning back validation results/errors. + +##### `Encryption` + +Contains components for JWT handling and passwords. The `Security` component is a wrapper for the `password_*` PHP classes, which are used for password hashing and verification. + +The `TokenManager` offers methods to issue, refresh and revoke tokens. It works in conjunction with the `TokenCache` to store or invalidate tokens stored in Cache (Redis) + +##### `DataSource`: + +The `User` / `Auth` folders contain repositories, sanitizers, mappers, validators and facades for each area. + +#### `Services`: + +Separated also in `User` and `Auth` it contains the classes that the action handler will invoke. The naming of these services shows what endpoint they are targeting and what HTTP method will invoke them. For example the `LoginPostService` will be a `POST` to the `/auth/login`. + +### `Responder` + +The `JsonResponder` responder is responsible for constructing the response with the desired output, and emitting it back to the caller. For the moment we have only implemented a JSON response with a specified array as the payload to be sent back. + +The responder receives the outcome of the Domain, by means of a `Payload` object. The object contains all the data necessary to inject in the response. -There are several middleware registered for this application and they are being executed one after another (order matters) before the action is executed. As a result, the application will stop executing if an error occurs, or if certain validations fail. +#### Response payload + +The application responds always with a specific JSON payload. The payload contains the following nodes: +- `data` - contains any data that are returned back (can be empty) +- `errors` - contains any errors occurred (can be empty) +- `meta` - array of information regarding the payload + - `code` - the application code returned + - `hash` - a `sha1` hash of the `data`, `errors` and timestamp + - `message` - `success` or `error` + - `timestamp` - the time in UTC format + + +## Request flow (example: login) + +1. Route matches and middleware runs (see Middleware section below). +2. `Action` extracts request body and calls `LoginPostService->handle($data)`. +3. `LoginPostService` calls the `AuthFacade->authenticate($input, $loginValidator)` (method injection). +4. `AuthFacade`: + - Builds DTO via `AuthInput`. + - Calls the supplied validator (`AuthLoginValidator`) which returns a `Result`. + - On success, fetches user via repository and verifies credentials (`Security`). + - Issues tokens via `TokenManager`. + - Returns a `Payload::success(...)`. +5. `Responder` builds JSON and returns HTTP response. + +## Validators + +- Specific validators exist for each potential input that needs to be validated +- Method injection is used for validators: the `AuthFacade` does not require a single validator in its constructor. Instead, callers pass the appropriate validator to each method: login uses `AuthLoginValidator`, logout/refresh use `AuthTokenValidator`. +- The validation `Result` supports `meta` data. Token validators may perform repository lookups and attach the resolved `User` to `ValidationResult->meta['user']` to avoid repeating DB queries. The facade reads that meta on success. + +## Token management and cache + +- `TokenManager` depends on a domain-specific `TokenCacheInterface` rather than a raw PSR cache. This keeps token-specific operations discoverable and testable. +- `TokenCache` enhances the Cache operations by providing token specific operations for storing and invalidating tokens. +- `TokenCacheInterface` defines token operations like `storeTokenInCache` and `invalidateForUser`. + +## Middleware sequence + +There are several middleware registered for this application and they are being executed one after another (order matters) before the action is executed. As a result, the application will stop executing if an error occurs, or if certain validations fail. Middleware returns early with a `Payload` error when validation fails. The middleware execution order is defined in the `RoutesEnum`. The available middleware is: @@ -54,13 +164,10 @@ The middleware execution order is defined in the `RoutesEnum`. The available mid - [ValidateTokenStructureMiddleware.php](src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php) - [ValidateTokenUserMiddleware.php](src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php) - - **NotFoundMiddleware** Checks if the route has been matched. If not, it will return a `Resource Not Found` payload - **HealthMiddleware** Invoked when the `/health` endpoint is called and returns a `OK` payload @@ -86,22 +193,6 @@ Checks all the claims of the JWT token to ensure that it validates. For instance Checks if the token has been revoked. If it has, an error is returned -## Responder -The responder is responsible for constructing the response with the desired output, and emitting it back to the caller. For the moment we have only implemented a JSON response with a specified array as the payload to be sent back. - -The responder receives the outcome of the Domain, by means of a `Payload` object. The object contains all the data necessary to inject in the response. - -### Response payload - -The application responds always with a specific JSON payload. The payload contains the following nodes: -- `data` - contains any data that are returned back (can be empty) -- `errors` - contains any errors occurred (can be empty) -- `meta` - array of information regarding the payload - - `code` - the application code returned - - `hash` - a `sha1` hash of the `data`, `errors` and timestamp - - `message` - `success` or `error` - - `timestamp` - the time in UTC format - - [adr_pattern]: https://github.com/pmjones/adr [pds_skeleton]: https://github.com/php-pds/skeleton +[dotenv]: https://github.com/vlucas/phpdotenv From 37e2e822c711f411bd2ec2bfc5f9a3645fdcd254 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 17:03:16 -0600 Subject: [PATCH 46/74] [#.x] - added validator enums for different inputs --- .../Validation/AbstractValidator.php | 63 +++++++++++++++++++ .../Enums/Input/AuthLoginInputEnum.php | 34 ++++++++++ .../Enums/Input/AuthTokenInputEnum.php | 26 ++++++++ .../Enums/Input/UserInputInsertEnum.php | 37 +++++++++++ .../Enums/Input/UserInputUpdateEnum.php | 43 +++++++++++++ 5 files changed, 203 insertions(+) create mode 100644 src/Domain/Components/DataSource/Validation/AbstractValidator.php create mode 100644 src/Domain/Components/Enums/Input/AuthLoginInputEnum.php create mode 100644 src/Domain/Components/Enums/Input/AuthTokenInputEnum.php create mode 100644 src/Domain/Components/Enums/Input/UserInputInsertEnum.php create mode 100644 src/Domain/Components/Enums/Input/UserInputUpdateEnum.php diff --git a/src/Domain/Components/DataSource/Validation/AbstractValidator.php b/src/Domain/Components/DataSource/Validation/AbstractValidator.php new file mode 100644 index 0000000..f433cfa --- /dev/null +++ b/src/Domain/Components/DataSource/Validation/AbstractValidator.php @@ -0,0 +1,63 @@ + + * + * 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\Components\DataSource\Validation; + +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; +use Phalcon\Api\Domain\Components\Enums\Input\ValidatorEnumInterface; +use Phalcon\Filter\Validation\ValidationInterface; +use Phalcon\Filter\Validation\ValidatorInterface as PhalconValidator; +use UnitEnum; + +abstract class AbstractValidator implements ValidatorInterface +{ + protected string $fields = ''; + + public function __construct( + private ValidationInterface $validation + ) { + } + + /** + * @param AuthInput $input + * + * @return list> + */ + protected function runValidations(mixed $input): array + { + $enum = $this->fields; + /** @var ValidatorEnumInterface[] $elements */ + $elements = $enum::cases(); + + /** @var ValidatorEnumInterface $element */ + foreach ($elements as $element) { + $validators = $element->validators(); + foreach ($validators as $validator) { + /** @var PhalconValidator $validatorObject */ + $validatorObject = new $validator(); + $this->validation->add($element->name, $validatorObject); + } + } + + $this->validation->validate($input); + $messages = $this->validation->getMessages(); + + $results = []; + foreach ($messages as $message) { + $results[] = [$message->getMessage()]; + } + + + return $results; + } +} diff --git a/src/Domain/Components/Enums/Input/AuthLoginInputEnum.php b/src/Domain/Components/Enums/Input/AuthLoginInputEnum.php new file mode 100644 index 0000000..560c39f --- /dev/null +++ b/src/Domain/Components/Enums/Input/AuthLoginInputEnum.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\Components\Enums\Input; + +use Phalcon\Filter\Validation\Validator\Email; +use Phalcon\Filter\Validation\Validator\PresenceOf; + +enum AuthLoginInputEnum implements ValidatorEnumInterface +{ + case email; + case password; + + public function validators(): array + { + return match ($this) { + self::email => [ + PresenceOf::class, + Email::class + ], + self::password => [PresenceOf::class], + }; + } +} diff --git a/src/Domain/Components/Enums/Input/AuthTokenInputEnum.php b/src/Domain/Components/Enums/Input/AuthTokenInputEnum.php new file mode 100644 index 0000000..af4d9bf --- /dev/null +++ b/src/Domain/Components/Enums/Input/AuthTokenInputEnum.php @@ -0,0 +1,26 @@ + + * + * 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\Components\Enums\Input; + +use Phalcon\Filter\Validation\Validator\PresenceOf; + +enum AuthTokenInputEnum implements ValidatorEnumInterface +{ + case token; + + public function validators(): array + { + return [PresenceOf::class]; + } +} diff --git a/src/Domain/Components/Enums/Input/UserInputInsertEnum.php b/src/Domain/Components/Enums/Input/UserInputInsertEnum.php new file mode 100644 index 0000000..69b35a3 --- /dev/null +++ b/src/Domain/Components/Enums/Input/UserInputInsertEnum.php @@ -0,0 +1,37 @@ + + * + * 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\Components\Enums\Input; + +use Phalcon\Filter\Validation\Validator\Email; +use Phalcon\Filter\Validation\Validator\PresenceOf; + +enum UserInputInsertEnum implements ValidatorEnumInterface +{ + case email; + case password; + case issuer; + case tokenPassword; + case tokenId; + + public function validators(): array + { + return match ($this) { + self::email => [ + PresenceOf::class, + Email::class + ], + default => [PresenceOf::class], + }; + } +} diff --git a/src/Domain/Components/Enums/Input/UserInputUpdateEnum.php b/src/Domain/Components/Enums/Input/UserInputUpdateEnum.php new file mode 100644 index 0000000..11c7132 --- /dev/null +++ b/src/Domain/Components/Enums/Input/UserInputUpdateEnum.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\Domain\Components\Enums\Input; + +use Phalcon\Filter\Validation\Validator\Email; +use Phalcon\Filter\Validation\Validator\Numericality; +use Phalcon\Filter\Validation\Validator\PresenceOf; + +enum UserInputUpdateEnum implements ValidatorEnumInterface +{ + case id; + case email; + case password; + case issuer; + case tokenPassword; + case tokenId; + + public function validators(): array + { + return match ($this) { + self::id => [ + PresenceOf::class, + Numericality::class + ], + self::email => [ + PresenceOf::class, + Email::class + ], + default => [PresenceOf::class], + }; + } +} From db39e933ce771eb1cf09bd9f1fea242d69b55f7c Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 17:03:27 -0600 Subject: [PATCH 47/74] [#.x] - added validator enum interface --- .../Enums/Input/ValidatorEnumInterface.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/Domain/Components/Enums/Input/ValidatorEnumInterface.php diff --git a/src/Domain/Components/Enums/Input/ValidatorEnumInterface.php b/src/Domain/Components/Enums/Input/ValidatorEnumInterface.php new file mode 100644 index 0000000..41633d5 --- /dev/null +++ b/src/Domain/Components/Enums/Input/ValidatorEnumInterface.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\Components\Enums\Input; + +use UnitEnum; + +interface ValidatorEnumInterface extends UnitEnum +{ + /** + * @return array + */ + public function validators(): array; +} From 133cb8573d7d48b67dad055ddee9c9746183dd5c Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 17:03:40 -0600 Subject: [PATCH 48/74] [#.x] - setting up cache folder for phpstan --- phpstan.neon | 1 + 1 file changed, 1 insertion(+) diff --git a/phpstan.neon b/phpstan.neon index 776ccd8..92dcb57 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,3 +2,4 @@ parameters: level: max paths: - src + tmpDir: tests/_output/ From d444bfb282d96f8408ce9b13fa57af9c4f860b03 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 17:04:05 -0600 Subject: [PATCH 49/74] [#.x] - separated validators for user insert and update --- .../DataSource/User/UserValidator.php | 39 ++------------- .../DataSource/User/UserValidatorTrait.php | 47 +++++++++++++++++++ .../DataSource/User/UserValidatorUpdate.php | 24 ++++++++++ 3 files changed, 76 insertions(+), 34 deletions(-) create mode 100644 src/Domain/Components/DataSource/User/UserValidatorTrait.php create mode 100644 src/Domain/Components/DataSource/User/UserValidatorUpdate.php diff --git a/src/Domain/Components/DataSource/User/UserValidator.php b/src/Domain/Components/DataSource/User/UserValidator.php index 3ee0bd9..331b1b7 100644 --- a/src/Domain/Components/DataSource/User/UserValidator.php +++ b/src/Domain/Components/DataSource/User/UserValidator.php @@ -13,41 +13,12 @@ namespace Phalcon\Api\Domain\Components\DataSource\User; -use Phalcon\Api\Domain\Components\DataSource\Validation\Result; -use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Components\DataSource\Validation\AbstractValidator; +use Phalcon\Api\Domain\Components\Enums\Input\UserInputInsertEnum; -final class UserValidator implements ValidatorInterface +final class UserValidator extends AbstractValidator { - /** - * Validate a UserInput and return an array of errors. - * Empty array means valid. - * - * @param UserInput $input - * - * @return Result - */ - public function validate(mixed $input): Result - { - $errors = []; - $required = [ - 'email', - 'password', - 'issuer', - 'tokenPassword', - 'tokenId', - ]; + use UserValidatorTrait; - foreach ($required as $name) { - $value = $input->$name; - if (true === empty($value)) { - $errors[] = ['Field ' . $name . ' cannot be empty.']; - } - } - - /** - * @todo add validators - */ - - return new Result($errors); - } + protected string $fields = UserInputInsertEnum::class; } diff --git a/src/Domain/Components/DataSource/User/UserValidatorTrait.php b/src/Domain/Components/DataSource/User/UserValidatorTrait.php new file mode 100644 index 0000000..1bae490 --- /dev/null +++ b/src/Domain/Components/DataSource/User/UserValidatorTrait.php @@ -0,0 +1,47 @@ + + * + * 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\Components\DataSource\User; + +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; +use Phalcon\Api\Domain\Components\DataSource\Validation\AbstractValidator; +use Phalcon\Api\Domain\Components\DataSource\Validation\Result; +use Phalcon\Api\Domain\Components\Enums\Input\UserInputInsertEnum; + +trait UserValidatorTrait +{ + /** + * Validate a AuthInput and return an array of errors. + * Empty array means valid. + * + * @param AuthInput $input + * + * @return Result + */ + public function validate(mixed $input): Result + { + $errors = $this->runValidations($input); + if (true !== empty($errors)) { + return Result::error($errors); + } + + return Result::success(); + } + + /** + * @param AuthInput $input + * + * @return array + */ + abstract protected function runValidations(mixed $input): array; +} diff --git a/src/Domain/Components/DataSource/User/UserValidatorUpdate.php b/src/Domain/Components/DataSource/User/UserValidatorUpdate.php new file mode 100644 index 0000000..2f1d0a0 --- /dev/null +++ b/src/Domain/Components/DataSource/User/UserValidatorUpdate.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\Components\DataSource\User; + +use Phalcon\Api\Domain\Components\DataSource\Validation\AbstractValidator; +use Phalcon\Api\Domain\Components\Enums\Input\UserInputUpdateEnum; + +final class UserValidatorUpdate extends AbstractValidator +{ + use UserValidatorTrait; + + protected string $fields = UserInputUpdateEnum::class; +} From a12f37892b507f67e21184c2746a89254e2dc2f2 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 17:04:35 -0600 Subject: [PATCH 50/74] [#.x] - refactored validators for auth to use the new format --- .../DataSource/Auth/AuthLoginValidator.php | 11 +++++++---- .../DataSource/Auth/AuthTokenValidator.php | 15 +++++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Domain/Components/DataSource/Auth/AuthLoginValidator.php b/src/Domain/Components/DataSource/Auth/AuthLoginValidator.php index c2a4d78..d6bf761 100644 --- a/src/Domain/Components/DataSource/Auth/AuthLoginValidator.php +++ b/src/Domain/Components/DataSource/Auth/AuthLoginValidator.php @@ -13,12 +13,15 @@ namespace Phalcon\Api\Domain\Components\DataSource\Auth; +use Phalcon\Api\Domain\Components\DataSource\Validation\AbstractValidator; use Phalcon\Api\Domain\Components\DataSource\Validation\Result; -use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Components\Enums\Input\AuthLoginInputEnum; -final class AuthLoginValidator implements ValidatorInterface +final class AuthLoginValidator extends AbstractValidator { + protected string $fields = AuthLoginInputEnum::class; + /** * Validate a AuthInput and return an array of errors. * Empty array means valid. @@ -29,8 +32,8 @@ final class AuthLoginValidator implements ValidatorInterface */ public function validate(mixed $input): Result { - /** @var AuthInput $input */ - if (true === empty($input->email) || true === empty($input->password)) { + $errors = $this->runValidations($input); + if (true !== empty($errors)) { return Result::error( [HttpCodesEnum::AppIncorrectCredentials->error()] ); diff --git a/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php b/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php index 7cb7257..4425a35 100644 --- a/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php +++ b/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php @@ -14,19 +14,25 @@ namespace Phalcon\Api\Domain\Components\DataSource\Auth; use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; +use Phalcon\Api\Domain\Components\DataSource\Validation\AbstractValidator; use Phalcon\Api\Domain\Components\DataSource\Validation\Result; -use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; use Phalcon\Api\Domain\Components\Encryption\TokenManagerInterface; use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Components\Enums\Input\AuthTokenInputEnum; use Phalcon\Encryption\Security\JWT\Token\Token; +use Phalcon\Filter\Validation\ValidationInterface; -final class AuthTokenValidator implements ValidatorInterface +final class AuthTokenValidator extends AbstractValidator { + protected string $fields = AuthTokenInputEnum::class; + public function __construct( private TokenManagerInterface $tokenManager, private UserRepositoryInterface $userRepository, + ValidationInterface $validator, ) { + parent::__construct($validator); } /** @@ -39,11 +45,12 @@ public function __construct( */ public function validate(mixed $input): Result { - /** @var AuthInput $input */ - if (true === empty($input->token)) { + $errors = $this->runValidations($input); + if (true !== empty($errors)) { return Result::error([HttpCodesEnum::AppTokenNotPresent->error()]); } + /** @var string $token */ $token = $input->token; $tokenObject = $this->tokenManager->getObject($token); if (null === $tokenObject) { From 34a894fd605d2b7fa1df6f787917a0ef4c4996a7 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 17:04:47 -0600 Subject: [PATCH 51/74] [#.x] - adjusted tests --- .../DataSource/User/UserValidatorTest.php | 20 ++++++++++++------- .../Services/User/UserServicePostTest.php | 11 +++++----- .../Services/User/UserServicePutTest.php | 11 +++++----- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php b/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php index f1ea417..0019146 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php +++ b/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php @@ -19,6 +19,7 @@ use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Filter\Validation\ValidationInterface; final class UserValidatorTest extends AbstractUnitTestCase { @@ -26,20 +27,23 @@ public function testError(): void { /** @var UserSanitizer $sanitizer */ $sanitizer = $this->container->get(Container::USER_SANITIZER); + /** @var ValidationInterface $validation */ + $validation = $this->container->get(Container::VALIDATION); $input = []; $userInput = UserInput::new($sanitizer, $input); - $validator = new UserValidator(); + $validator = new UserValidator($validation); $result = $validator->validate($userInput); $actual = $result->getErrors(); $expected = [ - ['Field email cannot be empty.'], - ['Field password cannot be empty.'], - ['Field issuer cannot be empty.'], - ['Field tokenPassword cannot be empty.'], - ['Field tokenId cannot be empty.'], + ['Field email is required'], + ['Field email must be an email address'], + ['Field password is required'], + ['Field issuer is required'], + ['Field tokenPassword is required'], + ['Field tokenId is required'], ]; $this->assertSame($expected, $actual); @@ -49,6 +53,8 @@ public function testSuccess(): void { /** @var UserSanitizer $sanitizer */ $sanitizer = $this->container->get(Container::USER_SANITIZER); + /** @var ValidationInterface $validation */ + $validation = $this->container->get(Container::VALIDATION); $faker = FakerFactory::create(); $input = [ @@ -61,7 +67,7 @@ public function testSuccess(): void $userInput = UserInput::new($sanitizer, $input); - $validator = new UserValidator(); + $validator = new UserValidator($validation); $result = $validator->validate($userInput); $actual = $result->getErrors(); diff --git a/tests/Unit/Domain/Services/User/UserServicePostTest.php b/tests/Unit/Domain/Services/User/UserServicePostTest.php index 006c795..b5dd795 100644 --- a/tests/Unit/Domain/Services/User/UserServicePostTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePostTest.php @@ -158,11 +158,12 @@ public function testServiceFailureValidation(): void $errors = $actual['errors']; $expected = [ - ['Field email cannot be empty.'], - ['Field password cannot be empty.'], - ['Field issuer cannot be empty.'], - ['Field tokenPassword cannot be empty.'], - ['Field tokenId cannot be empty.'], + ['Field email is required'], + ['Field email must be an email address'], + ['Field password is required'], + ['Field issuer is required'], + ['Field tokenPassword is required'], + ['Field tokenId is required'], ]; $actual = $errors; $this->assertSame($expected, $actual); diff --git a/tests/Unit/Domain/Services/User/UserServicePutTest.php b/tests/Unit/Domain/Services/User/UserServicePutTest.php index 0aa5de6..e24300c 100644 --- a/tests/Unit/Domain/Services/User/UserServicePutTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePutTest.php @@ -224,11 +224,12 @@ public function testServiceFailureValidation(): void $errors = $actual['errors']; $expected = [ - ['Field email cannot be empty.'], - ['Field password cannot be empty.'], - ['Field issuer cannot be empty.'], - ['Field tokenPassword cannot be empty.'], - ['Field tokenId cannot be empty.'], + ['Field email is required'], + ['Field email must be an email address'], + ['Field password is required'], + ['Field issuer is required'], + ['Field tokenPassword is required'], + ['Field tokenId is required'], ]; $actual = $errors; $this->assertSame($expected, $actual); From fe8341fed11cfb566a5f1036ec3d3e72ffa56598 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 17:05:08 -0600 Subject: [PATCH 52/74] [#.x] - new registrations --- src/Domain/Components/Container.php | 106 +++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 10 deletions(-) diff --git a/src/Domain/Components/Container.php b/src/Domain/Components/Container.php index 7ced6c9..12a16ad 100644 --- a/src/Domain/Components/Container.php +++ b/src/Domain/Components/Container.php @@ -23,6 +23,7 @@ use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; +use Phalcon\Api\Domain\Components\DataSource\User\UserValidatorUpdate; use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Encryption\Security; use Phalcon\Api\Domain\Components\Encryption\TokenCache; @@ -50,6 +51,7 @@ use Phalcon\Di\Service; use Phalcon\Events\Manager as EventsManager; use Phalcon\Filter\FilterFactory; +use Phalcon\Filter\Validation; use Phalcon\Http\Request; use Phalcon\Http\Response; use Phalcon\Logger\Adapter\Stream; @@ -92,6 +94,8 @@ class Container extends Di public const SECURITY = Security::class; /** @var string */ public const TIME = 'time'; + /** @var string */ + public const VALIDATION = Validation::class; /** * Middleware */ @@ -105,8 +109,9 @@ class Container extends Di /** * Facades */ - public const AUTH_FACADE = 'auth.facade'; - public const USER_FACADE = 'user.facade'; + public const AUTH_FACADE = 'auth.facade'; + public const USER_FACADE = 'user.facade'; + public const USER_FACADE_UPDATE = 'user.facade.update'; /** * Services */ @@ -137,9 +142,10 @@ class Container extends Di /** * Validators */ - public const AUTH_LOGIN_VALIDATOR = AuthLoginValidator::class; - public const AUTH_TOKEN_VALIDATOR = 'auth.validator.token'; - public const USER_VALIDATOR = UserValidator::class; + public const AUTH_LOGIN_VALIDATOR = 'auth.validator.login'; + public const AUTH_TOKEN_VALIDATOR = 'auth.validator.token'; + public const USER_VALIDATOR = 'user.validator.insert'; + public const USER_VALIDATOR_UPDATE = 'user.validator.update'; public function __construct() { @@ -158,14 +164,18 @@ public function __construct() self::RESPONSE => new Service(Response::class, true), self::ROUTER => $this->getServiceRouter(), - self::AUTH_FACADE => $this->getServiceFacadeAuth(), - self::USER_FACADE => $this->getServiceFacadeUser(), - self::USER_REPOSITORY => $this->getServiceRepositoryUser(), + self::AUTH_FACADE => $this->getServiceFacadeAuth(), + self::USER_FACADE => $this->getServiceFacadeUser(), + self::USER_FACADE_UPDATE => $this->getServiceFacadeUserUpdate(), + self::USER_REPOSITORY => $this->getServiceRepositoryUser(), self::AUTH_SANITIZER => $this->getServiceSanitizer(AuthSanitizer::class), self::USER_SANITIZER => $this->getServiceSanitizer(UserSanitizer::class), - self::AUTH_TOKEN_VALIDATOR => $this->getServiceValidatorAuthToken(), + self::AUTH_LOGIN_VALIDATOR => $this->getServiceValidator(AuthLoginValidator::class), + self::AUTH_TOKEN_VALIDATOR => $this->getServiceValidatorAuthToken(), + self::USER_VALIDATOR => $this->getServiceValidator(UserValidator::class), + self::USER_VALIDATOR_UPDATE => $this->getServiceValidator(UserValidatorUpdate::class), self::AUTH_LOGIN_POST_SERVICE => $this->getServiceAuthLoginPost(), self::AUTH_LOGOUT_POST_SERVICE => $this->getServiceAuthTokenPost(LogoutPostService::class), @@ -173,7 +183,7 @@ public function __construct() self::USER_DELETE_SERVICE => $this->getServiceUser(UserDeleteService::class), self::USER_GET_SERVICE => $this->getServiceUser(UserGetService::class), self::USER_POST_SERVICE => $this->getServiceUser(UserPostService::class), - self::USER_PUT_SERVICE => $this->getServiceUser(UserPutService::class), + self::USER_PUT_SERVICE => $this->getServiceUserUpdate(), ]; parent::__construct(); @@ -404,6 +414,40 @@ private function getServiceFacadeUser(): Service ); } + /** + * @return Service + */ + private function getServiceFacadeUserUpdate(): Service + { + return new Service( + [ + 'className' => UserFacade::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::USER_SANITIZER, + ], + [ + 'type' => 'service', + 'name' => self::USER_VALIDATOR_UPDATE, + ], + [ + 'type' => 'service', + 'name' => self::USER_MAPPER, + ], + [ + 'type' => 'service', + 'name' => self::USER_REPOSITORY, + ], + [ + 'type' => 'service', + 'name' => self::SECURITY, + ], + ], + ] + ); + } + /** * @return Service */ @@ -585,6 +629,44 @@ private function getServiceUser(string $className): Service ); } + /** + * @return Service + */ + private function getServiceUserUpdate(): Service + { + return new Service( + [ + 'className' => UserPutService::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::USER_FACADE_UPDATE, + ], + ], + ] + ); + } + + /** + * @param class-string $className + * + * @return Service + */ + private function getServiceValidator(string $className): Service + { + return new Service( + [ + 'className' => $className, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::VALIDATION, + ], + ], + ] + ); + } + /** * @return Service */ @@ -602,6 +684,10 @@ private function getServiceValidatorAuthToken(): Service 'type' => 'service', 'name' => self::USER_REPOSITORY, ], + [ + 'type' => 'service', + 'name' => self::VALIDATION, + ], ], ] ); From 302364962d22509f1e0267c730a1d54e32a92869 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 17:23:55 -0600 Subject: [PATCH 53/74] [#.x] - new absint validator --- .../DataSource/Validation/AbsInt.php | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/Domain/Components/DataSource/Validation/AbsInt.php diff --git a/src/Domain/Components/DataSource/Validation/AbsInt.php b/src/Domain/Components/DataSource/Validation/AbsInt.php new file mode 100644 index 0000000..0618698 --- /dev/null +++ b/src/Domain/Components/DataSource/Validation/AbsInt.php @@ -0,0 +1,61 @@ + + * + * 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\Components\DataSource\Validation; + +use Phalcon\Filter\Validation; +use Phalcon\Filter\Validation\AbstractValidator; + +use Phalcon\Filter\Validation\Validator\Numericality; + +use function preg_match; + +class AbsInt extends Numericality +{ + /** + * @var string|null + */ + protected string | null $template = "Field :field is not a valid absolute integer and greater than 0"; + + /** + * Executes the validation + * + * @param Validation $validation + * @param string $field + * + * @return bool + * @throws Validation\Exception + */ + public function validate(Validation $validation, string $field): bool + { + $result = parent::validate($validation, $field); + + if (false === $result) { + return false; + } + + // Dump spaces in the string if we have any + $value = $validation->getValue($field); + $value = abs((int)$value); + + if ($value <= 0) { + $validation->appendMessage( + $this->messageFactory($validation, $field) + ); + + return false; + } + + return true; + } +} From 64e96cba2a2bd03f6e62d6b7c0b2b7b671c82078 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 17:24:13 -0600 Subject: [PATCH 54/74] [#.x] - changed user put to use the absint validator --- .../Components/DataSource/Validation/AbstractValidator.php | 1 + src/Domain/Components/Enums/Input/UserInputUpdateEnum.php | 4 ++-- tests/Unit/Domain/Services/User/UserServicePutTest.php | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Domain/Components/DataSource/Validation/AbstractValidator.php b/src/Domain/Components/DataSource/Validation/AbstractValidator.php index f433cfa..dda1b32 100644 --- a/src/Domain/Components/DataSource/Validation/AbstractValidator.php +++ b/src/Domain/Components/DataSource/Validation/AbstractValidator.php @@ -35,6 +35,7 @@ public function __construct( */ protected function runValidations(mixed $input): array { + /** @var UnitEnum $enum */ $enum = $this->fields; /** @var ValidatorEnumInterface[] $elements */ $elements = $enum::cases(); diff --git a/src/Domain/Components/Enums/Input/UserInputUpdateEnum.php b/src/Domain/Components/Enums/Input/UserInputUpdateEnum.php index 11c7132..3d09aeb 100644 --- a/src/Domain/Components/Enums/Input/UserInputUpdateEnum.php +++ b/src/Domain/Components/Enums/Input/UserInputUpdateEnum.php @@ -13,8 +13,8 @@ namespace Phalcon\Api\Domain\Components\Enums\Input; +use Phalcon\Api\Domain\Components\DataSource\Validation\AbsInt; use Phalcon\Filter\Validation\Validator\Email; -use Phalcon\Filter\Validation\Validator\Numericality; use Phalcon\Filter\Validation\Validator\PresenceOf; enum UserInputUpdateEnum implements ValidatorEnumInterface @@ -31,7 +31,7 @@ public function validators(): array return match ($this) { self::id => [ PresenceOf::class, - Numericality::class + AbsInt::class ], self::email => [ PresenceOf::class, diff --git a/tests/Unit/Domain/Services/User/UserServicePutTest.php b/tests/Unit/Domain/Services/User/UserServicePutTest.php index e24300c..3513747 100644 --- a/tests/Unit/Domain/Services/User/UserServicePutTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePutTest.php @@ -199,6 +199,7 @@ public function testServiceFailureValidation(): void $userData = $this->getNewUserData(); unset( + $userData['usr_id'], $userData['usr_email'], $userData['usr_password'], $userData['usr_issuer'], @@ -224,6 +225,7 @@ public function testServiceFailureValidation(): void $errors = $actual['errors']; $expected = [ + ['Field id is not a valid absolute integer and greater than 0'], ['Field email is required'], ['Field email must be an email address'], ['Field password is required'], From 51e7ba41e02b8a796b17de8f2e501554f28e12e9 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 17:25:39 -0600 Subject: [PATCH 55/74] [#.x] - phpstan corrections --- src/Domain/Components/DataSource/Validation/AbsInt.php | 2 +- .../Components/DataSource/Validation/AbstractValidator.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Domain/Components/DataSource/Validation/AbsInt.php b/src/Domain/Components/DataSource/Validation/AbsInt.php index 0618698..4bfbb1a 100644 --- a/src/Domain/Components/DataSource/Validation/AbsInt.php +++ b/src/Domain/Components/DataSource/Validation/AbsInt.php @@ -44,7 +44,7 @@ public function validate(Validation $validation, string $field): bool return false; } - // Dump spaces in the string if we have any + /** @var string $value */ $value = $validation->getValue($field); $value = abs((int)$value); diff --git a/src/Domain/Components/DataSource/Validation/AbstractValidator.php b/src/Domain/Components/DataSource/Validation/AbstractValidator.php index dda1b32..bd94e0d 100644 --- a/src/Domain/Components/DataSource/Validation/AbstractValidator.php +++ b/src/Domain/Components/DataSource/Validation/AbstractValidator.php @@ -17,7 +17,6 @@ use Phalcon\Api\Domain\Components\Enums\Input\ValidatorEnumInterface; use Phalcon\Filter\Validation\ValidationInterface; use Phalcon\Filter\Validation\ValidatorInterface as PhalconValidator; -use UnitEnum; abstract class AbstractValidator implements ValidatorInterface { @@ -35,7 +34,6 @@ public function __construct( */ protected function runValidations(mixed $input): array { - /** @var UnitEnum $enum */ $enum = $this->fields; /** @var ValidatorEnumInterface[] $elements */ $elements = $enum::cases(); From bc3fc9a4a43820381762df9e262c62aaca3409c0 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 8 Nov 2025 21:44:40 -0600 Subject: [PATCH 56/74] [#.x] - moved Payload in ADR --- src/Domain/ADR/DomainInterface.php | 2 -- src/Domain/{Components => ADR}/Payload.php | 2 +- src/Domain/Components/DataSource/Auth/AuthFacade.php | 2 +- src/Domain/Components/DataSource/User/UserFacade.php | 2 +- src/Domain/Components/Encryption/TokenCache.php | 7 ++++++- .../Middleware/ValidateTokenRevokedMiddleware.php | 4 ++-- src/Domain/Services/Auth/LoginPostService.php | 2 +- src/Domain/Services/Auth/LogoutPostService.php | 2 +- src/Domain/Services/Auth/RefreshPostService.php | 2 +- src/Domain/Services/User/UserDeleteService.php | 2 +- src/Domain/Services/User/UserGetService.php | 2 +- src/Domain/Services/User/UserPostService.php | 2 +- src/Domain/Services/User/UserPutService.php | 2 +- tests/Fixtures/Domain/Services/ServiceFixture.php | 2 +- 14 files changed, 19 insertions(+), 16 deletions(-) rename src/Domain/{Components => ADR}/Payload.php (98%) diff --git a/src/Domain/ADR/DomainInterface.php b/src/Domain/ADR/DomainInterface.php index 346a756..d6c1d2b 100644 --- a/src/Domain/ADR/DomainInterface.php +++ b/src/Domain/ADR/DomainInterface.php @@ -13,8 +13,6 @@ namespace Phalcon\Api\Domain\ADR; -use Phalcon\Api\Domain\Components\Payload; - /** * @phpstan-import-type TAuthLoginInput from InputTypes * @phpstan-import-type TUserInput from InputTypes diff --git a/src/Domain/Components/Payload.php b/src/Domain/ADR/Payload.php similarity index 98% rename from src/Domain/Components/Payload.php rename to src/Domain/ADR/Payload.php index 860a218..7f1d7b7 100644 --- a/src/Domain/Components/Payload.php +++ b/src/Domain/ADR/Payload.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components; +namespace Phalcon\Api\Domain\ADR; use PayloadInterop\DomainStatus; use Phalcon\Api\Responder\ResponderTypes; diff --git a/src/Domain/Components/DataSource/Auth/AuthFacade.php b/src/Domain/Components/DataSource/Auth/AuthFacade.php index 60997c0..35007b1 100644 --- a/src/Domain/Components/DataSource/Auth/AuthFacade.php +++ b/src/Domain/Components/DataSource/Auth/AuthFacade.php @@ -14,6 +14,7 @@ namespace Phalcon\Api\Domain\Components\DataSource\Auth; use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\ADR\Payload; use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; @@ -21,7 +22,6 @@ use Phalcon\Api\Domain\Components\Encryption\Security; use Phalcon\Api\Domain\Components\Encryption\TokenManagerInterface; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Components\Payload; /** * @phpstan-import-type TAuthLoginInput from InputTypes diff --git a/src/Domain/Components/DataSource/User/UserFacade.php b/src/Domain/Components/DataSource/User/UserFacade.php index 03af5c3..b372a11 100644 --- a/src/Domain/Components/DataSource/User/UserFacade.php +++ b/src/Domain/Components/DataSource/User/UserFacade.php @@ -15,13 +15,13 @@ use PDOException; use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\ADR\Payload; use Phalcon\Api\Domain\Components\Constants\Dates; use Phalcon\Api\Domain\Components\DataSource\Interfaces\MapperInterface; use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; use Phalcon\Api\Domain\Components\Encryption\Security; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Components\Payload; use function array_filter; diff --git a/src/Domain/Components/Encryption/TokenCache.php b/src/Domain/Components/Encryption/TokenCache.php index b384f14..cef096e 100644 --- a/src/Domain/Components/Encryption/TokenCache.php +++ b/src/Domain/Components/Encryption/TokenCache.php @@ -59,7 +59,12 @@ public function invalidateForUser( * keys will come back with the prefix defined in the adapter. In order * to delete them, we need to remove the prefix because `delete()` will * automatically prepend each key with it. + * + * NOTE: This code will work with other adapters also, since + * `getKeys()` returns the keys of the storage adapter. This method + * exists in the Cache/Storage AdapterInterface */ + /** @var Redis $redis */ $redis = $this->cache->getAdapter(); $pattern = CacheConstants::getCacheTokenKey($domainUser, ''); @@ -69,7 +74,7 @@ public function invalidateForUser( $newKeys = []; /** @var string $key */ foreach ($keys as $key) { - $newKeys[] = str_replace($prefix, '', $key); + $newKeys[] = substr($key, strlen($prefix)); } return $this->cache->deleteMultiple($newKeys); diff --git a/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php b/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php index cd6e782..780d22c 100644 --- a/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php +++ b/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php @@ -18,10 +18,10 @@ use Phalcon\Api\Domain\Components\DataSource\User\User; use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Components\Env\EnvManager; -use Phalcon\Cache\Cache; use Phalcon\Http\RequestInterface; use Phalcon\Mvc\Micro; use Phalcon\Support\Registry; +use Psr\SimpleCache\CacheInterface; final class ValidateTokenRevokedMiddleware extends AbstractMiddleware { @@ -34,7 +34,7 @@ public function call(Micro $application): bool { /** @var RequestInterface $request */ $request = $application->getSharedService(Container::REQUEST); - /** @var Cache $cache */ + /** @var CacheInterface $cache */ $cache = $application->getSharedService(Container::CACHE); /** @var EnvManager $env */ $env = $application->getSharedService(Container::ENV); diff --git a/src/Domain/Services/Auth/LoginPostService.php b/src/Domain/Services/Auth/LoginPostService.php index 50749a3..34b29ec 100644 --- a/src/Domain/Services/Auth/LoginPostService.php +++ b/src/Domain/Services/Auth/LoginPostService.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Domain\Services\Auth; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Payload; +use Phalcon\Api\Domain\ADR\Payload; /** * @phpstan-import-type TAuthLoginInput from InputTypes diff --git a/src/Domain/Services/Auth/LogoutPostService.php b/src/Domain/Services/Auth/LogoutPostService.php index 90ad2c1..2ac289b 100644 --- a/src/Domain/Services/Auth/LogoutPostService.php +++ b/src/Domain/Services/Auth/LogoutPostService.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Domain\Services\Auth; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Payload; +use Phalcon\Api\Domain\ADR\Payload; /** * @phpstan-import-type TAuthLogoutInput from InputTypes diff --git a/src/Domain/Services/Auth/RefreshPostService.php b/src/Domain/Services/Auth/RefreshPostService.php index d30a53f..7d50910 100644 --- a/src/Domain/Services/Auth/RefreshPostService.php +++ b/src/Domain/Services/Auth/RefreshPostService.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Domain\Services\Auth; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Payload; +use Phalcon\Api\Domain\ADR\Payload; /** * @phpstan-import-type TAuthRefreshInput from InputTypes diff --git a/src/Domain/Services/User/UserDeleteService.php b/src/Domain/Services/User/UserDeleteService.php index 27e7f00..4ed07d0 100644 --- a/src/Domain/Services/User/UserDeleteService.php +++ b/src/Domain/Services/User/UserDeleteService.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Domain\Services\User; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Payload; +use Phalcon\Api\Domain\ADR\Payload; /** * @phpstan-import-type TUserInput from InputTypes diff --git a/src/Domain/Services/User/UserGetService.php b/src/Domain/Services/User/UserGetService.php index 6fe3716..433ea29 100644 --- a/src/Domain/Services/User/UserGetService.php +++ b/src/Domain/Services/User/UserGetService.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Domain\Services\User; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Payload; +use Phalcon\Api\Domain\ADR\Payload; /** * @phpstan-import-type TUserInput from InputTypes diff --git a/src/Domain/Services/User/UserPostService.php b/src/Domain/Services/User/UserPostService.php index ea77145..91928c0 100644 --- a/src/Domain/Services/User/UserPostService.php +++ b/src/Domain/Services/User/UserPostService.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Domain\Services\User; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Payload; +use Phalcon\Api\Domain\ADR\Payload; /** * @phpstan-import-type TUserInput from InputTypes diff --git a/src/Domain/Services/User/UserPutService.php b/src/Domain/Services/User/UserPutService.php index d1a5691..cb74dd7 100644 --- a/src/Domain/Services/User/UserPutService.php +++ b/src/Domain/Services/User/UserPutService.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Domain\Services\User; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Payload; +use Phalcon\Api\Domain\ADR\Payload; /** * @phpstan-import-type TUserInput from InputTypes diff --git a/tests/Fixtures/Domain/Services/ServiceFixture.php b/tests/Fixtures/Domain/Services/ServiceFixture.php index fc9dee9..c47933b 100644 --- a/tests/Fixtures/Domain/Services/ServiceFixture.php +++ b/tests/Fixtures/Domain/Services/ServiceFixture.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Tests\Fixtures\Domain\Services; use Phalcon\Api\Domain\ADR\DomainInterface; -use Phalcon\Api\Domain\Components\Payload; +use Phalcon\Api\Domain\ADR\Payload; final readonly class ServiceFixture implements DomainInterface { From 2b92021ea7628ce7309b4ad8c769067db5697029 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sun, 9 Nov 2025 07:39:31 -0600 Subject: [PATCH 57/74] [#.x] - phpcs and refactoring the container --- src/Domain/Components/Container.php | 449 ++---------------- .../DataSource/Validation/AbsInt.php | 4 - .../Enums/Container/AuthDefinitionsEnum.php | 137 ++++++ .../Enums/Container/CommonDefinitionsEnum.php | 84 ++++ .../Enums/Container/UserDefinitionsEnum.php | 173 +++++++ 5 files changed, 438 insertions(+), 409 deletions(-) create mode 100644 src/Domain/Components/Enums/Container/AuthDefinitionsEnum.php create mode 100644 src/Domain/Components/Enums/Container/CommonDefinitionsEnum.php create mode 100644 src/Domain/Components/Enums/Container/UserDefinitionsEnum.php diff --git a/src/Domain/Components/Container.php b/src/Domain/Components/Container.php index 12a16ad..3461fe8 100644 --- a/src/Domain/Components/Container.php +++ b/src/Domain/Components/Container.php @@ -14,20 +14,11 @@ namespace Phalcon\Api\Domain\Components; use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthFacade; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthLoginValidator; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthSanitizer; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthTokenValidator; -use Phalcon\Api\Domain\Components\DataSource\User\UserFacade; use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; -use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; -use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; -use Phalcon\Api\Domain\Components\DataSource\User\UserValidatorUpdate; -use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Encryption\Security; -use Phalcon\Api\Domain\Components\Encryption\TokenCache; -use Phalcon\Api\Domain\Components\Encryption\TokenManager; +use Phalcon\Api\Domain\Components\Enums\Container\AuthDefinitionsEnum; +use Phalcon\Api\Domain\Components\Enums\Container\CommonDefinitionsEnum; +use Phalcon\Api\Domain\Components\Enums\Container\UserDefinitionsEnum; use Phalcon\Api\Domain\Components\Env\EnvManager; use Phalcon\Api\Domain\Components\Middleware\HealthMiddleware; use Phalcon\Api\Domain\Components\Middleware\NotFoundMiddleware; @@ -36,13 +27,6 @@ use Phalcon\Api\Domain\Components\Middleware\ValidateTokenRevokedMiddleware; use Phalcon\Api\Domain\Components\Middleware\ValidateTokenStructureMiddleware; use Phalcon\Api\Domain\Components\Middleware\ValidateTokenUserMiddleware; -use Phalcon\Api\Domain\Services\Auth\LoginPostService; -use Phalcon\Api\Domain\Services\Auth\LogoutPostService; -use Phalcon\Api\Domain\Services\Auth\RefreshPostService; -use Phalcon\Api\Domain\Services\User\UserDeleteService; -use Phalcon\Api\Domain\Services\User\UserGetService; -use Phalcon\Api\Domain\Services\User\UserPostService; -use Phalcon\Api\Domain\Services\User\UserPutService; use Phalcon\Api\Responder\JsonResponder; use Phalcon\Cache\AdapterFactory; use Phalcon\Cache\Cache; @@ -56,10 +40,27 @@ use Phalcon\Http\Response; use Phalcon\Logger\Adapter\Stream; use Phalcon\Logger\Logger; -use Phalcon\Mvc\Router; use Phalcon\Storage\SerializerFactory; use Phalcon\Support\Registry; +use function sprintf; + +/** + * @phpstan-type TServiceService array{ + * type: string, + * name: string + * } + * + * @phpstan-type TServiceParameter array{ + * type: string, + * value: mixed + * } + * + * @phpstan-type TService array{ + * className: string, + * arguments: array + * } + */ class Container extends Di { /** @var string */ @@ -152,89 +153,44 @@ public function __construct() $this->services = [ self::CACHE => $this->getServiceCache(), self::CONNECTION => $this->getServiceConnection(), - self::ENV => $this->getServiceEnv(), + self::ENV => new Service(EnvManager::class, true), self::EVENTS_MANAGER => $this->getServiceEventsManger(), self::FILTER => $this->getServiceFilter(), - self::JWT_TOKEN => $this->getServiceJWTToken(), - self::JWT_TOKEN_CACHE => $this->getServiceJWTTokenCache(), - self::JWT_TOKEN_MANAGER => $this->getServiceJWTTokenManager(), + self::JWT_TOKEN => new Service(CommonDefinitionsEnum::JWTToken->definition(), true), + self::JWT_TOKEN_CACHE => new Service(CommonDefinitionsEnum::JWTTokenCache->definition(), true), + self::JWT_TOKEN_MANAGER => new Service(CommonDefinitionsEnum::JWTTokenManager->definition(), true), self::LOGGER => $this->getServiceLogger(), self::REGISTRY => new Service(Registry::class, true), self::REQUEST => new Service(Request::class, true), self::RESPONSE => new Service(Response::class, true), - self::ROUTER => $this->getServiceRouter(), + self::ROUTER => new Service(CommonDefinitionsEnum::Router->definition(), true), + + self::AUTH_FACADE => new Service(AuthDefinitionsEnum::AuthFacade->definition()), + self::USER_FACADE => new Service(UserDefinitionsEnum::UserFacade->definition()), + self::USER_FACADE_UPDATE => new Service(UserDefinitionsEnum::UserFacadeUpdate->definition()), - self::AUTH_FACADE => $this->getServiceFacadeAuth(), - self::USER_FACADE => $this->getServiceFacadeUser(), - self::USER_FACADE_UPDATE => $this->getServiceFacadeUserUpdate(), - self::USER_REPOSITORY => $this->getServiceRepositoryUser(), + self::USER_REPOSITORY => new Service(UserDefinitionsEnum::UserRepository->definition()), - self::AUTH_SANITIZER => $this->getServiceSanitizer(AuthSanitizer::class), - self::USER_SANITIZER => $this->getServiceSanitizer(UserSanitizer::class), + self::AUTH_SANITIZER => new Service(AuthDefinitionsEnum::AuthSanitizer->definition()), + self::USER_SANITIZER => new Service(UserDefinitionsEnum::UserSanitizer->definition()), - self::AUTH_LOGIN_VALIDATOR => $this->getServiceValidator(AuthLoginValidator::class), - self::AUTH_TOKEN_VALIDATOR => $this->getServiceValidatorAuthToken(), - self::USER_VALIDATOR => $this->getServiceValidator(UserValidator::class), - self::USER_VALIDATOR_UPDATE => $this->getServiceValidator(UserValidatorUpdate::class), + self::AUTH_LOGIN_VALIDATOR => new Service(AuthDefinitionsEnum::AuthLoginValidator->definition()), + self::AUTH_TOKEN_VALIDATOR => new Service(AuthDefinitionsEnum::AuthTokenValidator->definition()), + self::USER_VALIDATOR => new Service(UserDefinitionsEnum::UserValidator->definition()), + self::USER_VALIDATOR_UPDATE => new Service(UserDefinitionsEnum::UserValidatorUpdate->definition()), - self::AUTH_LOGIN_POST_SERVICE => $this->getServiceAuthLoginPost(), - self::AUTH_LOGOUT_POST_SERVICE => $this->getServiceAuthTokenPost(LogoutPostService::class), - self::AUTH_REFRESH_POST_SERVICE => $this->getServiceAuthTokenPost(RefreshPostService::class), - self::USER_DELETE_SERVICE => $this->getServiceUser(UserDeleteService::class), - self::USER_GET_SERVICE => $this->getServiceUser(UserGetService::class), - self::USER_POST_SERVICE => $this->getServiceUser(UserPostService::class), - self::USER_PUT_SERVICE => $this->getServiceUserUpdate(), + self::AUTH_LOGIN_POST_SERVICE => new Service(AuthDefinitionsEnum::AuthLoginPost->definition()), + self::AUTH_LOGOUT_POST_SERVICE => new Service(AuthDefinitionsEnum::AuthLogoutPost->definition()), + self::AUTH_REFRESH_POST_SERVICE => new Service(AuthDefinitionsEnum::AuthRefreshPost->definition()), + self::USER_DELETE_SERVICE => new Service(UserDefinitionsEnum::UserDelete->definition()), + self::USER_GET_SERVICE => new Service(UserDefinitionsEnum::UserGet->definition()), + self::USER_POST_SERVICE => new Service(UserDefinitionsEnum::UserPost->definition()), + self::USER_PUT_SERVICE => new Service(UserDefinitionsEnum::UserPut->definition()), ]; parent::__construct(); } - /** - * @return Service - */ - private function getServiceAuthLoginPost(): Service - { - return new Service( - [ - 'className' => LoginPostService::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::AUTH_FACADE, - ], - [ - 'type' => 'service', - 'name' => self::AUTH_LOGIN_VALIDATOR, - ], - ], - ] - ); - } - - /** - * @param class-string $className - * - * @return Service - */ - private function getServiceAuthTokenPost(string $className): Service - { - return new Service( - [ - 'className' => $className, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::AUTH_FACADE, - ], - [ - 'type' => 'service', - 'name' => self::AUTH_TOKEN_VALIDATOR, - ], - ], - ] - ); - } - /** * @return Service */ @@ -321,19 +277,6 @@ function () { ); } - /** - * @return Service - */ - private function getServiceEnv(): Service - { - return new Service( - function () { - return new EnvManager(); - }, - true - ); - } - /** * @return Service */ @@ -350,104 +293,6 @@ function () { ); } - /** - * @return Service - */ - private function getServiceFacadeAuth(): Service - { - return new Service( - [ - 'className' => AuthFacade::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::USER_REPOSITORY, - ], - [ - 'type' => 'service', - 'name' => self::AUTH_SANITIZER, - ], - [ - 'type' => 'service', - 'name' => self::JWT_TOKEN_MANAGER, - ], - [ - 'type' => 'service', - 'name' => self::SECURITY, - ], - ], - ] - ); - } - - /** - * @return Service - */ - private function getServiceFacadeUser(): Service - { - return new Service( - [ - 'className' => UserFacade::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::USER_SANITIZER, - ], - [ - 'type' => 'service', - 'name' => self::USER_VALIDATOR, - ], - [ - 'type' => 'service', - 'name' => self::USER_MAPPER, - ], - [ - 'type' => 'service', - 'name' => self::USER_REPOSITORY, - ], - [ - 'type' => 'service', - 'name' => self::SECURITY, - ], - ], - ] - ); - } - - /** - * @return Service - */ - private function getServiceFacadeUserUpdate(): Service - { - return new Service( - [ - 'className' => UserFacade::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::USER_SANITIZER, - ], - [ - 'type' => 'service', - 'name' => self::USER_VALIDATOR_UPDATE, - ], - [ - 'type' => 'service', - 'name' => self::USER_MAPPER, - ], - [ - 'type' => 'service', - 'name' => self::USER_REPOSITORY, - ], - [ - 'type' => 'service', - 'name' => self::SECURITY, - ], - ], - ] - ); - } - /** * @return Service */ @@ -461,68 +306,6 @@ function () { ); } - /** - * @return Service - */ - private function getServiceJWTToken(): Service - { - return new Service( - [ - 'className' => JWTToken::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::ENV, - ], - ], - ] - ); - } - - /** - * @return Service - */ - private function getServiceJWTTokenCache(): Service - { - return new Service( - [ - 'className' => TokenCache::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::CACHE, - ], - ], - ] - ); - } - - /** - * @return Service - */ - private function getServiceJWTTokenManager(): Service - { - return new Service( - [ - 'className' => TokenManager::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::JWT_TOKEN_CACHE, - ], - [ - 'type' => 'service', - 'name' => self::ENV, - ], - [ - 'type' => 'service', - 'name' => self::JWT_TOKEN, - ], - ], - ] - ); - } - /** * @return Service */ @@ -548,148 +331,4 @@ function () { } ); } - - /** - * @return Service - */ - private function getServiceRepositoryUser(): Service - { - return new Service( - [ - 'className' => UserRepository::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::CONNECTION, - ], - [ - 'type' => 'service', - 'name' => self::USER_MAPPER, - ], - ], - ] - ); - } - - /** - * @return Service - */ - private function getServiceRouter(): Service - { - return new Service( - [ - 'className' => Router::class, - 'arguments' => [ - [ - 'type' => 'parameter', - 'value' => false, - ], - ], - ] - ); - } - - /** - * @param class-string $className - * - * @return Service - */ - private function getServiceSanitizer(string $className): Service - { - return new Service( - [ - 'className' => $className, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::FILTER, - ], - ], - ] - ); - } - - /** - * @param class-string $className - * - * @return Service - */ - private function getServiceUser(string $className): Service - { - return new Service( - [ - 'className' => $className, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::USER_FACADE, - ], - ], - ] - ); - } - - /** - * @return Service - */ - private function getServiceUserUpdate(): Service - { - return new Service( - [ - 'className' => UserPutService::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::USER_FACADE_UPDATE, - ], - ], - ] - ); - } - - /** - * @param class-string $className - * - * @return Service - */ - private function getServiceValidator(string $className): Service - { - return new Service( - [ - 'className' => $className, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::VALIDATION, - ], - ], - ] - ); - } - - /** - * @return Service - */ - private function getServiceValidatorAuthToken(): Service - { - return new Service( - [ - 'className' => AuthTokenValidator::class, - 'arguments' => [ - [ - 'type' => 'service', - 'name' => self::JWT_TOKEN_MANAGER, - ], - [ - 'type' => 'service', - 'name' => self::USER_REPOSITORY, - ], - [ - 'type' => 'service', - 'name' => self::VALIDATION, - ], - ], - ] - ); - } } diff --git a/src/Domain/Components/DataSource/Validation/AbsInt.php b/src/Domain/Components/DataSource/Validation/AbsInt.php index 4bfbb1a..6575b70 100644 --- a/src/Domain/Components/DataSource/Validation/AbsInt.php +++ b/src/Domain/Components/DataSource/Validation/AbsInt.php @@ -14,12 +14,8 @@ namespace Phalcon\Api\Domain\Components\DataSource\Validation; use Phalcon\Filter\Validation; -use Phalcon\Filter\Validation\AbstractValidator; - use Phalcon\Filter\Validation\Validator\Numericality; -use function preg_match; - class AbsInt extends Numericality { /** diff --git a/src/Domain/Components/Enums/Container/AuthDefinitionsEnum.php b/src/Domain/Components/Enums/Container/AuthDefinitionsEnum.php new file mode 100644 index 0000000..c87e8b9 --- /dev/null +++ b/src/Domain/Components/Enums/Container/AuthDefinitionsEnum.php @@ -0,0 +1,137 @@ + + * + * 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\Components\Enums\Container; + +use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthFacade; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthLoginValidator; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthSanitizer; +use Phalcon\Api\Domain\Components\DataSource\Auth\AuthTokenValidator; +use Phalcon\Api\Domain\Services\Auth\LoginPostService; +use Phalcon\Api\Domain\Services\Auth\LogoutPostService; +use Phalcon\Api\Domain\Services\Auth\RefreshPostService; + +/** + * @phpstan-import-type TService from Container + */ +enum AuthDefinitionsEnum +{ + case AuthLoginPost; + case AuthLogoutPost; + case AuthRefreshPost; + case AuthFacade; + case AuthSanitizer; + case AuthLoginValidator; + case AuthTokenValidator; + + /** + * @return TService + */ + public function definition(): array + { + return match ($this) { + self::AuthLoginPost => [ + 'className' => LoginPostService::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::AUTH_FACADE, + ], + [ + 'type' => 'service', + 'name' => Container::AUTH_LOGIN_VALIDATOR, + ], + ], + ], + self::AuthLogoutPost => $this->getService(LogoutPostService::class), + self::AuthRefreshPost => $this->getService(RefreshPostService::class), + self::AuthFacade => [ + 'className' => AuthFacade::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::USER_REPOSITORY, + ], + [ + 'type' => 'service', + 'name' => Container::AUTH_SANITIZER, + ], + [ + 'type' => 'service', + 'name' => Container::JWT_TOKEN_MANAGER, + ], + [ + 'type' => 'service', + 'name' => Container::SECURITY, + ], + ], + ], + self::AuthSanitizer => [ + 'className' => AuthSanitizer::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::FILTER, + ], + ], + ], + self::AuthLoginValidator => [ + 'className' => AuthLoginValidator::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::VALIDATION, + ], + ], + ], + self::AuthTokenValidator => [ + 'className' => AuthTokenValidator::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::JWT_TOKEN_MANAGER, + ], + [ + 'type' => 'service', + 'name' => Container::USER_REPOSITORY, + ], + [ + 'type' => 'service', + 'name' => Container::VALIDATION, + ], + ], + ], + }; + } + + /** + * @return TService + */ + private function getService(string $className): array + { + return [ + 'className' => $className, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::AUTH_FACADE, + ], + [ + 'type' => 'service', + 'name' => Container::AUTH_TOKEN_VALIDATOR, + ], + ], + ]; + } +} diff --git a/src/Domain/Components/Enums/Container/CommonDefinitionsEnum.php b/src/Domain/Components/Enums/Container/CommonDefinitionsEnum.php new file mode 100644 index 0000000..6d09463 --- /dev/null +++ b/src/Domain/Components/Enums/Container/CommonDefinitionsEnum.php @@ -0,0 +1,84 @@ + + * + * 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\Components\Enums\Container; + +use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Components\Encryption\JWTToken; +use Phalcon\Api\Domain\Components\Encryption\TokenCache; +use Phalcon\Api\Domain\Components\Encryption\TokenManager; +use Phalcon\Mvc\Router; + +/** + * @phpstan-import-type TService from Container + */ +enum CommonDefinitionsEnum +{ + case JWTToken; + case JWTTokenCache; + case JWTTokenManager; + case Router; + + /** + * @return TService + */ + public function definition(): array + { + return match ($this) { + self::JWTToken => [ + 'className' => JWTToken::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::ENV, + ], + ], + ], + self::JWTTokenCache => [ + 'className' => TokenCache::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::CACHE, + ], + ], + ], + self::JWTTokenManager => [ + 'className' => TokenManager::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::JWT_TOKEN_CACHE, + ], + [ + 'type' => 'service', + 'name' => Container::ENV, + ], + [ + 'type' => 'service', + 'name' => Container::JWT_TOKEN, + ], + ], + ], + self::Router => [ + 'className' => Router::class, + 'arguments' => [ + [ + 'type' => 'parameter', + 'value' => false, + ], + ], + ] + }; + } +} diff --git a/src/Domain/Components/Enums/Container/UserDefinitionsEnum.php b/src/Domain/Components/Enums/Container/UserDefinitionsEnum.php new file mode 100644 index 0000000..36c950f --- /dev/null +++ b/src/Domain/Components/Enums/Container/UserDefinitionsEnum.php @@ -0,0 +1,173 @@ + + * + * 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\Components\Enums\Container; + +use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Components\DataSource\User\UserFacade; +use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; +use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; +use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; +use Phalcon\Api\Domain\Components\DataSource\User\UserValidatorUpdate; +use Phalcon\Api\Domain\Services\User\UserDeleteService; +use Phalcon\Api\Domain\Services\User\UserGetService; +use Phalcon\Api\Domain\Services\User\UserPostService; +use Phalcon\Api\Domain\Services\User\UserPutService; + +/** + * @phpstan-import-type TService from Container + */ +enum UserDefinitionsEnum +{ + case UserDelete; + case UserGet; + case UserPost; + case UserPut; + case UserFacade; + case UserFacadeUpdate; + case UserRepository; + case UserSanitizer; + case UserValidator; + case UserValidatorUpdate; + + /** + * @return TService + */ + public function definition(): array + { + return match ($this) { + self::UserDelete => $this->getService(UserDeleteService::class), + self::UserGet => $this->getService(UserGetService::class), + self::UserPost => $this->getService(UserPostService::class), + self::UserPut => [ + 'className' => UserPutService::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::USER_FACADE_UPDATE, + ], + ], + ], + self::UserFacade => [ + 'className' => UserFacade::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::USER_SANITIZER, + ], + [ + 'type' => 'service', + 'name' => Container::USER_VALIDATOR, + ], + [ + 'type' => 'service', + 'name' => Container::USER_MAPPER, + ], + [ + 'type' => 'service', + 'name' => Container::USER_REPOSITORY, + ], + [ + 'type' => 'service', + 'name' => Container::SECURITY, + ], + ], + ], + self::UserFacadeUpdate => [ + 'className' => UserFacade::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::USER_SANITIZER, + ], + [ + 'type' => 'service', + 'name' => Container::USER_VALIDATOR_UPDATE, + ], + [ + 'type' => 'service', + 'name' => Container::USER_MAPPER, + ], + [ + 'type' => 'service', + 'name' => Container::USER_REPOSITORY, + ], + [ + 'type' => 'service', + 'name' => Container::SECURITY, + ], + ], + ], + self::UserRepository => [ + 'className' => UserRepository::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::CONNECTION, + ], + [ + 'type' => 'service', + 'name' => Container::USER_MAPPER, + ], + ], + ], + self::UserSanitizer => [ + 'className' => UserSanitizer::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::FILTER, + ], + ], + ], + self::UserValidator => $this->getServiceValidator(UserValidator::class), + self::UserValidatorUpdate => $this->getServiceValidator(UserValidatorUpdate::class), + }; + } + + /** + * @param class-string $className + * + * @return TService + */ + private function getService(string $className): array + { + return [ + 'className' => $className, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::USER_FACADE, + ], + ], + ]; + } + + /** + * @param class-string $className + * + * @return TService + */ + private function getServiceValidator(string $className): array + { + return [ + 'className' => $className, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => Container::VALIDATION, + ], + ], + ]; + } +} From 94ae56b09081e885f4d756cf571fddd973a9bc29 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sun, 9 Nov 2025 15:13:20 -0600 Subject: [PATCH 58/74] [#.x] - added evmanager to definitions and phpstan --- src/Domain/Components/Container.php | 55 +++++++++++-------- .../Enums/Container/CommonDefinitionsEnum.php | 16 ++++++ 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/Domain/Components/Container.php b/src/Domain/Components/Container.php index 3461fe8..34a6f94 100644 --- a/src/Domain/Components/Container.php +++ b/src/Domain/Components/Container.php @@ -33,7 +33,6 @@ use Phalcon\DataMapper\Pdo\Connection; use Phalcon\Di\Di; use Phalcon\Di\Service; -use Phalcon\Events\Manager as EventsManager; use Phalcon\Filter\FilterFactory; use Phalcon\Filter\Validation; use Phalcon\Http\Request; @@ -46,19 +45,25 @@ use function sprintf; /** - * @phpstan-type TServiceService array{ - * type: string, - * name: string - * } * * @phpstan-type TServiceParameter array{ - * type: string, + * type: 'parameter', * value: mixed * } + * @phpstan-type TServiceService array{ + * type: 'service', + * name: string + * } + * @phpstan-type TServiceArguments array + * @phpstan-type TServiceCall array{ + * method: string, + * arguments: TServiceArguments + * } * * @phpstan-type TService array{ * className: string, - * arguments: array + * arguments?: TServiceArguments, + * calls?: array * } */ class Container extends Di @@ -151,10 +156,13 @@ class Container extends Di public function __construct() { $this->services = [ + /** + * Base services + */ self::CACHE => $this->getServiceCache(), self::CONNECTION => $this->getServiceConnection(), self::ENV => new Service(EnvManager::class, true), - self::EVENTS_MANAGER => $this->getServiceEventsManger(), + self::EVENTS_MANAGER => new Service(CommonDefinitionsEnum::EventsManager->definition(), true), self::FILTER => $this->getServiceFilter(), self::JWT_TOKEN => new Service(CommonDefinitionsEnum::JWTToken->definition(), true), self::JWT_TOKEN_CACHE => new Service(CommonDefinitionsEnum::JWTTokenCache->definition(), true), @@ -165,20 +173,35 @@ public function __construct() self::RESPONSE => new Service(Response::class, true), self::ROUTER => new Service(CommonDefinitionsEnum::Router->definition(), true), + /** + * Facades + */ self::AUTH_FACADE => new Service(AuthDefinitionsEnum::AuthFacade->definition()), self::USER_FACADE => new Service(UserDefinitionsEnum::UserFacade->definition()), self::USER_FACADE_UPDATE => new Service(UserDefinitionsEnum::UserFacadeUpdate->definition()), + /** + * Repositories + */ self::USER_REPOSITORY => new Service(UserDefinitionsEnum::UserRepository->definition()), + /** + * Sanitizers + */ self::AUTH_SANITIZER => new Service(AuthDefinitionsEnum::AuthSanitizer->definition()), self::USER_SANITIZER => new Service(UserDefinitionsEnum::UserSanitizer->definition()), + /** + * Validators + */ self::AUTH_LOGIN_VALIDATOR => new Service(AuthDefinitionsEnum::AuthLoginValidator->definition()), self::AUTH_TOKEN_VALIDATOR => new Service(AuthDefinitionsEnum::AuthTokenValidator->definition()), self::USER_VALIDATOR => new Service(UserDefinitionsEnum::UserValidator->definition()), self::USER_VALIDATOR_UPDATE => new Service(UserDefinitionsEnum::UserValidatorUpdate->definition()), + /** + * Services + */ self::AUTH_LOGIN_POST_SERVICE => new Service(AuthDefinitionsEnum::AuthLoginPost->definition()), self::AUTH_LOGOUT_POST_SERVICE => new Service(AuthDefinitionsEnum::AuthLogoutPost->definition()), self::AUTH_REFRESH_POST_SERVICE => new Service(AuthDefinitionsEnum::AuthRefreshPost->definition()), @@ -277,22 +300,6 @@ function () { ); } - /** - * @return Service - */ - private function getServiceEventsManger(): Service - { - return new Service( - function () { - $evm = new EventsManager(); - $evm->enablePriorities(true); - - return $evm; - }, - true - ); - } - /** * @return Service */ diff --git a/src/Domain/Components/Enums/Container/CommonDefinitionsEnum.php b/src/Domain/Components/Enums/Container/CommonDefinitionsEnum.php index 6d09463..b022647 100644 --- a/src/Domain/Components/Enums/Container/CommonDefinitionsEnum.php +++ b/src/Domain/Components/Enums/Container/CommonDefinitionsEnum.php @@ -17,6 +17,7 @@ use Phalcon\Api\Domain\Components\Encryption\JWTToken; use Phalcon\Api\Domain\Components\Encryption\TokenCache; use Phalcon\Api\Domain\Components\Encryption\TokenManager; +use Phalcon\Events\Manager as EventsManager; use Phalcon\Mvc\Router; /** @@ -24,6 +25,7 @@ */ enum CommonDefinitionsEnum { + case EventsManager; case JWTToken; case JWTTokenCache; case JWTTokenManager; @@ -35,6 +37,20 @@ enum CommonDefinitionsEnum public function definition(): array { return match ($this) { + self::EventsManager => [ + 'className' => EventsManager::class, + 'calls' => [ + [ + 'method' => 'enablePriorities', + 'arguments' => [ + [ + 'type' => 'parameter', + 'value' => true, + ] + ] + ] + ] + ], self::JWTToken => [ 'className' => JWTToken::class, 'arguments' => [ From 721590a56f1d96e56f6e2b0d1bb3904c27939e2c Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 11 Nov 2025 11:06:36 -0600 Subject: [PATCH 59/74] [#.x] - renamed folders, moved files to logical positions --- .../Constants/Cache.php | 4 +-- .../Constants/Dates.php | 2 +- .../Container.php | 30 +++++++++---------- .../DataSource/AbstractRepository.php | 4 +-- .../DataSource/AbstractValueObject.php} | 8 ++--- .../DataSource/Auth/DTO}/AuthInput.php | 6 ++-- .../DataSource/Auth/Facades}/AuthFacade.php | 17 ++++++----- .../Auth/Sanitizers}/AuthSanitizer.php | 4 +-- .../Auth/Validators}/AuthLoginValidator.php | 11 +++---- .../Auth/Validators}/AuthTokenValidator.php | 17 ++++++----- .../DataSource/Interfaces/MapperInterface.php | 8 ++--- .../Interfaces/SanitizerInterface.php | 2 +- .../DataSource/User/DTO}/User.php | 4 ++- .../DataSource/User/DTO}/UserInput.php | 9 ++++-- .../DataSource/User/Facades}/UserFacade.php | 18 ++++++----- .../DataSource/User/Mappers}/UserMapper.php | 7 +++-- .../User/Repositories}/UserRepository.php | 9 ++++-- .../Repositories}/UserRepositoryInterface.php | 5 +++- .../User/Sanitizers}/UserSanitizer.php | 4 +-- .../DataSource/User/UserTypes.php | 2 +- .../User/Validators}/UserValidator.php | 6 ++-- .../User/Validators}/UserValidatorTrait.php | 8 ++--- .../User/Validators}/UserValidatorUpdate.php | 6 ++-- .../DataSource/Validation/AbsInt.php | 3 +- .../Validation/AbstractValidator.php | 6 ++-- .../DataSource/Validation/Result.php | 2 +- .../Validation/ValidatorInterface.php | 2 +- .../Encryption/JWTToken.php | 16 +++++----- .../Encryption/Security.php | 2 +- .../Encryption/TokenCache.php | 12 ++++---- .../Encryption/TokenCacheInterface.php | 6 ++-- .../Encryption/TokenManager.php | 8 ++--- .../Encryption/TokenManagerInterface.php | 6 ++-- .../Enums/Common/FlagsEnum.php | 4 +-- .../Enums/Common/JWTEnum.php | 2 +- .../Enums/Container/AuthDefinitionsEnum.php | 12 ++++---- .../Enums/Container/CommonDefinitionsEnum.php | 10 +++---- .../Enums/Container/UserDefinitionsEnum.php | 14 ++++----- .../Enums/EnumsInterface.php | 2 +- .../Enums/Http/HttpCodesEnum.php | 4 +-- .../Enums/Http/RoutesEnum.php | 4 +-- .../Enums/Input/AuthLoginInputEnum.php | 2 +- .../Enums/Input/AuthTokenInputEnum.php | 2 +- .../Enums/Input/UserInputInsertEnum.php | 2 +- .../Enums/Input/UserInputUpdateEnum.php | 4 +-- .../Enums/Input/ValidatorEnumInterface.php | 2 +- .../Env/Adapters/AdapterInterface.php | 4 +-- .../Env/Adapters/DotEnv.php | 6 ++-- .../Env/EnvFactory.php | 8 ++--- .../Env/EnvManager.php | 4 +-- .../Env/EnvManagerTypes.php | 2 +- .../Exceptions/ExceptionTrait.php | 2 +- .../InvalidConfigurationArgumentException.php | 2 +- .../Exceptions/TokenValidationException.php | 2 +- .../Middleware/AbstractMiddleware.php | 6 ++-- .../Middleware/HealthMiddleware.php | 6 ++-- .../Middleware/NotFoundMiddleware.php | 4 +-- .../ValidateTokenClaimsMiddleware.php | 10 +++---- .../ValidateTokenPresenceMiddleware.php | 8 ++--- .../ValidateTokenRevokedMiddleware.php | 12 ++++---- .../ValidateTokenStructureMiddleware.php | 12 ++++---- .../ValidateTokenUserMiddleware.php | 10 +++---- .../Providers/ErrorHandlerProvider.php | 6 ++-- .../Providers/RouterProvider.php | 6 ++-- .../Services/Auth/AbstractAuthService.php | 4 +-- .../Services/User/AbstractUserService.php | 2 +- src/Responder/JsonResponder.php | 4 +-- .../ContainerTest.php | 0 .../DataSource/User/DTO}/UserInputTest.php | 8 ++--- .../DataSource/User/DTO}/UserTest.php | 4 +-- .../User/Mappers}/UserMapperTest.php | 8 ++--- .../User/Repositories}/UserRepositoryTest.php | 8 ++--- .../User/Sanitizers}/UserSanitizerTest.php | 6 ++-- .../User/Validators}/UserValidatorTest.php | 10 +++---- .../Encryption/JWTTokenTest.php | 0 .../Encryption/SecurityTest.php | 0 .../Enums/Common/FlagsEnumTest.php | 0 .../Enums/Http/HttpCodesEnumTest.php | 0 .../Enums/Http/RoutesEnumTest.php | 0 .../Env/Adapters/DotEnvTest.php | 0 .../Env/EnvFactoryTest.php | 0 .../Env/EnvManagerTest.php | 0 .../Middleware/HealthMiddlewareTest.php | 0 .../Middleware/NotFoundMiddlewareTest.php | 0 .../ValidateTokenClaimsMiddlewareTest.php | 0 .../ValidateTokenPresenceMiddlewareTest.php | 0 .../ValidateTokenRevokedMiddlewareTest.php | 0 .../ValidateTokenStructureMiddlewareTest.php | 0 .../ValidateTokenUserMiddlewareTest.php | 0 .../Providers/CacheDataProviderTest.php | 0 .../Providers/ErrorHandlerProviderTest.php | 0 .../Providers/RouterProviderTest.php | 0 92 files changed, 249 insertions(+), 233 deletions(-) rename src/Domain/{Components => Infrastructure}/Constants/Cache.php (91%) rename src/Domain/{Components => Infrastructure}/Constants/Dates.php (94%) rename src/Domain/{Components => Infrastructure}/Container.php (91%) rename src/Domain/{Components => Infrastructure}/DataSource/AbstractRepository.php (91%) rename src/Domain/{Components/DataSource/AbstractInput.php => Infrastructure/DataSource/AbstractValueObject.php} (83%) rename src/Domain/{Components/DataSource/Auth => Infrastructure/DataSource/Auth/DTO}/AuthInput.php (85%) rename src/Domain/{Components/DataSource/Auth => Infrastructure/DataSource/Auth/Facades}/AuthFacade.php (88%) rename src/Domain/{Components/DataSource/Auth => Infrastructure/DataSource/Auth/Sanitizers}/AuthSanitizer.php (92%) rename src/Domain/{Components/DataSource/Auth => Infrastructure/DataSource/Auth/Validators}/AuthLoginValidator.php (67%) rename src/Domain/{Components/DataSource/Auth => Infrastructure/DataSource/Auth/Validators}/AuthTokenValidator.php (78%) rename src/Domain/{Components => Infrastructure}/DataSource/Interfaces/MapperInterface.php (78%) rename src/Domain/{Components => Infrastructure}/DataSource/Interfaces/SanitizerInterface.php (89%) rename src/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/DTO}/User.php (92%) rename src/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/DTO}/UserInput.php (93%) rename src/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Facades}/UserFacade.php (91%) rename src/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Mappers}/UserMapper.php (90%) rename src/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Repositories}/UserRepository.php (87%) rename src/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Repositories}/UserRepositoryInterface.php (87%) rename src/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Sanitizers}/UserSanitizer.php (94%) rename src/Domain/{Components => Infrastructure}/DataSource/User/UserTypes.php (98%) rename src/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Validators}/UserValidator.php (64%) rename src/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Validators}/UserValidatorTrait.php (72%) rename src/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Validators}/UserValidatorUpdate.php (64%) rename src/Domain/{Components => Infrastructure}/DataSource/Validation/AbsInt.php (92%) rename src/Domain/{Components => Infrastructure}/DataSource/Validation/AbstractValidator.php (87%) rename src/Domain/{Components => Infrastructure}/DataSource/Validation/Result.php (96%) rename src/Domain/{Components => Infrastructure}/DataSource/Validation/ValidatorInterface.php (89%) rename src/Domain/{Components => Infrastructure}/Encryption/JWTToken.php (91%) rename src/Domain/{Components => Infrastructure}/Encryption/Security.php (95%) rename src/Domain/{Components => Infrastructure}/Encryption/TokenCache.php (91%) rename src/Domain/{Components => Infrastructure}/Encryption/TokenCacheInterface.php (83%) rename src/Domain/{Components => Infrastructure}/Encryption/TokenManager.php (92%) rename src/Domain/{Components => Infrastructure}/Encryption/TokenManagerInterface.php (89%) rename src/Domain/{Components => Infrastructure}/Enums/Common/FlagsEnum.php (82%) rename src/Domain/{Components => Infrastructure}/Enums/Common/JWTEnum.php (92%) rename src/Domain/{Components => Infrastructure}/Enums/Container/AuthDefinitionsEnum.php (90%) rename src/Domain/{Components => Infrastructure}/Enums/Container/CommonDefinitionsEnum.php (89%) rename src/Domain/{Components => Infrastructure}/Enums/Container/UserDefinitionsEnum.php (90%) rename src/Domain/{Components => Infrastructure}/Enums/EnumsInterface.php (89%) rename src/Domain/{Components => Infrastructure}/Enums/Http/HttpCodesEnum.php (97%) rename src/Domain/{Components => Infrastructure}/Enums/Http/RoutesEnum.php (97%) rename src/Domain/{Components => Infrastructure}/Enums/Input/AuthLoginInputEnum.php (92%) rename src/Domain/{Components => Infrastructure}/Enums/Input/AuthTokenInputEnum.php (89%) rename src/Domain/{Components => Infrastructure}/Enums/Input/UserInputInsertEnum.php (93%) rename src/Domain/{Components => Infrastructure}/Enums/Input/UserInputUpdateEnum.php (87%) rename src/Domain/{Components => Infrastructure}/Enums/Input/ValidatorEnumInterface.php (88%) rename src/Domain/{Components => Infrastructure}/Env/Adapters/AdapterInterface.php (82%) rename src/Domain/{Components => Infrastructure}/Env/Adapters/DotEnv.php (85%) rename src/Domain/{Components => Infrastructure}/Env/EnvFactory.php (82%) rename src/Domain/{Components => Infrastructure}/Env/EnvManager.php (96%) rename src/Domain/{Components => Infrastructure}/Env/EnvManagerTypes.php (90%) rename src/Domain/{Components => Infrastructure}/Exceptions/ExceptionTrait.php (89%) rename src/Domain/{Components => Infrastructure}/Exceptions/InvalidConfigurationArgumentException.php (87%) rename src/Domain/{Components => Infrastructure}/Exceptions/TokenValidationException.php (87%) rename src/Domain/{Components => Infrastructure}/Middleware/AbstractMiddleware.php (94%) rename src/Domain/{Components => Infrastructure}/Middleware/HealthMiddleware.php (87%) rename src/Domain/{Components => Infrastructure}/Middleware/NotFoundMiddleware.php (90%) rename src/Domain/{Components => Infrastructure}/Middleware/ValidateTokenClaimsMiddleware.php (87%) rename src/Domain/{Components => Infrastructure}/Middleware/ValidateTokenPresenceMiddleware.php (84%) rename src/Domain/{Components => Infrastructure}/Middleware/ValidateTokenRevokedMiddleware.php (82%) rename src/Domain/{Components => Infrastructure}/Middleware/ValidateTokenStructureMiddleware.php (83%) rename src/Domain/{Components => Infrastructure}/Middleware/ValidateTokenUserMiddleware.php (85%) rename src/Domain/{Components => Infrastructure}/Providers/ErrorHandlerProvider.php (94%) rename src/Domain/{Components => Infrastructure}/Providers/RouterProvider.php (95%) rename tests/Unit/Domain/{Components => Infrastructure}/ContainerTest.php (100%) rename tests/Unit/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/DTO}/UserInputTest.php (94%) rename tests/Unit/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/DTO}/UserTest.php (95%) rename tests/Unit/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Mappers}/UserMapperTest.php (96%) rename tests/Unit/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Repositories}/UserRepositoryTest.php (93%) rename tests/Unit/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Sanitizers}/UserSanitizerTest.php (94%) rename tests/Unit/Domain/{Components/DataSource/User => Infrastructure/DataSource/User/Validators}/UserValidatorTest.php (86%) rename tests/Unit/Domain/{Components => Infrastructure}/Encryption/JWTTokenTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Encryption/SecurityTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Enums/Common/FlagsEnumTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Enums/Http/HttpCodesEnumTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Enums/Http/RoutesEnumTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Env/Adapters/DotEnvTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Env/EnvFactoryTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Env/EnvManagerTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Middleware/HealthMiddlewareTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Middleware/NotFoundMiddlewareTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Middleware/ValidateTokenClaimsMiddlewareTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Middleware/ValidateTokenPresenceMiddlewareTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Middleware/ValidateTokenRevokedMiddlewareTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Middleware/ValidateTokenStructureMiddlewareTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Middleware/ValidateTokenUserMiddlewareTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Providers/CacheDataProviderTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Providers/ErrorHandlerProviderTest.php (100%) rename tests/Unit/Domain/{Components => Infrastructure}/Providers/RouterProviderTest.php (100%) diff --git a/src/Domain/Components/Constants/Cache.php b/src/Domain/Infrastructure/Constants/Cache.php similarity index 91% rename from src/Domain/Components/Constants/Cache.php rename to src/Domain/Infrastructure/Constants/Cache.php index be360a0..73f691c 100644 --- a/src/Domain/Components/Constants/Cache.php +++ b/src/Domain/Infrastructure/Constants/Cache.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Constants; +namespace Phalcon\Api\Domain\Infrastructure\Constants; -use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; use function sha1; diff --git a/src/Domain/Components/Constants/Dates.php b/src/Domain/Infrastructure/Constants/Dates.php similarity index 94% rename from src/Domain/Components/Constants/Dates.php rename to src/Domain/Infrastructure/Constants/Dates.php index c3f1f71..b80248a 100644 --- a/src/Domain/Components/Constants/Dates.php +++ b/src/Domain/Infrastructure/Constants/Dates.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Constants; +namespace Phalcon\Api\Domain\Infrastructure\Constants; use DateTimeImmutable; use DateTimeZone; diff --git a/src/Domain/Components/Container.php b/src/Domain/Infrastructure/Container.php similarity index 91% rename from src/Domain/Components/Container.php rename to src/Domain/Infrastructure/Container.php index 34a6f94..6c290d7 100644 --- a/src/Domain/Components/Container.php +++ b/src/Domain/Infrastructure/Container.php @@ -11,22 +11,22 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components; +namespace Phalcon\Api\Domain\Infrastructure; -use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\Encryption\Security; -use Phalcon\Api\Domain\Components\Enums\Container\AuthDefinitionsEnum; -use Phalcon\Api\Domain\Components\Enums\Container\CommonDefinitionsEnum; -use Phalcon\Api\Domain\Components\Enums\Container\UserDefinitionsEnum; -use Phalcon\Api\Domain\Components\Env\EnvManager; -use Phalcon\Api\Domain\Components\Middleware\HealthMiddleware; -use Phalcon\Api\Domain\Components\Middleware\NotFoundMiddleware; -use Phalcon\Api\Domain\Components\Middleware\ValidateTokenClaimsMiddleware; -use Phalcon\Api\Domain\Components\Middleware\ValidateTokenPresenceMiddleware; -use Phalcon\Api\Domain\Components\Middleware\ValidateTokenRevokedMiddleware; -use Phalcon\Api\Domain\Components\Middleware\ValidateTokenStructureMiddleware; -use Phalcon\Api\Domain\Components\Middleware\ValidateTokenUserMiddleware; +use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Encryption\Security; +use Phalcon\Api\Domain\Infrastructure\Enums\Container\AuthDefinitionsEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Container\CommonDefinitionsEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Container\UserDefinitionsEnum; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Middleware\HealthMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\NotFoundMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenClaimsMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenPresenceMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenRevokedMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenStructureMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\ValidateTokenUserMiddleware; use Phalcon\Api\Responder\JsonResponder; use Phalcon\Cache\AdapterFactory; use Phalcon\Cache\Cache; diff --git a/src/Domain/Components/DataSource/AbstractRepository.php b/src/Domain/Infrastructure/DataSource/AbstractRepository.php similarity index 91% rename from src/Domain/Components/DataSource/AbstractRepository.php rename to src/Domain/Infrastructure/DataSource/AbstractRepository.php index a5125f4..4ac90dc 100644 --- a/src/Domain/Components/DataSource/AbstractRepository.php +++ b/src/Domain/Infrastructure/DataSource/AbstractRepository.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource; +namespace Phalcon\Api\Domain\Infrastructure\DataSource; -use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; use Phalcon\DataMapper\Pdo\Connection; use Phalcon\DataMapper\Query\Delete; diff --git a/src/Domain/Components/DataSource/AbstractInput.php b/src/Domain/Infrastructure/DataSource/AbstractValueObject.php similarity index 83% rename from src/Domain/Components/DataSource/AbstractInput.php rename to src/Domain/Infrastructure/DataSource/AbstractValueObject.php index 16993b7..b3be385 100644 --- a/src/Domain/Components/DataSource/AbstractInput.php +++ b/src/Domain/Infrastructure/DataSource/AbstractValueObject.php @@ -11,13 +11,13 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource; +namespace Phalcon\Api\Domain\Infrastructure\DataSource; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; /** - * Base factory for input DTOs. + * Base factory for value objects such as input DTOs. * * Concrete DTOs must implement protected static function * @@ -27,7 +27,7 @@ * * @phpstan-import-type TInputSanitize from InputTypes */ -abstract class AbstractInput +abstract class AbstractValueObject { /** * Factory that accepts a SanitizerInterface and returns the concrete DTO. diff --git a/src/Domain/Components/DataSource/Auth/AuthInput.php b/src/Domain/Infrastructure/DataSource/Auth/DTO/AuthInput.php similarity index 85% rename from src/Domain/Components/DataSource/Auth/AuthInput.php rename to src/Domain/Infrastructure/DataSource/Auth/DTO/AuthInput.php index 3fe35d6..6194bd8 100644 --- a/src/Domain/Components/DataSource/Auth/AuthInput.php +++ b/src/Domain/Infrastructure/DataSource/Auth/DTO/AuthInput.php @@ -11,15 +11,15 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\Auth; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\AbstractInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\AbstractValueObject; /** * @phpstan-import-type TAuthInput from InputTypes */ -final class AuthInput extends AbstractInput +final class AuthInput extends AbstractValueObject { /** * @param string|null $email diff --git a/src/Domain/Components/DataSource/Auth/AuthFacade.php b/src/Domain/Infrastructure/DataSource/Auth/Facades/AuthFacade.php similarity index 88% rename from src/Domain/Components/DataSource/Auth/AuthFacade.php rename to src/Domain/Infrastructure/DataSource/Auth/Facades/AuthFacade.php index 35007b1..c4f24a8 100644 --- a/src/Domain/Components/DataSource/Auth/AuthFacade.php +++ b/src/Domain/Infrastructure/DataSource/Auth/Facades/AuthFacade.php @@ -11,17 +11,18 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\Auth; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Facades; use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\ADR\Payload; -use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; -use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; -use Phalcon\Api\Domain\Components\Encryption\Security; -use Phalcon\Api\Domain\Components\Encryption\TokenManagerInterface; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Infrastructure\Encryption\Security; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManagerInterface; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; /** * @phpstan-import-type TAuthLoginInput from InputTypes diff --git a/src/Domain/Components/DataSource/Auth/AuthSanitizer.php b/src/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizer.php similarity index 92% rename from src/Domain/Components/DataSource/Auth/AuthSanitizer.php rename to src/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizer.php index 3fa50de..8fde303 100644 --- a/src/Domain/Components/DataSource/Auth/AuthSanitizer.php +++ b/src/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizer.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\Auth; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; use Phalcon\Filter\Filter; use Phalcon\Filter\FilterInterface; diff --git a/src/Domain/Components/DataSource/Auth/AuthLoginValidator.php b/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidator.php similarity index 67% rename from src/Domain/Components/DataSource/Auth/AuthLoginValidator.php rename to src/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidator.php index d6bf761..45489cf 100644 --- a/src/Domain/Components/DataSource/Auth/AuthLoginValidator.php +++ b/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidator.php @@ -11,12 +11,13 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\Auth; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators; -use Phalcon\Api\Domain\Components\DataSource\Validation\AbstractValidator; -use Phalcon\Api\Domain\Components\DataSource\Validation\Result; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Components\Enums\Input\AuthLoginInputEnum; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbstractValidator; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\Result; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Input\AuthLoginInputEnum; final class AuthLoginValidator extends AbstractValidator { diff --git a/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php b/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidator.php similarity index 78% rename from src/Domain/Components/DataSource/Auth/AuthTokenValidator.php rename to src/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidator.php index 4425a35..ecd60f4 100644 --- a/src/Domain/Components/DataSource/Auth/AuthTokenValidator.php +++ b/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidator.php @@ -11,15 +11,16 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\Auth; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; -use Phalcon\Api\Domain\Components\DataSource\Validation\AbstractValidator; -use Phalcon\Api\Domain\Components\DataSource\Validation\Result; -use Phalcon\Api\Domain\Components\Encryption\TokenManagerInterface; -use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Components\Enums\Input\AuthTokenInputEnum; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbstractValidator; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\Result; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManagerInterface; +use Phalcon\Api\Domain\Infrastructure\Enums\Common\JWTEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Input\AuthTokenInputEnum; use Phalcon\Encryption\Security\JWT\Token\Token; use Phalcon\Filter\Validation\ValidationInterface; diff --git a/src/Domain/Components/DataSource/Interfaces/MapperInterface.php b/src/Domain/Infrastructure/DataSource/Interfaces/MapperInterface.php similarity index 78% rename from src/Domain/Components/DataSource/Interfaces/MapperInterface.php rename to src/Domain/Infrastructure/DataSource/Interfaces/MapperInterface.php index 9b1c7c7..4392806 100644 --- a/src/Domain/Components/DataSource/Interfaces/MapperInterface.php +++ b/src/Domain/Infrastructure/DataSource/Interfaces/MapperInterface.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\Interfaces; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\DataSource\User\UserInput; -use Phalcon\Api\Domain\Components\DataSource\User\UserTypes; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; /** * Contract for mapping between domain DTO/objects and persistence arrays. diff --git a/src/Domain/Components/DataSource/Interfaces/SanitizerInterface.php b/src/Domain/Infrastructure/DataSource/Interfaces/SanitizerInterface.php similarity index 89% rename from src/Domain/Components/DataSource/Interfaces/SanitizerInterface.php rename to src/Domain/Infrastructure/DataSource/Interfaces/SanitizerInterface.php index bf184b1..543430d 100644 --- a/src/Domain/Components/DataSource/Interfaces/SanitizerInterface.php +++ b/src/Domain/Infrastructure/DataSource/Interfaces/SanitizerInterface.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\Interfaces; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces; use Phalcon\Api\Domain\ADR\InputTypes; diff --git a/src/Domain/Components/DataSource/User/User.php b/src/Domain/Infrastructure/DataSource/User/DTO/User.php similarity index 92% rename from src/Domain/Components/DataSource/User/User.php rename to src/Domain/Infrastructure/DataSource/User/DTO/User.php index dd58248..ef3cb39 100644 --- a/src/Domain/Components/DataSource/User/User.php +++ b/src/Domain/Infrastructure/DataSource/User/DTO/User.php @@ -11,7 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\User; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO; + +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; use function get_object_vars; use function trim; diff --git a/src/Domain/Components/DataSource/User/UserInput.php b/src/Domain/Infrastructure/DataSource/User/DTO/UserInput.php similarity index 93% rename from src/Domain/Components/DataSource/User/UserInput.php rename to src/Domain/Infrastructure/DataSource/User/DTO/UserInput.php index de0c63a..cce2cca 100644 --- a/src/Domain/Components/DataSource/User/UserInput.php +++ b/src/Domain/Infrastructure/DataSource/User/DTO/UserInput.php @@ -11,18 +11,21 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\User; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\AbstractInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\AbstractValueObject; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; use function get_object_vars; /** + * Value object for input + * * @phpstan-import-type TUserInput from InputTypes * @phpstan-import-type TUser from UserTypes */ -final class UserInput extends AbstractInput +final class UserInput extends AbstractValueObject { /** * @param int $id diff --git a/src/Domain/Components/DataSource/User/UserFacade.php b/src/Domain/Infrastructure/DataSource/User/Facades/UserFacade.php similarity index 91% rename from src/Domain/Components/DataSource/User/UserFacade.php rename to src/Domain/Infrastructure/DataSource/User/Facades/UserFacade.php index b372a11..574cbc7 100644 --- a/src/Domain/Components/DataSource/User/UserFacade.php +++ b/src/Domain/Infrastructure/DataSource/User/Facades/UserFacade.php @@ -11,17 +11,21 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\User; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Facades; use PDOException; use Phalcon\Api\Domain\ADR\InputTypes; use Phalcon\Api\Domain\ADR\Payload; -use Phalcon\Api\Domain\Components\Constants\Dates; -use Phalcon\Api\Domain\Components\DataSource\Interfaces\MapperInterface; -use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; -use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; -use Phalcon\Api\Domain\Components\Encryption\Security; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Constants\Dates; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\MapperInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Infrastructure\Encryption\Security; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use function array_filter; diff --git a/src/Domain/Components/DataSource/User/UserMapper.php b/src/Domain/Infrastructure/DataSource/User/Mappers/UserMapper.php similarity index 90% rename from src/Domain/Components/DataSource/User/UserMapper.php rename to src/Domain/Infrastructure/DataSource/User/Mappers/UserMapper.php index b996be1..cc6b0c6 100644 --- a/src/Domain/Components/DataSource/User/UserMapper.php +++ b/src/Domain/Infrastructure/DataSource/User/Mappers/UserMapper.php @@ -11,9 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\User; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers; -use Phalcon\Api\Domain\Components\DataSource\Interfaces\MapperInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\MapperInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; /** * @phpstan-import-type TUser from UserTypes diff --git a/src/Domain/Components/DataSource/User/UserRepository.php b/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepository.php similarity index 87% rename from src/Domain/Components/DataSource/User/UserRepository.php rename to src/Domain/Infrastructure/DataSource/User/Repositories/UserRepository.php index 11c5bf9..b57a260 100644 --- a/src/Domain/Components/DataSource/User/UserRepository.php +++ b/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepository.php @@ -11,10 +11,13 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\User; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories; -use Phalcon\Api\Domain\Components\DataSource\AbstractRepository; -use Phalcon\Api\Domain\Components\Enums\Common\FlagsEnum; +use Phalcon\Api\Domain\Infrastructure\DataSource\AbstractRepository; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; +use Phalcon\Api\Domain\Infrastructure\Enums\Common\FlagsEnum; use Phalcon\DataMapper\Pdo\Connection; use Phalcon\DataMapper\Query\Insert; use Phalcon\DataMapper\Query\Select; diff --git a/src/Domain/Components/DataSource/User/UserRepositoryInterface.php b/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryInterface.php similarity index 87% rename from src/Domain/Components/DataSource/User/UserRepositoryInterface.php rename to src/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryInterface.php index 5afb71b..5c72fac 100644 --- a/src/Domain/Components/DataSource/User/UserRepositoryInterface.php +++ b/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryInterface.php @@ -11,7 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\User; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories; + +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; /** * @phpstan-import-type TCriteria from UserTypes diff --git a/src/Domain/Components/DataSource/User/UserSanitizer.php b/src/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizer.php similarity index 94% rename from src/Domain/Components/DataSource/User/UserSanitizer.php rename to src/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizer.php index 98c073d..762db1a 100644 --- a/src/Domain/Components/DataSource/User/UserSanitizer.php +++ b/src/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizer.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\User; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\Interfaces\SanitizerInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; use Phalcon\Filter\Filter; use Phalcon\Filter\FilterInterface; diff --git a/src/Domain/Components/DataSource/User/UserTypes.php b/src/Domain/Infrastructure/DataSource/User/UserTypes.php similarity index 98% rename from src/Domain/Components/DataSource/User/UserTypes.php rename to src/Domain/Infrastructure/DataSource/User/UserTypes.php index 3ccb744..f875630 100644 --- a/src/Domain/Components/DataSource/User/UserTypes.php +++ b/src/Domain/Infrastructure/DataSource/User/UserTypes.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\User; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User; /** * @phpstan-type TLoginResponsePayload array{ diff --git a/src/Domain/Components/DataSource/User/UserValidator.php b/src/Domain/Infrastructure/DataSource/User/Validators/UserValidator.php similarity index 64% rename from src/Domain/Components/DataSource/User/UserValidator.php rename to src/Domain/Infrastructure/DataSource/User/Validators/UserValidator.php index 331b1b7..c34c29e 100644 --- a/src/Domain/Components/DataSource/User/UserValidator.php +++ b/src/Domain/Infrastructure/DataSource/User/Validators/UserValidator.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\User; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators; -use Phalcon\Api\Domain\Components\DataSource\Validation\AbstractValidator; -use Phalcon\Api\Domain\Components\Enums\Input\UserInputInsertEnum; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbstractValidator; +use Phalcon\Api\Domain\Infrastructure\Enums\Input\UserInputInsertEnum; final class UserValidator extends AbstractValidator { diff --git a/src/Domain/Components/DataSource/User/UserValidatorTrait.php b/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTrait.php similarity index 72% rename from src/Domain/Components/DataSource/User/UserValidatorTrait.php rename to src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTrait.php index 1bae490..69fd2b9 100644 --- a/src/Domain/Components/DataSource/User/UserValidatorTrait.php +++ b/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTrait.php @@ -11,12 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\User; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; -use Phalcon\Api\Domain\Components\DataSource\Validation\AbstractValidator; -use Phalcon\Api\Domain\Components\DataSource\Validation\Result; -use Phalcon\Api\Domain\Components\Enums\Input\UserInputInsertEnum; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\Result; trait UserValidatorTrait { diff --git a/src/Domain/Components/DataSource/User/UserValidatorUpdate.php b/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdate.php similarity index 64% rename from src/Domain/Components/DataSource/User/UserValidatorUpdate.php rename to src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdate.php index 2f1d0a0..a8acfaa 100644 --- a/src/Domain/Components/DataSource/User/UserValidatorUpdate.php +++ b/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdate.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\User; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators; -use Phalcon\Api\Domain\Components\DataSource\Validation\AbstractValidator; -use Phalcon\Api\Domain\Components\Enums\Input\UserInputUpdateEnum; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbstractValidator; +use Phalcon\Api\Domain\Infrastructure\Enums\Input\UserInputUpdateEnum; final class UserValidatorUpdate extends AbstractValidator { diff --git a/src/Domain/Components/DataSource/Validation/AbsInt.php b/src/Domain/Infrastructure/DataSource/Validation/AbsInt.php similarity index 92% rename from src/Domain/Components/DataSource/Validation/AbsInt.php rename to src/Domain/Infrastructure/DataSource/Validation/AbsInt.php index 6575b70..3d79039 100644 --- a/src/Domain/Components/DataSource/Validation/AbsInt.php +++ b/src/Domain/Infrastructure/DataSource/Validation/AbsInt.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\Validation; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Validation; use Phalcon\Filter\Validation; use Phalcon\Filter\Validation\Validator\Numericality; @@ -42,7 +42,6 @@ public function validate(Validation $validation, string $field): bool /** @var string $value */ $value = $validation->getValue($field); - $value = abs((int)$value); if ($value <= 0) { $validation->appendMessage( diff --git a/src/Domain/Components/DataSource/Validation/AbstractValidator.php b/src/Domain/Infrastructure/DataSource/Validation/AbstractValidator.php similarity index 87% rename from src/Domain/Components/DataSource/Validation/AbstractValidator.php rename to src/Domain/Infrastructure/DataSource/Validation/AbstractValidator.php index bd94e0d..95cae33 100644 --- a/src/Domain/Components/DataSource/Validation/AbstractValidator.php +++ b/src/Domain/Infrastructure/DataSource/Validation/AbstractValidator.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\Validation; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Validation; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthInput; -use Phalcon\Api\Domain\Components\Enums\Input\ValidatorEnumInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; +use Phalcon\Api\Domain\Infrastructure\Enums\Input\ValidatorEnumInterface; use Phalcon\Filter\Validation\ValidationInterface; use Phalcon\Filter\Validation\ValidatorInterface as PhalconValidator; diff --git a/src/Domain/Components/DataSource/Validation/Result.php b/src/Domain/Infrastructure/DataSource/Validation/Result.php similarity index 96% rename from src/Domain/Components/DataSource/Validation/Result.php rename to src/Domain/Infrastructure/DataSource/Validation/Result.php index 4a81296..064d357 100644 --- a/src/Domain/Components/DataSource/Validation/Result.php +++ b/src/Domain/Infrastructure/DataSource/Validation/Result.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\Validation; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Validation; use Phalcon\Api\Domain\ADR\InputTypes; diff --git a/src/Domain/Components/DataSource/Validation/ValidatorInterface.php b/src/Domain/Infrastructure/DataSource/Validation/ValidatorInterface.php similarity index 89% rename from src/Domain/Components/DataSource/Validation/ValidatorInterface.php rename to src/Domain/Infrastructure/DataSource/Validation/ValidatorInterface.php index 72979d2..e79bacd 100644 --- a/src/Domain/Components/DataSource/Validation/ValidatorInterface.php +++ b/src/Domain/Infrastructure/DataSource/Validation/ValidatorInterface.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\DataSource\Validation; +namespace Phalcon\Api\Domain\Infrastructure\DataSource\Validation; /** * Validator contract. Accepts a DTO or input and returns a Result. diff --git a/src/Domain/Components/Encryption/JWTToken.php b/src/Domain/Infrastructure/Encryption/JWTToken.php similarity index 91% rename from src/Domain/Components/Encryption/JWTToken.php rename to src/Domain/Infrastructure/Encryption/JWTToken.php index dc7be76..a00026e 100644 --- a/src/Domain/Components/Encryption/JWTToken.php +++ b/src/Domain/Infrastructure/Encryption/JWTToken.php @@ -11,18 +11,18 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Encryption; +namespace Phalcon\Api\Domain\Infrastructure\Encryption; use DateTimeImmutable; use InvalidArgumentException; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; -use Phalcon\Api\Domain\Components\Enums\Common\FlagsEnum; -use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; -use Phalcon\Api\Domain\Components\Env\EnvManager; -use Phalcon\Api\Domain\Components\Exceptions\TokenValidationException; +use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; +use Phalcon\Api\Domain\Infrastructure\Enums\Common\FlagsEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Common\JWTEnum; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Exceptions\TokenValidationException; use Phalcon\Encryption\Security\JWT\Builder; use Phalcon\Encryption\Security\JWT\Signer\Hmac; use Phalcon\Encryption\Security\JWT\Token\Parser; diff --git a/src/Domain/Components/Encryption/Security.php b/src/Domain/Infrastructure/Encryption/Security.php similarity index 95% rename from src/Domain/Components/Encryption/Security.php rename to src/Domain/Infrastructure/Encryption/Security.php index 78ae798..3499788 100644 --- a/src/Domain/Components/Encryption/Security.php +++ b/src/Domain/Infrastructure/Encryption/Security.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Encryption; +namespace Phalcon\Api\Domain\Infrastructure\Encryption; use function password_hash; use function password_verify; diff --git a/src/Domain/Components/Encryption/TokenCache.php b/src/Domain/Infrastructure/Encryption/TokenCache.php similarity index 91% rename from src/Domain/Components/Encryption/TokenCache.php rename to src/Domain/Infrastructure/Encryption/TokenCache.php index cef096e..c974469 100644 --- a/src/Domain/Components/Encryption/TokenCache.php +++ b/src/Domain/Infrastructure/Encryption/TokenCache.php @@ -11,20 +11,18 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Encryption; +namespace Phalcon\Api\Domain\Infrastructure\Encryption; use DateTimeImmutable; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Components\Constants\Dates; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Infrastructure\Constants\Dates; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Cache\Adapter\Redis; use Phalcon\Cache\Cache; use Psr\SimpleCache\InvalidArgumentException; -use function str_replace; - /** * Small component to issue/rotate/revoke tokens and * interact with cache. diff --git a/src/Domain/Components/Encryption/TokenCacheInterface.php b/src/Domain/Infrastructure/Encryption/TokenCacheInterface.php similarity index 83% rename from src/Domain/Components/Encryption/TokenCacheInterface.php rename to src/Domain/Infrastructure/Encryption/TokenCacheInterface.php index d73fffd..238d2a9 100644 --- a/src/Domain/Components/Encryption/TokenCacheInterface.php +++ b/src/Domain/Infrastructure/Encryption/TokenCacheInterface.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Encryption; +namespace Phalcon\Api\Domain\Infrastructure\Encryption; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Psr\SimpleCache\InvalidArgumentException; interface TokenCacheInterface diff --git a/src/Domain/Components/Encryption/TokenManager.php b/src/Domain/Infrastructure/Encryption/TokenManager.php similarity index 92% rename from src/Domain/Components/Encryption/TokenManager.php rename to src/Domain/Infrastructure/Encryption/TokenManager.php index 50ef6c0..af67d06 100644 --- a/src/Domain/Components/Encryption/TokenManager.php +++ b/src/Domain/Infrastructure/Encryption/TokenManager.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Encryption; +namespace Phalcon\Api\Domain\Infrastructure\Encryption; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; -use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Encryption\Security\JWT\Token\Token; use Throwable; diff --git a/src/Domain/Components/Encryption/TokenManagerInterface.php b/src/Domain/Infrastructure/Encryption/TokenManagerInterface.php similarity index 89% rename from src/Domain/Components/Encryption/TokenManagerInterface.php rename to src/Domain/Infrastructure/Encryption/TokenManagerInterface.php index 589dead..48386fc 100644 --- a/src/Domain/Components/Encryption/TokenManagerInterface.php +++ b/src/Domain/Infrastructure/Encryption/TokenManagerInterface.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Encryption; +namespace Phalcon\Api\Domain\Infrastructure\Encryption; use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepositoryInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepositoryInterface; use Phalcon\Encryption\Security\JWT\Token\Token; /** diff --git a/src/Domain/Components/Enums/Common/FlagsEnum.php b/src/Domain/Infrastructure/Enums/Common/FlagsEnum.php similarity index 82% rename from src/Domain/Components/Enums/Common/FlagsEnum.php rename to src/Domain/Infrastructure/Enums/Common/FlagsEnum.php index 0a0be5b..78c5785 100644 --- a/src/Domain/Components/Enums/Common/FlagsEnum.php +++ b/src/Domain/Infrastructure/Enums/Common/FlagsEnum.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Common; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Common; -use Phalcon\Api\Domain\Components\Enums\EnumsInterface; +use Phalcon\Api\Domain\Infrastructure\Enums\EnumsInterface; enum FlagsEnum: int implements EnumsInterface { diff --git a/src/Domain/Components/Enums/Common/JWTEnum.php b/src/Domain/Infrastructure/Enums/Common/JWTEnum.php similarity index 92% rename from src/Domain/Components/Enums/Common/JWTEnum.php rename to src/Domain/Infrastructure/Enums/Common/JWTEnum.php index c37ff35..41a497c 100644 --- a/src/Domain/Components/Enums/Common/JWTEnum.php +++ b/src/Domain/Infrastructure/Enums/Common/JWTEnum.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Common; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Common; enum JWTEnum: string { diff --git a/src/Domain/Components/Enums/Container/AuthDefinitionsEnum.php b/src/Domain/Infrastructure/Enums/Container/AuthDefinitionsEnum.php similarity index 90% rename from src/Domain/Components/Enums/Container/AuthDefinitionsEnum.php rename to src/Domain/Infrastructure/Enums/Container/AuthDefinitionsEnum.php index c87e8b9..12a0f0a 100644 --- a/src/Domain/Components/Enums/Container/AuthDefinitionsEnum.php +++ b/src/Domain/Infrastructure/Enums/Container/AuthDefinitionsEnum.php @@ -11,13 +11,13 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Container; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Container; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthFacade; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthLoginValidator; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthSanitizer; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthTokenValidator; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Facades\AuthFacade; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers\AuthSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators\AuthLoginValidator; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators\AuthTokenValidator; use Phalcon\Api\Domain\Services\Auth\LoginPostService; use Phalcon\Api\Domain\Services\Auth\LogoutPostService; use Phalcon\Api\Domain\Services\Auth\RefreshPostService; diff --git a/src/Domain/Components/Enums/Container/CommonDefinitionsEnum.php b/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php similarity index 89% rename from src/Domain/Components/Enums/Container/CommonDefinitionsEnum.php rename to src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php index b022647..0f5547f 100644 --- a/src/Domain/Components/Enums/Container/CommonDefinitionsEnum.php +++ b/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Container; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Container; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Encryption\JWTToken; -use Phalcon\Api\Domain\Components\Encryption\TokenCache; -use Phalcon\Api\Domain\Components\Encryption\TokenManager; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenCache; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; use Phalcon\Events\Manager as EventsManager; use Phalcon\Mvc\Router; diff --git a/src/Domain/Components/Enums/Container/UserDefinitionsEnum.php b/src/Domain/Infrastructure/Enums/Container/UserDefinitionsEnum.php similarity index 90% rename from src/Domain/Components/Enums/Container/UserDefinitionsEnum.php rename to src/Domain/Infrastructure/Enums/Container/UserDefinitionsEnum.php index 36c950f..38e3565 100644 --- a/src/Domain/Components/Enums/Container/UserDefinitionsEnum.php +++ b/src/Domain/Infrastructure/Enums/Container/UserDefinitionsEnum.php @@ -11,14 +11,14 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Container; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Container; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserFacade; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; -use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; -use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; -use Phalcon\Api\Domain\Components\DataSource\User\UserValidatorUpdate; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Facades\UserFacade; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators\UserValidator; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators\UserValidatorUpdate; use Phalcon\Api\Domain\Services\User\UserDeleteService; use Phalcon\Api\Domain\Services\User\UserGetService; use Phalcon\Api\Domain\Services\User\UserPostService; diff --git a/src/Domain/Components/Enums/EnumsInterface.php b/src/Domain/Infrastructure/Enums/EnumsInterface.php similarity index 89% rename from src/Domain/Components/Enums/EnumsInterface.php rename to src/Domain/Infrastructure/Enums/EnumsInterface.php index b763f88..34128e6 100644 --- a/src/Domain/Components/Enums/EnumsInterface.php +++ b/src/Domain/Infrastructure/Enums/EnumsInterface.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums; +namespace Phalcon\Api\Domain\Infrastructure\Enums; use BackedEnum; diff --git a/src/Domain/Components/Enums/Http/HttpCodesEnum.php b/src/Domain/Infrastructure/Enums/Http/HttpCodesEnum.php similarity index 97% rename from src/Domain/Components/Enums/Http/HttpCodesEnum.php rename to src/Domain/Infrastructure/Enums/Http/HttpCodesEnum.php index cdb07e7..19f561c 100644 --- a/src/Domain/Components/Enums/Http/HttpCodesEnum.php +++ b/src/Domain/Infrastructure/Enums/Http/HttpCodesEnum.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Http; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Http; -use Phalcon\Api\Domain\Components\Enums\EnumsInterface; +use Phalcon\Api\Domain\Infrastructure\Enums\EnumsInterface; /** * Enumeration for Http Codes. These are not only the regular HTTP codes diff --git a/src/Domain/Components/Enums/Http/RoutesEnum.php b/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php similarity index 97% rename from src/Domain/Components/Enums/Http/RoutesEnum.php rename to src/Domain/Infrastructure/Enums/Http/RoutesEnum.php index f703473..3e4bf35 100644 --- a/src/Domain/Components/Enums/Http/RoutesEnum.php +++ b/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Http; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Http; -use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Infrastructure\Container; use function str_replace; diff --git a/src/Domain/Components/Enums/Input/AuthLoginInputEnum.php b/src/Domain/Infrastructure/Enums/Input/AuthLoginInputEnum.php similarity index 92% rename from src/Domain/Components/Enums/Input/AuthLoginInputEnum.php rename to src/Domain/Infrastructure/Enums/Input/AuthLoginInputEnum.php index 560c39f..700e722 100644 --- a/src/Domain/Components/Enums/Input/AuthLoginInputEnum.php +++ b/src/Domain/Infrastructure/Enums/Input/AuthLoginInputEnum.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Input; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Input; use Phalcon\Filter\Validation\Validator\Email; use Phalcon\Filter\Validation\Validator\PresenceOf; diff --git a/src/Domain/Components/Enums/Input/AuthTokenInputEnum.php b/src/Domain/Infrastructure/Enums/Input/AuthTokenInputEnum.php similarity index 89% rename from src/Domain/Components/Enums/Input/AuthTokenInputEnum.php rename to src/Domain/Infrastructure/Enums/Input/AuthTokenInputEnum.php index af4d9bf..4af780a 100644 --- a/src/Domain/Components/Enums/Input/AuthTokenInputEnum.php +++ b/src/Domain/Infrastructure/Enums/Input/AuthTokenInputEnum.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Input; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Input; use Phalcon\Filter\Validation\Validator\PresenceOf; diff --git a/src/Domain/Components/Enums/Input/UserInputInsertEnum.php b/src/Domain/Infrastructure/Enums/Input/UserInputInsertEnum.php similarity index 93% rename from src/Domain/Components/Enums/Input/UserInputInsertEnum.php rename to src/Domain/Infrastructure/Enums/Input/UserInputInsertEnum.php index 69b35a3..438cbde 100644 --- a/src/Domain/Components/Enums/Input/UserInputInsertEnum.php +++ b/src/Domain/Infrastructure/Enums/Input/UserInputInsertEnum.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Input; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Input; use Phalcon\Filter\Validation\Validator\Email; use Phalcon\Filter\Validation\Validator\PresenceOf; diff --git a/src/Domain/Components/Enums/Input/UserInputUpdateEnum.php b/src/Domain/Infrastructure/Enums/Input/UserInputUpdateEnum.php similarity index 87% rename from src/Domain/Components/Enums/Input/UserInputUpdateEnum.php rename to src/Domain/Infrastructure/Enums/Input/UserInputUpdateEnum.php index 3d09aeb..e4449d6 100644 --- a/src/Domain/Components/Enums/Input/UserInputUpdateEnum.php +++ b/src/Domain/Infrastructure/Enums/Input/UserInputUpdateEnum.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Input; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Input; -use Phalcon\Api\Domain\Components\DataSource\Validation\AbsInt; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbsInt; use Phalcon\Filter\Validation\Validator\Email; use Phalcon\Filter\Validation\Validator\PresenceOf; diff --git a/src/Domain/Components/Enums/Input/ValidatorEnumInterface.php b/src/Domain/Infrastructure/Enums/Input/ValidatorEnumInterface.php similarity index 88% rename from src/Domain/Components/Enums/Input/ValidatorEnumInterface.php rename to src/Domain/Infrastructure/Enums/Input/ValidatorEnumInterface.php index 41633d5..9c0fdba 100644 --- a/src/Domain/Components/Enums/Input/ValidatorEnumInterface.php +++ b/src/Domain/Infrastructure/Enums/Input/ValidatorEnumInterface.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Enums\Input; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Input; use UnitEnum; diff --git a/src/Domain/Components/Env/Adapters/AdapterInterface.php b/src/Domain/Infrastructure/Env/Adapters/AdapterInterface.php similarity index 82% rename from src/Domain/Components/Env/Adapters/AdapterInterface.php rename to src/Domain/Infrastructure/Env/Adapters/AdapterInterface.php index bab4b30..f9ed7ff 100644 --- a/src/Domain/Components/Env/Adapters/AdapterInterface.php +++ b/src/Domain/Infrastructure/Env/Adapters/AdapterInterface.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Env\Adapters; +namespace Phalcon\Api\Domain\Infrastructure\Env\Adapters; -use Phalcon\Api\Domain\Components\Env\EnvManagerTypes; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManagerTypes; /** * @phpstan-import-type TDotEnvOptions from EnvManagerTypes diff --git a/src/Domain/Components/Env/Adapters/DotEnv.php b/src/Domain/Infrastructure/Env/Adapters/DotEnv.php similarity index 85% rename from src/Domain/Components/Env/Adapters/DotEnv.php rename to src/Domain/Infrastructure/Env/Adapters/DotEnv.php index 0133a82..c349bcc 100644 --- a/src/Domain/Components/Env/Adapters/DotEnv.php +++ b/src/Domain/Infrastructure/Env/Adapters/DotEnv.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Env\Adapters; +namespace Phalcon\Api\Domain\Infrastructure\Env\Adapters; use Dotenv\Dotenv as ParentDotEnv; use Exception; -use Phalcon\Api\Domain\Components\Env\EnvManagerTypes; -use Phalcon\Api\Domain\Components\Exceptions\InvalidConfigurationArgumentException; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManagerTypes; +use Phalcon\Api\Domain\Infrastructure\Exceptions\InvalidConfigurationArgumentException; /** * @phpstan-import-type TDotEnvOptions from EnvManagerTypes diff --git a/src/Domain/Components/Env/EnvFactory.php b/src/Domain/Infrastructure/Env/EnvFactory.php similarity index 82% rename from src/Domain/Components/Env/EnvFactory.php rename to src/Domain/Infrastructure/Env/EnvFactory.php index 4ce3ba1..f6044a6 100644 --- a/src/Domain/Components/Env/EnvFactory.php +++ b/src/Domain/Infrastructure/Env/EnvFactory.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Env; +namespace Phalcon\Api\Domain\Infrastructure\Env; -use Phalcon\Api\Domain\Components\Env\Adapters\AdapterInterface; -use Phalcon\Api\Domain\Components\Env\Adapters\DotEnv; -use Phalcon\Api\Domain\Components\Exceptions\InvalidConfigurationArgumentException; +use Phalcon\Api\Domain\Infrastructure\Env\Adapters\AdapterInterface; +use Phalcon\Api\Domain\Infrastructure\Env\Adapters\DotEnv; +use Phalcon\Api\Domain\Infrastructure\Exceptions\InvalidConfigurationArgumentException; class EnvFactory { diff --git a/src/Domain/Components/Env/EnvManager.php b/src/Domain/Infrastructure/Env/EnvManager.php similarity index 96% rename from src/Domain/Components/Env/EnvManager.php rename to src/Domain/Infrastructure/Env/EnvManager.php index 94fd405..4a2d214 100644 --- a/src/Domain/Components/Env/EnvManager.php +++ b/src/Domain/Infrastructure/Env/EnvManager.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Env; +namespace Phalcon\Api\Domain\Infrastructure\Env; -use Phalcon\Api\Domain\Components\Constants\Dates; +use Phalcon\Api\Domain\Infrastructure\Constants\Dates; use Phalcon\Support\Collection; use function array_merge; diff --git a/src/Domain/Components/Env/EnvManagerTypes.php b/src/Domain/Infrastructure/Env/EnvManagerTypes.php similarity index 90% rename from src/Domain/Components/Env/EnvManagerTypes.php rename to src/Domain/Infrastructure/Env/EnvManagerTypes.php index 3acf713..b7347e0 100644 --- a/src/Domain/Components/Env/EnvManagerTypes.php +++ b/src/Domain/Infrastructure/Env/EnvManagerTypes.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Env; +namespace Phalcon\Api\Domain\Infrastructure\Env; /** * @phpstan-type TDotEnvOptions array{ diff --git a/src/Domain/Components/Exceptions/ExceptionTrait.php b/src/Domain/Infrastructure/Exceptions/ExceptionTrait.php similarity index 89% rename from src/Domain/Components/Exceptions/ExceptionTrait.php rename to src/Domain/Infrastructure/Exceptions/ExceptionTrait.php index 1323f88..6a39f51 100644 --- a/src/Domain/Components/Exceptions/ExceptionTrait.php +++ b/src/Domain/Infrastructure/Exceptions/ExceptionTrait.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Exceptions; +namespace Phalcon\Api\Domain\Infrastructure\Exceptions; trait ExceptionTrait { diff --git a/src/Domain/Components/Exceptions/InvalidConfigurationArgumentException.php b/src/Domain/Infrastructure/Exceptions/InvalidConfigurationArgumentException.php similarity index 87% rename from src/Domain/Components/Exceptions/InvalidConfigurationArgumentException.php rename to src/Domain/Infrastructure/Exceptions/InvalidConfigurationArgumentException.php index fe2f4a0..9ecf809 100644 --- a/src/Domain/Components/Exceptions/InvalidConfigurationArgumentException.php +++ b/src/Domain/Infrastructure/Exceptions/InvalidConfigurationArgumentException.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Exceptions; +namespace Phalcon\Api\Domain\Infrastructure\Exceptions; use InvalidArgumentException; diff --git a/src/Domain/Components/Exceptions/TokenValidationException.php b/src/Domain/Infrastructure/Exceptions/TokenValidationException.php similarity index 87% rename from src/Domain/Components/Exceptions/TokenValidationException.php rename to src/Domain/Infrastructure/Exceptions/TokenValidationException.php index 1acb086..08e5284 100644 --- a/src/Domain/Components/Exceptions/TokenValidationException.php +++ b/src/Domain/Infrastructure/Exceptions/TokenValidationException.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Exceptions; +namespace Phalcon\Api\Domain\Infrastructure\Exceptions; use InvalidArgumentException; diff --git a/src/Domain/Components/Middleware/AbstractMiddleware.php b/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php similarity index 94% rename from src/Domain/Components/Middleware/AbstractMiddleware.php rename to src/Domain/Infrastructure/Middleware/AbstractMiddleware.php index 6dfcee8..998b3c2 100644 --- a/src/Domain/Components/Middleware/AbstractMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Middleware; +namespace Phalcon\Api\Domain\Infrastructure\Middleware; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Api\Responder\ResponderInterface; use Phalcon\Api\Responder\ResponderTypes; use Phalcon\Domain\Payload; diff --git a/src/Domain/Components/Middleware/HealthMiddleware.php b/src/Domain/Infrastructure/Middleware/HealthMiddleware.php similarity index 87% rename from src/Domain/Components/Middleware/HealthMiddleware.php rename to src/Domain/Infrastructure/Middleware/HealthMiddleware.php index 6e01eea..9093fdd 100644 --- a/src/Domain/Components/Middleware/HealthMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/HealthMiddleware.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Middleware; +namespace Phalcon\Api\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Events\Exception as EventsException; use Phalcon\Http\Request; use Phalcon\Http\Response\Exception; diff --git a/src/Domain/Components/Middleware/NotFoundMiddleware.php b/src/Domain/Infrastructure/Middleware/NotFoundMiddleware.php similarity index 90% rename from src/Domain/Components/Middleware/NotFoundMiddleware.php rename to src/Domain/Infrastructure/Middleware/NotFoundMiddleware.php index 47293c2..76a31d8 100644 --- a/src/Domain/Components/Middleware/NotFoundMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/NotFoundMiddleware.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Middleware; +namespace Phalcon\Api\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Events\Event; use Phalcon\Events\Exception as EventsException; use Phalcon\Http\Response\Exception; diff --git a/src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddleware.php similarity index 87% rename from src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php rename to src/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddleware.php index 4580e87..3dfb26e 100644 --- a/src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddleware.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Middleware; +namespace Phalcon\Api\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\Encryption\JWTToken; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Encryption\Security\JWT\Token\Token; use Phalcon\Events\Exception as EventsException; use Phalcon\Http\Response\Exception; diff --git a/src/Domain/Components/Middleware/ValidateTokenPresenceMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php similarity index 84% rename from src/Domain/Components/Middleware/ValidateTokenPresenceMiddleware.php rename to src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php index dbe48ad..7c08858 100644 --- a/src/Domain/Components/Middleware/ValidateTokenPresenceMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Middleware; +namespace Phalcon\Api\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Events\Exception as EventsException; use Phalcon\Http\Request; use Phalcon\Http\Response\Exception; diff --git a/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddleware.php similarity index 82% rename from src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php rename to src/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddleware.php index 780d22c..cd93845 100644 --- a/src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddleware.php @@ -11,13 +11,13 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Middleware; +namespace Phalcon\Api\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Http\RequestInterface; use Phalcon\Mvc\Micro; use Phalcon\Support\Registry; diff --git a/src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php similarity index 83% rename from src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php rename to src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php index 57b8b9b..dc1535a 100644 --- a/src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php @@ -11,13 +11,13 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Middleware; +namespace Phalcon\Api\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Encryption\JWTToken; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Components\Env\EnvManager; -use Phalcon\Api\Domain\Components\Exceptions\TokenValidationException; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Exceptions\TokenValidationException; use Phalcon\Events\Exception as EventsException; use Phalcon\Http\Request; use Phalcon\Http\Response\Exception; diff --git a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php similarity index 85% rename from src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php rename to src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php index 04503ee..3b3ff39 100644 --- a/src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Middleware; +namespace Phalcon\Api\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; -use Phalcon\Api\Domain\Components\Encryption\JWTToken; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Encryption\Security\JWT\Token\Token; use Phalcon\Events\Exception as EventsException; use Phalcon\Http\Response\Exception; diff --git a/src/Domain/Components/Providers/ErrorHandlerProvider.php b/src/Domain/Infrastructure/Providers/ErrorHandlerProvider.php similarity index 94% rename from src/Domain/Components/Providers/ErrorHandlerProvider.php rename to src/Domain/Infrastructure/Providers/ErrorHandlerProvider.php index 7cf3b31..1dd1ffa 100644 --- a/src/Domain/Components/Providers/ErrorHandlerProvider.php +++ b/src/Domain/Infrastructure/Providers/ErrorHandlerProvider.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Providers; +namespace Phalcon\Api\Domain\Infrastructure\Providers; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Di\DiInterface; use Phalcon\Di\ServiceProviderInterface; use Phalcon\Logger\Logger; diff --git a/src/Domain/Components/Providers/RouterProvider.php b/src/Domain/Infrastructure/Providers/RouterProvider.php similarity index 95% rename from src/Domain/Components/Providers/RouterProvider.php rename to src/Domain/Infrastructure/Providers/RouterProvider.php index c17da85..2d184ae 100644 --- a/src/Domain/Components/Providers/RouterProvider.php +++ b/src/Domain/Infrastructure/Providers/RouterProvider.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Components\Providers; +namespace Phalcon\Api\Domain\Infrastructure\Providers; use Phalcon\Api\Action\ActionHandler; use Phalcon\Api\Domain\ADR\DomainInterface; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Enums\Http\RoutesEnum; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\RoutesEnum; use Phalcon\Api\Responder\ResponderInterface; use Phalcon\Di\DiInterface; use Phalcon\Di\ServiceProviderInterface; diff --git a/src/Domain/Services/Auth/AbstractAuthService.php b/src/Domain/Services/Auth/AbstractAuthService.php index ac877dd..8e4dd98 100644 --- a/src/Domain/Services/Auth/AbstractAuthService.php +++ b/src/Domain/Services/Auth/AbstractAuthService.php @@ -14,8 +14,8 @@ namespace Phalcon\Api\Domain\Services\Auth; use Phalcon\Api\Domain\ADR\DomainInterface; -use Phalcon\Api\Domain\Components\DataSource\Auth\AuthFacade; -use Phalcon\Api\Domain\Components\DataSource\Validation\ValidatorInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Facades\AuthFacade; +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\ValidatorInterface; abstract class AbstractAuthService implements DomainInterface { diff --git a/src/Domain/Services/User/AbstractUserService.php b/src/Domain/Services/User/AbstractUserService.php index c3368c4..347ef53 100644 --- a/src/Domain/Services/User/AbstractUserService.php +++ b/src/Domain/Services/User/AbstractUserService.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Domain\Services\User; use Phalcon\Api\Domain\ADR\DomainInterface; -use Phalcon\Api\Domain\Components\DataSource\User\UserFacade; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Facades\UserFacade; abstract class AbstractUserService implements DomainInterface { diff --git a/src/Responder/JsonResponder.php b/src/Responder/JsonResponder.php index 36ea2ff..d0c8bbd 100644 --- a/src/Responder/JsonResponder.php +++ b/src/Responder/JsonResponder.php @@ -14,8 +14,8 @@ namespace Phalcon\Api\Responder; use Exception as BaseException; -use Phalcon\Api\Domain\Components\Constants\Dates; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Constants\Dates; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Domain\Payload; use Phalcon\Http\ResponseInterface; diff --git a/tests/Unit/Domain/Components/ContainerTest.php b/tests/Unit/Domain/Infrastructure/ContainerTest.php similarity index 100% rename from tests/Unit/Domain/Components/ContainerTest.php rename to tests/Unit/Domain/Infrastructure/ContainerTest.php diff --git a/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserInputTest.php similarity index 94% rename from tests/Unit/Domain/Components/DataSource/User/UserInputTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserInputTest.php index 5ee9857..8e48f75 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserInputTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserInputTest.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; use Faker\Factory as FakerFactory; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserInput; -use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; use Phalcon\Api\Tests\AbstractUnitTestCase; use function json_encode; diff --git a/tests/Unit/Domain/Components/DataSource/User/UserTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserTest.php similarity index 95% rename from tests/Unit/Domain/Components/DataSource/User/UserTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserTest.php index 9d35178..2c9ec2c 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; -use Phalcon\Api\Domain\Components\DataSource\User\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; use Phalcon\Api\Tests\AbstractUnitTestCase; use function rand; diff --git a/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Mappers/UserMapperTest.php similarity index 96% rename from tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/User/Mappers/UserMapperTest.php index 6052b43..8361dbf 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserMapperTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Mappers/UserMapperTest.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; use Faker\Factory as FakerFactory; -use Phalcon\Api\Domain\Components\Constants\Dates; -use Phalcon\Api\Domain\Components\DataSource\User\UserInput; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Constants\Dates; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; use Phalcon\Api\Tests\AbstractUnitTestCase; use function json_encode; diff --git a/tests/Unit/Domain/Components/DataSource/User/UserRepositoryTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryTest.php similarity index 93% rename from tests/Unit/Domain/Components/DataSource/User/UserRepositoryTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryTest.php index 6c377bc..e5ef672 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserRepositoryTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryTest.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\User; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; diff --git a/tests/Unit/Domain/Components/DataSource/User/UserSanitizerTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizerTest.php similarity index 94% rename from tests/Unit/Domain/Components/DataSource/User/UserSanitizerTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizerTest.php index 481651f..a6fc885 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserSanitizerTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizerTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Filter\Filter; diff --git a/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTest.php similarity index 86% rename from tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php rename to tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTest.php index 0019146..b0b8649 100644 --- a/tests/Unit/Domain/Components/DataSource/User/UserValidatorTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTest.php @@ -11,13 +11,13 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; use Faker\Factory as FakerFactory; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserInput; -use Phalcon\Api\Domain\Components\DataSource\User\UserSanitizer; -use Phalcon\Api\Domain\Components\DataSource\User\UserValidator; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators\UserValidator; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Filter\Validation\ValidationInterface; diff --git a/tests/Unit/Domain/Components/Encryption/JWTTokenTest.php b/tests/Unit/Domain/Infrastructure/Encryption/JWTTokenTest.php similarity index 100% rename from tests/Unit/Domain/Components/Encryption/JWTTokenTest.php rename to tests/Unit/Domain/Infrastructure/Encryption/JWTTokenTest.php diff --git a/tests/Unit/Domain/Components/Encryption/SecurityTest.php b/tests/Unit/Domain/Infrastructure/Encryption/SecurityTest.php similarity index 100% rename from tests/Unit/Domain/Components/Encryption/SecurityTest.php rename to tests/Unit/Domain/Infrastructure/Encryption/SecurityTest.php diff --git a/tests/Unit/Domain/Components/Enums/Common/FlagsEnumTest.php b/tests/Unit/Domain/Infrastructure/Enums/Common/FlagsEnumTest.php similarity index 100% rename from tests/Unit/Domain/Components/Enums/Common/FlagsEnumTest.php rename to tests/Unit/Domain/Infrastructure/Enums/Common/FlagsEnumTest.php diff --git a/tests/Unit/Domain/Components/Enums/Http/HttpCodesEnumTest.php b/tests/Unit/Domain/Infrastructure/Enums/Http/HttpCodesEnumTest.php similarity index 100% rename from tests/Unit/Domain/Components/Enums/Http/HttpCodesEnumTest.php rename to tests/Unit/Domain/Infrastructure/Enums/Http/HttpCodesEnumTest.php diff --git a/tests/Unit/Domain/Components/Enums/Http/RoutesEnumTest.php b/tests/Unit/Domain/Infrastructure/Enums/Http/RoutesEnumTest.php similarity index 100% rename from tests/Unit/Domain/Components/Enums/Http/RoutesEnumTest.php rename to tests/Unit/Domain/Infrastructure/Enums/Http/RoutesEnumTest.php diff --git a/tests/Unit/Domain/Components/Env/Adapters/DotEnvTest.php b/tests/Unit/Domain/Infrastructure/Env/Adapters/DotEnvTest.php similarity index 100% rename from tests/Unit/Domain/Components/Env/Adapters/DotEnvTest.php rename to tests/Unit/Domain/Infrastructure/Env/Adapters/DotEnvTest.php diff --git a/tests/Unit/Domain/Components/Env/EnvFactoryTest.php b/tests/Unit/Domain/Infrastructure/Env/EnvFactoryTest.php similarity index 100% rename from tests/Unit/Domain/Components/Env/EnvFactoryTest.php rename to tests/Unit/Domain/Infrastructure/Env/EnvFactoryTest.php diff --git a/tests/Unit/Domain/Components/Env/EnvManagerTest.php b/tests/Unit/Domain/Infrastructure/Env/EnvManagerTest.php similarity index 100% rename from tests/Unit/Domain/Components/Env/EnvManagerTest.php rename to tests/Unit/Domain/Infrastructure/Env/EnvManagerTest.php diff --git a/tests/Unit/Domain/Components/Middleware/HealthMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/HealthMiddlewareTest.php similarity index 100% rename from tests/Unit/Domain/Components/Middleware/HealthMiddlewareTest.php rename to tests/Unit/Domain/Infrastructure/Middleware/HealthMiddlewareTest.php diff --git a/tests/Unit/Domain/Components/Middleware/NotFoundMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/NotFoundMiddlewareTest.php similarity index 100% rename from tests/Unit/Domain/Components/Middleware/NotFoundMiddlewareTest.php rename to tests/Unit/Domain/Infrastructure/Middleware/NotFoundMiddlewareTest.php diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenClaimsMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddlewareTest.php similarity index 100% rename from tests/Unit/Domain/Components/Middleware/ValidateTokenClaimsMiddlewareTest.php rename to tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddlewareTest.php diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenPresenceMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddlewareTest.php similarity index 100% rename from tests/Unit/Domain/Components/Middleware/ValidateTokenPresenceMiddlewareTest.php rename to tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddlewareTest.php diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddlewareTest.php similarity index 100% rename from tests/Unit/Domain/Components/Middleware/ValidateTokenRevokedMiddlewareTest.php rename to tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddlewareTest.php diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenStructureMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddlewareTest.php similarity index 100% rename from tests/Unit/Domain/Components/Middleware/ValidateTokenStructureMiddlewareTest.php rename to tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddlewareTest.php diff --git a/tests/Unit/Domain/Components/Middleware/ValidateTokenUserMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenUserMiddlewareTest.php similarity index 100% rename from tests/Unit/Domain/Components/Middleware/ValidateTokenUserMiddlewareTest.php rename to tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenUserMiddlewareTest.php diff --git a/tests/Unit/Domain/Components/Providers/CacheDataProviderTest.php b/tests/Unit/Domain/Infrastructure/Providers/CacheDataProviderTest.php similarity index 100% rename from tests/Unit/Domain/Components/Providers/CacheDataProviderTest.php rename to tests/Unit/Domain/Infrastructure/Providers/CacheDataProviderTest.php diff --git a/tests/Unit/Domain/Components/Providers/ErrorHandlerProviderTest.php b/tests/Unit/Domain/Infrastructure/Providers/ErrorHandlerProviderTest.php similarity index 100% rename from tests/Unit/Domain/Components/Providers/ErrorHandlerProviderTest.php rename to tests/Unit/Domain/Infrastructure/Providers/ErrorHandlerProviderTest.php diff --git a/tests/Unit/Domain/Components/Providers/RouterProviderTest.php b/tests/Unit/Domain/Infrastructure/Providers/RouterProviderTest.php similarity index 100% rename from tests/Unit/Domain/Components/Providers/RouterProviderTest.php rename to tests/Unit/Domain/Infrastructure/Providers/RouterProviderTest.php From 5ab3633209d15fb8d943d5ab5869dd43f46a4c03 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 11 Nov 2025 11:06:48 -0600 Subject: [PATCH 60/74] [#.x] - adjusted and added more tests --- tests/AbstractUnitTestCase.php | 8 +- tests/Unit/Action/ActionHandlerTest.php | 2 +- tests/Unit/Domain/ADR/InputTest.php | 70 +++++++++ tests/Unit/Domain/ADR/PayloadTest.php | 136 ++++++++++++++++++ .../Infrastructure/Constants/CacheTest.php | 76 ++++++++++ .../Infrastructure/Constants/DatesTest.php | 50 +++++++ .../Domain/Infrastructure/ContainerTest.php | 4 +- .../DataSource/Auth/DTO/AuthInputTest.php | 52 +++++++ .../Auth/Sanitizers/AuthSanitizerTest.php | 59 ++++++++ .../Validators/AuthLoginValidatorTest.php | 69 +++++++++ .../Validators/AuthTokenValidatorTest.php | 107 ++++++++++++++ .../DataSource/User/DTO/UserInputTest.php | 3 +- .../DataSource/User/DTO/UserTest.php | 2 +- .../User/Mappers/UserMapperTest.php | 2 +- .../User/Repositories/UserRepositoryTest.php | 2 +- .../User/Sanitizers/UserSanitizerTest.php | 2 +- .../User/Validators/UserValidatorTest.php | 2 +- .../Validators/UserValidatorUpdateTest.php | 80 +++++++++++ .../DataSource/Validation/AbsIntTest.php | 77 ++++++++++ .../DataSource/Validation/ResultTest.php | 78 ++++++++++ .../Encryption/JWTTokenTest.php | 12 +- .../Encryption/SecurityTest.php | 6 +- .../Encryption/TokenCacheTest.php | 101 +++++++++++++ .../Encryption/TokenManagerTest.php | 99 +++++++++++++ .../Enums/Common/FlagsEnumTest.php | 4 +- .../Enums/Http/HttpCodesEnumTest.php | 4 +- .../Enums/Http/RoutesEnumTest.php | 6 +- .../Env/Adapters/DotEnvTest.php | 10 +- .../Infrastructure/Env/EnvFactoryTest.php | 8 +- .../Infrastructure/Env/EnvManagerTest.php | 6 +- .../Middleware/HealthMiddlewareTest.php | 4 +- .../Middleware/NotFoundMiddlewareTest.php | 6 +- .../ValidateTokenClaimsMiddlewareTest.php | 6 +- .../ValidateTokenPresenceMiddlewareTest.php | 6 +- .../ValidateTokenRevokedMiddlewareTest.php | 10 +- .../ValidateTokenStructureMiddlewareTest.php | 8 +- .../ValidateTokenUserMiddlewareTest.php | 8 +- .../Providers/CacheDataProviderTest.php | 4 +- .../Providers/ErrorHandlerProviderTest.php | 8 +- .../Providers/RouterProviderTest.php | 6 +- .../Services/Auth/LoginPostServiceTest.php | 4 +- .../Services/Auth/LogoutPostServiceTest.php | 10 +- .../Services/Auth/RefreshPostServiceTest.php | 8 +- .../Services/User/UserServiceDeleteTest.php | 2 +- .../Services/User/UserServiceDispatchTest.php | 12 +- .../Services/User/UserServiceGetTest.php | 2 +- .../Services/User/UserServicePostTest.php | 6 +- .../Services/User/UserServicePutTest.php | 6 +- tests/Unit/Responder/JsonResponderTest.php | 4 +- 49 files changed, 1155 insertions(+), 102 deletions(-) create mode 100644 tests/Unit/Domain/ADR/InputTest.php create mode 100644 tests/Unit/Domain/ADR/PayloadTest.php create mode 100644 tests/Unit/Domain/Infrastructure/Constants/CacheTest.php create mode 100644 tests/Unit/Domain/Infrastructure/Constants/DatesTest.php create mode 100644 tests/Unit/Domain/Infrastructure/DataSource/Auth/DTO/AuthInputTest.php create mode 100644 tests/Unit/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizerTest.php create mode 100644 tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidatorTest.php create mode 100644 tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidatorTest.php create mode 100644 tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdateTest.php create mode 100644 tests/Unit/Domain/Infrastructure/DataSource/Validation/AbsIntTest.php create mode 100644 tests/Unit/Domain/Infrastructure/DataSource/Validation/ResultTest.php create mode 100644 tests/Unit/Domain/Infrastructure/Encryption/TokenCacheTest.php create mode 100644 tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php diff --git a/tests/AbstractUnitTestCase.php b/tests/AbstractUnitTestCase.php index 23fbede..381d2ee 100644 --- a/tests/AbstractUnitTestCase.php +++ b/tests/AbstractUnitTestCase.php @@ -16,10 +16,10 @@ use DateTimeImmutable; use Faker\Factory; use PDO; -use Phalcon\Api\Domain\Components\Constants\Dates; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Encryption\Security; -use Phalcon\Api\Domain\Components\Enums\Common\JWTEnum; +use Phalcon\Api\Domain\Infrastructure\Constants\Dates; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Encryption\Security; +use Phalcon\Api\Domain\Infrastructure\Enums\Common\JWTEnum; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\DataMapper\Pdo\Connection; use Phalcon\Encryption\Security\JWT\Builder; diff --git a/tests/Unit/Action/ActionHandlerTest.php b/tests/Unit/Action/ActionHandlerTest.php index 7ba4ae9..c7463a6 100644 --- a/tests/Unit/Action/ActionHandlerTest.php +++ b/tests/Unit/Action/ActionHandlerTest.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Action; use Phalcon\Api\Action\ActionHandler; -use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Responder\ResponderInterface; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Services\ServiceFixture; diff --git a/tests/Unit/Domain/ADR/InputTest.php b/tests/Unit/Domain/ADR/InputTest.php new file mode 100644 index 0000000..a6a6431 --- /dev/null +++ b/tests/Unit/Domain/ADR/InputTest.php @@ -0,0 +1,70 @@ + + * + * 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\ADR; + +use Faker\Factory; +use Phalcon\Api\Domain\ADR\Input; +use Phalcon\Http\Request; +use PHPUnit\Framework\Attributes\BackupGlobals; +use PHPUnit\Framework\TestCase; + +#[BackupGlobals(true)] +final class InputTest extends TestCase +{ + public function testInvoke(): void + { + $faker = Factory::create(); + $postData = [ + 'post1' => $faker->word(), + 'post2' => $faker->word(), + ]; + $getData = [ + 'get1' => $faker->word(), + 'get2' => $faker->word(), + ]; + $putData = [ + 'put1' => $faker->word(), + 'put2' => $faker->word(), + ]; + + $mockRequest = $this + ->getMockBuilder(Request::class) + ->onlyMethods( + [ + 'getQuery', + 'getPost', + 'getPut', + ] + ) + ->getMock() + ; + $mockRequest->method('getQuery')->willReturn($getData); + $mockRequest->method('getPost')->willReturn($postData); + $mockRequest->method('getPut')->willReturn($putData); + + $input = new Input(); + + $expected = [ + 'get1' => $getData['get1'], + 'get2' => $getData['get2'], + 'post1' => $postData['post1'], + 'post2' => $postData['post2'], + 'put1' => $putData['put1'], + 'put2' => $putData['put2'], + ]; + $actual = $input->__invoke($mockRequest); + $this->assertSame($expected, $actual); + } + +} diff --git a/tests/Unit/Domain/ADR/PayloadTest.php b/tests/Unit/Domain/ADR/PayloadTest.php new file mode 100644 index 0000000..4350b35 --- /dev/null +++ b/tests/Unit/Domain/ADR/PayloadTest.php @@ -0,0 +1,136 @@ + + * + * 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\ADR; + +use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +final class PayloadTest extends AbstractUnitTestCase +{ + public function testCreatedContainsDataAndStatusCreated(): void + { + $data = ['id' => 1]; + $payload = Payload::created($data); + + $expected = Payload::class; + $actual = get_class($payload); + $this->assertSame($expected, $actual); + + $expected = DomainStatus::CREATED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $expected = ['data' => $data]; + $actual = $payload->getResult(); + $this->assertSame($expected, $actual); + } + + public function testDeletedContainsDataAndStatusDeleted(): void + { + $data = ['deleted' => true]; + $payload = Payload::deleted($data); + + $expected = DomainStatus::DELETED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $expected = ['data' => $data]; + $actual = $payload->getResult(); + $this->assertSame($expected, $actual); + } + + public function testUpdatedContainsDataAndStatusUpdated(): void + { + $data = ['updated' => true]; + $payload = Payload::updated($data); + + $expected = DomainStatus::UPDATED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $expected = ['data' => $data]; + $actual = $payload->getResult(); + $this->assertSame($expected, $actual); + } + + public function testSuccessContainsDataAndStatusSuccess(): void + { + $data = ['items' => [1, 2, 3]]; + $payload = Payload::success($data); + + $expected = DomainStatus::SUCCESS; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $expected = ['data' => $data]; + $actual = $payload->getResult(); + $this->assertSame($expected, $actual); + } + + public function testErrorContainsErrorsAndStatusError(): void + { + $errors = [['something' => 'went wrong']]; + $payload = Payload::error($errors); + + $expected = DomainStatus::ERROR; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $expected = ['errors' => $errors]; + $actual = $payload->getResult(); + $this->assertSame($expected, $actual); + } + + public function testInvalidContainsErrorsAndStatusInvalid(): void + { + $errors = [['field' => 'invalid']]; + $payload = Payload::invalid($errors); + + $expected = DomainStatus::INVALID; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $expected = ['errors' => $errors]; + $actual = $payload->getResult(); + $this->assertSame($expected, $actual); + } + + public function testUnauthorizedContainsErrorsAndStatusUnauthorized(): void + { + $errors = [['reason' => 'no access']]; + $payload = Payload::unauthorized($errors); + + $expected = DomainStatus::UNAUTHORIZED; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $expected = ['errors' => $errors]; + $actual = $payload->getResult(); + $this->assertSame($expected, $actual); + } + + public function testNotFoundReturnsDefaultErrorAndStatusNotFound(): void + { + $payload = Payload::notFound(); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $expected = ['errors' => [['Record(s) not found']]]; + $actual = $payload->getResult(); + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/Constants/CacheTest.php b/tests/Unit/Domain/Infrastructure/Constants/CacheTest.php new file mode 100644 index 0000000..30d27b6 --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/Constants/CacheTest.php @@ -0,0 +1,76 @@ + + * + * 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\Infrastructure\Constants; + +use Phalcon\Api\Domain\Infrastructure\Constants\Cache; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +use function sha1; +use function uniqid; + +final class CacheTest extends AbstractUnitTestCase +{ + public function testConstants(): void + { + $expected = 86400; + $actual = Cache::CACHE_LIFETIME_DAY; + $this->assertSame($expected, $actual); + + $expected = 3600; + $actual = Cache::CACHE_LIFETIME_HOUR; + $this->assertSame($expected, $actual); + + $expected = 60; + $actual = Cache::CACHE_LIFETIME_MINUTE; + $this->assertSame($expected, $actual); + + $expected = 2592000; + $actual = Cache::CACHE_LIFETIME_MONTH; + $this->assertSame($expected, $actual); + + $expected = 14400; + $actual = Cache::CACHE_TOKEN_EXPIRY; + $this->assertSame($expected, $actual); + + $token = uniqid('tok-'); + $shaToken = sha1($token); + + $newUser = $this->getNewUserData(); + $newUser['usr_id'] = 1; + $domainUser = new User( + $newUser['usr_id'], + $newUser['usr_status_flag'], + $newUser['usr_email'], + $newUser['usr_password'], + $newUser['usr_name_prefix'], + $newUser['usr_name_first'], + $newUser['usr_name_middle'], + $newUser['usr_name_last'], + $newUser['usr_name_suffix'], + $newUser['usr_issuer'], + $newUser['usr_token_password'], + $newUser['usr_token_id'], + $newUser['usr_preferences'], + $newUser['usr_created_date'], + $newUser['usr_created_usr_id'], + $newUser['usr_updated_date'], + $newUser['usr_updated_usr_id'] + ); + + $expected = 'tk-' . $domainUser->id . '-' . $shaToken; + $actual = Cache::getCacheTokenKey($domainUser, $token); + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/Constants/DatesTest.php b/tests/Unit/Domain/Infrastructure/Constants/DatesTest.php new file mode 100644 index 0000000..2909f28 --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/Constants/DatesTest.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\Tests\Unit\Domain\Infrastructure\Constants; + +use DateTimeImmutable; +use Phalcon\Api\Domain\Infrastructure\Constants\Dates; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +final class DatesTest extends AbstractUnitTestCase +{ + public function testConstants(): void + { + $expected = 'Y-m-d'; + $actual = Dates::DATE_FORMAT; + $this->assertSame($expected, $actual); + + $expected = 'NOW()'; + $actual = Dates::DATE_NOW; + $this->assertSame($expected, $actual); + + $expected = 'Y-m-d H:i:s'; + $actual = Dates::DATE_TIME_FORMAT; + $this->assertSame($expected, $actual); + + $expected = 'Y-m-d\\TH:i:sp'; + $actual = Dates::DATE_TIME_UTC_FORMAT; + $this->assertSame($expected, $actual); + + $expected = 'UTC'; + $actual = Dates::DATE_TIME_ZONE; + $this->assertSame($expected, $actual); + + $now = new DateTimeImmutable(); + $expected = $now->format('Y-m-d'); + + $actual = Dates::toUTC(format: 'Y-m-d'); + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/ContainerTest.php b/tests/Unit/Domain/Infrastructure/ContainerTest.php index c40a728..643afe4 100644 --- a/tests/Unit/Domain/Infrastructure/ContainerTest.php +++ b/tests/Unit/Domain/Infrastructure/ContainerTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure; -use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Events\Manager as EventsManager; use Phalcon\Filter\Filter; diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Auth/DTO/AuthInputTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Auth/DTO/AuthInputTest.php new file mode 100644 index 0000000..14fa5bc --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/DataSource/Auth/DTO/AuthInputTest.php @@ -0,0 +1,52 @@ + + * + * 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\Infrastructure\DataSource\Auth\DTO; + +use Faker\Factory as FakerFactory; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers\AuthSanitizer; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +final class AuthInputTest extends AbstractUnitTestCase +{ + public function testObject(): void + { + /** @var AuthSanitizer $sanitizer */ + $sanitizer = $this->container->get(Container::AUTH_SANITIZER); + $faker = FakerFactory::create(); + + // Build an input with many fields present + $input = [ + 'email' => " Foo.Bar+tag@Example.COM ", + 'password' => $faker->password(), + 'token' => $faker->password(), + ]; + + $sanitized = $sanitizer->sanitize($input); + $authInput = AuthInput::new($sanitizer, $input); + + $expected = $sanitized['email']; + $actual = $authInput->email; + $this->assertSame($expected, $actual); + + $expected = $sanitized['password']; + $actual = $authInput->password; + $this->assertSame($expected, $actual); + + $expected = $sanitized['token']; + $actual = $authInput->token; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizerTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizerTest.php new file mode 100644 index 0000000..3d0d693 --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizerTest.php @@ -0,0 +1,59 @@ + + * + * 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\Infrastructure\DataSource\Auth\Sanitizers; + +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers\AuthSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Filter\Filter; + +final class AuthSanitizerTest extends AbstractUnitTestCase +{ + public function testEmpty(): void + { + /** @var Filter $filter */ + $filter = $this->container->getShared(Container::FILTER); + $sanitizer = new AuthSanitizer($filter); + + $expected = [ + 'email' => null, + 'password' => null, + 'token' => null, + ]; + $actual = $sanitizer->sanitize([]); + $this->assertSame($expected, $actual); + } + + public function testObject(): void + { + /** @var Filter $filter */ + $filter = $this->container->getShared(Container::FILTER); + $sanitizer = new AuthSanitizer($filter); + + $userData = [ + 'email' => 'John.Doe (newsletter) +spam@example.COM', + 'password' => 'some ', + 'token' => 'some ', + ]; + + $expected = [ + 'email' => 'John.Doenewsletter+spam@example.COM', + 'password' => 'some ', + 'token' => 'some ', + ]; + $actual = $sanitizer->sanitize($userData); + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidatorTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidatorTest.php new file mode 100644 index 0000000..db79869 --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidatorTest.php @@ -0,0 +1,69 @@ + + * + * 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\Infrastructure\DataSource\Auth\Validators; + +use Faker\Factory as FakerFactory; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers\AuthSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators\AuthLoginValidator; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Filter\Validation\ValidationInterface; + +final class AuthLoginValidatorTest extends AbstractUnitTestCase +{ + public function testError(): void + { + /** @var AuthSanitizer $sanitizer */ + $sanitizer = $this->container->get(Container::AUTH_SANITIZER); + /** @var ValidationInterface $validation */ + $validation = $this->container->get(Container::VALIDATION); + + $input = []; + $userInput = AuthInput::new($sanitizer, $input); + + $validator = new AuthLoginValidator($validation); + $result = $validator->validate($userInput); + $actual = $result->getErrors(); + + $expected = [ + HttpCodesEnum::AppIncorrectCredentials->error(), + ]; + + $this->assertSame($expected, $actual); + } + + public function testSuccess(): void + { + /** @var AuthSanitizer $sanitizer */ + $sanitizer = $this->container->get(Container::AUTH_SANITIZER); + /** @var ValidationInterface $validation */ + $validation = $this->container->get(Container::VALIDATION); + $faker = FakerFactory::create(); + + $input = [ + 'email' => $faker->safeEmail(), + 'password' => $faker->password(), + ]; + $userInput = AuthInput::new($sanitizer, $input); + + $validator = new AuthLoginValidator($validation); + $result = $validator->validate($userInput); + $actual = $result->getErrors(); + + $expected = []; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidatorTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidatorTest.php new file mode 100644 index 0000000..333d74d --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidatorTest.php @@ -0,0 +1,107 @@ + + * + * 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\Infrastructure\DataSource\Auth\Validators; + +use Exception; +use Faker\Factory; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers\AuthSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Validators\AuthTokenValidator; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenCacheInterface; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Filter\Validation\ValidationInterface; + +final class AuthTokenValidatorTest extends AbstractUnitTestCase +{ + public function testFailureTokenNotPresent(): void + { + /** @var AuthSanitizer $sanitizer */ + $sanitizer = $this->container->get(Container::AUTH_SANITIZER); + /** @var TokenManager $tokenManager */ + $tokenManager = $this->container->get(Container::JWT_TOKEN_MANAGER); + /** @var UserRepository $repository */ + $repository = $this->container->get(Container::USER_REPOSITORY); + /** @var ValidationInterface $validation */ + $validation = $this->container->get(Container::VALIDATION); + + $input = []; + $userInput = AuthInput::new($sanitizer, $input); + + $validator = new AuthTokenValidator($tokenManager, $repository, $validation); + $result = $validator->validate($userInput); + $actual = $result->getErrors(); + + $expected = [ + HttpCodesEnum::AppTokenNotPresent->error(), + ]; + + $this->assertSame($expected, $actual); + } + + public function testFailureTokenNotValid(): void + { + /** @var EnvManager $env */ + $env = $this->container->get(Container::ENV); + /** @var TokenCacheInterface $tokenCache */ + $tokenCache = $this->container->get(Container::JWT_TOKEN_CACHE); + /** @var AuthSanitizer $sanitizer */ + $sanitizer = $this->container->get(Container::AUTH_SANITIZER); + /** @var UserRepository $repository */ + $repository = $this->container->get(Container::USER_REPOSITORY); + /** @var ValidationInterface $validation */ + $validation = $this->container->get(Container::VALIDATION); + + $mockJWTToken = $this + ->getMockBuilder(JWTToken::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getObject' + ] + ) + ->getMock() + ; + $mockJWTToken + ->method('getObject') + ->willThrowException(new Exception('error')) + ; + + $userData = $this->getNewUserData(); + $userData['usr_id'] = rand(1, 100); + $token = $this->getUserToken($userData); + + $tokenManager = new TokenManager($tokenCache, $env, $mockJWTToken); + + $input = [ + 'token' => $token, + ]; + $userInput = AuthInput::new($sanitizer, $input); + + $validator = new AuthTokenValidator($tokenManager, $repository, $validation); + $result = $validator->validate($userInput); + $actual = $result->getErrors(); + + $expected = [ + HttpCodesEnum::AppTokenNotValid->error(), + ]; + + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserInputTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserInputTest.php index 8e48f75..d210cb9 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserInputTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserInputTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\DTO; use Faker\Factory as FakerFactory; use Phalcon\Api\Domain\Infrastructure\Container; @@ -53,7 +53,6 @@ public function testToArray(): void $sanitized = $sanitizer->sanitize($input); $userInput = UserInput::new($sanitizer, $input); - // For each property use $expected and $actual then assertSame $expected = $sanitized['id']; $actual = $userInput->id; $this->assertSame($expected, $actual); diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserTest.php index 2c9ec2c..198d47b 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/DTO/UserTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\DTO; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; use Phalcon\Api\Tests\AbstractUnitTestCase; diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Mappers/UserMapperTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Mappers/UserMapperTest.php index 8361dbf..4a03d93 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/Mappers/UserMapperTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Mappers/UserMapperTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Mappers; use Faker\Factory as FakerFactory; use Phalcon\Api\Domain\Infrastructure\Constants\Dates; diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryTest.php index e5ef672..07d0070 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Repositories; use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizerTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizerTest.php index a6fc885..60964a4 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizerTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizerTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Sanitizers; use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTest.php index b0b8649..155fdd0 100644 --- a/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTest.php +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\DataSource\User\Validators; use Faker\Factory as FakerFactory; use Phalcon\Api\Domain\Infrastructure\Container; diff --git a/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdateTest.php b/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdateTest.php new file mode 100644 index 0000000..afab4ff --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdateTest.php @@ -0,0 +1,80 @@ + + * + * 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\Infrastructure\DataSource\User\Validators; + +use Faker\Factory as FakerFactory; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\UserInput; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers\UserSanitizer; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators\UserValidator; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators\UserValidatorUpdate; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Filter\Validation\ValidationInterface; + +final class UserValidatorUpdateTest extends AbstractUnitTestCase +{ + public function testError(): void + { + /** @var UserSanitizer $sanitizer */ + $sanitizer = $this->container->get(Container::USER_SANITIZER); + /** @var ValidationInterface $validation */ + $validation = $this->container->get(Container::VALIDATION); + + $input = []; + $userInput = UserInput::new($sanitizer, $input); + + $validator = new UserValidatorUpdate($validation); + $result = $validator->validate($userInput); + $actual = $result->getErrors(); + + $expected = [ + ['Field id is not a valid absolute integer and greater than 0'], + ['Field email is required'], + ['Field email must be an email address'], + ['Field password is required'], + ['Field issuer is required'], + ['Field tokenPassword is required'], + ['Field tokenId is required'], + ]; + + $this->assertSame($expected, $actual); + } + + public function testSuccess(): void + { + /** @var UserSanitizer $sanitizer */ + $sanitizer = $this->container->get(Container::USER_SANITIZER); + /** @var ValidationInterface $validation */ + $validation = $this->container->get(Container::VALIDATION); + $faker = FakerFactory::create(); + + $input = [ + 'id' => $faker->numberBetween(1, 100), + 'email' => $faker->safeEmail(), + 'password' => $faker->password(), + 'issuer' => $faker->company(), + 'tokenPassword' => $faker->password(), + 'tokenId' => $faker->uuid(), + ]; + + $userInput = UserInput::new($sanitizer, $input); + + $validator = new UserValidatorUpdate($validation); + $result = $validator->validate($userInput); + $actual = $result->getErrors(); + + $expected = []; + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Validation/AbsIntTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Validation/AbsIntTest.php new file mode 100644 index 0000000..d01141b --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/DataSource/Validation/AbsIntTest.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\Tests\Unit\Domain\Infrastructure\DataSource\Validation; + +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbsInt; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Filter\Validation; +use PHPUnit\Framework\Attributes\DataProvider; + +final class AbsIntTest extends AbstractUnitTestCase +{ + /** + * @return array + */ + public static function getExamples(): array + { + return [ + [ + 'amount' => 123, + 'expected' => 0, + ], + [ + 'amount' => 123.12, + 'expected' => 0, + ], + [ + 'amount' => '-12,000', + 'expected' => 1, + ], + [ + 'amount' => '-12,0@0', + 'expected' => 1, + ], + [ + 'amount' => '-12,0@@0', + 'expected' => 1, + ], + [ + 'amount' => '123abc', + 'expected' => 1, + ], + [ + 'amount' => '123.12e3', + 'expected' => 1, + ], + ]; + } + + #[DataProvider('getExamples')] + public function testValidator( + mixed $amount, + int $expected + ): void { + $validation = new Validation(); + $validation->add('amount', new AbsInt()); + + $messages = $validation->validate( + [ + 'amount' => $amount, + ] + ); + + $actual = $messages->count(); + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/DataSource/Validation/ResultTest.php b/tests/Unit/Domain/Infrastructure/DataSource/Validation/ResultTest.php new file mode 100644 index 0000000..63a60bb --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/DataSource/Validation/ResultTest.php @@ -0,0 +1,78 @@ + + * + * 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\Infrastructure\DataSource\Validation; + +use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\Result; +use Phalcon\Api\Tests\AbstractUnitTestCase; + +final class ResultTest extends AbstractUnitTestCase +{ + public function testSuccessIsValidAndHasNoErrors(): void + { + $result = Result::success(); + + $actual = $result->isValid(); + $this->assertTrue($actual); + + $expected = []; + $actual = $result->getErrors(); + $this->assertSame($expected, $actual); + } + + public function testErrorIsInvalidAndReturnsErrors(): void + { + $errors = ['field' => ['must not be empty']]; + $result = Result::error($errors); + + $expected = $errors; + $actual = $result->getErrors(); + $this->assertSame($expected, $actual); + } + + public function testGetMetaReturnsDefaultWhenMissing(): void + { + $result = new Result([], ['present' => 123]); + + $expected = 123; + $actual = $result->getMeta('present'); + $this->assertSame($expected, $actual); + + $actual = $result->getMeta('uknown'); + $this->assertNull($actual); + + $expected = 'default'; + $actual = $result->getMeta('unknown', 'default'); + $this->assertSame($expected, $actual); + } + + public function testSetMetaAddsOrUpdatesMeta(): void + { + $result = new Result(); + + $actual = $result->getMeta('key'); + $this->assertNull($actual); + + $result->setMeta('key', 'value'); + + $expected = 'value'; + $actual = $result->getMeta('key'); + $this->assertSame($expected, $actual); + + $result->setMeta('key', 42); + + $expected = 42; + $actual = $result->getMeta('key'); + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/Encryption/JWTTokenTest.php b/tests/Unit/Domain/Infrastructure/Encryption/JWTTokenTest.php index 5d93a4c..5236ae0 100644 --- a/tests/Unit/Domain/Infrastructure/Encryption/JWTTokenTest.php +++ b/tests/Unit/Domain/Infrastructure/Encryption/JWTTokenTest.php @@ -11,13 +11,13 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Encryption; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Encryption; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; -use Phalcon\Api\Domain\Components\Encryption\JWTToken; -use Phalcon\Api\Domain\Components\Exceptions\TokenValidationException; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Exceptions\TokenValidationException; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Encryption\Security\JWT\Token\Token; diff --git a/tests/Unit/Domain/Infrastructure/Encryption/SecurityTest.php b/tests/Unit/Domain/Infrastructure/Encryption/SecurityTest.php index be1d7df..d505046 100644 --- a/tests/Unit/Domain/Infrastructure/Encryption/SecurityTest.php +++ b/tests/Unit/Domain/Infrastructure/Encryption/SecurityTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Encryption; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Encryption; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Encryption\Security; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Encryption\Security; use Phalcon\Api\Tests\AbstractUnitTestCase; final class SecurityTest extends AbstractUnitTestCase diff --git a/tests/Unit/Domain/Infrastructure/Encryption/TokenCacheTest.php b/tests/Unit/Domain/Infrastructure/Encryption/TokenCacheTest.php new file mode 100644 index 0000000..411ab54 --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/Encryption/TokenCacheTest.php @@ -0,0 +1,101 @@ + + * + * 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\Infrastructure\Encryption; + +use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenCache; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Cache\Cache; + +use function rand; +use function uniqid; + +final class TokenCacheTest extends AbstractUnitTestCase +{ + public function testStoreAndInvalidateForUser(): void + { + /** @var EnvManager $env */ + $env = $this->container->get(Container::ENV); + /** @var Cache $cache */ + $cache = $this->container->get(Container::CACHE); + /** @var TokenCache $tokenCache */ + $tokenCache = $this->container->get(Container::JWT_TOKEN_CACHE); + + /** + * Empty cache + */ + $cache->getAdapter()->clear(); + + /** + * Create user data + */ + $newUser = $this->getNewUserData(); + $newUser['usr_id'] = rand(1, 100); + $domainUser = new User( + $newUser['usr_id'], + $newUser['usr_status_flag'], + $newUser['usr_email'], + $newUser['usr_password'], + $newUser['usr_name_prefix'], + $newUser['usr_name_first'], + $newUser['usr_name_middle'], + $newUser['usr_name_last'], + $newUser['usr_name_suffix'], + $newUser['usr_issuer'], + $newUser['usr_token_password'], + $newUser['usr_token_id'], + $newUser['usr_preferences'], + $newUser['usr_created_date'], + $newUser['usr_created_usr_id'], + $newUser['usr_updated_date'], + $newUser['usr_updated_usr_id'] + ); + + $token1 = uniqid('tok-'); + $token2 = uniqid('tok-'); + + $tokenKey1 = CacheConstants::getCacheTokenKey($domainUser, $token1); + $tokenKey2 = CacheConstants::getCacheTokenKey($domainUser, $token2); + + /** + * Store + */ + $actual = $tokenCache->storeTokenInCache($env, $domainUser, $token1); + $this->assertTrue($actual); + $actual = $tokenCache->storeTokenInCache($env, $domainUser, $token2); + $this->assertTrue($actual); + + /** + * Check the cache + */ + $actual = $cache->has($tokenKey1); + $this->assertTrue($actual); + $actual = $cache->has($tokenKey2); + $this->assertTrue($actual); + + /** + * Invalidate + */ + $actual = $tokenCache->invalidateForUser($env, $domainUser); + $this->assertTrue($actual); + + $actual = $cache->has($tokenKey1); + $this->assertFalse($actual); + $actual = $cache->has($tokenKey2); + $this->assertFalse($actual); + } +} diff --git a/tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php b/tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php new file mode 100644 index 0000000..868ea0d --- /dev/null +++ b/tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php @@ -0,0 +1,99 @@ + + * + * 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\Infrastructure\Encryption; + +use Exception; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManager; +use Phalcon\Api\Domain\Infrastructure\Encryption\TokenCacheInterface; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Encryption\Security\JWT\Token\Token; + +use function rand; + +final class TokenManagerTest extends AbstractUnitTestCase +{ + public function testGetObjectTokenEmpty(): void + { + /** @var EnvManager $env */ + $env = $this->container->get(Container::ENV); + /** @var TokenCacheInterface $tokenCache */ + $tokenCache = $this->container->get(Container::JWT_TOKEN_CACHE); + /** @var JWTToken $jwtToken */ + $jwtToken = $this->container->get(Container::JWT_TOKEN); + + $manager = new TokenManager($tokenCache, $env, $jwtToken); + + $expected = null; + $actual = $manager->getObject(''); + $this->assertSame($expected, $actual); + + $actual = $manager->getObject(null); + $this->assertSame($expected, $actual); + } + + public function testGetObjectWithException(): void + { + /** @var EnvManager $env */ + $env = $this->container->get(Container::ENV); + /** @var TokenCacheInterface $tokenCache */ + $tokenCache = $this->container->get(Container::JWT_TOKEN_CACHE); + $mockJWTToken = $this + ->getMockBuilder(JWTToken::class) + ->disableOriginalConstructor() + ->onlyMethods( + [ + 'getObject', + ] + ) + ->getMock() + ; + $mockJWTToken + ->method('getObject') + ->willThrowException(new Exception('error')) + ; + + $manager = new TokenManager($tokenCache, $env, $mockJWTToken); + + $expected = null; + $actual = $manager->getObject(''); + $this->assertSame($expected, $actual); + + $actual = $manager->getObject(null); + $this->assertSame($expected, $actual); + } + + public function testGetObjectSuccess(): void + { + /** @var EnvManager $env */ + $env = $this->container->get(Container::ENV); + /** @var TokenCacheInterface $tokenCache */ + $tokenCache = $this->container->get(Container::JWT_TOKEN_CACHE); + /** @var JWTToken $jwtToken */ + $jwtToken = $this->container->get(Container::JWT_TOKEN); + $userData = $this->getNewUserData(); + $userData['usr_id'] = rand(1, 100); + + $token = $this->getUserToken($userData); + + $manager = new TokenManager($tokenCache, $env, $jwtToken); + + $expected = Token::class; + $actual = $manager->getObject($token); + $this->assertInstanceOf($expected, $actual); + + } +} diff --git a/tests/Unit/Domain/Infrastructure/Enums/Common/FlagsEnumTest.php b/tests/Unit/Domain/Infrastructure/Enums/Common/FlagsEnumTest.php index 6c95c0f..613f813 100644 --- a/tests/Unit/Domain/Infrastructure/Enums/Common/FlagsEnumTest.php +++ b/tests/Unit/Domain/Infrastructure/Enums/Common/FlagsEnumTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Enums\Common; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Enums\Common; -use Phalcon\Api\Domain\Components\Enums\Common\FlagsEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Common\FlagsEnum; use Phalcon\Api\Tests\AbstractUnitTestCase; final class FlagsEnumTest extends AbstractUnitTestCase diff --git a/tests/Unit/Domain/Infrastructure/Enums/Http/HttpCodesEnumTest.php b/tests/Unit/Domain/Infrastructure/Enums/Http/HttpCodesEnumTest.php index 88f0e0b..b02f254 100644 --- a/tests/Unit/Domain/Infrastructure/Enums/Http/HttpCodesEnumTest.php +++ b/tests/Unit/Domain/Infrastructure/Enums/Http/HttpCodesEnumTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Enums\Http; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Enums\Http; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\DataProvider; diff --git a/tests/Unit/Domain/Infrastructure/Enums/Http/RoutesEnumTest.php b/tests/Unit/Domain/Infrastructure/Enums/Http/RoutesEnumTest.php index 42592d4..df14ca9 100644 --- a/tests/Unit/Domain/Infrastructure/Enums/Http/RoutesEnumTest.php +++ b/tests/Unit/Domain/Infrastructure/Enums/Http/RoutesEnumTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Enums\Http; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Enums\Http; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Enums\Http\RoutesEnum; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\RoutesEnum; use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\DataProvider; diff --git a/tests/Unit/Domain/Infrastructure/Env/Adapters/DotEnvTest.php b/tests/Unit/Domain/Infrastructure/Env/Adapters/DotEnvTest.php index 5d40f00..e4703a7 100644 --- a/tests/Unit/Domain/Infrastructure/Env/Adapters/DotEnvTest.php +++ b/tests/Unit/Domain/Infrastructure/Env/Adapters/DotEnvTest.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Env\Adapters; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Env\Adapters; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Env\Adapters\DotEnv; -use Phalcon\Api\Domain\Components\Env\EnvManager; -use Phalcon\Api\Domain\Components\Exceptions\InvalidConfigurationArgumentException; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Env\Adapters\DotEnv; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Exceptions\InvalidConfigurationArgumentException; use Phalcon\Api\Tests\AbstractUnitTestCase; final class DotEnvTest extends AbstractUnitTestCase diff --git a/tests/Unit/Domain/Infrastructure/Env/EnvFactoryTest.php b/tests/Unit/Domain/Infrastructure/Env/EnvFactoryTest.php index 90aa664..c869af9 100644 --- a/tests/Unit/Domain/Infrastructure/Env/EnvFactoryTest.php +++ b/tests/Unit/Domain/Infrastructure/Env/EnvFactoryTest.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Env; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Env; -use Phalcon\Api\Domain\Components\Env\Adapters\DotEnv; -use Phalcon\Api\Domain\Components\Env\EnvFactory; -use Phalcon\Api\Domain\Components\Exceptions\InvalidConfigurationArgumentException; +use Phalcon\Api\Domain\Infrastructure\Env\Adapters\DotEnv; +use Phalcon\Api\Domain\Infrastructure\Env\EnvFactory; +use Phalcon\Api\Domain\Infrastructure\Exceptions\InvalidConfigurationArgumentException; use Phalcon\Api\Tests\AbstractUnitTestCase; final class EnvFactoryTest extends AbstractUnitTestCase diff --git a/tests/Unit/Domain/Infrastructure/Env/EnvManagerTest.php b/tests/Unit/Domain/Infrastructure/Env/EnvManagerTest.php index 1e8f32c..3ff8f53 100644 --- a/tests/Unit/Domain/Infrastructure/Env/EnvManagerTest.php +++ b/tests/Unit/Domain/Infrastructure/Env/EnvManagerTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Env; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Env; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Infrastructure/Middleware/HealthMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/HealthMiddlewareTest.php index 153a256..e8c8f2b 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/HealthMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/HealthMiddlewareTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Middleware; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Middleware\HealthMiddleware; +use Phalcon\Api\Domain\Infrastructure\Middleware\HealthMiddleware; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Infrastructure/Middleware/NotFoundMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/NotFoundMiddlewareTest.php index 146fac8..e258055 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/NotFoundMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/NotFoundMiddlewareTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Middleware; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Components\Middleware\NotFoundMiddleware; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Middleware\NotFoundMiddleware; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Events\Event; use Phalcon\Mvc\Micro; diff --git a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddlewareTest.php index 0489971..d4bcf8c 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddlewareTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Middleware; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Mvc\Micro; diff --git a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddlewareTest.php index bbc0517..e5d5111 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddlewareTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Middleware; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddlewareTest.php index 5f2de88..c0c04a6 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddlewareTest.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Middleware; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Cache\Cache; diff --git a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddlewareTest.php index f7f5f99..bd96962 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddlewareTest.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Middleware; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenUserMiddlewareTest.php b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenUserMiddlewareTest.php index c3459e0..c412f1d 100644 --- a/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenUserMiddlewareTest.php +++ b/tests/Unit/Domain/Infrastructure/Middleware/ValidateTokenUserMiddlewareTest.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Middleware; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Middleware; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Mvc\Micro; diff --git a/tests/Unit/Domain/Infrastructure/Providers/CacheDataProviderTest.php b/tests/Unit/Domain/Infrastructure/Providers/CacheDataProviderTest.php index a7b8887..3bbbc81 100644 --- a/tests/Unit/Domain/Infrastructure/Providers/CacheDataProviderTest.php +++ b/tests/Unit/Domain/Infrastructure/Providers/CacheDataProviderTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Providers; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Providers; -use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Cache\Cache; diff --git a/tests/Unit/Domain/Infrastructure/Providers/ErrorHandlerProviderTest.php b/tests/Unit/Domain/Infrastructure/Providers/ErrorHandlerProviderTest.php index a77e9ad..6992abe 100644 --- a/tests/Unit/Domain/Infrastructure/Providers/ErrorHandlerProviderTest.php +++ b/tests/Unit/Domain/Infrastructure/Providers/ErrorHandlerProviderTest.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Providers; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Providers; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Env\EnvManager; -use Phalcon\Api\Domain\Components\Providers\ErrorHandlerProvider; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Providers\ErrorHandlerProvider; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Logger\Logger; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Infrastructure/Providers/RouterProviderTest.php b/tests/Unit/Domain/Infrastructure/Providers/RouterProviderTest.php index 0429c1b..d0a993b 100644 --- a/tests/Unit/Domain/Infrastructure/Providers/RouterProviderTest.php +++ b/tests/Unit/Domain/Infrastructure/Providers/RouterProviderTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Phalcon\Api\Tests\Unit\Domain\Components\Providers; +namespace Phalcon\Api\Tests\Unit\Domain\Infrastructure\Providers; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Providers\RouterProvider; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Providers\RouterProvider; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; diff --git a/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php b/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php index fe69a9a..3bc5aae 100644 --- a/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php +++ b/tests/Unit/Domain/Services/Auth/LoginPostServiceTest.php @@ -15,8 +15,8 @@ use Faker\Factory; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Services\Auth\LoginPostService; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; diff --git a/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php b/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php index cf2e648..326be2b 100644 --- a/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php +++ b/tests/Unit/Domain/Services/Auth/LogoutPostServiceTest.php @@ -14,11 +14,11 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\Auth; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\Encryption\JWTToken; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Services\Auth\LoginPostService; use Phalcon\Api\Domain\Services\Auth\LogoutPostService; use Phalcon\Api\Tests\AbstractUnitTestCase; diff --git a/tests/Unit/Domain/Services/Auth/RefreshPostServiceTest.php b/tests/Unit/Domain/Services/Auth/RefreshPostServiceTest.php index 406b74d..fdf5937 100644 --- a/tests/Unit/Domain/Services/Auth/RefreshPostServiceTest.php +++ b/tests/Unit/Domain/Services/Auth/RefreshPostServiceTest.php @@ -14,10 +14,10 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\Auth; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\Encryption\JWTToken; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Encryption\JWTToken; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Domain\Services\Auth\LoginPostService; use Phalcon\Api\Domain\Services\Auth\RefreshPostService; use Phalcon\Api\Tests\AbstractUnitTestCase; diff --git a/tests/Unit/Domain/Services/User/UserServiceDeleteTest.php b/tests/Unit/Domain/Services/User/UserServiceDeleteTest.php index eb3fa4d..dde46b4 100644 --- a/tests/Unit/Domain/Services/User/UserServiceDeleteTest.php +++ b/tests/Unit/Domain/Services/User/UserServiceDeleteTest.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\User; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Services\User\UserDeleteService; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; diff --git a/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php b/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php index 9741943..128da7b 100644 --- a/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php +++ b/tests/Unit/Domain/Services/User/UserServiceDispatchTest.php @@ -14,12 +14,12 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\User; use DateTimeImmutable; -use Phalcon\Api\Domain\Components\Constants\Cache as CacheConstants; -use Phalcon\Api\Domain\Components\Constants\Dates; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\Enums\Http\RoutesEnum; -use Phalcon\Api\Domain\Components\Env\EnvManager; +use Phalcon\Api\Domain\Infrastructure\Constants\Cache as CacheConstants; +use Phalcon\Api\Domain\Infrastructure\Constants\Dates; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\RoutesEnum; +use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; use Phalcon\Cache\Cache; diff --git a/tests/Unit/Domain/Services/User/UserServiceGetTest.php b/tests/Unit/Domain/Services/User/UserServiceGetTest.php index d062474..0bcbd48 100644 --- a/tests/Unit/Domain/Services/User/UserServiceGetTest.php +++ b/tests/Unit/Domain/Services/User/UserServiceGetTest.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\User; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Components\Container; +use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Services\User\UserGetService; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; diff --git a/tests/Unit/Domain/Services/User/UserServicePostTest.php b/tests/Unit/Domain/Services/User/UserServicePostTest.php index b5dd795..d3a92c6 100644 --- a/tests/Unit/Domain/Services/User/UserServicePostTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePostTest.php @@ -16,9 +16,9 @@ use DateTimeImmutable; use PayloadInterop\DomainStatus; use PDOException; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; use Phalcon\Api\Domain\Services\User\UserPostService; use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Services/User/UserServicePutTest.php b/tests/Unit/Domain/Services/User/UserServicePutTest.php index 3513747..799f9a3 100644 --- a/tests/Unit/Domain/Services/User/UserServicePutTest.php +++ b/tests/Unit/Domain/Services/User/UserServicePutTest.php @@ -16,9 +16,9 @@ use DateTimeImmutable; use PayloadInterop\DomainStatus; use PDOException; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\DataSource\User\UserMapper; -use Phalcon\Api\Domain\Components\DataSource\User\UserRepository; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; +use Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories\UserRepository; use Phalcon\Api\Domain\Services\User\UserPutService; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; diff --git a/tests/Unit/Responder/JsonResponderTest.php b/tests/Unit/Responder/JsonResponderTest.php index a31b6b8..52815b9 100644 --- a/tests/Unit/Responder/JsonResponderTest.php +++ b/tests/Unit/Responder/JsonResponderTest.php @@ -14,8 +14,8 @@ namespace Phalcon\Api\Tests\Unit\Responder; use PayloadInterop\DomainStatus; -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Enums\Http\HttpCodesEnum; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Responder\ResponderInterface; use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Domain\Payload; From 172ab904d91677654fab5d43b1bce35e0198101e Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 11 Nov 2025 11:07:18 -0600 Subject: [PATCH 61/74] [#.x] - adjusting references --- public/index.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/index.php b/public/index.php index 9d75c6d..921e41d 100644 --- a/public/index.php +++ b/public/index.php @@ -2,9 +2,9 @@ declare(strict_types=1); -use Phalcon\Api\Domain\Components\Container; -use Phalcon\Api\Domain\Components\Providers\ErrorHandlerProvider; -use Phalcon\Api\Domain\Components\Providers\RouterProvider; +use Phalcon\Api\Domain\Infrastructure\Container; +use Phalcon\Api\Domain\Infrastructure\Providers\ErrorHandlerProvider; +use Phalcon\Api\Domain\Infrastructure\Providers\RouterProvider; use Phalcon\Di\ServiceProviderInterface; use Phalcon\Mvc\Micro; From c3d657195fa77f13c81dec6037452bdca8d85ded Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Tue, 11 Nov 2025 11:17:38 -0600 Subject: [PATCH 62/74] [#.x] - updated readme --- README.md | 64 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index ac5552a..db2f735 100644 --- a/README.md +++ b/README.md @@ -65,17 +65,41 @@ Contains a handler that translate HTTP requests into Domain calls. For example, ### `Domain` layer -#### `Components` +#### `ADR` -##### `Container` +- `Payload`: A uniform result object used across Domain → Responder. +- `Input`: Class collecting request input and used to pass it to the domain +- Interfaces for domain and `Input` -The application uses the `Phalcon\Di\Di` container with minimal components lazy loaded. Each non "core" component is also registered there (i.e. domain services, responder etc.) and all necessary dependencies are injected based on the service definitions. +#### `Infrastructure` -Additionally there are two `Providers` that are also registered in the DI container for further functionality. The `ErrorHandlerProvider` which caters for the starting up/shut down of the application and error logging, and the very important `RoutesProvider` which handles registering all the routes that the application serves. +##### `Constants` + +Classes with constants and helper methods used throughout the application + +##### `DataSource` + +**`Auth`** -##### `Payload` +Contains Data Transfer Objects (DTOs) to move data from input to domain and from database back to domain. A Facade is available for orchestration, sanitizer for input as well as validators. -A uniform result object used across Domain → Responder. +**`Interfaces`** + +Mapper and Sanitizer interfaces + +**`User`** + +Contains Data Transfer Objects (DTOs) to move data from input to domain and from database back to domain. A Facade is available for orchestration, a repository for database operations, sanitizer for input as well as validators. + +**`Validation`** + +Contains the `ValidatorInterface` for all validators, a `Result` object for returning back validation results/errors and the `AbsInt` validator to check the id for `Put` operations. + +##### `Encryption` + +Contains components for JWT handling and passwords. The `Security` component is a wrapper for the `password_*` PHP classes, which are used for password hashing and verification. + +The `TokenManager` offers methods to issue, refresh and revoke tokens. It works in conjunction with the `TokenCache` to store or invalidate tokens stored in Cache (Redis) ##### `Enums` @@ -89,19 +113,15 @@ Finally, the `RoutesEnum` also holds the middleware array, which defines their e The environment manager and adapters. It reads environment variables using [DotEnv][dotenv] as the main adapter but can be extended if necessary. -##### `Validation` - -Contains the `ValidatorInterface` for all validators and a `Result` object for returning back validation results/errors. +##### `Exceptions` -##### `Encryption` +Exception classes used in the application. -Contains components for JWT handling and passwords. The `Security` component is a wrapper for the `password_*` PHP classes, which are used for password hashing and verification. - -The `TokenManager` offers methods to issue, refresh and revoke tokens. It works in conjunction with the `TokenCache` to store or invalidate tokens stored in Cache (Redis) +##### `Container` -##### `DataSource`: +The application uses the `Phalcon\Di\Di` container with minimal components lazy loaded. Each non "core" component is also registered there (i.e. domain services, responder etc.) and all necessary dependencies are injected based on the service definitions. -The `User` / `Auth` folders contain repositories, sanitizers, mappers, validators and facades for each area. +Additionally there are two `Providers` that are also registered in the DI container for further functionality. The `ErrorHandlerProvider` which caters for the starting up/shut down of the application and error logging, and the very important `RoutesProvider` which handles registering all the routes that the application serves. #### `Services`: @@ -156,13 +176,13 @@ There are several middleware registered for this application and they are being The middleware execution order is defined in the `RoutesEnum`. The available middleware is: -- [NotFoundMiddleware.php](src/Domain/Components/Middleware/NotFoundMiddleware.php) -- [HealthMiddleware.php](src/Domain/Components/Middleware/HealthMiddleware.php) -- [ValidateTokenClaimsMiddleware.php](src/Domain/Components/Middleware/ValidateTokenClaimsMiddleware.php) -- [ValidateTokenPresenceMiddleware.php](src/Domain/Components/Middleware/ValidateTokenPresenceMiddleware.php) -- [ValidateTokenRevokedMiddleware.php](src/Domain/Components/Middleware/ValidateTokenRevokedMiddleware.php) -- [ValidateTokenStructureMiddleware.php](src/Domain/Components/Middleware/ValidateTokenStructureMiddleware.php) -- [ValidateTokenUserMiddleware.php](src/Domain/Components/Middleware/ValidateTokenUserMiddleware.php) +- [NotFoundMiddleware.php](src/Domain/Infrastructure/Middleware/NotFoundMiddleware.php) +- [HealthMiddleware.php](src/Domain/Infrastructure/Middleware/HealthMiddleware.php) +- [ValidateTokenClaimsMiddleware.php](src/Domain/Infrastructure/Middleware/ValidateTokenClaimsMiddleware.php) +- [ValidateTokenPresenceMiddleware.php](src/Domain/Infrastructure/Middleware/ValidateTokenPresenceMiddleware.php) +- [ValidateTokenRevokedMiddleware.php](src/Domain/Infrastructure/Middleware/ValidateTokenRevokedMiddleware.php) +- [ValidateTokenStructureMiddleware.php](src/Domain/Infrastructure/Middleware/ValidateTokenStructureMiddleware.php) +- [ValidateTokenUserMiddleware.php](src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php) **NotFoundMiddleware** From b92f934e821fc0e15244bac887543788efe1d519 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:31:01 -0600 Subject: [PATCH 63/74] [#.x] - changing class to final --- src/Domain/Infrastructure/DataSource/Validation/AbsInt.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Domain/Infrastructure/DataSource/Validation/AbsInt.php b/src/Domain/Infrastructure/DataSource/Validation/AbsInt.php index 3d79039..2344291 100644 --- a/src/Domain/Infrastructure/DataSource/Validation/AbsInt.php +++ b/src/Domain/Infrastructure/DataSource/Validation/AbsInt.php @@ -16,7 +16,7 @@ use Phalcon\Filter\Validation; use Phalcon\Filter\Validation\Validator\Numericality; -class AbsInt extends Numericality +final class AbsInt extends Numericality { /** * @var string|null From 80fe750a9849d93780618f1b7e84e1946b5247b8 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:32:07 -0600 Subject: [PATCH 64/74] [#.x] - correcting payload references --- .../Middleware/AbstractMiddleware.php | 5 ++-- src/Responder/JsonResponder.php | 2 +- tests/Unit/Responder/JsonResponderTest.php | 27 +++---------------- 3 files changed, 6 insertions(+), 28 deletions(-) diff --git a/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php b/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php index 998b3c2..3fed350 100644 --- a/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/AbstractMiddleware.php @@ -13,12 +13,11 @@ namespace Phalcon\Api\Domain\Infrastructure\Middleware; -use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\ADR\Payload; use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\Env\EnvManager; use Phalcon\Api\Responder\ResponderInterface; use Phalcon\Api\Responder\ResponderTypes; -use Phalcon\Domain\Payload; use Phalcon\Events\Exception as EventsException; use Phalcon\Http\RequestInterface; use Phalcon\Http\Response\Exception as ResponseException; @@ -100,7 +99,7 @@ protected function halt( 'errors' => $errors, ]; - $payload = new Payload(DomainStatus::SUCCESS, $results); + $payload = Payload::success($results); $responder->__invoke($response, $payload); } diff --git a/src/Responder/JsonResponder.php b/src/Responder/JsonResponder.php index d0c8bbd..066cae1 100644 --- a/src/Responder/JsonResponder.php +++ b/src/Responder/JsonResponder.php @@ -14,9 +14,9 @@ namespace Phalcon\Api\Responder; use Exception as BaseException; +use Phalcon\Api\Domain\ADR\Payload; use Phalcon\Api\Domain\Infrastructure\Constants\Dates; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; -use Phalcon\Domain\Payload; use Phalcon\Http\ResponseInterface; use function sha1; diff --git a/tests/Unit/Responder/JsonResponderTest.php b/tests/Unit/Responder/JsonResponderTest.php index 52815b9..91d82f9 100644 --- a/tests/Unit/Responder/JsonResponderTest.php +++ b/tests/Unit/Responder/JsonResponderTest.php @@ -13,12 +13,11 @@ namespace Phalcon\Api\Tests\Unit\Responder; -use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\Infrastructure\Container; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Responder\ResponderInterface; use Phalcon\Api\Tests\AbstractUnitTestCase; -use Phalcon\Domain\Payload; +use Phalcon\Api\Domain\ADR\Payload; use Phalcon\Http\ResponseInterface; use PHPUnit\Framework\Attributes\BackupGlobals; @@ -38,17 +37,7 @@ public function testFailure(): void $responder = $this->container->get(Container::RESPONDER_JSON); $errorContent = uniqid('error-'); - $payload = new Payload( - DomainStatus::UNAUTHORIZED, - [ - 'code' => HttpCodesEnum::Unauthorized->value, - 'message' => HttpCodesEnum::Unauthorized->text(), - 'data' => [], - 'errors' => [ - [1234 => $errorContent], - ], - ] - ); + $payload = Payload::unauthorized([[1234 => $errorContent]]); ob_start(); $outputResponse = $responder->__invoke($response, $payload); @@ -115,17 +104,7 @@ public function testSuccess(): void $responder = $this->container->getShared(Container::RESPONDER_JSON); $dataContent = uniqid('data-'); - $payload = new Payload( - DomainStatus::SUCCESS, - [ - 'code' => HttpCodesEnum::OK->value, - 'message' => HttpCodesEnum::OK->text(), - 'data' => [ - $dataContent, - ], - 'errors' => [], - ] - ); + $payload = Payload::success([$dataContent]); ob_start(); $outputResponse = $responder->__invoke($response, $payload); From 5f1f742f33b83df573d05a9fffc31a6cc4a0d3ee Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:32:43 -0600 Subject: [PATCH 65/74] [#.x] - switching sanitizers to use enums --- .../DataSource/AbstractSanitizer.php | 76 ++++++++++++++++ .../Auth/Sanitizers/AuthSanitizer.php | 67 +------------- .../User/Sanitizers/UserSanitizer.php | 87 +------------------ .../Enums/Sanitizers/AuthSanitizersEnum.php | 36 ++++++++ .../Sanitizers/SanitizersEnumInterface.php | 23 +++++ .../Enums/Sanitizers/UserSanitizersEnum.php | 63 ++++++++++++++ 6 files changed, 206 insertions(+), 146 deletions(-) create mode 100644 src/Domain/Infrastructure/DataSource/AbstractSanitizer.php create mode 100644 src/Domain/Infrastructure/Enums/Sanitizers/AuthSanitizersEnum.php create mode 100644 src/Domain/Infrastructure/Enums/Sanitizers/SanitizersEnumInterface.php create mode 100644 src/Domain/Infrastructure/Enums/Sanitizers/UserSanitizersEnum.php diff --git a/src/Domain/Infrastructure/DataSource/AbstractSanitizer.php b/src/Domain/Infrastructure/DataSource/AbstractSanitizer.php new file mode 100644 index 0000000..bd61f7d --- /dev/null +++ b/src/Domain/Infrastructure/DataSource/AbstractSanitizer.php @@ -0,0 +1,76 @@ + + * + * 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\Infrastructure\DataSource; + +use Phalcon\Api\Domain\ADR\InputTypes; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; +use Phalcon\Api\Domain\Infrastructure\Enums\Sanitizers\SanitizersEnumInterface; +use Phalcon\Filter\FilterInterface; + +/** + * @phpstan-import-type TInputSanitize from InputTypes + */ +abstract class AbstractSanitizer implements SanitizerInterface +{ + protected string $enum = ''; + + public function __construct( + private readonly FilterInterface $filter, + ) { + } + + /** + * @param TInputSanitize $input + * + * @return TInputSanitize + */ + public function sanitize(array $input): array + { + $enum = $this->enum; + /** @var SanitizersEnumInterface[] $fields */ + $fields = $enum::cases(); + + /** + * Set defaults + */ + $sanitized = []; + + /** + * Sanitize all fields. The fields can be `null` meaning they + * were not defined in the input array. If the value exists + * then it will be sanitized. + * + * If there is no sanitizer defined, the value will be left intact. + */ + /** @var SanitizersEnumInterface $field */ + foreach ($fields as $field) { + $value = $input[$field->name] ?? $field->default(); + + if (null !== $value) { + $sanitizer = $field->sanitizer(); + if (true !== empty($sanitizer)) { + $value = $this->filter->sanitize($value, $sanitizer); + } + } + + $sanitized[$field->name] = $value; + } + + /** + * Return sanitized array + */ + /** @var TInputSanitize $sanitized */ + return $sanitized; + } +} diff --git a/src/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizer.php b/src/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizer.php index 8fde303..8f8c6ac 100644 --- a/src/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizer.php +++ b/src/Domain/Infrastructure/DataSource/Auth/Sanitizers/AuthSanitizer.php @@ -13,69 +13,10 @@ namespace Phalcon\Api\Domain\Infrastructure\DataSource\Auth\Sanitizers; -use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; -use Phalcon\Filter\Filter; -use Phalcon\Filter\FilterInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\AbstractSanitizer; +use Phalcon\Api\Domain\Infrastructure\Enums\Sanitizers\AuthSanitizersEnum; -/** - * @phpstan-import-type TAuthInput from InputTypes - */ -final class AuthSanitizer implements SanitizerInterface +final class AuthSanitizer extends AbstractSanitizer { - public function __construct( - private readonly FilterInterface $filter, - ) { - } - - /** - * Return a sanitized array of the input - * - * @param TAuthInput $input - * - * @return TAuthInput - */ - public function sanitize(array $input): array - { - /** @var array $fields */ - $fields = [ - 'email' => null, - 'password' => null, - 'token' => null, - ]; - - /** - * Sanitize all the fields. The fields can be `null` meaning they - * were not defined with the input or a value. If the value exists - * we will sanitize it - */ - $sanitized = []; - foreach ($fields as $name => $defaultValue) { - $value = $input[$name] ?? $defaultValue; - - if (null !== $value) { - $sanitizer = $this->getSanitizer($name); - if (true !== empty($sanitizer)) { - $value = $this->filter->sanitize($value, $sanitizer); - } - } - $sanitized[$name] = $value; - } - - /** @var TAuthInput $sanitized */ - return $sanitized; - } - - /** - * @param string $name - * - * @return string - */ - private function getSanitizer(string $name): string - { - return match ($name) { - 'email' => Filter::FILTER_EMAIL, - default => '', - }; - } + protected string $enum = AuthSanitizersEnum::class; } diff --git a/src/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizer.php b/src/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizer.php index 762db1a..f158eb4 100644 --- a/src/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizer.php +++ b/src/Domain/Infrastructure/DataSource/User/Sanitizers/UserSanitizer.php @@ -13,89 +13,10 @@ namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Sanitizers; -use Phalcon\Api\Domain\ADR\InputTypes; -use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\SanitizerInterface; -use Phalcon\Filter\Filter; -use Phalcon\Filter\FilterInterface; +use Phalcon\Api\Domain\Infrastructure\DataSource\AbstractSanitizer; +use Phalcon\Api\Domain\Infrastructure\Enums\Sanitizers\UserSanitizersEnum; -/** - * @phpstan-import-type TUserInput from InputTypes - */ -final class UserSanitizer implements SanitizerInterface +final class UserSanitizer extends AbstractSanitizer { - public function __construct( - private readonly FilterInterface $filter, - ) { - } - - /** - * Return a sanitized array of the input - * - * @param TUserInput $input - * - * @return TUserInput - */ - public function sanitize(array $input): array - { - $fields = [ - 'id' => 0, - 'status' => 0, - 'email' => null, - 'password' => null, - 'namePrefix' => null, - 'nameFirst' => null, - 'nameLast' => null, - 'nameMiddle' => null, - 'nameSuffix' => null, - 'issuer' => null, - 'tokenPassword' => null, - 'tokenId' => null, - 'preferences' => null, - 'createdDate' => null, - 'createdUserId' => 0, - 'updatedDate' => null, - 'updatedUserId' => 0, - ]; - - /** - * Sanitize all the fields. The fields can be `null` meaning they - * were not defined with the input or a value. If the value exists - * we will sanitize it - */ - $sanitized = []; - foreach ($fields as $name => $defaultValue) { - $value = $input[$name] ?? $defaultValue; - - if (null !== $value) { - $sanitizer = $this->getSanitizer($name); - if (true !== empty($sanitizer)) { - $value = $this->filter->sanitize($value, $sanitizer); - } - } - $sanitized[$name] = $value; - } - - /** @var TUserInput $sanitized */ - return $sanitized; - } - - /** - * @param string $name - * - * @return string - */ - private function getSanitizer(string $name): string - { - return match ($name) { - 'id', - 'status', - 'createdUserId', - 'updatedUserId' => Filter::FILTER_ABSINT, - 'email' => Filter::FILTER_EMAIL, - 'password', - 'tokenId', - 'tokenPassword' => '', // Password will be distorted - default => Filter::FILTER_STRING, - }; - } + protected string $enum = UserSanitizersEnum::class; } diff --git a/src/Domain/Infrastructure/Enums/Sanitizers/AuthSanitizersEnum.php b/src/Domain/Infrastructure/Enums/Sanitizers/AuthSanitizersEnum.php new file mode 100644 index 0000000..2018e0f --- /dev/null +++ b/src/Domain/Infrastructure/Enums/Sanitizers/AuthSanitizersEnum.php @@ -0,0 +1,36 @@ + + * + * 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\Infrastructure\Enums\Sanitizers; + +use Phalcon\Filter\Filter; + +enum AuthSanitizersEnum implements SanitizersEnumInterface +{ + case email; + case password; + case token; + + public function default(): mixed + { + return null; + } + + public function sanitizer(): string + { + return match ($this) { + self::email => Filter::FILTER_EMAIL, + default => '' + }; + } +} diff --git a/src/Domain/Infrastructure/Enums/Sanitizers/SanitizersEnumInterface.php b/src/Domain/Infrastructure/Enums/Sanitizers/SanitizersEnumInterface.php new file mode 100644 index 0000000..0738cfd --- /dev/null +++ b/src/Domain/Infrastructure/Enums/Sanitizers/SanitizersEnumInterface.php @@ -0,0 +1,23 @@ + + * + * 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\Infrastructure\Enums\Sanitizers; + +use UnitEnum; + +interface SanitizersEnumInterface extends UnitEnum +{ + public function default(): mixed; + + public function sanitizer(): string; +} diff --git a/src/Domain/Infrastructure/Enums/Sanitizers/UserSanitizersEnum.php b/src/Domain/Infrastructure/Enums/Sanitizers/UserSanitizersEnum.php new file mode 100644 index 0000000..ff61d68 --- /dev/null +++ b/src/Domain/Infrastructure/Enums/Sanitizers/UserSanitizersEnum.php @@ -0,0 +1,63 @@ + + * + * 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\Infrastructure\Enums\Sanitizers; + +use Phalcon\Filter\Filter; + +enum UserSanitizersEnum implements SanitizersEnumInterface +{ + case id; + case status; + case email; + case password; + case namePrefix; + case nameFirst; + case nameLast; + case nameMiddle; + case nameSuffix; + case issuer; + case tokenPassword; + case tokenId; + case preferences; + case createdDate; + case createdUserId; + case updatedDate; + case updatedUserId; + + public function default(): mixed + { + return match ($this) { + self::id, + self::status, + self::createdUserId, + self::updatedUserId => 0, + default => null + }; + } + + public function sanitizer(): string + { + return match ($this) { + self::id, + self::status, + self::createdUserId, + self::updatedUserId => Filter::FILTER_ABSINT, + self::email => Filter::FILTER_EMAIL, + self::password, + self::tokenId, + self::tokenPassword => '', // Password will be distorted + default => Filter::FILTER_STRING + }; + } +} From 4db5beea33070ced67a3d20e5212636dc9447a50 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:33:38 -0600 Subject: [PATCH 66/74] [#.x] - refactoring and correcting validator references --- .../DataSource/Auth/Validators/AuthLoginValidator.php | 4 ++-- .../DataSource/Auth/Validators/AuthTokenValidator.php | 4 ++-- .../DataSource/User/Validators/UserValidator.php | 4 ++-- .../DataSource/User/Validators/UserValidatorUpdate.php | 4 ++-- .../DataSource/Validation/AbstractValidator.php | 2 +- .../AuthLoginValidatorEnum.php} | 4 ++-- .../AuthTokenValidatorEnum.php} | 4 ++-- .../UserInputInsertEnum.php => Validators/UserInsertEnum.php} | 4 ++-- .../UserInputUpdateEnum.php => Validators/UserUpdateEnum.php} | 4 ++-- .../Enums/{Input => Validators}/ValidatorEnumInterface.php | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) rename src/Domain/Infrastructure/Enums/{Input/AuthLoginInputEnum.php => Validators/AuthLoginValidatorEnum.php} (84%) rename src/Domain/Infrastructure/Enums/{Input/AuthTokenInputEnum.php => Validators/AuthTokenValidatorEnum.php} (77%) rename src/Domain/Infrastructure/Enums/{Input/UserInputInsertEnum.php => Validators/UserInsertEnum.php} (85%) rename src/Domain/Infrastructure/Enums/{Input/UserInputUpdateEnum.php => Validators/UserUpdateEnum.php} (88%) rename src/Domain/Infrastructure/Enums/{Input => Validators}/ValidatorEnumInterface.php (87%) diff --git a/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidator.php b/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidator.php index 45489cf..a987221 100644 --- a/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidator.php +++ b/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthLoginValidator.php @@ -17,11 +17,11 @@ use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbstractValidator; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\Result; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Infrastructure\Enums\Input\AuthLoginInputEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Validators\AuthLoginValidatorEnum; final class AuthLoginValidator extends AbstractValidator { - protected string $fields = AuthLoginInputEnum::class; + protected string $fields = AuthLoginValidatorEnum::class; /** * Validate a AuthInput and return an array of errors. diff --git a/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidator.php b/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidator.php index ecd60f4..6b2ed17 100644 --- a/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidator.php +++ b/src/Domain/Infrastructure/DataSource/Auth/Validators/AuthTokenValidator.php @@ -20,13 +20,13 @@ use Phalcon\Api\Domain\Infrastructure\Encryption\TokenManagerInterface; use Phalcon\Api\Domain\Infrastructure\Enums\Common\JWTEnum; use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; -use Phalcon\Api\Domain\Infrastructure\Enums\Input\AuthTokenInputEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Validators\AuthTokenValidatorEnum; use Phalcon\Encryption\Security\JWT\Token\Token; use Phalcon\Filter\Validation\ValidationInterface; final class AuthTokenValidator extends AbstractValidator { - protected string $fields = AuthTokenInputEnum::class; + protected string $fields = AuthTokenValidatorEnum::class; public function __construct( private TokenManagerInterface $tokenManager, diff --git a/src/Domain/Infrastructure/DataSource/User/Validators/UserValidator.php b/src/Domain/Infrastructure/DataSource/User/Validators/UserValidator.php index c34c29e..729aee7 100644 --- a/src/Domain/Infrastructure/DataSource/User/Validators/UserValidator.php +++ b/src/Domain/Infrastructure/DataSource/User/Validators/UserValidator.php @@ -14,11 +14,11 @@ namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbstractValidator; -use Phalcon\Api\Domain\Infrastructure\Enums\Input\UserInputInsertEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Validators\UserInsertEnum; final class UserValidator extends AbstractValidator { use UserValidatorTrait; - protected string $fields = UserInputInsertEnum::class; + protected string $fields = UserInsertEnum::class; } diff --git a/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdate.php b/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdate.php index a8acfaa..291eea1 100644 --- a/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdate.php +++ b/src/Domain/Infrastructure/DataSource/User/Validators/UserValidatorUpdate.php @@ -14,11 +14,11 @@ namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Validators; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbstractValidator; -use Phalcon\Api\Domain\Infrastructure\Enums\Input\UserInputUpdateEnum; +use Phalcon\Api\Domain\Infrastructure\Enums\Validators\UserUpdateEnum; final class UserValidatorUpdate extends AbstractValidator { use UserValidatorTrait; - protected string $fields = UserInputUpdateEnum::class; + protected string $fields = UserUpdateEnum::class; } diff --git a/src/Domain/Infrastructure/DataSource/Validation/AbstractValidator.php b/src/Domain/Infrastructure/DataSource/Validation/AbstractValidator.php index 95cae33..2048065 100644 --- a/src/Domain/Infrastructure/DataSource/Validation/AbstractValidator.php +++ b/src/Domain/Infrastructure/DataSource/Validation/AbstractValidator.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Domain\Infrastructure\DataSource\Validation; use Phalcon\Api\Domain\Infrastructure\DataSource\Auth\DTO\AuthInput; -use Phalcon\Api\Domain\Infrastructure\Enums\Input\ValidatorEnumInterface; +use Phalcon\Api\Domain\Infrastructure\Enums\Validators\ValidatorEnumInterface; use Phalcon\Filter\Validation\ValidationInterface; use Phalcon\Filter\Validation\ValidatorInterface as PhalconValidator; diff --git a/src/Domain/Infrastructure/Enums/Input/AuthLoginInputEnum.php b/src/Domain/Infrastructure/Enums/Validators/AuthLoginValidatorEnum.php similarity index 84% rename from src/Domain/Infrastructure/Enums/Input/AuthLoginInputEnum.php rename to src/Domain/Infrastructure/Enums/Validators/AuthLoginValidatorEnum.php index 700e722..cc536cf 100644 --- a/src/Domain/Infrastructure/Enums/Input/AuthLoginInputEnum.php +++ b/src/Domain/Infrastructure/Enums/Validators/AuthLoginValidatorEnum.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Input; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Validators; use Phalcon\Filter\Validation\Validator\Email; use Phalcon\Filter\Validation\Validator\PresenceOf; -enum AuthLoginInputEnum implements ValidatorEnumInterface +enum AuthLoginValidatorEnum implements ValidatorEnumInterface { case email; case password; diff --git a/src/Domain/Infrastructure/Enums/Input/AuthTokenInputEnum.php b/src/Domain/Infrastructure/Enums/Validators/AuthTokenValidatorEnum.php similarity index 77% rename from src/Domain/Infrastructure/Enums/Input/AuthTokenInputEnum.php rename to src/Domain/Infrastructure/Enums/Validators/AuthTokenValidatorEnum.php index 4af780a..70b7df4 100644 --- a/src/Domain/Infrastructure/Enums/Input/AuthTokenInputEnum.php +++ b/src/Domain/Infrastructure/Enums/Validators/AuthTokenValidatorEnum.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Input; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Validators; use Phalcon\Filter\Validation\Validator\PresenceOf; -enum AuthTokenInputEnum implements ValidatorEnumInterface +enum AuthTokenValidatorEnum implements ValidatorEnumInterface { case token; diff --git a/src/Domain/Infrastructure/Enums/Input/UserInputInsertEnum.php b/src/Domain/Infrastructure/Enums/Validators/UserInsertEnum.php similarity index 85% rename from src/Domain/Infrastructure/Enums/Input/UserInputInsertEnum.php rename to src/Domain/Infrastructure/Enums/Validators/UserInsertEnum.php index 438cbde..f9a11f7 100644 --- a/src/Domain/Infrastructure/Enums/Input/UserInputInsertEnum.php +++ b/src/Domain/Infrastructure/Enums/Validators/UserInsertEnum.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Input; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Validators; use Phalcon\Filter\Validation\Validator\Email; use Phalcon\Filter\Validation\Validator\PresenceOf; -enum UserInputInsertEnum implements ValidatorEnumInterface +enum UserInsertEnum implements ValidatorEnumInterface { case email; case password; diff --git a/src/Domain/Infrastructure/Enums/Input/UserInputUpdateEnum.php b/src/Domain/Infrastructure/Enums/Validators/UserUpdateEnum.php similarity index 88% rename from src/Domain/Infrastructure/Enums/Input/UserInputUpdateEnum.php rename to src/Domain/Infrastructure/Enums/Validators/UserUpdateEnum.php index e4449d6..31ee31f 100644 --- a/src/Domain/Infrastructure/Enums/Input/UserInputUpdateEnum.php +++ b/src/Domain/Infrastructure/Enums/Validators/UserUpdateEnum.php @@ -11,13 +11,13 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Input; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Validators; use Phalcon\Api\Domain\Infrastructure\DataSource\Validation\AbsInt; use Phalcon\Filter\Validation\Validator\Email; use Phalcon\Filter\Validation\Validator\PresenceOf; -enum UserInputUpdateEnum implements ValidatorEnumInterface +enum UserUpdateEnum implements ValidatorEnumInterface { case id; case email; diff --git a/src/Domain/Infrastructure/Enums/Input/ValidatorEnumInterface.php b/src/Domain/Infrastructure/Enums/Validators/ValidatorEnumInterface.php similarity index 87% rename from src/Domain/Infrastructure/Enums/Input/ValidatorEnumInterface.php rename to src/Domain/Infrastructure/Enums/Validators/ValidatorEnumInterface.php index 9c0fdba..3821a33 100644 --- a/src/Domain/Infrastructure/Enums/Input/ValidatorEnumInterface.php +++ b/src/Domain/Infrastructure/Enums/Validators/ValidatorEnumInterface.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Phalcon\Api\Domain\Infrastructure\Enums\Input; +namespace Phalcon\Api\Domain\Infrastructure\Enums\Validators; use UnitEnum; From 4278501208dee6070ed9aae14851605cfb54bfa7 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:33:50 -0600 Subject: [PATCH 67/74] [#.x] - fixing typo --- .../Infrastructure/Middleware/ValidateTokenUserMiddleware.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php b/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php index 3b3ff39..4dc4ec1 100644 --- a/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php +++ b/src/Domain/Infrastructure/Middleware/ValidateTokenUserMiddleware.php @@ -64,7 +64,7 @@ public function call(Micro $application): bool /** * If we are here everything is fine and we need to keep the user - * as a "session" user in the transport + * as a "session" user in the registry */ $registry->set('user', $domainUser); From ec8adc4653b9277ddc7c843309b078f29b9e2389 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:34:07 -0600 Subject: [PATCH 68/74] [#.x] - reformatting --- .../Infrastructure/Enums/Http/RoutesEnum.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php b/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php index 3e4bf35..32ab547 100644 --- a/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php +++ b/src/Domain/Infrastructure/Enums/Http/RoutesEnum.php @@ -22,18 +22,19 @@ */ enum RoutesEnum: int { - /** - * Methods - */ - public const DELETE = 'delete'; /** * Events */ public const EVENT_BEFORE = 'before'; public const EVENT_FINISH = 'finish'; - public const GET = 'get'; - public const POST = 'post'; - public const PUT = 'put'; + + /** + * Methods + */ + public const DELETE = 'delete'; + public const GET = 'get'; + public const POST = 'post'; + public const PUT = 'put'; case authLoginPost = 11; case authLogoutPost = 12; From e6922e2fa3c0af41acb2812936f3e83e35f0fac4 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:34:24 -0600 Subject: [PATCH 69/74] [#.x] - fixing payload reference --- src/Responder/ResponderInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Responder/ResponderInterface.php b/src/Responder/ResponderInterface.php index 7b5342a..dfaceb2 100644 --- a/src/Responder/ResponderInterface.php +++ b/src/Responder/ResponderInterface.php @@ -13,7 +13,7 @@ namespace Phalcon\Api\Responder; -use Phalcon\Domain\Payload; +use Phalcon\Api\Domain\ADR\Payload; use Phalcon\Http\ResponseInterface; interface ResponderInterface From 0bc5f3d166080263fdd1e4966fb5e81d2360bcd6 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:34:34 -0600 Subject: [PATCH 70/74] [#.x] - phpcs --- .../Enums/Container/CommonDefinitionsEnum.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php b/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php index 0f5547f..c3f426f 100644 --- a/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php +++ b/src/Domain/Infrastructure/Enums/Container/CommonDefinitionsEnum.php @@ -37,19 +37,19 @@ enum CommonDefinitionsEnum public function definition(): array { return match ($this) { - self::EventsManager => [ + self::EventsManager => [ 'className' => EventsManager::class, 'calls' => [ [ - 'method' => 'enablePriorities', + 'method' => 'enablePriorities', 'arguments' => [ [ - 'type' => 'parameter', + 'type' => 'parameter', 'value' => true, - ] - ] - ] - ] + ], + ], + ], + ], ], self::JWTToken => [ 'className' => JWTToken::class, From b8a7607eaffa914c25c9b7a84437d6ae9a4bb92b Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:34:45 -0600 Subject: [PATCH 71/74] [#.x] - correcting test --- tests/Unit/Action/ActionHandlerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Action/ActionHandlerTest.php b/tests/Unit/Action/ActionHandlerTest.php index c7463a6..a572428 100644 --- a/tests/Unit/Action/ActionHandlerTest.php +++ b/tests/Unit/Action/ActionHandlerTest.php @@ -39,8 +39,8 @@ public function testInvoke(): void $responder = $this->container->getShared(Container::RESPONDER_JSON); $getData = [ - 'key' => uniqid('key-'), - 'data' => [ + 'key' => uniqid('key-'), + 'value' => [ uniqid('data-'), ], ]; From e0bae456b0338b7cec0261648d0e432de1dfbfa3 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:35:06 -0600 Subject: [PATCH 72/74] [#.x] - minor changes for clarity --- .../User/Repositories/UserRepository.php | 22 ++++++++++--------- .../Repositories/UserRepositoryInterface.php | 10 ++++----- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepository.php b/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepository.php index b57a260..7052084 100644 --- a/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepository.php +++ b/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepository.php @@ -14,8 +14,8 @@ namespace Phalcon\Api\Domain\Infrastructure\DataSource\User\Repositories; use Phalcon\Api\Domain\Infrastructure\DataSource\AbstractRepository; +use Phalcon\Api\Domain\Infrastructure\DataSource\Interfaces\MapperInterface; use Phalcon\Api\Domain\Infrastructure\DataSource\User\DTO\User; -use Phalcon\Api\Domain\Infrastructure\DataSource\User\Mappers\UserMapper; use Phalcon\Api\Domain\Infrastructure\DataSource\User\UserTypes; use Phalcon\Api\Domain\Infrastructure\Enums\Common\FlagsEnum; use Phalcon\DataMapper\Pdo\Connection; @@ -41,7 +41,7 @@ class UserRepository extends AbstractRepository implements UserRepositoryInterfa public function __construct( Connection $connection, - private readonly UserMapper $mapper, + private readonly MapperInterface $mapper, ) { parent::__construct($connection); } @@ -110,16 +110,17 @@ public function findOneBy(array $criteria): ?User /** - * @param TUserDbRecordOptional $user + * + * @param TUserDbRecordOptional $columns * * @return int */ - public function insert(array $user): int + public function insert(array $columns): int { $insert = Insert::new($this->connection); $insert ->into($this->table) - ->columns($user) + ->columns($columns) ->perform() ; @@ -127,20 +128,21 @@ public function insert(array $user): int } /** - * @param TUserDbRecordOptional $user + * @param int $recordId + * @param TUserDbRecordOptional $columns * * @return int */ - public function update(int $userId, array $user): int + public function update(int $recordId, array $columns): int { $update = Update::new($this->connection); $update ->table($this->table) - ->columns($user) - ->where('usr_id = ', $userId) + ->columns($columns) + ->where($this->idField . ' = ', $recordId) ->perform() ; - return $userId; + return $recordId; } } diff --git a/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryInterface.php b/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryInterface.php index 5c72fac..a8a1572 100644 --- a/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryInterface.php +++ b/src/Domain/Infrastructure/DataSource/User/Repositories/UserRepositoryInterface.php @@ -58,17 +58,17 @@ public function findById(int $recordId): ?User; public function findOneBy(array $criteria): ?User; /** - * @param TUserDbRecordOptional $user + * @param TUserDbRecordOptional $columns * * @return int */ - public function insert(array $user): int; + public function insert(array $columns): int; /** - * @param int $userId - * @param TUserDbRecordOptional $user + * @param int $recordId + * @param TUserDbRecordOptional $columns * * @return int */ - public function update(int $userId, array $user): int; + public function update(int $recordId, array $columns): int; } From 8c45d18e91fbdbe769037b5769a474fab5fb42b7 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:35:21 -0600 Subject: [PATCH 73/74] [#.x] - reworked payload for better results --- src/Domain/ADR/Payload.php | 85 +++++++++++++++++++++------ tests/Unit/Domain/ADR/PayloadTest.php | 31 ++++++---- 2 files changed, 88 insertions(+), 28 deletions(-) diff --git a/src/Domain/ADR/Payload.php b/src/Domain/ADR/Payload.php index 7f1d7b7..dc211af 100644 --- a/src/Domain/ADR/Payload.php +++ b/src/Domain/ADR/Payload.php @@ -14,19 +14,26 @@ namespace Phalcon\Api\Domain\ADR; use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Responder\ResponderTypes; use Phalcon\Domain\Payload as PhalconPayload; +use function array_key_exists; +use function var_dump; + /** * @phpstan-import-type TData from ResponderTypes * @phpstan-import-type TErrors from ResponderTypes + * @phpstan-import-type TResponsePayload from ResponderTypes + * @phpstan-type TPayloadDataInput TData|TResponsePayload + * @phpstan-type TPayloadErrorInput TErrors|TResponsePayload */ final class Payload extends PhalconPayload { /** - * @param string $status - * @param TData $data - * @param TErrors $errors + * @param string $status + * @param TPayloadDataInput $data + * @param TPayloadDataInput $errors */ private function __construct( string $status, @@ -34,18 +41,8 @@ private function __construct( array $errors = [] ) { $result = []; - - if (true !== empty($data)) { - $result = [ - 'data' => $data, - ]; - } - - if (true !== empty($errors)) { - $result = [ - 'errors' => $errors, - ]; - } + $result = $this->mergePart($result, $data, 'data'); + $result = $this->mergePart($result, $errors, 'errors'); parent::__construct($status, $result); } @@ -97,7 +94,13 @@ public static function notFound(): self { return new self( status: DomainStatus::NOT_FOUND, - errors: [['Record(s) not found']] + errors: [ + 'code' => HttpCodesEnum::NotFound->value, + 'message' => HttpCodesEnum::NotFound->text(), + 'data' => [], + 'errors' => [['Record(s) not found']], + ] + ); } @@ -118,7 +121,15 @@ public static function success(array $data): self */ public static function unauthorized(array $errors): self { - return new self(status: DomainStatus::UNAUTHORIZED, errors: $errors); + return new self( + status: DomainStatus::UNAUTHORIZED, + errors:[ + 'code' => HttpCodesEnum::Unauthorized->value, + 'message' => HttpCodesEnum::Unauthorized->text(), + 'data' => [], + 'errors' => $errors, + ] + ); } /** @@ -128,6 +139,44 @@ public static function unauthorized(array $errors): self */ public static function updated(array $data): self { - return new self(DomainStatus::UPDATED, $data); + return new self( + DomainStatus::UPDATED, + [ + 'data' => $data + ] + ); } + + /** + * Merge a part into the result. If the part already contains the + * $key, assume it's a preformatted payload and return it. Otherwise + * attach the part under the $key. + * + * @param TPayloadDataInput $existing + * @param TPayloadDataInput $element + * @param string $key + * + * @return TPayloadDataInput + */ + private function mergePart( + array $existing, + array $element, + string $key + ): array { + if (empty($element)) { + return $existing; + } + + if (array_key_exists($key, $element)) { + return $element; + } + + /** + * preserve any existing keys in $existing and add the new named key + */ + $existing[$key] = $element; + + return $existing; + } + } diff --git a/tests/Unit/Domain/ADR/PayloadTest.php b/tests/Unit/Domain/ADR/PayloadTest.php index 4350b35..4ec98c7 100644 --- a/tests/Unit/Domain/ADR/PayloadTest.php +++ b/tests/Unit/Domain/ADR/PayloadTest.php @@ -15,11 +15,12 @@ use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\ADR\Payload; +use Phalcon\Api\Domain\Infrastructure\Enums\Http\HttpCodesEnum; use Phalcon\Api\Tests\AbstractUnitTestCase; final class PayloadTest extends AbstractUnitTestCase { - public function testCreatedContainsDataAndStatusCreated(): void + public function testCreated(): void { $data = ['id' => 1]; $payload = Payload::created($data); @@ -37,7 +38,7 @@ public function testCreatedContainsDataAndStatusCreated(): void $this->assertSame($expected, $actual); } - public function testDeletedContainsDataAndStatusDeleted(): void + public function testDeleted(): void { $data = ['deleted' => true]; $payload = Payload::deleted($data); @@ -51,7 +52,7 @@ public function testDeletedContainsDataAndStatusDeleted(): void $this->assertSame($expected, $actual); } - public function testUpdatedContainsDataAndStatusUpdated(): void + public function testUpdated(): void { $data = ['updated' => true]; $payload = Payload::updated($data); @@ -65,7 +66,7 @@ public function testUpdatedContainsDataAndStatusUpdated(): void $this->assertSame($expected, $actual); } - public function testSuccessContainsDataAndStatusSuccess(): void + public function testSuccess(): void { $data = ['items' => [1, 2, 3]]; $payload = Payload::success($data); @@ -79,7 +80,7 @@ public function testSuccessContainsDataAndStatusSuccess(): void $this->assertSame($expected, $actual); } - public function testErrorContainsErrorsAndStatusError(): void + public function testError(): void { $errors = [['something' => 'went wrong']]; $payload = Payload::error($errors); @@ -93,7 +94,7 @@ public function testErrorContainsErrorsAndStatusError(): void $this->assertSame($expected, $actual); } - public function testInvalidContainsErrorsAndStatusInvalid(): void + public function testInvalid(): void { $errors = [['field' => 'invalid']]; $payload = Payload::invalid($errors); @@ -107,7 +108,7 @@ public function testInvalidContainsErrorsAndStatusInvalid(): void $this->assertSame($expected, $actual); } - public function testUnauthorizedContainsErrorsAndStatusUnauthorized(): void + public function testUnauthorized(): void { $errors = [['reason' => 'no access']]; $payload = Payload::unauthorized($errors); @@ -116,12 +117,17 @@ public function testUnauthorizedContainsErrorsAndStatusUnauthorized(): void $actual = $payload->getStatus(); $this->assertSame($expected, $actual); - $expected = ['errors' => $errors]; + $expected = [ + 'code' => HttpCodesEnum::Unauthorized->value, + 'message' => HttpCodesEnum::Unauthorized->text(), + 'data' => [], + 'errors' => $errors + ]; $actual = $payload->getResult(); $this->assertSame($expected, $actual); } - public function testNotFoundReturnsDefaultErrorAndStatusNotFound(): void + public function testNotFound(): void { $payload = Payload::notFound(); @@ -129,7 +135,12 @@ public function testNotFoundReturnsDefaultErrorAndStatusNotFound(): void $actual = $payload->getStatus(); $this->assertSame($expected, $actual); - $expected = ['errors' => [['Record(s) not found']]]; + $expected = [ + 'code' => HttpCodesEnum::NotFound->value, + 'message' => HttpCodesEnum::NotFound->text(), + 'data' => [], + 'errors' => [['Record(s) not found']] + ]; $actual = $payload->getResult(); $this->assertSame($expected, $actual); } From c8ec7f304f3626996524b9a5bb11fcc96ed32a4a Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Thu, 13 Nov 2025 08:35:44 -0600 Subject: [PATCH 74/74] [#.x] - phpcs --- src/Domain/ADR/Payload.php | 2 -- tests/Unit/Domain/ADR/InputTest.php | 1 - .../Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php | 1 - 3 files changed, 4 deletions(-) diff --git a/src/Domain/ADR/Payload.php b/src/Domain/ADR/Payload.php index dc211af..aad2cfb 100644 --- a/src/Domain/ADR/Payload.php +++ b/src/Domain/ADR/Payload.php @@ -100,7 +100,6 @@ public static function notFound(): self 'data' => [], 'errors' => [['Record(s) not found']], ] - ); } @@ -178,5 +177,4 @@ private function mergePart( return $existing; } - } diff --git a/tests/Unit/Domain/ADR/InputTest.php b/tests/Unit/Domain/ADR/InputTest.php index a6a6431..c82ecb3 100644 --- a/tests/Unit/Domain/ADR/InputTest.php +++ b/tests/Unit/Domain/ADR/InputTest.php @@ -66,5 +66,4 @@ public function testInvoke(): void $actual = $input->__invoke($mockRequest); $this->assertSame($expected, $actual); } - } diff --git a/tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php b/tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php index 868ea0d..a35a98c 100644 --- a/tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php +++ b/tests/Unit/Domain/Infrastructure/Encryption/TokenManagerTest.php @@ -94,6 +94,5 @@ public function testGetObjectSuccess(): void $expected = Token::class; $actual = $manager->getObject($token); $this->assertInstanceOf($expected, $actual); - } }