From b5ef5ae0fa9bd91ba22cd80ec52eb9c4be7578d1 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Mon, 16 Sep 2024 15:37:12 -0500 Subject: [PATCH 01/14] Update to the new base library / Fix Break Compatibility --- builder/PostCreateScript.php | 2 +- composer.json | 22 +-- config/config-dev.php | 9 +- config/config-prod.php | 4 +- config/config-staging.php | 4 +- config/config-test.php | 4 +- docs/login.md | 2 +- src/Model/User.php | 155 ++++++++++++++++++++-- src/Repository/BaseRepository.php | 4 +- src/Repository/DummyHexRepository.php | 3 +- src/Repository/UserDefinition.php | 4 +- src/Rest/DummyHexRest.php | 6 +- src/Rest/DummyRest.php | 6 +- src/Rest/Login.php | 8 +- src/Util/FakeApiRequester.php | 19 ++- src/Util/HexUuidLiteral.php | 49 ------- src/Util/HexUuidMysqlLiteral.php | 13 -- src/Util/JwtContext.php | 3 +- templates/codegen/repository.php.jinja | 3 +- templates/codegen/rest.php.jinja | 6 +- templates/codegen/test.php.jinja | 4 +- tests/Functional/Rest/BaseApiTestCase.php | 2 +- tests/Functional/Rest/Credentials.php | 2 +- tests/Functional/Rest/DummyHexTest.php | 4 +- tests/Functional/Rest/DummyTest.php | 4 +- 25 files changed, 211 insertions(+), 131 deletions(-) delete mode 100644 src/Util/HexUuidLiteral.php delete mode 100644 src/Util/HexUuidMysqlLiteral.php diff --git a/builder/PostCreateScript.php b/builder/PostCreateScript.php index 727ec6e..1b5f5fa 100755 --- a/builder/PostCreateScript.php +++ b/builder/PostCreateScript.php @@ -2,7 +2,7 @@ namespace Builder; -use ByJG\Util\JwtWrapper; +use ByJG\JwtWrapper\JwtWrapper; use ByJG\Util\Uri; use Composer\Script\Event; use RecursiveCallbackFilterIterator; diff --git a/composer.json b/composer.json index 7a4f1e0..adc43b4 100644 --- a/composer.json +++ b/composer.json @@ -9,18 +9,18 @@ "ext-json": "*", "ext-openssl": "*", "ext-curl": "*", - "byjg/config": "^4.9", - "byjg/anydataset-db": "^4.9", - "byjg/micro-orm": "^4.9", - "byjg/authuser": "^4.9", - "byjg/mailwrapper": "^4.9", - "byjg/restserver": "^4.9", + "byjg/config": "^5.0", + "byjg/anydataset-db": "^5.0", + "byjg/micro-orm": "^5.0", + "byjg/authuser": "^5.0", + "byjg/mailwrapper": "^5.0", + "byjg/restserver": "^5.0", "zircote/swagger-php": "^4.6.1", - "byjg/swagger-test": "^4.9", - "byjg/migration": "^4.9", - "byjg/php-daemonize": "^4.9", - "byjg/shortid": "^4.9", - "byjg/jinja-php": "^4.9" + "byjg/swagger-test": "^5.0", + "byjg/migration": "^5.0", + "byjg/php-daemonize": "^5.0", + "byjg/shortid": "^5.0", + "byjg/jinja-php": "^5.0" }, "require-dev": { "phpunit/phpunit": "5.7.*|7.4.*|^9.5" diff --git a/config/config-dev.php b/config/config-dev.php index ad76512..78f23b4 100644 --- a/config/config-dev.php +++ b/config/config-dev.php @@ -12,6 +12,9 @@ use ByJG\Config\DependencyInjection as DI; use ByJG\Config\Param; use ByJG\JinjaPhp\Loader\FileSystemLoader; +use ByJG\JwtWrapper\JwtHashHmacSecret; +use ByJG\JwtWrapper\JwtKeyInterface; +use ByJG\JwtWrapper\JwtWrapper; use ByJG\Mail\Envelope; use ByJG\Mail\MailerFactory; use ByJG\Mail\Wrapper\FakeSenderWrapper; @@ -22,8 +25,6 @@ use ByJG\RestServer\Middleware\JwtMiddleware; use ByJG\RestServer\OutputProcessor\JsonCleanOutputProcessor; use ByJG\RestServer\Route\OpenApiRouteList; -use ByJG\Util\JwtKeySecret; -use ByJG\Util\JwtWrapper; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use RestReferenceArchitecture\Model\User; @@ -49,12 +50,12 @@ ->withFactoryMethod('getInstance', [file_get_contents(__DIR__ . '/../public/docs/openapi.json')]) ->toSingleton(), - JwtKeySecret::class => DI::bind(JwtKeySecret::class) + JwtKeyInterface::class => DI::bind(JwtHashHmacSecret::class) ->withConstructorArgs(['jwt_super_secret_key']) ->toSingleton(), JwtWrapper::class => DI::bind(JwtWrapper::class) - ->withConstructorArgs([Param::get('API_SERVER'), Param::get(JwtKeySecret::class)]) + ->withConstructorArgs([Param::get('API_SERVER'), Param::get(JwtKeyInterface::class)]) ->toSingleton(), MailWrapperInterface::class => function () { diff --git a/config/config-prod.php b/config/config-prod.php index eb7e468..a3702e8 100644 --- a/config/config-prod.php +++ b/config/config-prod.php @@ -1,10 +1,10 @@ DI::bind(JwtKeySecret::class) + JwtKeyInterface::class => DI::bind(\ByJG\JwtWrapper\JwtHashHmacSecret::class) ->withConstructorArgs(['jwt_super_secret_key']) ->toSingleton(), ]; diff --git a/config/config-staging.php b/config/config-staging.php index b4fda16..bc37337 100644 --- a/config/config-staging.php +++ b/config/config-staging.php @@ -3,13 +3,13 @@ use ByJG\Cache\Psr16\BaseCacheEngine; use ByJG\Cache\Psr16\FileSystemCacheEngine; use ByJG\Config\DependencyInjection as DI; -use ByJG\Util\JwtKeySecret; +use ByJG\JwtWrapper\JwtKeyInterface; return [ BaseCacheEngine::class => DI::bind(FileSystemCacheEngine::class)->toSingleton(), - JwtKeySecret::class => DI::bind(JwtKeySecret::class) + JwtKeyInterface::class => DI::bind(\ByJG\JwtWrapper\JwtHashHmacSecret::class) ->withConstructorArgs(['jwt_super_secret_key']) ->toSingleton(), diff --git a/config/config-test.php b/config/config-test.php index 3443895..a3f1fca 100644 --- a/config/config-test.php +++ b/config/config-test.php @@ -1,10 +1,10 @@ DI::bind(JwtKeySecret::class) + JwtKeyInterface::class => DI::bind(\ByJG\JwtWrapper\JwtHashHmacSecret::class) ->withConstructorArgs(['jwt_super_secret_key']) ->toSingleton(), ]; diff --git a/docs/login.md b/docs/login.md index 3b4c5db..b81dbd7 100644 --- a/docs/login.md +++ b/docs/login.md @@ -82,7 +82,7 @@ Also, there is an endpoint to refresh the token. The endpoint is `/refresh` and To configure the key you can change here: ```php - JwtKeySecret::class => DI::bind(JwtKeySecret::class) + JwtKeyInterface::class => DI::bind(\ByJG\JwtWrapper\JwtHashHmacSecret::class) ->withConstructorArgs(['supersecretkeyyoushouldnotcommittogithub']) ->toSingleton(), ``` diff --git a/src/Model/User.php b/src/Model/User.php index f3c6930..5f119cf 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -4,7 +4,7 @@ use ByJG\Authenticate\Definition\PasswordDefinition; use ByJG\Authenticate\Model\UserModel; -use Exception; +use ByJG\Authenticate\Model\UserPropertiesModel; use OpenApi\Attributes as OA; use RestReferenceArchitecture\Psr11; @@ -29,63 +29,66 @@ class User extends UserModel * @var ?string */ #[OA\Property(type: "string", format: "string")] - protected $userid; + protected ?string $userid = null; /** * @var ?string */ #[OA\Property(type: "string", format: "string")] - protected $name; + protected ?string $name = null; /** * @var ?string */ #[OA\Property(type: "string", format: "string")] - protected $email; + protected ?string $email = null; /** * @var ?string */ #[OA\Property(type: "string", format: "string")] - protected $username; + protected ?string $username = null; + /** * @var ?string */ #[OA\Property(type: "string", format: "string")] - protected $password; + protected ?string $password = null; /** * @var ?string */ #[OA\Property(type: "string", format: "string")] - protected $created; + protected ?string $created = null; /** * @var ?string */ #[OA\Property(type: "string", format: "string")] - protected $updated; + protected ?string $updated = null; /** * @var ?string */ #[OA\Property(type: "string", format: "string")] - protected $admin = "no"; + protected ?string $admin = null; /** * @OA\Property() * @var ?string */ - protected $uuid; + protected ?string $uuid = null; + + protected array $propertyList = []; /** - * User constructor. + * UserModel constructor. + * * @param string $name * @param string $email * @param string $username * @param string $password * @param string $admin - * @throws Exception */ public function __construct(string $name = "", string $email = "", string $username = "", string $password = "", string $admin = "") { @@ -94,6 +97,134 @@ public function __construct(string $name = "", string $email = "", string $usern $this->withPasswordDefinition(Psr11::container()->get(PasswordDefinition::class)); } + + /** + * @return string|null + */ + public function getUserid(): ?string + { + return $this->userid; + } + + /** + * @param string|null $userid + */ + public function setUserid(?string $userid): void + { + $this->userid = $userid; + } + + /** + * @return string|null + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param string|null $name + */ + public function setName(?string $name): void + { + $this->name = $name; + } + + /** + * @return string|null + */ + public function getEmail(): ?string + { + return $this->email; + } + + /** + * @param string|null $email + */ + public function setEmail(?string $email): void + { + $this->email = $email; + } + + /** + * @return string|null + */ + public function getUsername(): ?string + { + return $this->username; + } + + /** + * @param string|null $username + */ + public function setUsername(?string $username): void + { + $this->username = $username; + } + + /** + * @return string|null + */ + public function getPassword(): ?string + { + return $this->password; + } + + /** + * @param string|null $password + */ + public function setPassword(?string $password): void + { + // Password len equals to 40 means that the password is already encrypted with sha1 + if (!empty($password) && strlen($password) != 40 && !empty($this->passwordDefinition) && !$this->passwordDefinition->matchPassword($password)) { + throw new InvalidArgumentException("Password does not match the password definition"); + } + $this->password = $password; + } + + /** + * @return string|null + */ + public function getCreated(): ?string + { + return $this->created; + } + + /** + * @param string|null $created + */ + public function setCreated(?string $created): void + { + $this->created = $created; + } + + /** + * @return string|null + */ + public function getAdmin(): ?string + { + return $this->admin; + } + + /** + * @param string|null $admin + */ + public function setAdmin(?string $admin): void + { + $this->admin = $admin; + } + + public function set(string $name, string|null $value): void + { + $property = $this->get($name, true); + if (empty($property)) { + $property = new UserPropertiesModel($name, $value ?? ""); + $this->addProperty($property); + } else { + $property->setValue($value); + } + } + /** * @return ?string */ diff --git a/src/Repository/BaseRepository.php b/src/Repository/BaseRepository.php index 24c4ea4..7bcf74f 100644 --- a/src/Repository/BaseRepository.php +++ b/src/Repository/BaseRepository.php @@ -11,7 +11,8 @@ use ByJG\MicroOrm\Exception\OrmBeforeInvalidException; use ByJG\MicroOrm\Exception\OrmInvalidFieldsException; use ByJG\MicroOrm\FieldMapping; -use ByJG\MicroOrm\Literal; +use ByJG\MicroOrm\Literal\HexUuidLiteral; +use ByJG\MicroOrm\Literal\Literal; use ByJG\MicroOrm\Mapper; use ByJG\MicroOrm\Query; use ByJG\MicroOrm\Repository; @@ -19,7 +20,6 @@ use ByJG\Serializer\Exception\InvalidArgumentException; use ReflectionException; use RestReferenceArchitecture\Psr11; -use RestReferenceArchitecture\Util\HexUuidLiteral; abstract class BaseRepository { diff --git a/src/Repository/DummyHexRepository.php b/src/Repository/DummyHexRepository.php index 56ba82f..0575414 100644 --- a/src/Repository/DummyHexRepository.php +++ b/src/Repository/DummyHexRepository.php @@ -5,6 +5,7 @@ use ByJG\AnyDataset\Db\DbDriverInterface; use ByJG\MicroOrm\FieldMapping; use ByJG\MicroOrm\Mapper; +use ByJG\MicroOrm\MapperClosure; use ByJG\MicroOrm\Repository; use RestReferenceArchitecture\Model\DummyHex; @@ -27,7 +28,7 @@ public function __construct(DbDriverInterface $dbDriver) $this->setClosureFixBinaryUUID($mapper); - $mapper->addFieldMapping(FieldMapping::create('uuid')->withFieldName('uuid')->withUpdateFunction(Mapper::doNotUpdateClosure())); + $mapper->addFieldMapping(FieldMapping::create('uuid')->withFieldName('uuid')->withUpdateFunction(MapperClosure::readonly())); $this->repository = new Repository($dbDriver, $mapper); } diff --git a/src/Repository/UserDefinition.php b/src/Repository/UserDefinition.php index 1e2af64..526c6fb 100644 --- a/src/Repository/UserDefinition.php +++ b/src/Repository/UserDefinition.php @@ -4,9 +4,9 @@ use ByJG\AnyDataset\Db\DbDriverInterface; use ByJG\Authenticate\Model\UserModel; -use ByJG\MicroOrm\Literal; +use ByJG\MicroOrm\Literal\HexUuidLiteral; +use ByJG\MicroOrm\Literal\Literal; use RestReferenceArchitecture\Psr11; -use RestReferenceArchitecture\Util\HexUuidLiteral; class UserDefinition extends \ByJG\Authenticate\Definition\UserDefinition { diff --git a/src/Rest/DummyHexRest.php b/src/Rest/DummyHexRest.php index ca65cf6..e5ccf60 100644 --- a/src/Rest/DummyHexRest.php +++ b/src/Rest/DummyHexRest.php @@ -16,7 +16,7 @@ use ByJG\RestServer\Exception\Error404Exception; use ByJG\RestServer\HttpRequest; use ByJG\RestServer\HttpResponse; -use ByJG\Serializer\BinderObject; +use ByJG\Serializer\ObjectCopy; use OpenApi\Attributes as OA; use ReflectionException; use RestReferenceArchitecture\Model\DummyHex; @@ -232,7 +232,7 @@ public function postDummyHex(HttpResponse $response, HttpRequest $request): void $payload = OpenApiContext::validateRequest($request); $model = new DummyHex(); - BinderObject::bind($payload, $model); + ObjectCopy::copy($payload, $model); $dummyHexRepo = Psr11::container()->get(DummyHexRepository::class); $dummyHexRepo->save($model); @@ -295,7 +295,7 @@ public function putDummyHex(HttpResponse $response, HttpRequest $request): void if (empty($model)) { throw new Error404Exception('Id not found'); } - BinderObject::bind($payload, $model); + ObjectCopy::copy($payload, $model); $dummyHexRepo->save($model); } diff --git a/src/Rest/DummyRest.php b/src/Rest/DummyRest.php index 80a7373..d57c20c 100644 --- a/src/Rest/DummyRest.php +++ b/src/Rest/DummyRest.php @@ -16,7 +16,7 @@ use ByJG\RestServer\Exception\Error404Exception; use ByJG\RestServer\HttpRequest; use ByJG\RestServer\HttpResponse; -use ByJG\Serializer\BinderObject; +use ByJG\Serializer\ObjectCopy; use OpenApi\Attributes as OA; use ReflectionException; use RestReferenceArchitecture\Model\Dummy; @@ -232,7 +232,7 @@ public function postDummy(HttpResponse $response, HttpRequest $request): void $payload = OpenApiContext::validateRequest($request); $model = new Dummy(); - BinderObject::bind($payload, $model); + ObjectCopy::copy($payload, $model); $dummyRepo = Psr11::container()->get(DummyRepository::class); $dummyRepo->save($model); @@ -295,7 +295,7 @@ public function putDummy(HttpResponse $response, HttpRequest $request): void if (empty($model)) { throw new Error404Exception('Id not found'); } - BinderObject::bind($payload, $model); + ObjectCopy::copy($payload, $model); $dummyRepo->save($model); } diff --git a/src/Rest/Login.php b/src/Rest/Login.php index 4ede128..7c27b73 100644 --- a/src/Rest/Login.php +++ b/src/Rest/Login.php @@ -4,16 +4,16 @@ use ByJG\Authenticate\UsersDBDataset; use ByJG\Mail\Wrapper\MailWrapperInterface; +use ByJG\MicroOrm\Literal\HexUuidLiteral; use ByJG\RestServer\Exception\Error401Exception; use ByJG\RestServer\Exception\Error422Exception; use ByJG\RestServer\HttpRequest; use ByJG\RestServer\HttpResponse; -use ByJG\RestServer\ResponseBag; +use ByJG\RestServer\SerializationRuleEnum; use OpenApi\Attributes as OA; use RestReferenceArchitecture\Model\User; use RestReferenceArchitecture\Psr11; use RestReferenceArchitecture\Repository\BaseRepository; -use RestReferenceArchitecture\Util\HexUuidLiteral; use RestReferenceArchitecture\Util\JwtContext; use RestReferenceArchitecture\Util\OpenApiContext; @@ -63,7 +63,7 @@ public function post(HttpResponse $response, HttpRequest $request) $user = $users->isValidUser($json->username, $json->password); $metadata = JwtContext::createUserMetadata($user); - $response->getResponseBag()->setSerializationRule(ResponseBag::SINGLE_OBJECT); + $response->getResponseBag()->setSerializationRule(SerializationRuleEnum::SingleObject); $response->write(['token' => JwtContext::createToken($metadata)]); $response->write(['data' => $metadata]); } @@ -114,7 +114,7 @@ public function refreshToken(HttpResponse $response, HttpRequest $request) $metadata = JwtContext::createUserMetadata($user); - $response->getResponseBag()->setSerializationRule(ResponseBag::SINGLE_OBJECT); + $response->getResponseBag()->setSerializationRule(SerializationRuleEnum::SingleObject); $response->write(['token' => JwtContext::createToken($metadata)]); $response->write(['data' => $metadata]); diff --git a/src/Util/FakeApiRequester.php b/src/Util/FakeApiRequester.php index e828366..839dccb 100644 --- a/src/Util/FakeApiRequester.php +++ b/src/Util/FakeApiRequester.php @@ -9,12 +9,15 @@ use ByJG\Config\Exception\DependencyInjectionException; use ByJG\Config\Exception\InvalidDateException; use ByJG\Config\Exception\KeyNotFoundException; +use ByJG\RestServer\Exception\ClassNotFoundException; +use ByJG\RestServer\Exception\Error404Exception; +use ByJG\RestServer\Exception\Error405Exception; +use ByJG\RestServer\Exception\Error520Exception; +use ByJG\RestServer\Exception\InvalidClassException; use ByJG\RestServer\Middleware\JwtMiddleware; use ByJG\RestServer\MockRequestHandler; use ByJG\RestServer\Route\OpenApiRouteList; -use ByJG\Util\Exception\MessageException; -use ByJG\Util\MockClient; -use ByJG\Util\Psr7\Response; +use ByJG\WebRequest\MockClient; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; @@ -30,7 +33,7 @@ class FakeApiRequester extends AbstractRequester { /** * @param RequestInterface $request - * @return Response|ResponseInterface + * @return ResponseInterface * @throws ConfigException * @throws ConfigNotFoundException * @throws DependencyInjectionException @@ -38,9 +41,13 @@ class FakeApiRequester extends AbstractRequester * @throws InvalidDateException * @throws KeyNotFoundException * @throws ReflectionException - * @throws MessageException + * @throws ClassNotFoundException + * @throws Error404Exception + * @throws Error405Exception + * @throws Error520Exception + * @throws InvalidClassException */ - protected function handleRequest(RequestInterface $request) + protected function handleRequest(RequestInterface $request): ResponseInterface { $mock = new MockRequestHandler(Psr11::container()->get(LoggerInterface::class)); $mock->withMiddleware(Psr11::container()->get(JwtMiddleware::class)); diff --git a/src/Util/HexUuidLiteral.php b/src/Util/HexUuidLiteral.php deleted file mode 100644 index cfb1af3..0000000 --- a/src/Util/HexUuidLiteral.php +++ /dev/null @@ -1,49 +0,0 @@ -__toString()); - } - - if (preg_match("/^X'(.*)'$/", $item, $matches)) { - $item = $matches[1]; - } - - if (is_string($item) && !ctype_print($item) && strlen($item) === 16) { - $item = bin2hex($item); - } - - if (preg_match("/^\w{8}-?\w{4}-?\w{4}-?\w{4}-?\w{12}$/", $item)) { - $item = preg_replace("/^(\w{8})-?(\w{4})-?(\w{4})-?(\w{4})-?(\w{12})$/", "$1-$2-$3-$4-$5", $item); - } else if ($throwErrorIfInvalid) { - throw new InvalidArgumentException("Invalid UUID format"); - } else { - return $item; - } - - return strtoupper($item); - } -} diff --git a/src/Util/HexUuidMysqlLiteral.php b/src/Util/HexUuidMysqlLiteral.php deleted file mode 100644 index 80ca784..0000000 --- a/src/Util/HexUuidMysqlLiteral.php +++ /dev/null @@ -1,13 +0,0 @@ -setClosureFixBinaryUUID($mapper); {% for field in fields -%} {% if 'GENERATED' in field.extra -%} - $mapper->addFieldMapping(FieldMapping::create('{{ field.property }}')->withFieldName('{{ field.field }}')->withUpdateFunction(Mapper::doNotUpdateClosure())); + $mapper->addFieldMapping(FieldMapping::create('{{ field.property }}')->withFieldName('{{ field.field }}')->withUpdateFunction(MapperClosure::readonly())); {% else %}{% if field.property != field.field -%} $mapper->addFieldMapping(FieldMapping::create('{{ field.property }}')->withFieldName('{{ field.field }}')); {% endif %}{% endif %} diff --git a/templates/codegen/rest.php.jinja b/templates/codegen/rest.php.jinja index 27f5d96..531edea 100644 --- a/templates/codegen/rest.php.jinja +++ b/templates/codegen/rest.php.jinja @@ -16,7 +16,7 @@ use ByJG\RestServer\Exception\Error403Exception; use ByJG\RestServer\Exception\Error404Exception; use ByJG\RestServer\HttpRequest; use ByJG\RestServer\HttpResponse; -use ByJG\Serializer\BinderObject; +use ByJG\Serializer\ObjectCopy; use OpenApi\Attributes as OA; use ReflectionException; use {{ namespace }}\Model\{{ className }}; @@ -234,7 +234,7 @@ class {{ className }}Rest $payload = OpenApiContext::validateRequest($request); $model = new {{ className }}(); - BinderObject::bind($payload, $model); + ObjectCopy::copy($payload, $model); ${{ varTableName }}Repo = Psr11::container()->get({{ className }}Repository::class); ${{ varTableName }}Repo->save($model); @@ -297,7 +297,7 @@ class {{ className }}Rest if (empty($model)) { throw new Error404Exception('Id not found'); } - BinderObject::bind($payload, $model); + ObjectCopy::copy($payload, $model); ${{ varTableName }}Repo->save($model); } diff --git a/templates/codegen/test.php.jinja b/templates/codegen/test.php.jinja index ab21122..048ba1b 100644 --- a/templates/codegen/test.php.jinja +++ b/templates/codegen/test.php.jinja @@ -4,7 +4,7 @@ namespace Test\Functional\Rest; use ByJG\RestServer\Exception\Error401Exception; use ByJG\RestServer\Exception\Error403Exception; -use ByJG\Serializer\BinderObject; +use ByJG\Serializer\ObjectCopy; use {{ namespace }}\Util\FakeApiRequester; use {{ namespace }}\Model\{{ className }}; use {{ namespace }}\Repository\BaseRepository; @@ -31,7 +31,7 @@ class {{ className }}Test extends BaseApiTestCase return $sample; } - BinderObject::bind($sample, $model = new {{ className }}()); + ObjectCopy::copy($sample, $model = new {{ className }}()); return $model; } diff --git a/tests/Functional/Rest/BaseApiTestCase.php b/tests/Functional/Rest/BaseApiTestCase.php index f438c11..232b62a 100644 --- a/tests/Functional/Rest/BaseApiTestCase.php +++ b/tests/Functional/Rest/BaseApiTestCase.php @@ -7,8 +7,8 @@ use ByJG\ApiTools\Base\Schema; use ByJG\DbMigration\Database\MySqlDatabase; use ByJG\DbMigration\Migration; -use ByJG\Util\Psr7\Request; use ByJG\Util\Uri; +use ByJG\WebRequest\Psr7\Request; use Exception; use RestReferenceArchitecture\Psr11; diff --git a/tests/Functional/Rest/Credentials.php b/tests/Functional/Rest/Credentials.php index 13c966b..9d1d89a 100644 --- a/tests/Functional/Rest/Credentials.php +++ b/tests/Functional/Rest/Credentials.php @@ -2,8 +2,8 @@ namespace Test\Functional\Rest; -use ByJG\Util\Psr7\Request; use ByJG\Util\Uri; +use ByJG\WebRequest\Psr7\Request; use RestReferenceArchitecture\Psr11; use RestReferenceArchitecture\Util\FakeApiRequester; diff --git a/tests/Functional/Rest/DummyHexTest.php b/tests/Functional/Rest/DummyHexTest.php index 0cf34ac..52b14f8 100644 --- a/tests/Functional/Rest/DummyHexTest.php +++ b/tests/Functional/Rest/DummyHexTest.php @@ -4,7 +4,7 @@ use ByJG\RestServer\Exception\Error401Exception; use ByJG\RestServer\Exception\Error403Exception; -use ByJG\Serializer\BinderObject; +use ByJG\Serializer\ObjectCopy; use RestReferenceArchitecture\Model\DummyHex; use RestReferenceArchitecture\Repository\BaseRepository; use RestReferenceArchitecture\Util\FakeApiRequester; @@ -30,7 +30,7 @@ protected function getSampleData($array = false) return $sample; } - BinderObject::bind($sample, $model = new DummyHex()); + ObjectCopy::copy($sample, $model = new DummyHex()); return $model; } diff --git a/tests/Functional/Rest/DummyTest.php b/tests/Functional/Rest/DummyTest.php index 9477539..1f4d112 100644 --- a/tests/Functional/Rest/DummyTest.php +++ b/tests/Functional/Rest/DummyTest.php @@ -4,7 +4,7 @@ use ByJG\RestServer\Exception\Error401Exception; use ByJG\RestServer\Exception\Error403Exception; -use ByJG\Serializer\BinderObject; +use ByJG\Serializer\ObjectCopy; use RestReferenceArchitecture\Model\Dummy; use RestReferenceArchitecture\Util\FakeApiRequester; @@ -29,7 +29,7 @@ protected function getSampleData($array = false) return $sample; } - BinderObject::bind($sample, $model = new Dummy()); + ObjectCopy::copy($sample, $model = new Dummy()); return $model; } From cef8d9c208336db1ecea0c6ceaafef2aa2a5a286 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Mon, 16 Sep 2024 16:25:55 -0500 Subject: [PATCH 02/14] Update PostCreateScript --- builder/PostCreateScript.php | 51 +++++++++++++++++++++++++++++++---- src/Util/FakeApiRequester.php | 12 +++++---- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/builder/PostCreateScript.php b/builder/PostCreateScript.php index 1b5f5fa..a09489f 100755 --- a/builder/PostCreateScript.php +++ b/builder/PostCreateScript.php @@ -2,6 +2,7 @@ namespace Builder; +use ByJG\AnyDataset\Db\Factory; use ByJG\JwtWrapper\JwtWrapper; use ByJG\Util\Uri; use Composer\Script\Event; @@ -123,17 +124,57 @@ public static function run(Event $event) $currentPhpVersion = PHP_MAJOR_VERSION . "." .PHP_MINOR_VERSION; + $validatePHPVersion = function ($arg) { + $validPHPVersions = ['8.1', '8.2', '8.3']; + if (in_array($arg, $validPHPVersions)) { + return $arg; + } + throw new \Exception('Only the PHP versions ' . implode(', ', $validPHPVersions) . ' are supported'); + }; + + $validateNamespace = function ($arg) { + if (empty($arg) || !preg_match('/^[A-Z][a-zA-Z0-9]*$/', $arg)) { + throw new \Exception('Namespace must be one word in CamelCase'); + } + return $arg; + }; + + $validateComposer = function ($arg) { + if (empty($arg) || !preg_match('/^[a-z0-9-]+\/[a-z0-9-]+$/', $arg)) { + throw new \Exception('Invalid Composer name'); + } + return $arg; + }; + + $validateURI = function ($arg) { + $uri = new Uri($arg); + if (empty($uri->getScheme())) { + throw new \Exception('Invalid URI'); + } + Factory::getRegisteredDrivers($uri->getScheme()); + return $arg; + }; + + $validateTimeZone = function ($arg) { + if (empty($arg) || !in_array($arg, timezone_identifiers_list())) { + throw new \Exception('Invalid Timezone'); + } + return $arg; + }; + + $maxRetries = 5; + $stdIo->write("========================================================"); $stdIo->write(" Setup Project"); $stdIo->write(" Answer the questions below"); $stdIo->write("========================================================"); $stdIo->write(""); $stdIo->write("Project Directory: " . $workdir); - $phpVersion = $stdIo->ask("PHP Version [$currentPhpVersion]: ", $currentPhpVersion); - $namespace = $stdIo->ask('Project namespace [MyRest]: ', 'MyRest'); - $composerName = $stdIo->ask('Composer name [me/myrest]: ', 'me/myrest'); - $mysqlConnection = $stdIo->ask('MySQL connection DEV [mysql://root:mysqlp455w0rd@mysql-container/mydb]: ', 'mysql://root:mysqlp455w0rd@mysql-container/mydb'); - $timezone = $stdIo->ask('Timezone [UTC]: ', 'UTC'); + $phpVersion = $stdIo->askAndValidate("PHP Version [$currentPhpVersion]: ", $validatePHPVersion, $maxRetries, $currentPhpVersion); + $namespace = $stdIo->askAndValidate('Project namespace [MyRest]: ', $validateNamespace, $maxRetries, 'MyRest'); + $composerName = $stdIo->askAndValidate('Composer name [me/myrest]: ', $validateComposer, $maxRetries, 'me/myrest'); + $mysqlConnection = $stdIo->askAndValidate('MySQL connection DEV [mysql://root:mysqlp455w0rd@mysql-container/mydb]: ', $validateURI, $maxRetries, 'mysql://root:mysqlp455w0rd@mysql-container/mydb'); + $timezone = $stdIo->askAndValidate('Timezone [UTC]: ', $validateTimeZone, $maxRetries, 'UTC'); $stdIo->ask('Press to continue'); $script = new PostCreateScript(); diff --git a/src/Util/FakeApiRequester.php b/src/Util/FakeApiRequester.php index 839dccb..f252db9 100644 --- a/src/Util/FakeApiRequester.php +++ b/src/Util/FakeApiRequester.php @@ -17,6 +17,7 @@ use ByJG\RestServer\Middleware\JwtMiddleware; use ByJG\RestServer\MockRequestHandler; use ByJG\RestServer\Route\OpenApiRouteList; +use ByJG\WebRequest\Exception\RequestException; use ByJG\WebRequest\MockClient; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -34,18 +35,19 @@ class FakeApiRequester extends AbstractRequester /** * @param RequestInterface $request * @return ResponseInterface + * @throws ClassNotFoundException * @throws ConfigException * @throws ConfigNotFoundException * @throws DependencyInjectionException - * @throws InvalidArgumentException - * @throws InvalidDateException - * @throws KeyNotFoundException - * @throws ReflectionException - * @throws ClassNotFoundException * @throws Error404Exception * @throws Error405Exception * @throws Error520Exception + * @throws InvalidArgumentException * @throws InvalidClassException + * @throws InvalidDateException + * @throws KeyNotFoundException + * @throws ReflectionException + * @throws RequestException */ protected function handleRequest(RequestInterface $request): ResponseInterface { From 6759baeff04430b3386fa79b32ca329c5ce25861 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Mon, 16 Sep 2024 17:20:20 -0500 Subject: [PATCH 03/14] Update CodeGen Templates --- builder/Scripts.php | 10 +++++-- src/Model/Dummy.php | 22 +++++++++------ src/Model/DummyHex.php | 37 ++++++++++++++++---------- src/Repository/BaseRepository.php | 29 +++++--------------- src/Repository/DummyHexRepository.php | 21 ++++----------- src/Repository/DummyRepository.php | 20 ++++---------- templates/codegen/model.php.jinja | 20 +++++++++----- templates/codegen/repository.php.jinja | 28 ++++--------------- 8 files changed, 80 insertions(+), 107 deletions(-) diff --git a/builder/Scripts.php b/builder/Scripts.php index 3f6b500..de45432 100755 --- a/builder/Scripts.php +++ b/builder/Scripts.php @@ -221,6 +221,7 @@ public function runCodeGenerator(array $arguments) $tableDefinition = $dbDriver->getIterator("EXPLAIN " . strtolower($table))->toArray(); $tableIndexes = $dbDriver->getIterator("SHOW INDEX FROM " . strtolower($table))->toArray(); + $autoIncrement = false; // Convert DB Types to PHP Types foreach ($tableDefinition as $key => $field) { @@ -230,6 +231,10 @@ public function runCodeGenerator(array $arguments) return strtoupper($matches[1]); }, $field['field']); + if ($field['extra'] == 'auto_increment') { + $autoIncrement = true; + } + switch ($type) { case 'int': case 'tinyint': @@ -292,7 +297,7 @@ public function runCodeGenerator(array $arguments) } } - // Create an array with non nullable fields but primary keys + // Create an array with non-nullable fields but primary keys $nonNullableFields = []; foreach ($tableDefinition as $field) { if ($field['null'] == 'NO' && $field['key'] != 'PRI') { @@ -300,7 +305,7 @@ public function runCodeGenerator(array $arguments) } } - // Create an array with non nullable fields but primary keys + // Create an array with non-nullable fields but primary keys foreach ($tableIndexes as $key => $field) { $tableIndexes[$key]['camelColumnName'] = preg_replace_callback('/_(.?)/', function($match) { return strtoupper($match[1]); @@ -309,6 +314,7 @@ public function runCodeGenerator(array $arguments) $data = [ 'namespace' => 'RestReferenceArchitecture', + 'autoIncrement' => $autoIncrement ? 'yes' : 'no', 'restTag' => ucwords(explode('_', strtolower($table))[0]), 'restPath' => str_replace('_', '/', strtolower($table)), 'className' => preg_replace_callback('/(?:^|_)(.?)/', function($match) { diff --git a/src/Model/Dummy.php b/src/Model/Dummy.php index 16a6c8f..3049d31 100644 --- a/src/Model/Dummy.php +++ b/src/Model/Dummy.php @@ -1,6 +1,9 @@ id; } /** * @param int|null $id - * @return Dummy + * @return $this */ - public function setId(?int $id): Dummy + public function setId(int|null $id): static { $this->id = $id; return $this; @@ -46,16 +52,16 @@ public function setId(?int $id): Dummy /** * @return string|null */ - public function getField(): ?string + public function getField(): string|null { return $this->field; } /** * @param string|null $field - * @return Dummy + * @return $this */ - public function setField(?string $field): Dummy + public function setField(string|null $field): static { $this->field = $field; return $this; diff --git a/src/Model/DummyHex.php b/src/Model/DummyHex.php index 95a1e21..ae317c8 100644 --- a/src/Model/DummyHex.php +++ b/src/Model/DummyHex.php @@ -1,6 +1,11 @@ id; } /** - * @param string|null $id - * @return DummyHex + * @param string|HexUuidLiteral|null $id + * @return $this */ - public function setId(?string $id): DummyHex + public function setId(string|HexUuidLiteral|null $id): static { $this->id = $id; return $this; @@ -52,16 +61,16 @@ public function setId(?string $id): DummyHex /** * @return string|null */ - public function getUuid(): ?string + public function getUuid(): string|null { return $this->uuid; } /** * @param string|null $uuid - * @return DummyHex + * @return $this */ - public function setUuid(?string $uuid): DummyHex + public function setUuid(string|null $uuid): static { $this->uuid = $uuid; return $this; @@ -70,16 +79,16 @@ public function setUuid(?string $uuid): DummyHex /** * @return string|null */ - public function getField(): ?string + public function getField(): string|null { return $this->field; } /** * @param string|null $field - * @return DummyHex + * @return $this */ - public function setField(?string $field): DummyHex + public function setField(string|null $field): static { $this->field = $field; return $this; diff --git a/src/Repository/BaseRepository.php b/src/Repository/BaseRepository.php index 7bcf74f..7fd8802 100644 --- a/src/Repository/BaseRepository.php +++ b/src/Repository/BaseRepository.php @@ -10,6 +10,8 @@ use ByJG\Config\Exception\KeyNotFoundException; use ByJG\MicroOrm\Exception\OrmBeforeInvalidException; use ByJG\MicroOrm\Exception\OrmInvalidFieldsException; +use ByJG\MicroOrm\Exception\RepositoryReadOnlyException; +use ByJG\MicroOrm\Exception\UpdateConstraintException; use ByJG\MicroOrm\FieldMapping; use ByJG\MicroOrm\Literal\HexUuidLiteral; use ByJG\MicroOrm\Literal\Literal; @@ -36,7 +38,7 @@ abstract class BaseRepository */ public function get($itemId) { - return $this->repository->get($this->prepareUuidQuery($itemId)); + return $this->repository->get(HexUuidLiteral::create($itemId)); } public function getMapper() @@ -55,28 +57,6 @@ public function getByQuery($query) return $this->repository->getByQuery($query); } - protected function prepareUuidQuery($itemId) - { - $result = []; - foreach ((array)$itemId as $item) { - if ($item instanceof Literal) { - $result[] = $item; - continue; - } - $hydratedItem = preg_replace('/[^0-9A-F\-]/', '', $item); - if (preg_match("/^\w{8}-?\w{4}-?\w{4}-?\w{4}-?\w{12}$/", $hydratedItem)) { - $result[] = new HexUuidLiteral($hydratedItem); - } else { - $result[] = $item; - } - } - - if (count($result) == 1) { - return $result[0]; - } - return $result; - } - /** * @param int|null $page * @param int $size @@ -208,11 +188,14 @@ protected function setClosureFixBinaryUUID(?Mapper $mapper, $binPropertyName = ' /** * @param $model + * @param UpdateConstraint|null $updateConstraint * @return mixed * @throws InvalidArgumentException * @throws OrmBeforeInvalidException * @throws OrmInvalidFieldsException * @throws \ByJG\MicroOrm\Exception\InvalidArgumentException + * @throws RepositoryReadOnlyException + * @throws UpdateConstraintException */ public function save($model, ?UpdateConstraint $updateConstraint = null) { diff --git a/src/Repository/DummyHexRepository.php b/src/Repository/DummyHexRepository.php index 0575414..b3e2c8e 100644 --- a/src/Repository/DummyHexRepository.php +++ b/src/Repository/DummyHexRepository.php @@ -3,10 +3,9 @@ namespace RestReferenceArchitecture\Repository; use ByJG\AnyDataset\Db\DbDriverInterface; -use ByJG\MicroOrm\FieldMapping; -use ByJG\MicroOrm\Mapper; -use ByJG\MicroOrm\MapperClosure; +use ByJG\MicroOrm\Exception\OrmModelInvalidException; use ByJG\MicroOrm\Repository; +use ReflectionException; use RestReferenceArchitecture\Model\DummyHex; class DummyHexRepository extends BaseRepository @@ -15,22 +14,12 @@ class DummyHexRepository extends BaseRepository * DummyHexRepository constructor. * * @param DbDriverInterface $dbDriver - * + * @throws OrmModelInvalidException + * @throws ReflectionException */ public function __construct(DbDriverInterface $dbDriver) { - $mapper = new Mapper( - DummyHex::class, - 'dummyhex', - 'id' - ); - $mapper->withPrimaryKeySeedFunction(BaseRepository::getClosureNewUUID()); - - - $this->setClosureFixBinaryUUID($mapper); - $mapper->addFieldMapping(FieldMapping::create('uuid')->withFieldName('uuid')->withUpdateFunction(MapperClosure::readonly())); - - $this->repository = new Repository($dbDriver, $mapper); + $this->repository = new Repository($dbDriver, DummyHex::class); } diff --git a/src/Repository/DummyRepository.php b/src/Repository/DummyRepository.php index 8400156..35948cb 100644 --- a/src/Repository/DummyRepository.php +++ b/src/Repository/DummyRepository.php @@ -3,9 +3,10 @@ namespace RestReferenceArchitecture\Repository; use ByJG\AnyDataset\Db\DbDriverInterface; -use ByJG\MicroOrm\Mapper; +use ByJG\MicroOrm\Exception\OrmModelInvalidException; use ByJG\MicroOrm\Query; use ByJG\MicroOrm\Repository; +use ReflectionException; use RestReferenceArchitecture\Model\Dummy; class DummyRepository extends BaseRepository @@ -14,23 +15,12 @@ class DummyRepository extends BaseRepository * DummyRepository constructor. * * @param DbDriverInterface $dbDriver - * + * @throws OrmModelInvalidException + * @throws ReflectionException */ public function __construct(DbDriverInterface $dbDriver) { - $mapper = new Mapper( - Dummy::class, - 'dummy', - 'id' - ); - // $mapper->withPrimaryKeySeedFunction(BaseRepository::getClosureNewUUID()); - - - // Table UUID Definition - // $this->setClosureFixBinaryUUID($mapper); - - - $this->repository = new Repository($dbDriver, $mapper); + $this->repository = new Repository($dbDriver, Dummy::class); } diff --git a/templates/codegen/model.php.jinja b/templates/codegen/model.php.jinja index 5e26588..b7a155b 100644 --- a/templates/codegen/model.php.jinja +++ b/templates/codegen/model.php.jinja @@ -1,6 +1,12 @@ 0 %}, "{{ nonNullableFields | join('", "')}}"{% endif %}], type: "object", xml: new OA\Xml(name: "{{ className }}"))] +#[Table{% if autoIncrement == "no" %}MySqlUuidPK{% endif %}Attribute("{{ tableName }}")] class {{ className }} { {% for field in fields %} @@ -15,23 +22,24 @@ class {{ className }} * @var {{ field.php_type }}|null */ #[OA\Property(type: "{{ field.openapi_type }}", format: "{{ field.openapi_format }}"{% if field.null == "YES" %}, nullable: true{% endif %})] - protected ?{{ field.php_type }} ${{ field.property }} = null; + #[Field{% if 'binary' in field.type %}Uuid{% endif %}Attribute({% if field.key == "PRI" %}primaryKey: true, {% endif %}fieldName: "{{ field.field }}"{% if 'VIRTUAL' in field.extra %}, syncWithDb: false{% endif %})] + protected {{ field.php_type }}{% if 'binary' in field.type %}|HexUuidLiteral{% endif %}|null ${{ field.property }} = null; {% endfor %} {% for field in fields %} /** - * @return {{ field.php_type }}|null + * @return {{ field.php_type }}{% if 'binary' in field.type %}|HexUuidLiteral{% endif %}|null */ - public function get{{ field.property | capitalize }}(): ?{{ field.php_type }} + public function get{{ field.property | capitalize }}(): {{ field.php_type }}{% if 'binary' in field.type %}|HexUuidLiteral{% endif %}|null { return $this->{{ field.property }}; } /** - * @param {{ field.php_type }}|null ${{ field.property }} - * @return {{ className }} + * @param {{ field.php_type }}{% if 'binary' in field.type %}|HexUuidLiteral{% endif %}|null ${{ field.property }} + * @return $this */ - public function set{{ field.property | capitalize }}(?{{ field.php_type }} ${{ field.property }}): {{ className }} + public function set{{ field.property | capitalize }}({{ field.php_type }}{% if 'binary' in field.type %}|HexUuidLiteral{% endif %}|null ${{ field.property }}): static { $this->{{ field.property }} = ${{ field.property }}; return $this; diff --git a/templates/codegen/repository.php.jinja b/templates/codegen/repository.php.jinja index d8f30dd..c764417 100644 --- a/templates/codegen/repository.php.jinja +++ b/templates/codegen/repository.php.jinja @@ -3,10 +3,9 @@ namespace {{ namespace }}\Repository; use {{ namespace }}\Psr11; +use ByJG\MicroOrm\Exception\OrmModelInvalidException; +use ReflectionException; use ByJG\AnyDataset\Db\DbDriverInterface; -use ByJG\MicroOrm\FieldMapping; -use ByJG\MicroOrm\Mapper; -use ByJG\MicroOrm\MapperClosure; use ByJG\MicroOrm\Query; use ByJG\MicroOrm\Repository; use {{ namespace }}\Model\{{ className }}; @@ -17,29 +16,12 @@ class {{ className }}Repository extends BaseRepository * {{ className }}Repository constructor. * * @param DbDriverInterface $dbDriver - * + * @throws OrmModelInvalidException + * @throws ReflectionException */ public function __construct(DbDriverInterface $dbDriver) { - $mapper = new Mapper( - {{ className }}::class, - '{{ tableName }}', - 'id' - ); - // $mapper->withPrimaryKeySeedFunction(BaseRepository::getClosureNewUUID()); - - - // Table UUID Definition - // $this->setClosureFixBinaryUUID($mapper); -{% for field in fields -%} -{% if 'GENERATED' in field.extra -%} - $mapper->addFieldMapping(FieldMapping::create('{{ field.property }}')->withFieldName('{{ field.field }}')->withUpdateFunction(MapperClosure::readonly())); -{% else %}{% if field.property != field.field -%} - $mapper->addFieldMapping(FieldMapping::create('{{ field.property }}')->withFieldName('{{ field.field }}')); -{% endif %}{% endif %} -{% endfor %} - - $this->repository = new Repository($dbDriver, $mapper); + $this->repository = new Repository($dbDriver, {{ className }}::class); } {% for index in indexes -%} {% if index.key_name != 'PRIMARY' -%} From a348f89a4e057438c27482007fd99d3662712bd8 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Tue, 17 Sep 2024 08:32:24 -0500 Subject: [PATCH 04/14] Fix UUID keyGen --- docker-compose-image.yml | 34 ------------------------------- src/Repository/BaseRepository.php | 6 ++++-- 2 files changed, 4 insertions(+), 36 deletions(-) delete mode 100644 docker-compose-image.yml diff --git a/docker-compose-image.yml b/docker-compose-image.yml deleted file mode 100644 index 7a3dd91..0000000 --- a/docker-compose-image.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: '3.2' -services: - rest: - image: resttest:dev - container_name: resttest - build: - context: . - dockerfile: docker/Dockerfile - ports: - - "8080:80" - environment: - - APP_ENV=dev - networks: - - net - - mysql-container: - image: mysql:8.0 - container_name: mysql-container - environment: - MYSQL_ROOT_PASSWORD: mysqlp455w0rd - TZ: UTC - volumes: - - mysql-volume:/var/lib/mysql - ports: - - "3306:3306" - networks: - - net - -volumes: - mysql-volume: - -networks: - net: - diff --git a/src/Repository/BaseRepository.php b/src/Repository/BaseRepository.php index 7fd8802..5b68d6f 100644 --- a/src/Repository/BaseRepository.php +++ b/src/Repository/BaseRepository.php @@ -203,8 +203,10 @@ public function save($model, ?UpdateConstraint $updateConstraint = null) $primaryKey = $this->repository->getMapper()->getPrimaryKey()[0]; - if ($model->{"get$primaryKey"}() instanceof Literal) { - $model->{"set$primaryKey"}(HexUuidLiteral::getUuidFromLiteral($model->{"get$primaryKey"}())); + if ($model->{"get$primaryKey"}() instanceof HexUuidLiteral) { + /** @var HexUuidLiteral $literal */ + $literal = $model->{"get$primaryKey"}(); + $model->{"set$primaryKey"}($literal->formatUuid()); } return $model; From 7ba0095d4d859d3968991e8dbb6606122a9488dc Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Thu, 19 Sep 2024 12:25:40 -0500 Subject: [PATCH 05/14] More compatibility adjustments --- builder/Scripts.php | 8 ++-- docker/Dockerfile | 3 +- phpunit.xml.dist | 41 ++++++++++--------- src/Model/User.php | 20 ++++----- src/OpenApiSpec.php | 19 ++++----- src/Psr11.php | 7 +++- src/Rest/Login.php | 30 ++++++-------- src/Util/JwtContext.php | 7 ++-- src/Util/OpenApiContext.php | 7 ++-- templates/codegen/test.php.jinja | 2 +- .../{Functional => }/Rest/BaseApiTestCase.php | 4 +- tests/{Functional => }/Rest/Credentials.php | 2 +- tests/{Functional => }/Rest/DummyHexTest.php | 2 +- tests/{Functional => }/Rest/DummyTest.php | 2 +- tests/{Functional => }/Rest/LoginTest.php | 2 +- .../Rest/SampleProtectedTest.php | 2 +- tests/{Functional => }/Rest/SampleTest.php | 2 +- 17 files changed, 77 insertions(+), 83 deletions(-) rename tests/{Functional => }/Rest/BaseApiTestCase.php (92%) rename tests/{Functional => }/Rest/Credentials.php (97%) rename tests/{Functional => }/Rest/DummyHexTest.php (99%) rename tests/{Functional => }/Rest/DummyTest.php (99%) rename tests/{Functional => }/Rest/LoginTest.php (99%) rename tests/{Functional => }/Rest/SampleProtectedTest.php (98%) rename tests/{Functional => }/Rest/SampleTest.php (93%) diff --git a/builder/Scripts.php b/builder/Scripts.php index de45432..f54d485 100755 --- a/builder/Scripts.php +++ b/builder/Scripts.php @@ -87,7 +87,7 @@ public static function codeGenerator(Event $event) * @throws ConfigException * @throws InvalidDateException */ - public function runMigrate($arguments) + public function runMigrate($arguments): void { $argumentList = $this->extractArguments($arguments); if (isset($argumentList["command"])) { @@ -160,7 +160,7 @@ protected function extractArguments(array $arguments, bool $hasCmd = true): arra * @param array $arguments * @return void */ - public function runGenOpenApiDocs(array $arguments) + public function runGenOpenApiDocs(array $arguments): void { $docPath = $this->workdir . '/public/docs/'; @@ -187,7 +187,7 @@ public function runGenOpenApiDocs(array $arguments) * @throws \ByJG\Serializer\Exception\InvalidArgumentException * @throws Exception */ - public function runCodeGenerator(array $arguments) + public function runCodeGenerator(array $arguments): void { // Get Table Name $table = null; @@ -379,7 +379,7 @@ public function runCodeGenerator(array $arguments) echo "Processing Test for table $table...\n"; $template = $loader->getTemplate('test.php'); if ($save) { - $file = __DIR__ . '/../tests/Functional/Rest/' . $data['className'] . 'Test.php'; + $file = __DIR__ . '/../tests/Rest/' . $data['className'] . 'Test.php'; file_put_contents($file, $template->render($data)); echo "File saved in $file\n"; } else { diff --git a/docker/Dockerfile b/docker/Dockerfile index a08a37f..77eac31 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -31,4 +31,5 @@ COPY templates /srv/templates COPY composer.* /srv COPY phpunit.xml.dist /srv COPY db /srv/db -RUN composer install --no-dev --no-interaction --no-progress --no-scripts --optimize-autoloader +RUN composer install --no-dev --no-interaction --no-progress --no-scripts --optimize-autoloader && \ + composer dump-autoload --optimize --classmap-authoritative -q diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 39d5297..ff5e401 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,33 +6,36 @@ and open the template in the editor. --> - + stopOnFailure="false" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"> - - - ./src - - + + + ./src + + - - - ./tests/ - - + + + ./tests/Rest + ./tests/ + + - - - - + + + + - - - + + + diff --git a/src/Model/User.php b/src/Model/User.php index 5f119cf..74c90e5 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -2,11 +2,11 @@ namespace RestReferenceArchitecture\Model; -use ByJG\Authenticate\Definition\PasswordDefinition; use ByJG\Authenticate\Model\UserModel; use ByJG\Authenticate\Model\UserPropertiesModel; +use ByJG\MicroOrm\Literal\HexUuidLiteral; +use InvalidArgumentException; use OpenApi\Attributes as OA; -use RestReferenceArchitecture\Psr11; #[OA\Schema(required: ["email"], type: "object", xml: new OA\Xml(name: "User"))] class User extends UserModel @@ -26,10 +26,10 @@ class User extends UserModel const ROLE_USER = 'user'; /** - * @var ?string + * @var ?string|int|HexUuidLiteral */ #[OA\Property(type: "string", format: "string")] - protected ?string $userid = null; + protected string|int|HexUuidLiteral|null $userid = null; /** * @var ?string @@ -90,26 +90,24 @@ class User extends UserModel * @param string $password * @param string $admin */ - public function __construct(string $name = "", string $email = "", string $username = "", string $password = "", string $admin = "") + public function __construct(string $name = "", string $email = "", string $username = "", string $password = "", string $admin = "no") { parent::__construct($name, $email, $username, $password, $admin); - - $this->withPasswordDefinition(Psr11::container()->get(PasswordDefinition::class)); } /** - * @return string|null + * @return string|HexUuidLiteral|int|null */ - public function getUserid(): ?string + public function getUserid(): string|HexUuidLiteral|int|null { return $this->userid; } /** - * @param string|null $userid + * @param string|HexUuidLiteral|int|null $userid */ - public function setUserid(?string $userid): void + public function setUserid(string|HexUuidLiteral|int|null $userid): void { $this->userid = $userid; } diff --git a/src/OpenApiSpec.php b/src/OpenApiSpec.php index 808204a..0bb7ab0 100644 --- a/src/OpenApiSpec.php +++ b/src/OpenApiSpec.php @@ -1,6 +1,7 @@ build($env); } diff --git a/src/Rest/Login.php b/src/Rest/Login.php index 7c27b73..e0f5957 100644 --- a/src/Rest/Login.php +++ b/src/Rest/Login.php @@ -55,12 +55,10 @@ class Login )] public function post(HttpResponse $response, HttpRequest $request) { - OpenApiContext::validateRequest($request); - - $json = json_decode($request->payload()); + $json = OpenApiContext::validateRequest($request); $users = Psr11::container()->get(UsersDBDataset::class); - $user = $users->isValidUser($json->username, $json->password); + $user = $users->isValidUser($json["username"], $json["password"]); $metadata = JwtContext::createUserMetadata($user); $response->getResponseBag()->setSerializationRule(SerializationRuleEnum::SingleObject); @@ -149,12 +147,10 @@ public function refreshToken(HttpResponse $response, HttpRequest $request) )] public function postResetRequest(HttpResponse $response, HttpRequest $request) { - OpenApiContext::validateRequest($request); - - $json = json_decode($request->payload()); + $json = OpenApiContext::validateRequest($request); $users = Psr11::container()->get(UsersDBDataset::class); - $user = $users->getByEmail($json->email); + $user = $users->getByEmail($json["email"]); $token = BaseRepository::getUuid(); $code = rand(10000, 99999); @@ -168,7 +164,7 @@ public function postResetRequest(HttpResponse $response, HttpRequest $request) // Send email using MailWrapper $mailWrapper = Psr11::container()->get(MailWrapperInterface::class); - $envelope = Psr11::container()->get('MAIL_ENVELOPE', [$json->email, "RestReferenceArchitecture - Password Reset", "email_code.html", [ + $envelope = Psr11::container()->get('MAIL_ENVELOPE', [$json["email"], "RestReferenceArchitecture - Password Reset", "email_code.html", [ "code" => trim(chunk_split($code, 1, ' ')), "expire" => 10 ]]); @@ -181,18 +177,16 @@ public function postResetRequest(HttpResponse $response, HttpRequest $request) protected function validateResetToken($response, $request): array { - OpenApiContext::validateRequest($request); - - $json = json_decode($request->payload()); + $json = OpenApiContext::validateRequest($request); $users = Psr11::container()->get(UsersDBDataset::class); - $user = $users->getByEmail($json->email); + $user = $users->getByEmail($json["email"]); if (is_null($user)) { throw new Error422Exception("Invalid data"); } - if ($user->get("resettoken") != $json->token) { + if ($user->get("resettoken") != $json["token"]) { throw new Error422Exception("Invalid data"); } @@ -241,14 +235,14 @@ public function postConfirmCode(HttpResponse $response, HttpRequest $request) { list($users, $user, $json) = $this->validateResetToken($response, $request); - if ($user->get("resetcode") != $json->code) { + if ($user->get("resetcode") != $json["code"]) { throw new Error422Exception("Invalid data"); } $user->set("resetallowed", "yes"); $users->save($user); - $response->write(['token' => $json->token]); + $response->write(['token' => $json["token"]]); } /** @@ -293,13 +287,13 @@ public function postResetPassword(HttpResponse $response, HttpRequest $request) throw new Error422Exception("Invalid data"); } - $user->setPassword($json->password); + $user->setPassword($json["password"]); $user->set("resettoken", null); $user->set("resettokenexpire", null); $user->set("resetcode", null); $user->set("resetallowed", null); $users->save($user); - $response->write(['token' => $json->token]); + $response->write(['token' => $json["token"]]); } } diff --git a/src/Util/JwtContext.php b/src/Util/JwtContext.php index cbfc768..d149a79 100644 --- a/src/Util/JwtContext.php +++ b/src/Util/JwtContext.php @@ -2,7 +2,6 @@ namespace RestReferenceArchitecture\Util; -use ByJG\Authenticate\Model\UserModel; use ByJG\Config\Exception\ConfigException; use ByJG\Config\Exception\ConfigNotFoundException; use ByJG\Config\Exception\DependencyInjectionException; @@ -24,11 +23,11 @@ class JwtContext protected static ?HttpRequest $request; /** - * @param ?UserModel $user + * @param ?User $user * @return array * @throws Error401Exception */ - public static function createUserMetadata(?UserModel $user): array + public static function createUserMetadata(?User $user): array { if (is_null($user)) { throw new Error401Exception('Username or password is invalid'); @@ -52,7 +51,7 @@ public static function createUserMetadata(?UserModel $user): array * @throws KeyNotFoundException * @throws ReflectionException */ - public static function createToken($properties = []) + public static function createToken(array $properties = []) { $jwt = Psr11::container()->get(JwtWrapper::class); $jwtData = $jwt->createJwtData($properties, 60 * 60 * 24 * 7); // 7 Dias diff --git a/src/Util/OpenApiContext.php b/src/Util/OpenApiContext.php index 4766791..5abf66b 100644 --- a/src/Util/OpenApiContext.php +++ b/src/Util/OpenApiContext.php @@ -5,6 +5,7 @@ use ByJG\ApiTools\Base\Schema; use ByJG\RestServer\Exception\Error400Exception; use ByJG\RestServer\HttpRequest; +use ByJG\Serializer\Serialize; use Exception; use RestReferenceArchitecture\Psr11; @@ -21,7 +22,7 @@ public static function validateRequest(HttpRequest $request) $bodyRequestDef = $schema->getRequestParameters($path, $method); // Validate the request body (payload) - if (str_contains($request->getHeader('Content-Type'), 'multipart/')) { + if (str_contains($request->getHeader('Content-Type') ?? "", 'multipart/')) { $requestBody = $request->post(); $files = $request->uploadedFiles()->getKeys(); $requestBody = array_merge($requestBody, array_combine($files, $files)); @@ -35,6 +36,6 @@ public static function validateRequest(HttpRequest $request) throw new Error400Exception(explode("\n", $ex->getMessage())[0]); } - return $requestBody; + return Serialize::from($requestBody)->withDoNotParseNullValues()->toArray(); } -} \ No newline at end of file +} diff --git a/templates/codegen/test.php.jinja b/templates/codegen/test.php.jinja index 048ba1b..a44ef8f 100644 --- a/templates/codegen/test.php.jinja +++ b/templates/codegen/test.php.jinja @@ -1,6 +1,6 @@ Date: Tue, 29 Oct 2024 18:09:51 -0500 Subject: [PATCH 06/14] More compatibility adjustments --- composer.json | 2 +- src/Repository/BaseRepository.php | 61 ++++++++++++++++--------------- src/Util/OpenApiContext.php | 14 +++++-- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/composer.json b/composer.json index adc43b4..5d1f77c 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "prefer-stable": true, "license": "MIT", "require": { - "php": ">=8.1", + "php": ">=8.1 <8.4", "ext-json": "*", "ext-openssl": "*", "ext-curl": "*", diff --git a/src/Repository/BaseRepository.php b/src/Repository/BaseRepository.php index 5b68d6f..d90a452 100644 --- a/src/Repository/BaseRepository.php +++ b/src/Repository/BaseRepository.php @@ -10,8 +10,6 @@ use ByJG\Config\Exception\KeyNotFoundException; use ByJG\MicroOrm\Exception\OrmBeforeInvalidException; use ByJG\MicroOrm\Exception\OrmInvalidFieldsException; -use ByJG\MicroOrm\Exception\RepositoryReadOnlyException; -use ByJG\MicroOrm\Exception\UpdateConstraintException; use ByJG\MicroOrm\FieldMapping; use ByJG\MicroOrm\Literal\HexUuidLiteral; use ByJG\MicroOrm\Literal\Literal; @@ -41,6 +39,11 @@ public function get($itemId) return $this->repository->get(HexUuidLiteral::create($itemId)); } + public function getRepository(): Repository + { + return $this->repository; + } + public function getMapper() { return $this->repository->getMapper(); @@ -84,7 +87,7 @@ public function listGeneric($tableName, $fields = [], $page = 0, $size = 20, $or $object = $query->build($this->repository->getDbDriver()); - $iterator = $this->repository->getDbDriver()->getIterator($object["sql"], $object["params"]); + $iterator = $this->repository->getDbDriver()->getIterator($object->getSql(), $object->getParameters()); return $iterator->toArray(); } @@ -158,26 +161,31 @@ public static function getUuid() protected function setClosureFixBinaryUUID(?Mapper $mapper, $binPropertyName = 'id', $uuidStrPropertyName = 'uuid') { $fieldMapping = FieldMapping::create($binPropertyName) - ->withUpdateFunction(function ($value, $instance) { - if (empty($value)) { - return null; - } - if (!($value instanceof Literal)) { - $value = new HexUuidLiteral($value); - } - return $value; - }) - ->withSelectFunction(function ($value, $instance) use ($binPropertyName, $uuidStrPropertyName) { - if (!empty($uuidStrPropertyName)) { - $fieldValue = $instance->{'get' . $uuidStrPropertyName}(); - } else { - $fieldValue = HexUuidLiteral::getFormattedUuid($instance->{'get' . $binPropertyName}(), false); + ->withUpdateFunction( + function ($value, $instance) { + if (empty($value)) { + return null; + } + if (!($value instanceof Literal)) { + $value = new HexUuidLiteral($value); + } + return $value; } - if (is_null($fieldValue)) { - return null; + ) + ->withSelectFunction( + function ($value, $instance) use ($binPropertyName, $uuidStrPropertyName) { + if (!empty($uuidStrPropertyName)) { + $fieldValue = $instance->{'get' . $uuidStrPropertyName}(); + } else { + $itemValue = $instance->{'get' . $binPropertyName}(); + $fieldValue = HexUuidLiteral::getFormattedUuid($itemValue, false, $itemValue); + } + if (is_null($fieldValue)) { + return null; + } + return $fieldValue; } - return $fieldValue; - }); + ); if (!empty($mapper)) { $mapper->addFieldMapping($fieldMapping); @@ -187,15 +195,12 @@ protected function setClosureFixBinaryUUID(?Mapper $mapper, $binPropertyName = ' } /** - * @param $model - * @param UpdateConstraint|null $updateConstraint + * @param $model * @return mixed * @throws InvalidArgumentException * @throws OrmBeforeInvalidException * @throws OrmInvalidFieldsException * @throws \ByJG\MicroOrm\Exception\InvalidArgumentException - * @throws RepositoryReadOnlyException - * @throws UpdateConstraintException */ public function save($model, ?UpdateConstraint $updateConstraint = null) { @@ -203,10 +208,8 @@ public function save($model, ?UpdateConstraint $updateConstraint = null) $primaryKey = $this->repository->getMapper()->getPrimaryKey()[0]; - if ($model->{"get$primaryKey"}() instanceof HexUuidLiteral) { - /** @var HexUuidLiteral $literal */ - $literal = $model->{"get$primaryKey"}(); - $model->{"set$primaryKey"}($literal->formatUuid()); + if ($model->{"get$primaryKey"}() instanceof Literal) { + $model->{"set$primaryKey"}(HexUuidLiteral::create($model->{"get$primaryKey"}())); } return $model; diff --git a/src/Util/OpenApiContext.php b/src/Util/OpenApiContext.php index 5abf66b..480f3b4 100644 --- a/src/Util/OpenApiContext.php +++ b/src/Util/OpenApiContext.php @@ -18,9 +18,6 @@ public static function validateRequest(HttpRequest $request) $path = $request->getRequestPath(); $method = $request->server('REQUEST_METHOD'); - // Returns a SwaggerRequestBody instance - $bodyRequestDef = $schema->getRequestParameters($path, $method); - // Validate the request body (payload) if (str_contains($request->getHeader('Content-Type') ?? "", 'multipart/')) { $requestBody = $request->post(); @@ -31,7 +28,16 @@ public static function validateRequest(HttpRequest $request) } try { - $bodyRequestDef->match($requestBody); + // Validate the request path and query against the OpenAPI schema + $schema->getPathDefinition($path, $method); + + if (!empty($requestBody)) { + // Returns a SwaggerRequestBody instance + $bodyRequestDef = $schema->getRequestParameters($path, $method); + $bodyRequestDef->match($requestBody); + } else { + return []; + } } catch (Exception $ex) { throw new Error400Exception(explode("\n", $ex->getMessage())[0]); } From 94e2886f93f613c0808d5360f64513a451a882db Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Tue, 18 Mar 2025 21:15:33 -0500 Subject: [PATCH 07/14] Upgrade to PHP 8.3 --- builder/PostCreateScript.php | 4 ++-- docker/Dockerfile | 4 ++-- docs/getting_started.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/builder/PostCreateScript.php b/builder/PostCreateScript.php index a09489f..27d9483 100755 --- a/builder/PostCreateScript.php +++ b/builder/PostCreateScript.php @@ -51,8 +51,8 @@ public function execute($workdir, $namespace, $composerName, $phpVersion, $mysql foreach ($files as $file) { $contents = file_get_contents("$workdir/$file"); $contents = str_replace('ENV TZ=UTC', "ENV TZ=$timezone", $contents); - $contents = str_replace('php:8.1-fpm', "php:$phpVersion-fpm", $contents); - $contents = str_replace('php81', "php$phpVersionMSimple", $contents); + $contents = str_replace('php:8.3-fpm', "php:$phpVersion-fpm", $contents); + $contents = str_replace('php83', "php$phpVersionMSimple", $contents); file_put_contents( "$workdir/$file", $contents diff --git a/docker/Dockerfile b/docker/Dockerfile index 77eac31..87e9347 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM byjg/php:8.1-fpm-nginx +FROM byjg/php:8.3-fpm-nginx-2025.03 # Refer to the documentation to setup the environment variables # https://github.com/byjg/docker-php/blob/master/docs/environment.md @@ -10,7 +10,7 @@ WORKDIR /srv # Setup Docker/Fpm -COPY ./docker/fpm/php /etc/php81/conf.d +COPY ./docker/fpm/php /etc/php83/conf.d COPY ./docker/nginx/conf.d /etc/nginx/http.d/ # Setup DateFile diff --git a/docs/getting_started.md b/docs/getting_started.md index 6992862..dcc4aac 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -29,7 +29,7 @@ Required PHP extensions: ```bash mkdir ~/tutorial -composer create-project byjg/rest-reference-architecture ~/tutorial 4.9.* +composer create-project byjg/rest-reference-architecture ~/tutorial 5.0.* ``` or the latest development version: From 02e1ad66fb878d5464973d91e676b1939722f2d4 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Tue, 18 Mar 2025 21:54:52 -0500 Subject: [PATCH 08/14] More adjustents --- .github/workflows/build-app-image.yml | 10 ++++---- builder/BaseScripts.php | 10 ++++---- builder/PostCreateScript.php | 24 +++++++++++------- builder/Scripts.php | 16 ++++++++---- composer.json | 2 +- config/config-dev.env | 2 +- config/config-prod.php | 3 ++- config/config-staging.php | 3 ++- config/config-test.php | 3 ++- docker/fpm/php/custom.ini | 2 ++ src/Model/User.php | 16 +++++++++--- src/OpenApiSpec.php | 15 +++++++----- src/Psr11.php | 11 ++++++--- src/Util/OpenApiContext.php | 35 +++++++++++++++++++++------ 14 files changed, 102 insertions(+), 50 deletions(-) diff --git a/.github/workflows/build-app-image.yml b/.github/workflows/build-app-image.yml index d77f6a1..4d0c9f3 100644 --- a/.github/workflows/build-app-image.yml +++ b/.github/workflows/build-app-image.yml @@ -57,18 +57,18 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # Login against a Docker registry to pull the GHCR image # https://github.com/docker/login-action - name: Log into registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -85,7 +85,7 @@ jobs: # Build image locally to enable to run the test # https://github.com/docker/build-push-action - name: Build Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: docker/Dockerfile diff --git a/builder/BaseScripts.php b/builder/BaseScripts.php index b8dee10..4c7ce32 100644 --- a/builder/BaseScripts.php +++ b/builder/BaseScripts.php @@ -14,7 +14,7 @@ class BaseScripts { - protected $workdir; + protected string|false $workdir; protected string $systemOs; public function __construct() @@ -47,9 +47,9 @@ public function fixDir($command) /** * Execute the given command by displaying console output live to the user. * - * @param string|array $cmd : command to be executed - * @return array exit_status : exit status of the executed command - * output : console output of the executed command + * @param array|string $cmd : command to be executed + * @return array|null exit_status : exit status of the executed command + * output : console output of the executed command * @throws ConfigException * @throws ConfigNotFoundException * @throws DependencyInjectionException @@ -58,7 +58,7 @@ public function fixDir($command) * @throws KeyNotFoundException * @throws ReflectionException */ - protected function liveExecuteCommand($cmd): ?array + protected function liveExecuteCommand(array|string $cmd): ?array { // while (@ ob_end_flush()); // end all output buffers if any diff --git a/builder/PostCreateScript.php b/builder/PostCreateScript.php index 27d9483..ce0c01a 100755 --- a/builder/PostCreateScript.php +++ b/builder/PostCreateScript.php @@ -6,16 +6,17 @@ use ByJG\JwtWrapper\JwtWrapper; use ByJG\Util\Uri; use Composer\Script\Event; +use Exception; use RecursiveCallbackFilterIterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; class PostCreateScript { - public function execute($workdir, $namespace, $composerName, $phpVersion, $mysqlConnection, $timezone) + public function execute($workdir, $namespace, $composerName, $phpVersion, $mysqlConnection, $timezone): void { // ------------------------------------------------ - // Defining function to interatively walking through the directories + // Defining function to interactively walking through the directories $directory = new RecursiveDirectoryIterator($workdir); $filter = new RecursiveCallbackFilterIterator($directory, function ($current/*, $key, $iterator*/) { // Skip hidden files and directories. @@ -88,12 +89,12 @@ public function execute($workdir, $namespace, $composerName, $phpVersion, $mysql $objects = new RecursiveIteratorIterator($filter); foreach ($objects as $name => $object) { $contents = file_get_contents($name); - if (strpos($contents, 'RestReferenceArchitecture') !== false) { + if (str_contains($contents, 'RestReferenceArchitecture')) { echo "$name\n"; // Replace inside Quotes $contents = preg_replace( - "/([\'\"])RestReferenceArchitecture(.*?[\'\"])/", + "/(['\"])RestReferenceArchitecture(.*?['\"])/", '$1' . str_replace('\\', '\\\\\\\\', $namespace) . '$2', $contents ); @@ -117,6 +118,11 @@ public function execute($workdir, $namespace, $composerName, $phpVersion, $mysql } } + /** + * @param Event $event + * @return void + * @throws Exception + */ public static function run(Event $event) { $workdir = realpath(__DIR__ . '/..'); @@ -129,19 +135,19 @@ public static function run(Event $event) if (in_array($arg, $validPHPVersions)) { return $arg; } - throw new \Exception('Only the PHP versions ' . implode(', ', $validPHPVersions) . ' are supported'); + throw new Exception('Only the PHP versions ' . implode(', ', $validPHPVersions) . ' are supported'); }; $validateNamespace = function ($arg) { if (empty($arg) || !preg_match('/^[A-Z][a-zA-Z0-9]*$/', $arg)) { - throw new \Exception('Namespace must be one word in CamelCase'); + throw new Exception('Namespace must be one word in CamelCase'); } return $arg; }; $validateComposer = function ($arg) { if (empty($arg) || !preg_match('/^[a-z0-9-]+\/[a-z0-9-]+$/', $arg)) { - throw new \Exception('Invalid Composer name'); + throw new Exception('Invalid Composer name'); } return $arg; }; @@ -149,7 +155,7 @@ public static function run(Event $event) $validateURI = function ($arg) { $uri = new Uri($arg); if (empty($uri->getScheme())) { - throw new \Exception('Invalid URI'); + throw new Exception('Invalid URI'); } Factory::getRegisteredDrivers($uri->getScheme()); return $arg; @@ -157,7 +163,7 @@ public static function run(Event $event) $validateTimeZone = function ($arg) { if (empty($arg) || !in_array($arg, timezone_identifiers_list())) { - throw new \Exception('Invalid Timezone'); + throw new Exception('Invalid Timezone'); } return $arg; }; diff --git a/builder/Scripts.php b/builder/Scripts.php index f54d485..5302603 100755 --- a/builder/Scripts.php +++ b/builder/Scripts.php @@ -11,6 +11,7 @@ use ByJG\DbMigration\Database\MySqlDatabase; use ByJG\DbMigration\Exception\InvalidMigrationFile; use ByJG\DbMigration\Migration; +use ByJG\JinjaPhp\Exception\TemplateParseException; use ByJG\JinjaPhp\Loader\FileSystemLoader; use ByJG\Util\Uri; use Composer\Script\Event; @@ -39,7 +40,7 @@ public function __construct() * @throws KeyNotFoundException * @throws ReflectionException */ - public static function migrate(Event $event) + public static function migrate(Event $event): void { $migrate = new Scripts(); $migrate->runMigrate($event->getArguments()); @@ -53,7 +54,7 @@ public static function migrate(Event $event) * @throws KeyNotFoundException * @throws ReflectionException */ - public static function genOpenApiDocs(Event $event) + public static function genOpenApiDocs(Event $event): void { $build = new Scripts(); $build->runGenOpenApiDocs($event->getArguments()); @@ -62,14 +63,16 @@ public static function genOpenApiDocs(Event $event) /** * @param Event $event * @return void + * @throws ConfigException * @throws ConfigNotFoundException * @throws DependencyInjectionException * @throws InvalidArgumentException + * @throws InvalidDateException * @throws KeyNotFoundException * @throws ReflectionException - * @throws \ByJG\Serializer\Exception\InvalidArgumentException + * @throws TemplateParseException */ - public static function codeGenerator(Event $event) + public static function codeGenerator(Event $event): void { $build = new Scripts(); $build->runCodeGenerator($event->getArguments()); @@ -86,6 +89,7 @@ public static function codeGenerator(Event $event) * @throws ReflectionException * @throws ConfigException * @throws InvalidDateException + * @throws Exception */ public function runMigrate($arguments): void { @@ -179,12 +183,14 @@ public function runGenOpenApiDocs(array $arguments): void /** * @param array $arguments * @return void + * @throws ConfigException * @throws ConfigNotFoundException * @throws DependencyInjectionException * @throws InvalidArgumentException + * @throws InvalidDateException * @throws KeyNotFoundException * @throws ReflectionException - * @throws \ByJG\Serializer\Exception\InvalidArgumentException + * @throws TemplateParseException * @throws Exception */ public function runCodeGenerator(array $arguments): void diff --git a/composer.json b/composer.json index 5d1f77c..5069cdc 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "migrate": "Builder\\Scripts::migrate", "codegen": "Builder\\Scripts::codeGenerator", "openapi": "Builder\\Scripts::genOpenApiDocs", - "compile": "composer run openapi && composer run test", + "compile": "git pull && composer run openapi && composer run test", "up-local-dev": "docker compose -f docker-compose-dev.yml up -d", "down-local-dev": "docker compose -f docker-compose-dev.yml down", "post-create-project-cmd": "Builder\\PostCreateScript::run" diff --git a/config/config-dev.env b/config/config-dev.env index 39aed66..0f333ee 100644 --- a/config/config-dev.env +++ b/config/config-dev.env @@ -5,5 +5,5 @@ API_SERVER=localhost API_SCHEMA=http DBDRIVER_CONNECTION=mysql://root:mysqlp455w0rd@mysql-container/mydb EMAIL_CONNECTION=smtp://username:password@mail.example.com -EMAIL_TRANSACTIONAL_FROM=No Reply +EMAIL_TRANSACTIONAL_FROM='No Reply ' CORS_SERVERS=.* diff --git a/config/config-prod.php b/config/config-prod.php index a3702e8..564120f 100644 --- a/config/config-prod.php +++ b/config/config-prod.php @@ -1,10 +1,11 @@ DI::bind(\ByJG\JwtWrapper\JwtHashHmacSecret::class) + JwtKeyInterface::class => DI::bind(JwtHashHmacSecret::class) ->withConstructorArgs(['jwt_super_secret_key']) ->toSingleton(), ]; diff --git a/config/config-staging.php b/config/config-staging.php index bc37337..7637465 100644 --- a/config/config-staging.php +++ b/config/config-staging.php @@ -3,13 +3,14 @@ use ByJG\Cache\Psr16\BaseCacheEngine; use ByJG\Cache\Psr16\FileSystemCacheEngine; use ByJG\Config\DependencyInjection as DI; +use ByJG\JwtWrapper\JwtHashHmacSecret; use ByJG\JwtWrapper\JwtKeyInterface; return [ BaseCacheEngine::class => DI::bind(FileSystemCacheEngine::class)->toSingleton(), - JwtKeyInterface::class => DI::bind(\ByJG\JwtWrapper\JwtHashHmacSecret::class) + JwtKeyInterface::class => DI::bind(JwtHashHmacSecret::class) ->withConstructorArgs(['jwt_super_secret_key']) ->toSingleton(), diff --git a/config/config-test.php b/config/config-test.php index a3f1fca..e36bc90 100644 --- a/config/config-test.php +++ b/config/config-test.php @@ -1,10 +1,11 @@ DI::bind(\ByJG\JwtWrapper\JwtHashHmacSecret::class) + JwtKeyInterface::class => DI::bind(JwtHashHmacSecret::class) ->withConstructorArgs(['jwt_super_secret_key']) ->toSingleton(), ]; diff --git a/docker/fpm/php/custom.ini b/docker/fpm/php/custom.ini index 2f645c1..a64bed2 100644 --- a/docker/fpm/php/custom.ini +++ b/docker/fpm/php/custom.ini @@ -1,3 +1,5 @@ +expose_php = off + date.timezone = UTC max_execution_time = 300 diff --git a/src/Model/User.php b/src/Model/User.php index 74c90e5..bf57ee5 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -2,12 +2,17 @@ namespace RestReferenceArchitecture\Model; +use ByJG\Authenticate\Definition\PasswordDefinition; use ByJG\Authenticate\Model\UserModel; use ByJG\Authenticate\Model\UserPropertiesModel; +use ByJG\MicroOrm\Attributes\TableAttribute; use ByJG\MicroOrm\Literal\HexUuidLiteral; +use Exception; use InvalidArgumentException; use OpenApi\Attributes as OA; +use RestReferenceArchitecture\Psr11; +#[TableAttribute("users")] #[OA\Schema(required: ["email"], type: "object", xml: new OA\Xml(name: "User"))] class User extends UserModel { @@ -89,10 +94,13 @@ class User extends UserModel * @param string $username * @param string $password * @param string $admin + * @throws Exception */ public function __construct(string $name = "", string $email = "", string $username = "", string $password = "", string $admin = "no") { parent::__construct($name, $email, $username, $password, $admin); + + $this->withPasswordDefinition(Psr11::container()->get(PasswordDefinition::class)); } @@ -173,9 +181,11 @@ public function getPassword(): ?string */ public function setPassword(?string $password): void { - // Password len equals to 40 means that the password is already encrypted with sha1 - if (!empty($password) && strlen($password) != 40 && !empty($this->passwordDefinition) && !$this->passwordDefinition->matchPassword($password)) { - throw new InvalidArgumentException("Password does not match the password definition"); + if (!empty($this->passwordDefinition) && !empty($password) && strlen($password) != 40) { + $result = $this->passwordDefinition->matchPassword($password); + if ($result != PasswordDefinition::SUCCESS) { + throw new InvalidArgumentException("Password does not match the password definition [{$result}]"); + } } $this->password = $password; } diff --git a/src/OpenApiSpec.php b/src/OpenApiSpec.php index 0bb7ab0..c851915 100644 --- a/src/OpenApiSpec.php +++ b/src/OpenApiSpec.php @@ -25,12 +25,15 @@ #[OA\Schema( schema: 'error', properties: [ - new OA\Property('error', properties: [ - new OA\Property(property: 'type', description: 'A class de Exceção', type: 'string'), - new OA\Property(property: 'message', description: 'A mensagem de erro', type: 'string'), - new OA\Property(property: 'file', description: 'O arquivo que gerou o erro', type: 'string'), - new OA\Property(property: 'line', description: 'A linha do erro', type: 'integer'), - ]) + new OA\Property( + 'error', + properties: [ + new OA\Property(property: 'type', description: 'A class de Exceção', type: 'string'), + new OA\Property(property: 'message', description: 'A mensagem de erro', type: 'string'), + new OA\Property(property: 'file', description: 'O arquivo que gerou o erro', type: 'string'), + new OA\Property(property: 'line', description: 'A linha do erro', type: 'integer'), + ] + ) ], type: 'object' )] diff --git a/src/Psr11.php b/src/Psr11.php index f938998..9ec5d5e 100644 --- a/src/Psr11.php +++ b/src/Psr11.php @@ -8,7 +8,6 @@ use ByJG\Config\Environment; use ByJG\Config\Exception\ConfigException; use ByJG\Config\Exception\ConfigNotFoundException; -use ByJG\Config\Exception\InvalidDateException; use Psr\SimpleCache\InvalidArgumentException; class Psr11 @@ -18,11 +17,10 @@ class Psr11 /** * @param string|null $env - * @return Container + * @return Container|null * @throws ConfigException * @throws ConfigNotFoundException * @throws InvalidArgumentException - * @throws InvalidDateException */ public static function container(?string $env = null): ?Container { @@ -53,7 +51,12 @@ public static function environment(): ?Definition ->addEnvironment($test) ->addEnvironment($staging) ->addEnvironment($prod) - ; + ->withOSEnvironment( + [ + 'TAG_VERSION', + 'TAG_COMMIT', + ] + ); } return self::$definition; diff --git a/src/Util/OpenApiContext.php b/src/Util/OpenApiContext.php index 480f3b4..88a3944 100644 --- a/src/Util/OpenApiContext.php +++ b/src/Util/OpenApiContext.php @@ -3,15 +3,32 @@ namespace RestReferenceArchitecture\Util; use ByJG\ApiTools\Base\Schema; +use ByJG\Config\Exception\ConfigException; +use ByJG\Config\Exception\ConfigNotFoundException; +use ByJG\Config\Exception\DependencyInjectionException; +use ByJG\Config\Exception\InvalidDateException; +use ByJG\Config\Exception\KeyNotFoundException; use ByJG\RestServer\Exception\Error400Exception; use ByJG\RestServer\HttpRequest; use ByJG\Serializer\Serialize; use Exception; +use Psr\SimpleCache\InvalidArgumentException; +use ReflectionException; use RestReferenceArchitecture\Psr11; class OpenApiContext { - public static function validateRequest(HttpRequest $request) + /** + * @throws DependencyInjectionException + * @throws InvalidDateException + * @throws ConfigNotFoundException + * @throws KeyNotFoundException + * @throws Error400Exception + * @throws InvalidArgumentException + * @throws ConfigException + * @throws ReflectionException + */ + public static function validateRequest(HttpRequest $request, bool $allowNull = false) { $schema = Psr11::container()->get(Schema::class); @@ -30,18 +47,20 @@ public static function validateRequest(HttpRequest $request) try { // Validate the request path and query against the OpenAPI schema $schema->getPathDefinition($path, $method); + // Returns a SwaggerRequestBody instance + $bodyRequestDef = $schema->getRequestParameters($path, $method); + $bodyRequestDef->match($requestBody); - if (!empty($requestBody)) { - // Returns a SwaggerRequestBody instance - $bodyRequestDef = $schema->getRequestParameters($path, $method); - $bodyRequestDef->match($requestBody); - } else { - return []; - } } catch (Exception $ex) { throw new Error400Exception(explode("\n", $ex->getMessage())[0]); } + $requestBody = empty($requestBody) ? [] : $requestBody; + + if ($allowNull) { + return $requestBody; + } + return Serialize::from($requestBody)->withDoNotParseNullValues()->toArray(); } } From 35124240b6186c3a034e9aad290d8999a2d75a45 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Tue, 18 Mar 2025 21:54:52 -0500 Subject: [PATCH 09/14] Add Windows guide and enhance getting started docs --- .github/workflows/build-app-image.yml | 10 +- builder/BaseScripts.php | 10 +- builder/PostCreateScript.php | 24 ++- builder/Scripts.php | 16 +- composer.json | 2 +- config/config-dev.env | 2 +- config/config-prod.php | 3 +- config/config-staging.php | 3 +- config/config-test.php | 3 +- ...000.sql => 00000-rollback-table-users.sql} | 0 ...00001.sql => 00001-create-table-users.sql} | 0 docker/fpm/php/custom.ini | 2 + docs/getting_started.md | 73 ++++--- docs/getting_started_01_create_table.md | 129 +++++------ docs/getting_started_02_add_new_field.md | 26 +-- docs/getting_started_03_create_rest_method.md | 204 +++++++++++------- docs/windows.md | 27 +++ src/Model/User.php | 16 +- src/OpenApiSpec.php | 15 +- src/Psr11.php | 11 +- src/Util/OpenApiContext.php | 35 ++- 21 files changed, 353 insertions(+), 258 deletions(-) rename db/migrations/down/{00000.sql => 00000-rollback-table-users.sql} (100%) rename db/migrations/up/{00001.sql => 00001-create-table-users.sql} (100%) create mode 100644 docs/windows.md diff --git a/.github/workflows/build-app-image.yml b/.github/workflows/build-app-image.yml index d77f6a1..4d0c9f3 100644 --- a/.github/workflows/build-app-image.yml +++ b/.github/workflows/build-app-image.yml @@ -57,18 +57,18 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # Login against a Docker registry to pull the GHCR image # https://github.com/docker/login-action - name: Log into registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -85,7 +85,7 @@ jobs: # Build image locally to enable to run the test # https://github.com/docker/build-push-action - name: Build Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . file: docker/Dockerfile diff --git a/builder/BaseScripts.php b/builder/BaseScripts.php index b8dee10..4c7ce32 100644 --- a/builder/BaseScripts.php +++ b/builder/BaseScripts.php @@ -14,7 +14,7 @@ class BaseScripts { - protected $workdir; + protected string|false $workdir; protected string $systemOs; public function __construct() @@ -47,9 +47,9 @@ public function fixDir($command) /** * Execute the given command by displaying console output live to the user. * - * @param string|array $cmd : command to be executed - * @return array exit_status : exit status of the executed command - * output : console output of the executed command + * @param array|string $cmd : command to be executed + * @return array|null exit_status : exit status of the executed command + * output : console output of the executed command * @throws ConfigException * @throws ConfigNotFoundException * @throws DependencyInjectionException @@ -58,7 +58,7 @@ public function fixDir($command) * @throws KeyNotFoundException * @throws ReflectionException */ - protected function liveExecuteCommand($cmd): ?array + protected function liveExecuteCommand(array|string $cmd): ?array { // while (@ ob_end_flush()); // end all output buffers if any diff --git a/builder/PostCreateScript.php b/builder/PostCreateScript.php index 27d9483..ce0c01a 100755 --- a/builder/PostCreateScript.php +++ b/builder/PostCreateScript.php @@ -6,16 +6,17 @@ use ByJG\JwtWrapper\JwtWrapper; use ByJG\Util\Uri; use Composer\Script\Event; +use Exception; use RecursiveCallbackFilterIterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; class PostCreateScript { - public function execute($workdir, $namespace, $composerName, $phpVersion, $mysqlConnection, $timezone) + public function execute($workdir, $namespace, $composerName, $phpVersion, $mysqlConnection, $timezone): void { // ------------------------------------------------ - // Defining function to interatively walking through the directories + // Defining function to interactively walking through the directories $directory = new RecursiveDirectoryIterator($workdir); $filter = new RecursiveCallbackFilterIterator($directory, function ($current/*, $key, $iterator*/) { // Skip hidden files and directories. @@ -88,12 +89,12 @@ public function execute($workdir, $namespace, $composerName, $phpVersion, $mysql $objects = new RecursiveIteratorIterator($filter); foreach ($objects as $name => $object) { $contents = file_get_contents($name); - if (strpos($contents, 'RestReferenceArchitecture') !== false) { + if (str_contains($contents, 'RestReferenceArchitecture')) { echo "$name\n"; // Replace inside Quotes $contents = preg_replace( - "/([\'\"])RestReferenceArchitecture(.*?[\'\"])/", + "/(['\"])RestReferenceArchitecture(.*?['\"])/", '$1' . str_replace('\\', '\\\\\\\\', $namespace) . '$2', $contents ); @@ -117,6 +118,11 @@ public function execute($workdir, $namespace, $composerName, $phpVersion, $mysql } } + /** + * @param Event $event + * @return void + * @throws Exception + */ public static function run(Event $event) { $workdir = realpath(__DIR__ . '/..'); @@ -129,19 +135,19 @@ public static function run(Event $event) if (in_array($arg, $validPHPVersions)) { return $arg; } - throw new \Exception('Only the PHP versions ' . implode(', ', $validPHPVersions) . ' are supported'); + throw new Exception('Only the PHP versions ' . implode(', ', $validPHPVersions) . ' are supported'); }; $validateNamespace = function ($arg) { if (empty($arg) || !preg_match('/^[A-Z][a-zA-Z0-9]*$/', $arg)) { - throw new \Exception('Namespace must be one word in CamelCase'); + throw new Exception('Namespace must be one word in CamelCase'); } return $arg; }; $validateComposer = function ($arg) { if (empty($arg) || !preg_match('/^[a-z0-9-]+\/[a-z0-9-]+$/', $arg)) { - throw new \Exception('Invalid Composer name'); + throw new Exception('Invalid Composer name'); } return $arg; }; @@ -149,7 +155,7 @@ public static function run(Event $event) $validateURI = function ($arg) { $uri = new Uri($arg); if (empty($uri->getScheme())) { - throw new \Exception('Invalid URI'); + throw new Exception('Invalid URI'); } Factory::getRegisteredDrivers($uri->getScheme()); return $arg; @@ -157,7 +163,7 @@ public static function run(Event $event) $validateTimeZone = function ($arg) { if (empty($arg) || !in_array($arg, timezone_identifiers_list())) { - throw new \Exception('Invalid Timezone'); + throw new Exception('Invalid Timezone'); } return $arg; }; diff --git a/builder/Scripts.php b/builder/Scripts.php index f54d485..5302603 100755 --- a/builder/Scripts.php +++ b/builder/Scripts.php @@ -11,6 +11,7 @@ use ByJG\DbMigration\Database\MySqlDatabase; use ByJG\DbMigration\Exception\InvalidMigrationFile; use ByJG\DbMigration\Migration; +use ByJG\JinjaPhp\Exception\TemplateParseException; use ByJG\JinjaPhp\Loader\FileSystemLoader; use ByJG\Util\Uri; use Composer\Script\Event; @@ -39,7 +40,7 @@ public function __construct() * @throws KeyNotFoundException * @throws ReflectionException */ - public static function migrate(Event $event) + public static function migrate(Event $event): void { $migrate = new Scripts(); $migrate->runMigrate($event->getArguments()); @@ -53,7 +54,7 @@ public static function migrate(Event $event) * @throws KeyNotFoundException * @throws ReflectionException */ - public static function genOpenApiDocs(Event $event) + public static function genOpenApiDocs(Event $event): void { $build = new Scripts(); $build->runGenOpenApiDocs($event->getArguments()); @@ -62,14 +63,16 @@ public static function genOpenApiDocs(Event $event) /** * @param Event $event * @return void + * @throws ConfigException * @throws ConfigNotFoundException * @throws DependencyInjectionException * @throws InvalidArgumentException + * @throws InvalidDateException * @throws KeyNotFoundException * @throws ReflectionException - * @throws \ByJG\Serializer\Exception\InvalidArgumentException + * @throws TemplateParseException */ - public static function codeGenerator(Event $event) + public static function codeGenerator(Event $event): void { $build = new Scripts(); $build->runCodeGenerator($event->getArguments()); @@ -86,6 +89,7 @@ public static function codeGenerator(Event $event) * @throws ReflectionException * @throws ConfigException * @throws InvalidDateException + * @throws Exception */ public function runMigrate($arguments): void { @@ -179,12 +183,14 @@ public function runGenOpenApiDocs(array $arguments): void /** * @param array $arguments * @return void + * @throws ConfigException * @throws ConfigNotFoundException * @throws DependencyInjectionException * @throws InvalidArgumentException + * @throws InvalidDateException * @throws KeyNotFoundException * @throws ReflectionException - * @throws \ByJG\Serializer\Exception\InvalidArgumentException + * @throws TemplateParseException * @throws Exception */ public function runCodeGenerator(array $arguments): void diff --git a/composer.json b/composer.json index 5d1f77c..5069cdc 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "migrate": "Builder\\Scripts::migrate", "codegen": "Builder\\Scripts::codeGenerator", "openapi": "Builder\\Scripts::genOpenApiDocs", - "compile": "composer run openapi && composer run test", + "compile": "git pull && composer run openapi && composer run test", "up-local-dev": "docker compose -f docker-compose-dev.yml up -d", "down-local-dev": "docker compose -f docker-compose-dev.yml down", "post-create-project-cmd": "Builder\\PostCreateScript::run" diff --git a/config/config-dev.env b/config/config-dev.env index 39aed66..0f333ee 100644 --- a/config/config-dev.env +++ b/config/config-dev.env @@ -5,5 +5,5 @@ API_SERVER=localhost API_SCHEMA=http DBDRIVER_CONNECTION=mysql://root:mysqlp455w0rd@mysql-container/mydb EMAIL_CONNECTION=smtp://username:password@mail.example.com -EMAIL_TRANSACTIONAL_FROM=No Reply +EMAIL_TRANSACTIONAL_FROM='No Reply ' CORS_SERVERS=.* diff --git a/config/config-prod.php b/config/config-prod.php index a3702e8..564120f 100644 --- a/config/config-prod.php +++ b/config/config-prod.php @@ -1,10 +1,11 @@ DI::bind(\ByJG\JwtWrapper\JwtHashHmacSecret::class) + JwtKeyInterface::class => DI::bind(JwtHashHmacSecret::class) ->withConstructorArgs(['jwt_super_secret_key']) ->toSingleton(), ]; diff --git a/config/config-staging.php b/config/config-staging.php index bc37337..7637465 100644 --- a/config/config-staging.php +++ b/config/config-staging.php @@ -3,13 +3,14 @@ use ByJG\Cache\Psr16\BaseCacheEngine; use ByJG\Cache\Psr16\FileSystemCacheEngine; use ByJG\Config\DependencyInjection as DI; +use ByJG\JwtWrapper\JwtHashHmacSecret; use ByJG\JwtWrapper\JwtKeyInterface; return [ BaseCacheEngine::class => DI::bind(FileSystemCacheEngine::class)->toSingleton(), - JwtKeyInterface::class => DI::bind(\ByJG\JwtWrapper\JwtHashHmacSecret::class) + JwtKeyInterface::class => DI::bind(JwtHashHmacSecret::class) ->withConstructorArgs(['jwt_super_secret_key']) ->toSingleton(), diff --git a/config/config-test.php b/config/config-test.php index a3f1fca..e36bc90 100644 --- a/config/config-test.php +++ b/config/config-test.php @@ -1,10 +1,11 @@ DI::bind(\ByJG\JwtWrapper\JwtHashHmacSecret::class) + JwtKeyInterface::class => DI::bind(JwtHashHmacSecret::class) ->withConstructorArgs(['jwt_super_secret_key']) ->toSingleton(), ]; diff --git a/db/migrations/down/00000.sql b/db/migrations/down/00000-rollback-table-users.sql similarity index 100% rename from db/migrations/down/00000.sql rename to db/migrations/down/00000-rollback-table-users.sql diff --git a/db/migrations/up/00001.sql b/db/migrations/up/00001-create-table-users.sql similarity index 100% rename from db/migrations/up/00001.sql rename to db/migrations/up/00001-create-table-users.sql diff --git a/docker/fpm/php/custom.ini b/docker/fpm/php/custom.ini index 2f645c1..a64bed2 100644 --- a/docker/fpm/php/custom.ini +++ b/docker/fpm/php/custom.ini @@ -1,3 +1,5 @@ +expose_php = off + date.timezone = UTC max_execution_time = 300 diff --git a/docs/getting_started.md b/docs/getting_started.md index dcc4aac..e9a85c2 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -2,11 +2,14 @@ ## Requirements -Docker engine, PHP and an IDE. +- **Docker Engine**: For containerizing your application +- **PHP 8.3+**: For local development +- **IDE**: Any development environment of your choice -You'll need PHP 8.1 or higher installed in your machine. Preferrable the same version as you want to work with your project. +> **Windows Users**: If you don't have PHP installed in WSL2, please follow the [Windows](windows.md) guide. + +### Required PHP Extensions -Required PHP extensions: - ctype - curl - dom @@ -27,19 +30,21 @@ Required PHP extensions: ## Installation -```bash +Choose one of the following installation methods: + +```shell script +# Standard installation mkdir ~/tutorial composer create-project byjg/rest-reference-architecture ~/tutorial 5.0.* -``` -or the latest development version: - -```bash +# OR Latest development version mkdir ~/tutorial composer -sdev create-project byjg/rest-reference-architecture ~/tutorial master ``` -This process will ask some questions to setup your project. You can use the following below as a guide: +### Setup Configuration + +The installation will prompt you for configuration details: ```text > Builder\PostCreateScript::run @@ -48,43 +53,38 @@ This process will ask some questions to setup your project. You can use the foll Answer the questions below ======================================================== -Project Directory: /tmp/tutorial -PHP Version [7.4]: 8.1 +Project Directory: ~/tutorial +PHP Version [8.3]: 8.3 Project namespace [MyRest]: Tutorial Composer name [me/myrest]: MySQL connection DEV [mysql://root:mysqlp455w0rd@mysql-container/mydb]: -Timezone [UTC]: -Press to continue +Timezone [UTC]: ``` -Tip: The docker composer will create MySQL container named as `mysql-container` ([ref](https://github.com/byjg/php-rest-template/blob/master/docker-compose-dev.yml#L20)). -If you want to be able to access your MySQL container from your machine you need to add the following entry in your `/etc/hosts` file: - +**Tip**: To access the MySQL container locally, add this to your `/etc/hosts` file: ``` 127.0.0.1 mysql-container ``` - ## Running the Project -```bash +```shell cd ~/tutorial docker-compose -f docker-compose-dev.yml up -d ``` -## Creating the Database +## Database Setup -```bash -# Important this will destroy ALL DB data and create a fresh new database based on the migration +```shell +# Create a fresh database (warning: destroys existing data) APP_ENV=dev composer run migrate -- reset --yes -# *IF* your local PHP is not properly setup you can run this instead: -# export CONTAINER_NAME=# it is the second part of the composer name. e.g. me/myrest, it should be "myrest" +# Alternative if local PHP isn't configured: +# export CONTAINER_NAME=myrest # second part of your composer name # docker exec -it $CONTAINER_NAME composer run migrate -- reset --yes ``` -The result should be: - +Expected output: ```text > Builder\Scripts::migrate > Command: reset @@ -92,32 +92,31 @@ Doing reset, 0 Doing migrate, 1 ``` -## Testing the Project +## Verify Installation -```bash +```shell script curl http://localhost:8080/sample/ping ``` -The result: - +Expected response: ```json {"result":"pong"} ``` -## Running the Unit Tests - -```bash -APP_ENV=dev composer run test # Alternatively you can run `./vendor/bin/phpunit` +## Run Tests +```shell script +APP_ENV=dev composer run test # OR: docker exec -it $CONTAINER_NAME composer run test ``` -## Accessing the Swagger Documentation +## Documentation -```bash +Access the Swagger documentation: +```shell script open http://localhost:8080/docs ``` -## Continue the Tutorial +## Next Steps -You can continue this tutorial by following the next step: [creating a new table and crud](getting_started_01_create_table.md). +Continue with [creating a new table and CRUD operations](getting_started_01_create_table.md). diff --git a/docs/getting_started_01_create_table.md b/docs/getting_started_01_create_table.md index 3178a4b..c142885 100644 --- a/docs/getting_started_01_create_table.md +++ b/docs/getting_started_01_create_table.md @@ -1,13 +1,12 @@ # Getting Started - Creating a Table -After [create the project](getting_started.md) you can start to create your own tables. +After [creating the project](getting_started.md), you're ready to create your own tables. -## Create the table +## Create the Table -You need to create a new file in the `migrations` folder. The file name must be in the format `0000X.sql` where `X` is a number. -The number is used to order the execution of the scripts. +Create a new migration file in the `migrations` folder using the format `0000X-message.sql`, where `X` represents a sequential number that determines execution order. -Create a file `db/migrations/up/00002.sql` with the following content: +1. Create an "up" migration file `db/migrations/up/00002-create-table-example.sql`: ```sql create table example_crud @@ -19,30 +18,30 @@ create table example_crud ); ``` -To have consistency, we need to create the down script. The down script is used to rollback the changes. -Create a file `db/migrations/down/00001.sql` with the following content: +2. Create a corresponding "down" migration file `db/migrations/down/00001-rollback-table-example.sql` for rollbacks: ```sql drop table example_crud; ``` -## Run the migration +## Run the Migration -```bash +Apply your migrations with: + +```shell APP_ENV=dev composer run migrate -- update ``` -The result should be: - +Expected output: ```text > Builder\Scripts::migrate > Command: update Doing migrate, 2 ``` -If you want to rollback the changes: +To rollback changes: -```bash +```shell APP_ENV=dev composer run migrate -- update --up-to=1 ``` @@ -54,79 +53,55 @@ The result should be: Doing migrate, 1 ``` -## Generate the CRUD +Remember to run the migrate update again to apply the changes. -```bash -APP_ENV=dev composer run migrate -- update # Make sure DB is update -APP_ENV=dev composer run codegen -- --table example_crud --save all # (can be rest, model, test, repo, config) -``` -This will create the following files: +## Generate CRUD Components with the Code Generator -- ./src/Rest/ExampleCrudRest.php -- ./src/Model/ExampleCrud.php -- ./src/Repository/ExampleCrudRepository.php -- ./tests/Functional/Rest/ExampleCrudTest.php +Generate all necessary files for your new table: -To finalize the setup we need to generate the config. -Run the command bellow copy it contents and save it into the file `config/config-dev.php` +```shell +# Ensure DB is updated first +APP_ENV=dev composer run migrate -- update -```bash -APP_ENV=dev composer run codegen -- --table example_crud config +# Generate files (options: rest, model, test, repo, config, or all) +APP_ENV=dev composer run codegen -- --table example_crud --save all ``` -## First test - -The CodeGen is able to create the Unit Test for you. - -It is available in the file `tests/Functional/Rest/ExampleCrudTest.php`. +This creates: +- `./src/Rest/ExampleCrudRest.php` +- `./src/Model/ExampleCrud.php` +- `./src/Repository/ExampleCrudRepository.php` +- `./tests/Functional/Rest/ExampleCrudTest.php` -And you can run by invoking the command: +You have a manual step to generate the configuration by running the command below and adding it to `config/config-dev.php` -```bash -composer run test -``` - -This first test will fail because we don't have the endpoint yet. - -```text -ERRORS! -Tests: 36, Assertions: 104, Errors: 2, Failures: 6. -Script ./vendor/bin/phpunit handling the test event returned with error code 2 +```shell +APP_ENV=dev composer run codegen -- --table example_crud config ``` -Let's create them now. - -## Generate the endpoints from the OpenAPI Documentation +## Run the Tests -The OpenAPI documentation is generated automatically based on the code. -It is an important step because the documentation is used to create the endpoints and map them to the code. +The automatically generated test is located at `tests/Functional/Rest/ExampleCrudTest.php`. -If we don't generate the OpenAPI documentation, the new endpoints will not be available. +Run it: -```bash -composer run openapi -``` - -## Fixing the unit test - -Now, the endpoint errors passed, but the unit test still failing. - -```bash +```shell composer run test ``` -```text -PDOException: SQLSTATE[22007]: Invalid datetime format: 1292 Incorrect datetime value: 'birthdate' for column 'birthdate' at row 1 +Initial tests will fail because we need to: -ERRORS! -Tests: 36, Assertions: 111, Errors: 1. -Script ./vendor/bin/phpunit handling the test event returned with error code 2 +1. Generate OpenAPI documentation to create the endpoints: + +```shell +composer run openapi ``` -That's because the data used to test is not correct. +2. Fix the test data by updating `tests/Functional/Rest/ExampleCrudTest.php`: -Let's open the file `tests/Functional/Rest/ExampleCrudTest.php` and change the data to: + +Locate: ```php protected function getSampleData($array = false) @@ -140,26 +115,24 @@ Let's open the file `tests/Functional/Rest/ExampleCrudTest.php` and change the d ... ``` -Let's change the line: - -```text - 'birthdate' => 'birthdate', +And Change: +```php +'birthdate' => 'birthdate', ``` -to +To: -```text - 'birthdate' => '2023-01-01 00:00:00', +```php +'birthdate' => '2023-01-01 00:00:00', ``` -and run the unit test again: - -```bash +3. Run the tests again: +```shell composer run test ``` -And voila! The test passed! +Your tests should now pass successfully! -## Continue the Tutorial +## Next Steps -You can continue this tutorial by following the next step: [Add a new field](getting_started_02_add_new_field.md) +Continue with [Adding a New Field](getting_started_02_add_new_field.md) to enhance your implementation. diff --git a/docs/getting_started_02_add_new_field.md b/docs/getting_started_02_add_new_field.md index 669e4b6..aaf6644 100644 --- a/docs/getting_started_02_add_new_field.md +++ b/docs/getting_started_02_add_new_field.md @@ -1,20 +1,20 @@ # Getting Started - Adding a new field to the Table -Now we have the table `example_crud` created in the [previous tutorial](getting_started_01_create_table.md), +Now we have the table `example_crud` created in the [previous tutorial](getting_started_01_create_table.md), let's modify it to add a new field `status`. ## Changing the table We need to add the proper field in the `up` script and remove it in the `down` script. -`db/migrations/up/00003.sql`: +`db/migrations/up/00003-add-field-status.sql`: ```sql alter table example_crud add status varchar(10) null; ``` -`db/migrations/down/00002.sql`: +`db/migrations/down/00002-rollback-field-status.sql`: ```sql alter table example_crud @@ -23,7 +23,7 @@ alter table example_crud ## Run the migration -```bash +```shell APP_ENV=dev composer run migrate -- update ``` @@ -40,6 +40,8 @@ Open the file: `src/Model/ExampleCrud.php` and add the field `status`: #[OA\Property(type: "string", format: "string", nullable: true)] protected ?string $status = null; + /** + * @return string|null */ public function getStatus(): ?string { @@ -60,17 +62,17 @@ Open the file: `src/Model/ExampleCrud.php` and add the field `status`: ## Adding the field status to the `Repository` -As we are just adding a new field, and we already updated the Model to support this new field +As we are just adding a new field, and we already updated the Model to support this new field we don't need to change the `Repository` class. ## Adding the field status to the `Rest` -We just need to allow the rest receive the new field. If we don't do it the API will throw an error. +We just need to allow the rest receive the new field. If we don't do it the API will throw an error. Open the file: `src/Rest/ExampleCrudRest.php` and add the attribute `status` to method `postExampleCrud()`: ```php - #[OA\RequestBody( +#[OA\RequestBody( description: "The object DummyHex to be created", required: true, content: new OA\JsonContent( @@ -93,10 +95,9 @@ We only need to change our method `getSample()` to return the status. Open the file: `tests/Functional/Rest/ExampleCrudTest.php` ```php - protected function getSampleData($array = false) +protected function getSampleData($array = false) { $sample = [ - 'name' => 'name', 'birthdate' => '2023-01-01 00:00:00', 'code' => 1, @@ -107,7 +108,7 @@ Open the file: `tests/Functional/Rest/ExampleCrudTest.php` ## Update the OpenAPI -```bash +```shell composer run openapi ``` @@ -115,11 +116,10 @@ composer run openapi If everything is ok, the tests should pass: -```bash +```shell composer run test ``` ## Continue the tutorial -[Next: Creating a rest method](getting_started_03_create_rest_method.md) - +[Next: Creating a rest method](getting_started_03_create_rest_method.md) \ No newline at end of file diff --git a/docs/getting_started_03_create_rest_method.md b/docs/getting_started_03_create_rest_method.md index ab9024a..3929f15 100644 --- a/docs/getting_started_03_create_rest_method.md +++ b/docs/getting_started_03_create_rest_method.md @@ -1,31 +1,35 @@ -# Getting Started - Creating a Rest Method +I'll help you fix and improve this text. Let me analyze the Markdown document first. -This part of the tutorial we are going to create a new Rest Method to update the status of the `example_crud` table. +# Getting Started - Creating a REST Method -We will cover the following topics: +In this tutorial, we'll create a new REST method to update the status of the `example_crud` table. + +We'll cover the following topics: - OpenAPI Attributes -- Protect the endpoint -- Validate the input -- Save to the Database -- Return the result -- Unit Test +- Protecting the endpoint +- Validating input +- Saving to the database +- Returning results +- Unit testing ## OpenAPI Attributes -The first step is to add the OpenAPI attributes to the Rest Method. -We use the [zircote/swagger-php](https://zircote.github.io/swagger-php/guide/) library to add the attributes. +First, we'll add OpenAPI attributes to our REST method using +the [zircote/swagger-php](https://zircote.github.io/swagger-php/guide/) library. + +While the OpenAPI specification offers numerous attributes, we must define at least these three essential sets: -The list of OpenAPI attributes is to vast, however, there are a minimal of 3 sets of of attributes we must define. +### 1. Method Attribute -The first set is to define what will be the method attribute. It can be: +This defines the HTTP method: -- OA\Get - to retrieve data -- OA\Post - to create data -- OA\Put - For Update -- OA\Delete - For Delete/Cancel +- `OA\Get` - For retrieving data +- `OA\Post` - For creating data +- `OA\Put` - For updating data +- `OA\Delete` - For deleting/canceling data -e.g. +Example: ```php #[OA\Put( @@ -34,101 +38,96 @@ e.g. ["jwt-token" => []] ], tags: ["Example"], - description: 'Update the status of the ExampleCrud', + description: "Update the status of the ExampleCrud" )] ``` -The `security` attribute is used to define the security schema. If you don't define it, the endpoint will be public. +The `security` attribute defines the security schema. Without it, the endpoint remains public. + +### 2. Request Attribute -The second set it the request attribute. It can be, `OA\RequestBody` or `OA\Parameter` attribute. -It is used to define the input of the method. +This defines the input to the method using `OA\RequestBody` or `OA\Parameter`. -e.g. +Example: ```php #[OA\RequestBody( description: "The status to be updated", required: true, content: new OA\JsonContent( - required: [ "status" ], + required: ["status"], properties: [ new OA\Property(property: "id", type: "integer", format: "int32"), - new OA\Property(property: "status", type: "string", format: "string") + new OA\Property(property: "status", type: "string") ] ) )] ``` -The third set is the response attribute. It is `OA\Response` attribute. +### 3. Response Attribute + +This defines the expected output using `OA\Response`. ```php #[OA\Response( response: 200, - description: "The object rto be created", + description: "The operation result", content: new OA\JsonContent( - required: [ "result" ], + required: ["result"], properties: [ - new OA\Property(property: "result", type: "string", format: "string") + new OA\Property(property: "result", type: "string") ] ) )] ``` -The attributes need to be in the beginning of the method. The method can be anywhere in the code, -but the follow the pattern we will put it in the end of the class `ExampleCrudRest`. - -e.g +Place these attributes at the beginning of your method. Following our pattern, we'll add this method at the end of the `ExampleCrudRest` class: ```php - "123", "name" => "John Doe", - "role" => "admin" or "user" + "role" => "admin" // or "user" ] ``` -## Validate the input +## Validating Input -The next step is to validate the input. We will check if the request matches with the OpenAPI attributes. +Next, validate that the incoming request matches the OpenAPI specifications: ```php public function putExampleCrudStatus(HttpResponse $response, HttpRequest $request) @@ -141,66 +140,111 @@ public function putExampleCrudStatus(HttpResponse $response, HttpRequest $reques } ``` -## Call the Repository +## Updating Status in the Repository -As we have the payload with the correct information, we can call the repository to update the status. +After validating the payload, we can update the record status using the repository pattern: ```php +/** + * Update the status of an Example CRUD record + * + * @param HttpResponse $response + * @param HttpRequest $request + * @return void + */ public function putExampleCrudStatus(HttpResponse $response, HttpRequest $request) { -... + // Previous code for payload validation... + + // Update the record status $exampleCrudRepo = Psr11::container()->get(ExampleCrudRepository::class); $model = $exampleCrudRepo->get($payload["id"]); + + if (!$model) { + throw new NotFoundException("Record not found"); + } + $model->setStatus($payload["status"]); $exampleCrudRepo->save($model); + + // Return response... +} ``` -## Return the result +## Returning the Response -The last step is to return the result as specified in the OpenAPI attributes. +After updating the record, we need to return a standardized response as specified in our OpenAPI schema: ```php public function putExampleCrudStatus(HttpResponse $response, HttpRequest $request) { -... + // Previous code for update logic... + + // Return standardized response $response->write([ "result" => "ok" ]); } ``` -## Unit Test +## Unit Testing -A vital piece of our code is to guarantee it will continue to run as expected. -To do that we need to create a unit test to validate the code. +To ensure our endpoint works correctly and continues to function as expected, we'll create a functional test. This test simulates calling the endpoint and validates both the response format and the business logic. -The test we will create is a functional test that will fake calling the endpoint -and validate the result if is matching with the OpenAPI attributes and if processing what is expected. - -We will add the test in the file `tests/Functional/Rest/ExampleCrudTest.php`. +Create or update the test file `tests/Functional/Rest/ExampleCrudTest.php`: ```php +/** + * @covers \YourNamespace\Controller\ExampleCrudController + */ public function testUpdateStatus() { - // If you need to login to get a token, use the code below - $result = json_decode($this->assertRequest(Credentials::requestLogin(Credentials::getAdminUser()))->getBody()->getContents(), true); + // Authenticate to get a valid token (if required) + $authResult = json_decode( + $this->assertRequest(Credentials::requestLogin(Credentials::getAdminUser())) + ->getBody() + ->getContents(), + true + ); + + // Prepare test data + $recordId = 1; + $newStatus = 'new status'; - // Execute the unit test - $request = new FakeApiRequester(); // It will mock the API call + // Create mock API request + $request = new FakeApiRequester(); $request - ->withPsr7Request($this->getPsr7Request()) // PSR7 Request to be used - ->withMethod('PUT') // Method to be used - ->withPath("/example/crud/status") // Path to be used - ->withRequestBody(json_encode([ // Request Body to be used - 'id' => 1, - 'status' => 'new status' + ->withPsr7Request($this->getPsr7Request()) + ->withMethod('PUT') + ->withPath("/example/crud/status") + ->withRequestBody(json_encode([ + 'id' => $recordId, + 'status' => $newStatus ])) - ->assertResponseCode(200) // Expected Response Code - ->withRequestHeader([ // If your method requires a token use this. - "Authorization" => "Bearer " . $result['token'] + ->withRequestHeader([ + "Authorization" => "Bearer " . $authResult['token'], + "Content-Type" => "application/json" ]) - ; - $body = $this->assertRequest($request); - $bodyAr = json_decode($body->getBody()->getContents(), true); // If necessary work with the result of the request + ->assertResponseCode(200); + + // Execute the request and get response + $response = $this->assertRequest($request); + $responseData = json_decode($response->getBody()->getContents(), true); + + // There is no necessary to Assert expected response format and data + // because the assertRequest will do it for you. + // $this->assertIsArray($responseData); + // $this->assertArrayHasKey('result', $responseData); + // $this->assertEquals('ok', $responseData['result']); + + // Verify the database was updated correctly + $repository = Psr11::container()->get(ExampleCrudRepository::class); + $updatedRecord = $repository->get($recordId); + $this->assertEquals($newStatus, $updatedRecord->getStatus()); } ``` + +This test performs the following validations: +1. Ensures the endpoint returns a 200 status code +2. Verifies the response has the expected JSON structure +3. Confirms the database record was actually updated with the new status \ No newline at end of file diff --git a/docs/windows.md b/docs/windows.md new file mode 100644 index 0000000..da2f63b --- /dev/null +++ b/docs/windows.md @@ -0,0 +1,27 @@ +# Running on Windows Without PHP + +This project is primarily designed for Linux environments, but can be easily run on Windows using Docker. + +## Prerequisites + +- Docker Desktop installed and running on your Windows machine +- No need for a local PHP installation or WSL2 configuration + +## Quick Start + +1. Open Command Prompt or PowerShell +2. Navigate to your desired project location: + +```textmate +cd C:\Users\MyUser\Projects +``` + +3. Launch a containerized PHP environment with the following command: + +```textmate +docker run -it --rm -v %cd%:/root -w /root byjg/php:8.3-cli bash +``` + +4. Once inside the container shell, you can run all PHP commands normally as if you had PHP installed locally. + +When referencing the current directory, use `/root/something` instead of `~/something`. diff --git a/src/Model/User.php b/src/Model/User.php index 74c90e5..bf57ee5 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -2,12 +2,17 @@ namespace RestReferenceArchitecture\Model; +use ByJG\Authenticate\Definition\PasswordDefinition; use ByJG\Authenticate\Model\UserModel; use ByJG\Authenticate\Model\UserPropertiesModel; +use ByJG\MicroOrm\Attributes\TableAttribute; use ByJG\MicroOrm\Literal\HexUuidLiteral; +use Exception; use InvalidArgumentException; use OpenApi\Attributes as OA; +use RestReferenceArchitecture\Psr11; +#[TableAttribute("users")] #[OA\Schema(required: ["email"], type: "object", xml: new OA\Xml(name: "User"))] class User extends UserModel { @@ -89,10 +94,13 @@ class User extends UserModel * @param string $username * @param string $password * @param string $admin + * @throws Exception */ public function __construct(string $name = "", string $email = "", string $username = "", string $password = "", string $admin = "no") { parent::__construct($name, $email, $username, $password, $admin); + + $this->withPasswordDefinition(Psr11::container()->get(PasswordDefinition::class)); } @@ -173,9 +181,11 @@ public function getPassword(): ?string */ public function setPassword(?string $password): void { - // Password len equals to 40 means that the password is already encrypted with sha1 - if (!empty($password) && strlen($password) != 40 && !empty($this->passwordDefinition) && !$this->passwordDefinition->matchPassword($password)) { - throw new InvalidArgumentException("Password does not match the password definition"); + if (!empty($this->passwordDefinition) && !empty($password) && strlen($password) != 40) { + $result = $this->passwordDefinition->matchPassword($password); + if ($result != PasswordDefinition::SUCCESS) { + throw new InvalidArgumentException("Password does not match the password definition [{$result}]"); + } } $this->password = $password; } diff --git a/src/OpenApiSpec.php b/src/OpenApiSpec.php index 0bb7ab0..c851915 100644 --- a/src/OpenApiSpec.php +++ b/src/OpenApiSpec.php @@ -25,12 +25,15 @@ #[OA\Schema( schema: 'error', properties: [ - new OA\Property('error', properties: [ - new OA\Property(property: 'type', description: 'A class de Exceção', type: 'string'), - new OA\Property(property: 'message', description: 'A mensagem de erro', type: 'string'), - new OA\Property(property: 'file', description: 'O arquivo que gerou o erro', type: 'string'), - new OA\Property(property: 'line', description: 'A linha do erro', type: 'integer'), - ]) + new OA\Property( + 'error', + properties: [ + new OA\Property(property: 'type', description: 'A class de Exceção', type: 'string'), + new OA\Property(property: 'message', description: 'A mensagem de erro', type: 'string'), + new OA\Property(property: 'file', description: 'O arquivo que gerou o erro', type: 'string'), + new OA\Property(property: 'line', description: 'A linha do erro', type: 'integer'), + ] + ) ], type: 'object' )] diff --git a/src/Psr11.php b/src/Psr11.php index f938998..9ec5d5e 100644 --- a/src/Psr11.php +++ b/src/Psr11.php @@ -8,7 +8,6 @@ use ByJG\Config\Environment; use ByJG\Config\Exception\ConfigException; use ByJG\Config\Exception\ConfigNotFoundException; -use ByJG\Config\Exception\InvalidDateException; use Psr\SimpleCache\InvalidArgumentException; class Psr11 @@ -18,11 +17,10 @@ class Psr11 /** * @param string|null $env - * @return Container + * @return Container|null * @throws ConfigException * @throws ConfigNotFoundException * @throws InvalidArgumentException - * @throws InvalidDateException */ public static function container(?string $env = null): ?Container { @@ -53,7 +51,12 @@ public static function environment(): ?Definition ->addEnvironment($test) ->addEnvironment($staging) ->addEnvironment($prod) - ; + ->withOSEnvironment( + [ + 'TAG_VERSION', + 'TAG_COMMIT', + ] + ); } return self::$definition; diff --git a/src/Util/OpenApiContext.php b/src/Util/OpenApiContext.php index 480f3b4..88a3944 100644 --- a/src/Util/OpenApiContext.php +++ b/src/Util/OpenApiContext.php @@ -3,15 +3,32 @@ namespace RestReferenceArchitecture\Util; use ByJG\ApiTools\Base\Schema; +use ByJG\Config\Exception\ConfigException; +use ByJG\Config\Exception\ConfigNotFoundException; +use ByJG\Config\Exception\DependencyInjectionException; +use ByJG\Config\Exception\InvalidDateException; +use ByJG\Config\Exception\KeyNotFoundException; use ByJG\RestServer\Exception\Error400Exception; use ByJG\RestServer\HttpRequest; use ByJG\Serializer\Serialize; use Exception; +use Psr\SimpleCache\InvalidArgumentException; +use ReflectionException; use RestReferenceArchitecture\Psr11; class OpenApiContext { - public static function validateRequest(HttpRequest $request) + /** + * @throws DependencyInjectionException + * @throws InvalidDateException + * @throws ConfigNotFoundException + * @throws KeyNotFoundException + * @throws Error400Exception + * @throws InvalidArgumentException + * @throws ConfigException + * @throws ReflectionException + */ + public static function validateRequest(HttpRequest $request, bool $allowNull = false) { $schema = Psr11::container()->get(Schema::class); @@ -30,18 +47,20 @@ public static function validateRequest(HttpRequest $request) try { // Validate the request path and query against the OpenAPI schema $schema->getPathDefinition($path, $method); + // Returns a SwaggerRequestBody instance + $bodyRequestDef = $schema->getRequestParameters($path, $method); + $bodyRequestDef->match($requestBody); - if (!empty($requestBody)) { - // Returns a SwaggerRequestBody instance - $bodyRequestDef = $schema->getRequestParameters($path, $method); - $bodyRequestDef->match($requestBody); - } else { - return []; - } } catch (Exception $ex) { throw new Error400Exception(explode("\n", $ex->getMessage())[0]); } + $requestBody = empty($requestBody) ? [] : $requestBody; + + if ($allowNull) { + return $requestBody; + } + return Serialize::from($requestBody)->withDoNotParseNullValues()->toArray(); } } From a91008871d58df2d89f9e0e7eaad9912568adb26 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 19 Mar 2025 10:20:18 -0500 Subject: [PATCH 10/14] Refactor: Replace `Psr11::container()->get` with `Psr11::get` Simplified service container calls across the codebase by introducing and using the new `Psr11::get` method. This change improves readability and reduces verbosity in service retrieval logic. --- builder/BaseScripts.php | 2 +- builder/Scripts.php | 2 +- config/config-dev.php | 6 ++--- docs/getting_started_03_create_rest_method.md | 4 +-- docs/psr11.md | 2 +- docs/psr11_di.md | 2 +- public/app.php | 4 +-- src/Model/User.php | 2 +- src/Psr11.php | 27 ++++++++++++++++++- src/Repository/BaseRepository.php | 4 +-- src/Repository/UserDefinition.php | 2 +- src/Rest/DummyHexRest.php | 8 +++--- src/Rest/DummyRest.php | 8 +++--- src/Rest/Login.php | 12 ++++----- src/Util/FakeApiRequester.php | 6 ++--- src/Util/JwtContext.php | 2 +- src/Util/OpenApiContext.php | 2 +- templates/codegen/rest.php.jinja | 8 +++--- tests/Rest/BaseApiTestCase.php | 6 ++--- tests/Rest/Credentials.php | 4 +-- tests/Rest/LoginTest.php | 10 +++---- 21 files changed, 74 insertions(+), 49 deletions(-) diff --git a/builder/BaseScripts.php b/builder/BaseScripts.php index 4c7ce32..7d4be8a 100644 --- a/builder/BaseScripts.php +++ b/builder/BaseScripts.php @@ -122,7 +122,7 @@ protected function replaceVariables($variableValue) foreach ($args[0] as $arg) { $variableValue = str_replace( $arg, - Psr11::container()->get(substr($arg,1, -1)), + Psr11::get(substr($arg,1, -1)), $variableValue ); } diff --git a/builder/Scripts.php b/builder/Scripts.php index 5302603..11710b7 100755 --- a/builder/Scripts.php +++ b/builder/Scripts.php @@ -223,7 +223,7 @@ public function runCodeGenerator(array $arguments): void $save = in_array("--save", $arguments); /** @var DbDriverInterface $dbDriver */ - $dbDriver = Psr11::container()->get(DbDriverInterface::class); + $dbDriver = Psr11::get(DbDriverInterface::class); $tableDefinition = $dbDriver->getIterator("EXPLAIN " . strtolower($table))->toArray(); $tableIndexes = $dbDriver->getIterator("SHOW INDEX FROM " . strtolower($table))->toArray(); diff --git a/config/config-dev.php b/config/config-dev.php index 78f23b4..ccb7393 100644 --- a/config/config-dev.php +++ b/config/config-dev.php @@ -59,7 +59,7 @@ ->toSingleton(), MailWrapperInterface::class => function () { - $apiKey = Psr11::container()->get('EMAIL_CONNECTION'); + $apiKey = Psr11::get('EMAIL_CONNECTION'); MailerFactory::registerMailer(MailgunApiWrapper::class); MailerFactory::registerMailer(FakeSenderWrapper::class); @@ -119,7 +119,7 @@ ->toSingleton(), 'CORS_SERVER_LIST' => function () { - return preg_split('/,(?![^{}]*})/', Psr11::container()->get('CORS_SERVERS')); + return preg_split('/,(?![^{}]*})/', Psr11::get('CORS_SERVERS')); }, JwtMiddleware::class => DI::bind(JwtMiddleware::class) @@ -160,7 +160,7 @@ if (Psr11::environment()->getCurrentEnvironment() != "prod") { $prefix = "[" . Psr11::environment()->getCurrentEnvironment() . "] "; } - return new Envelope(Psr11::container()->get('EMAIL_TRANSACTIONAL_FROM'), $to, $prefix . $subject, $body, true); + return new Envelope(Psr11::get('EMAIL_TRANSACTIONAL_FROM'), $to, $prefix . $subject, $body, true); }, ]; diff --git a/docs/getting_started_03_create_rest_method.md b/docs/getting_started_03_create_rest_method.md index 3929f15..dabd282 100644 --- a/docs/getting_started_03_create_rest_method.md +++ b/docs/getting_started_03_create_rest_method.md @@ -157,7 +157,7 @@ public function putExampleCrudStatus(HttpResponse $response, HttpRequest $reques // Previous code for payload validation... // Update the record status - $exampleCrudRepo = Psr11::container()->get(ExampleCrudRepository::class); + $exampleCrudRepo = Psr11::get(ExampleCrudRepository::class); $model = $exampleCrudRepo->get($payload["id"]); if (!$model) { @@ -238,7 +238,7 @@ public function testUpdateStatus() // $this->assertEquals('ok', $responseData['result']); // Verify the database was updated correctly - $repository = Psr11::container()->get(ExampleCrudRepository::class); + $repository = Psr11::get(ExampleCrudRepository::class); $updatedRecord = $repository->get($recordId); $this->assertEquals($newStatus, $updatedRecord->getStatus()); } diff --git a/docs/psr11.md b/docs/psr11.md index 874173a..ca75c48 100644 --- a/docs/psr11.md +++ b/docs/psr11.md @@ -40,7 +40,7 @@ The configuration is loaded by the [byjg/config](https://github.com/byjg/config) You just need to: ```php -Psr11::container()->get('WEB_SERVER'); +Psr11::get('WEB_SERVER'); ``` ## Defining the available environments diff --git a/docs/psr11_di.md b/docs/psr11_di.md index 410ebe3..73b2bed 100644 --- a/docs/psr11_di.md +++ b/docs/psr11_di.md @@ -25,7 +25,7 @@ return [ To use in your code, you just need to set the environment variable `APP_ENV` to the environment name (`dev` or `prod`) and call: ```php -Psr11::container()->get(BaseCacheEngine::class); +Psr11::get(BaseCacheEngine::class); ``` The application will return the correct implementation based on the environment. diff --git a/public/app.php b/public/app.php index 21d057f..a675491 100644 --- a/public/app.php +++ b/public/app.php @@ -10,8 +10,8 @@ class App { public static function run() { - $server = Psr11::container()->get(HttpRequestHandler::class); - $server->handle(Psr11::container()->get(OpenApiRouteList::class)); + $server = Psr11::get(HttpRequestHandler::class); + $server->handle(Psr11::get(OpenApiRouteList::class)); } } diff --git a/src/Model/User.php b/src/Model/User.php index bf57ee5..36f1186 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -100,7 +100,7 @@ public function __construct(string $name = "", string $email = "", string $usern { parent::__construct($name, $email, $username, $password, $admin); - $this->withPasswordDefinition(Psr11::container()->get(PasswordDefinition::class)); + $this->withPasswordDefinition(Psr11::get(PasswordDefinition::class)); } diff --git a/src/Psr11.php b/src/Psr11.php index 9ec5d5e..30a5cd6 100644 --- a/src/Psr11.php +++ b/src/Psr11.php @@ -8,7 +8,13 @@ use ByJG\Config\Environment; use ByJG\Config\Exception\ConfigException; use ByJG\Config\Exception\ConfigNotFoundException; +use ByJG\Config\Exception\DependencyInjectionException; +use ByJG\Config\Exception\KeyNotFoundException; +use Exception; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; use Psr\SimpleCache\InvalidArgumentException; +use ReflectionException; class Psr11 { @@ -21,12 +27,13 @@ class Psr11 * @throws ConfigException * @throws ConfigNotFoundException * @throws InvalidArgumentException + * @throws Exception */ public static function container(?string $env = null): ?Container { if (is_null(self::$container)) { if (PHP_INT_SIZE < 8) { - throw new \Exception("This application requires 64-bit PHP"); + throw new Exception("This application requires 64-bit PHP"); } self::$container = self::environment()->build($env); } @@ -34,6 +41,24 @@ public static function container(?string $env = null): ?Container return self::$container; } + /** + * @param string $id + * @param mixed ...$parameters + * @return mixed + * @throws ConfigException + * @throws ConfigNotFoundException + * @throws DependencyInjectionException + * @throws InvalidArgumentException + * @throws KeyNotFoundException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws ReflectionException + */ + public static function get(string $id, mixed ...$parameters): mixed + { + return Psr11::container()->get($id, ...$parameters); + } + /** * @return Definition|null * @throws ConfigException diff --git a/src/Repository/BaseRepository.php b/src/Repository/BaseRepository.php index d90a452..be2eb1a 100644 --- a/src/Repository/BaseRepository.php +++ b/src/Repository/BaseRepository.php @@ -133,7 +133,7 @@ public function model() public static function getClosureNewUUID(): \Closure { return function () { - return new Literal("X'" . Psr11::container()->get(DbDriverInterface::class)->getScalar("SELECT hex(uuid_to_bin(uuid()))") . "'"); + return new Literal("X'" . Psr11::get(DbDriverInterface::class)->getScalar("SELECT hex(uuid_to_bin(uuid()))") . "'"); }; } @@ -149,7 +149,7 @@ public static function getClosureNewUUID(): \Closure */ public static function getUuid() { - return Psr11::container()->get(DbDriverInterface::class)->getScalar("SELECT insert(insert(insert(insert(hex(uuid_to_bin(uuid())),9,0,'-'),14,0,'-'),19,0,'-'),24,0,'-')"); + return Psr11::get(DbDriverInterface::class)->getScalar("SELECT insert(insert(insert(insert(hex(uuid_to_bin(uuid())),9,0,'-'),14,0,'-'),19,0,'-'),24,0,'-')"); } /** diff --git a/src/Repository/UserDefinition.php b/src/Repository/UserDefinition.php index 526c6fb..51d7fee 100644 --- a/src/Repository/UserDefinition.php +++ b/src/Repository/UserDefinition.php @@ -18,7 +18,7 @@ public function __construct($table = 'users', $model = UserModel::class, $loginF $this->markPropertyAsReadOnly("created"); $this->markPropertyAsReadOnly("updated"); $this->defineGenerateKeyClosure(function () { - return new Literal("X'" . Psr11::container()->get(DbDriverInterface::class)->getScalar("SELECT hex(uuid_to_bin(uuid()))") . "'"); + return new Literal("X'" . Psr11::get(DbDriverInterface::class)->getScalar("SELECT hex(uuid_to_bin(uuid()))") . "'"); } ); diff --git a/src/Rest/DummyHexRest.php b/src/Rest/DummyHexRest.php index e5ccf60..ee2bd68 100644 --- a/src/Rest/DummyHexRest.php +++ b/src/Rest/DummyHexRest.php @@ -70,7 +70,7 @@ public function getDummyHex(HttpResponse $response, HttpRequest $request): void { JwtContext::requireAuthenticated($request); - $dummyHexRepo = Psr11::container()->get(DummyHexRepository::class); + $dummyHexRepo = Psr11::get(DummyHexRepository::class); $id = $request->param('id'); $result = $dummyHexRepo->get($id); @@ -156,7 +156,7 @@ public function listDummyHex(HttpResponse $response, HttpRequest $request): void { JwtContext::requireAuthenticated($request); - $repo = Psr11::container()->get(DummyHexRepository::class); + $repo = Psr11::get(DummyHexRepository::class); $page = $request->get('page'); $size = $request->get('size'); @@ -234,7 +234,7 @@ public function postDummyHex(HttpResponse $response, HttpRequest $request): void $model = new DummyHex(); ObjectCopy::copy($payload, $model); - $dummyHexRepo = Psr11::container()->get(DummyHexRepository::class); + $dummyHexRepo = Psr11::get(DummyHexRepository::class); $dummyHexRepo->save($model); $response->write([ "id" => $model->getId()]); @@ -290,7 +290,7 @@ public function putDummyHex(HttpResponse $response, HttpRequest $request): void $payload = OpenApiContext::validateRequest($request); - $dummyHexRepo = Psr11::container()->get(DummyHexRepository::class); + $dummyHexRepo = Psr11::get(DummyHexRepository::class); $model = $dummyHexRepo->get($payload['id']); if (empty($model)) { throw new Error404Exception('Id not found'); diff --git a/src/Rest/DummyRest.php b/src/Rest/DummyRest.php index d57c20c..0654064 100644 --- a/src/Rest/DummyRest.php +++ b/src/Rest/DummyRest.php @@ -70,7 +70,7 @@ public function getDummy(HttpResponse $response, HttpRequest $request): void { JwtContext::requireAuthenticated($request); - $dummyRepo = Psr11::container()->get(DummyRepository::class); + $dummyRepo = Psr11::get(DummyRepository::class); $id = $request->param('id'); $result = $dummyRepo->get($id); @@ -156,7 +156,7 @@ public function listDummy(HttpResponse $response, HttpRequest $request): void { JwtContext::requireAuthenticated($request); - $repo = Psr11::container()->get(DummyRepository::class); + $repo = Psr11::get(DummyRepository::class); $page = $request->get('page'); $size = $request->get('size'); @@ -234,7 +234,7 @@ public function postDummy(HttpResponse $response, HttpRequest $request): void $model = new Dummy(); ObjectCopy::copy($payload, $model); - $dummyRepo = Psr11::container()->get(DummyRepository::class); + $dummyRepo = Psr11::get(DummyRepository::class); $dummyRepo->save($model); $response->write([ "id" => $model->getId()]); @@ -290,7 +290,7 @@ public function putDummy(HttpResponse $response, HttpRequest $request): void $payload = OpenApiContext::validateRequest($request); - $dummyRepo = Psr11::container()->get(DummyRepository::class); + $dummyRepo = Psr11::get(DummyRepository::class); $model = $dummyRepo->get($payload['id']); if (empty($model)) { throw new Error404Exception('Id not found'); diff --git a/src/Rest/Login.php b/src/Rest/Login.php index e0f5957..e98d04b 100644 --- a/src/Rest/Login.php +++ b/src/Rest/Login.php @@ -57,7 +57,7 @@ public function post(HttpResponse $response, HttpRequest $request) { $json = OpenApiContext::validateRequest($request); - $users = Psr11::container()->get(UsersDBDataset::class); + $users = Psr11::get(UsersDBDataset::class); $user = $users->isValidUser($json["username"], $json["password"]); $metadata = JwtContext::createUserMetadata($user); @@ -107,7 +107,7 @@ public function refreshToken(HttpResponse $response, HttpRequest $request) throw new Error401Exception("You only can refresh the token 5 minutes before expire"); } - $users = Psr11::container()->get(UsersDBDataset::class); + $users = Psr11::get(UsersDBDataset::class); $user = $users->getById(new HexUuidLiteral(JwtContext::getUserId())); $metadata = JwtContext::createUserMetadata($user); @@ -149,7 +149,7 @@ public function postResetRequest(HttpResponse $response, HttpRequest $request) { $json = OpenApiContext::validateRequest($request); - $users = Psr11::container()->get(UsersDBDataset::class); + $users = Psr11::get(UsersDBDataset::class); $user = $users->getByEmail($json["email"]); $token = BaseRepository::getUuid(); @@ -163,8 +163,8 @@ public function postResetRequest(HttpResponse $response, HttpRequest $request) $users->save($user); // Send email using MailWrapper - $mailWrapper = Psr11::container()->get(MailWrapperInterface::class); - $envelope = Psr11::container()->get('MAIL_ENVELOPE', [$json["email"], "RestReferenceArchitecture - Password Reset", "email_code.html", [ + $mailWrapper = Psr11::get(MailWrapperInterface::class); + $envelope = Psr11::get('MAIL_ENVELOPE', [$json["email"], "RestReferenceArchitecture - Password Reset", "email_code.html", [ "code" => trim(chunk_split($code, 1, ' ')), "expire" => 10 ]]); @@ -179,7 +179,7 @@ protected function validateResetToken($response, $request): array { $json = OpenApiContext::validateRequest($request); - $users = Psr11::container()->get(UsersDBDataset::class); + $users = Psr11::get(UsersDBDataset::class); $user = $users->getByEmail($json["email"]); if (is_null($user)) { diff --git a/src/Util/FakeApiRequester.php b/src/Util/FakeApiRequester.php index f252db9..ba28271 100644 --- a/src/Util/FakeApiRequester.php +++ b/src/Util/FakeApiRequester.php @@ -51,10 +51,10 @@ class FakeApiRequester extends AbstractRequester */ protected function handleRequest(RequestInterface $request): ResponseInterface { - $mock = new MockRequestHandler(Psr11::container()->get(LoggerInterface::class)); - $mock->withMiddleware(Psr11::container()->get(JwtMiddleware::class)); + $mock = new MockRequestHandler(Psr11::get(LoggerInterface::class)); + $mock->withMiddleware(Psr11::get(JwtMiddleware::class)); $mock->withRequestObject($request); - $mock->handle(Psr11::container()->get(OpenApiRouteList::class), false, false); + $mock->handle(Psr11::get(OpenApiRouteList::class), false, false); $httpClient = new MockClient($mock->getPsr7Response()); return $httpClient->sendRequest($request); diff --git a/src/Util/JwtContext.php b/src/Util/JwtContext.php index d149a79..8b010f5 100644 --- a/src/Util/JwtContext.php +++ b/src/Util/JwtContext.php @@ -53,7 +53,7 @@ public static function createUserMetadata(?User $user): array */ public static function createToken(array $properties = []) { - $jwt = Psr11::container()->get(JwtWrapper::class); + $jwt = Psr11::get(JwtWrapper::class); $jwtData = $jwt->createJwtData($properties, 60 * 60 * 24 * 7); // 7 Dias return $jwt->generateToken($jwtData); } diff --git a/src/Util/OpenApiContext.php b/src/Util/OpenApiContext.php index 88a3944..49f97d5 100644 --- a/src/Util/OpenApiContext.php +++ b/src/Util/OpenApiContext.php @@ -30,7 +30,7 @@ class OpenApiContext */ public static function validateRequest(HttpRequest $request, bool $allowNull = false) { - $schema = Psr11::container()->get(Schema::class); + $schema = Psr11::get(Schema::class); $path = $request->getRequestPath(); $method = $request->server('REQUEST_METHOD'); diff --git a/templates/codegen/rest.php.jinja b/templates/codegen/rest.php.jinja index 531edea..22ef5ac 100644 --- a/templates/codegen/rest.php.jinja +++ b/templates/codegen/rest.php.jinja @@ -70,7 +70,7 @@ class {{ className }}Rest { JwtContext::requireAuthenticated($request); - ${{ varTableName }}Repo = Psr11::container()->get({{ className }}Repository::class); + ${{ varTableName }}Repo = Psr11::get({{ className }}Repository::class); $id = $request->param('id'); $result = ${{ varTableName }}Repo->get($id); @@ -156,7 +156,7 @@ class {{ className }}Rest { JwtContext::requireAuthenticated($request); - $repo = Psr11::container()->get({{ className }}Repository::class); + $repo = Psr11::get({{ className }}Repository::class); $page = $request->get('page'); $size = $request->get('size'); @@ -236,7 +236,7 @@ class {{ className }}Rest $model = new {{ className }}(); ObjectCopy::copy($payload, $model); - ${{ varTableName }}Repo = Psr11::container()->get({{ className }}Repository::class); + ${{ varTableName }}Repo = Psr11::get({{ className }}Repository::class); ${{ varTableName }}Repo->save($model); $response->write([ "id" => $model->getId()]); @@ -292,7 +292,7 @@ class {{ className }}Rest $payload = OpenApiContext::validateRequest($request); - ${{ varTableName }}Repo = Psr11::container()->get({{ className }}Repository::class); + ${{ varTableName }}Repo = Psr11::get({{ className }}Repository::class); $model = ${{ varTableName }}Repo->get($payload['{{ fields.0.field }}']); if (empty($model)) { throw new Error404Exception('Id not found'); diff --git a/tests/Rest/BaseApiTestCase.php b/tests/Rest/BaseApiTestCase.php index 480a599..74770e0 100644 --- a/tests/Rest/BaseApiTestCase.php +++ b/tests/Rest/BaseApiTestCase.php @@ -31,8 +31,8 @@ protected function tearDown(): void public function getPsr7Request(): Request { $uri = Uri::getInstanceFromString() - ->withScheme(Psr11::container()->get("API_SCHEMA")) - ->withHost(Psr11::container()->get("API_SERVER")); + ->withScheme(Psr11::get("API_SCHEMA")) + ->withHost(Psr11::get("API_SERVER")); return Request::getInstance($uri); } @@ -44,7 +44,7 @@ public function resetDb() throw new Exception("This test can only be executed in test environment"); } Migration::registerDatabase(MySqlDatabase::class); - $migration = new Migration(new Uri(Psr11::container()->get('DBDRIVER_CONNECTION')), __DIR__ . "/../../../db"); + $migration = new Migration(new Uri(Psr11::get('DBDRIVER_CONNECTION')), __DIR__ . "/../../../db"); $migration->prepareEnvironment(); $migration->reset(); self::$databaseReset = true; diff --git a/tests/Rest/Credentials.php b/tests/Rest/Credentials.php index c97ba2e..3444688 100644 --- a/tests/Rest/Credentials.php +++ b/tests/Rest/Credentials.php @@ -28,8 +28,8 @@ public static function getRegularUser(): array public static function requestLogin($cred): FakeApiRequester { $uri = Uri::getInstanceFromString() - ->withScheme(Psr11::container()->get("API_SCHEMA")) - ->withHost(Psr11::container()->get("API_SERVER")); + ->withScheme(Psr11::get("API_SCHEMA")) + ->withHost(Psr11::get("API_SERVER")); $psr7Request = Request::getInstance($uri); diff --git a/tests/Rest/LoginTest.php b/tests/Rest/LoginTest.php index 88a3891..f2370b1 100644 --- a/tests/Rest/LoginTest.php +++ b/tests/Rest/LoginTest.php @@ -41,7 +41,7 @@ public function testResetRequestOk() $email = Credentials::getRegularUser()["username"]; // Clear the reset token - $userRepo = Psr11::container()->get(UsersDBDataset::class); + $userRepo = Psr11::get(UsersDBDataset::class); $user = $userRepo->getByEmail($email); $user->set(User::PROP_RESETTOKEN, null); $user->set(User::PROP_RESETTOKENEXPIRE, null); @@ -69,7 +69,7 @@ public function testResetRequestOk() $this->assertRequest($request); // Check if the reset token was created - $userRepo = Psr11::container()->get(UsersDBDataset::class); + $userRepo = Psr11::get(UsersDBDataset::class); $user = $userRepo->getByEmail($email); $this->assertNotNull($user); $this->assertNotEmpty($user->get(User::PROP_RESETTOKEN)); @@ -83,7 +83,7 @@ public function testConfirmCodeFail() $email = Credentials::getRegularUser()["username"]; // Clear the reset token - $userRepo = Psr11::container()->get(UsersDBDataset::class); + $userRepo = Psr11::get(UsersDBDataset::class); $user = $userRepo->getByEmail($email); $this->assertNotNull($user); $this->assertNotEmpty($user->get(User::PROP_RESETTOKEN)); @@ -110,7 +110,7 @@ public function testConfirmCodeOk() $email = Credentials::getRegularUser()["username"]; // Clear the reset token - $userRepo = Psr11::container()->get(UsersDBDataset::class); + $userRepo = Psr11::get(UsersDBDataset::class); $user = $userRepo->getByEmail($email); $this->assertNotNull($user); $this->assertNotEmpty($user->get(User::PROP_RESETTOKEN)); @@ -144,7 +144,7 @@ public function testPasswordResetOk() $password = Credentials::getRegularUser()["password"]; // Clear the reset token - $userRepo = Psr11::container()->get(UsersDBDataset::class); + $userRepo = Psr11::get(UsersDBDataset::class); $user = $userRepo->getByEmail($email); $this->assertNotNull($user); $this->assertNotEmpty($user->get(User::PROP_RESETTOKEN)); From 5b3ee15cf48da3b63a68e7734a5f5b9ac8e2db96 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 19 Mar 2025 10:24:43 -0500 Subject: [PATCH 11/14] Remove unused 'docker-compose-image.yml' from file list The 'docker-compose-image.yml' file is no longer required and has been removed from the list of configuration files. This simplifies the script and avoids managing unnecessary files. --- builder/PostCreateScript.php | 1 - 1 file changed, 1 deletion(-) diff --git a/builder/PostCreateScript.php b/builder/PostCreateScript.php index ce0c01a..02d9111 100755 --- a/builder/PostCreateScript.php +++ b/builder/PostCreateScript.php @@ -68,7 +68,6 @@ public function execute($workdir, $namespace, $composerName, $phpVersion, $mysql 'config/config-prod.php', 'config/config-test.php', 'docker-compose-dev.yml', - 'docker-compose-image.yml' ]; $uri = new Uri($mysqlConnection); foreach ($files as $file) { From b22bd76619a0f61521ba9a682d8dfd408ebd1082 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 19 Mar 2025 10:30:16 -0500 Subject: [PATCH 12/14] Update composer command and fix docker-compose syntax Added a "composer update" command to the post-create script to ensure dependencies are updated after project setup. Also updated the documentation to replace the deprecated `docker-compose` command with the new `docker compose` syntax. --- builder/PostCreateScript.php | 2 ++ docs/getting_started.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/builder/PostCreateScript.php b/builder/PostCreateScript.php index 02d9111..c02e6e3 100755 --- a/builder/PostCreateScript.php +++ b/builder/PostCreateScript.php @@ -115,6 +115,8 @@ public function execute($workdir, $namespace, $composerName, $phpVersion, $mysql ); } } + + shell_exec("composer update"); } /** diff --git a/docs/getting_started.md b/docs/getting_started.md index e9a85c2..2ff6294 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -70,7 +70,7 @@ Timezone [UTC]: ```shell cd ~/tutorial -docker-compose -f docker-compose-dev.yml up -d +docker compose -f docker-compose-dev.yml up -d ``` ## Database Setup From 903203e9720305b983aaddcc85b18dddaa4c2944 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 19 Mar 2025 10:34:25 -0500 Subject: [PATCH 13/14] Initialize Git repository in PostCreateScript Add commands to initialize a Git repository, set the default branch to "main," and make the initial commit. This ensures version control is activated immediately after project creation. --- builder/PostCreateScript.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/builder/PostCreateScript.php b/builder/PostCreateScript.php index c02e6e3..14b781e 100755 --- a/builder/PostCreateScript.php +++ b/builder/PostCreateScript.php @@ -117,6 +117,10 @@ public function execute($workdir, $namespace, $composerName, $phpVersion, $mysql } shell_exec("composer update"); + shell_exec("git init"); + shell_exec("git branch -m main"); + shell_exec("git add ."); + shell_exec("git commit -m 'Initial commit'"); } /** From e3bde089c0be43eadd48131ea52e104a287da990 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 19 Mar 2025 10:45:28 -0500 Subject: [PATCH 14/14] Update file paths and emphasize test failure in docs Updated file references to `tests/Rest/ExampleCrudTest.php` for accuracy. Added formatting to emphasize expected test failure, improving clarity for users following the steps. --- docs/getting_started_01_create_table.md | 4 ++-- docs/getting_started_02_add_new_field.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/getting_started_01_create_table.md b/docs/getting_started_01_create_table.md index c142885..8356555 100644 --- a/docs/getting_started_01_create_table.md +++ b/docs/getting_started_01_create_table.md @@ -90,7 +90,7 @@ Run it: composer run test ``` -Initial tests will fail because we need to: +Initial tests **_will fail_** because we need to: 1. Generate OpenAPI documentation to create the endpoints: @@ -98,7 +98,7 @@ Initial tests will fail because we need to: composer run openapi ``` -2. Fix the test data by updating `tests/Functional/Rest/ExampleCrudTest.php`: +2. Fix the test data by updating `tests/Rest/ExampleCrudTest.php`: Locate: diff --git a/docs/getting_started_02_add_new_field.md b/docs/getting_started_02_add_new_field.md index aaf6644..783e94e 100644 --- a/docs/getting_started_02_add_new_field.md +++ b/docs/getting_started_02_add_new_field.md @@ -92,7 +92,7 @@ Open the file: `src/Rest/ExampleCrudRest.php` and add the attribute `status` to ## Adding the field status to the `Test` We only need to change our method `getSample()` to return the status. -Open the file: `tests/Functional/Rest/ExampleCrudTest.php` +Open the file: `tests/Rest/ExampleCrudTest.php` ```php protected function getSampleData($array = false)