diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..0b98694 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: Lansoweb diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md new file mode 100644 index 0000000..1d003a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -0,0 +1,35 @@ +--- +name: Bug Report +about: Create a bug report to help us improve +labels: bug +assignees: +--- + + +## Description + + +## Steps to reproduce + +1. Step one... +2. Step two... +3. Step three... + +## Expected behavior + + +## Screenshots or output + + +## Environment details + +- version of this package: *e.g. 1.0.0, 1.0.1, 1.1.0* +- PHP version: *e.g. 7.3.16, 7.4.4* +- OS: *e.g. Windows 10, Linux (Ubuntu 18.04.1), macOS Catalina (10.15.3)* + +## Additional context + diff --git a/.github/ISSUE_TEMPLATE/Feature_Request.md b/.github/ISSUE_TEMPLATE/Feature_Request.md new file mode 100644 index 0000000..fdde4b2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_Request.md @@ -0,0 +1,31 @@ +--- +name: Feature Request +about: Suggest a feature for this project +labels: enhancement +assignees: +--- + + + +## My feature title + + +## Background/problem + + +## Proposal/solution + + +## Alternatives + + +## Additional context + diff --git a/.github/ISSUE_TEMPLATE/Question.md b/.github/ISSUE_TEMPLATE/Question.md new file mode 100644 index 0000000..5c76b2a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Question.md @@ -0,0 +1,19 @@ +--- +name: Question +about: Ask a question about how to use this library +labels: question +assignees: +--- + + + +## How do I... ? + + +## Example code + diff --git a/.github/PULL_REQUEST_TEMPLATE/Pull_Request_Template.md b/.github/PULL_REQUEST_TEMPLATE/Pull_Request_Template.md new file mode 100644 index 0000000..df4646c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/Pull_Request_Template.md @@ -0,0 +1,30 @@ + + +## Description + + +## Motivation and context + + + +## How has this been tested? + + + + +## Types of changes + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) + +## PR checklist + + +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. +- [ ] I have read the **CONTRIBUTING** document. +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. +- [ ] I have run `composer test` locally, and there were no failures or errors. diff --git a/.github/workflows/coding-standard.yml b/.github/workflows/coding-standard.yml new file mode 100644 index 0000000..4d504e2 --- /dev/null +++ b/.github/workflows/coding-standard.yml @@ -0,0 +1,45 @@ +name: "Check Coding Standards" + +on: + pull_request: + push: + branches: + - "master" + +jobs: + coding-standards: + name: "Check Coding Standards" + + runs-on: ${{ matrix.operating-system }} + + strategy: + matrix: + dependencies: + - "highest" + php-version: + - "8.2" + operating-system: + - "ubuntu-latest" + + steps: + - name: "Checkout" + uses: "actions/checkout@v3" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "pcov" + php-version: "${{ matrix.php-version }}" + ini-values: memory_limit=-1 + tools: composer:v2 + + - uses: "ramsey/composer-install@v1" + with: + dependency-versions: "${{ matrix.dependencies }}" + composer-options: "${{ matrix.composer-options }}" + + - name: "phpcs" + run: "vendor/bin/phpcs" + + - name: "phpstan" + run: "vendor/bin/phpstan analyze" diff --git a/.github/workflows/release-on-milestone-closed.yml b/.github/workflows/release-on-milestone-closed.yml new file mode 100644 index 0000000..c7613e4 --- /dev/null +++ b/.github/workflows/release-on-milestone-closed.yml @@ -0,0 +1,65 @@ +name: "Automatic Releases" + +on: + milestone: + types: + - "closed" + +jobs: + release: + name: "GIT tag, release & create merge-up PR" + runs-on: ubuntu-latest + + steps: + - name: "Checkout" + uses: "actions/checkout@v3" + + - name: "Release" + uses: "laminas/automatic-releases@v1" + with: + command-name: "laminas:automatic-releases:release" + env: + "GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }} + "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} + "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} + "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} + + - name: "Create Merge-Up Pull Request" + uses: "laminas/automatic-releases@v1" + with: + command-name: "laminas:automatic-releases:create-merge-up-pull-request" + env: + "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} + "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} + "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} + "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} + + - name: "Create and/or Switch to new Release Branch" + uses: "laminas/automatic-releases@v1" + with: + command-name: "laminas:automatic-releases:switch-default-branch-to-next-minor" + env: + "GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }} + "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} + "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} + "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} + + - name: "Bump Changelog Version On Originating Release Branch" + uses: "laminas/automatic-releases@v1" + with: + command-name: "laminas:automatic-releases:bump-changelog" + env: + "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} + "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} + "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} + "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} + + - name: "Create new milestones" + uses: "laminas/automatic-releases@v1" + with: + command-name: "laminas:automatic-releases:create-milestones" + env: + "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} + "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} + "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} + "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} diff --git a/.gitignore b/.gitignore index e58358a..74180cb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .~lock.* vendor /composer.lock +/build diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b6b2db..ba7b649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,28 @@ All notable changes to this project will be documented in this file, in reverse - Nothing. +## 3.5.2 - TBD + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + ## 3.4.1 - 2020-03-06 ### Added diff --git a/composer.json b/composer.json index a28ef75..cc42555 100644 --- a/composer.json +++ b/composer.json @@ -1,59 +1,59 @@ { "name": "los/api-server", "description": "PHP api server middleware", + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Leandro Silva", + "homepage": "https://sillotec.com/" + } + ], + "homepage": "https://github.com/lansoweb/api-server", "require": { - "php": "^7.4 | ^8.0", + "php": "^8.0", "ext-json": "*", - "psr/container": "^1.0 | ^2.0", - "psr/http-message": "^1.0", - "laminas/laminas-inputfilter": "^2.7", - "laminas/laminas-paginator": "^2.7", + "laminas/laminas-diactoros": "^3.2", "laminas/laminas-eventmanager": "^3.0", - "psr/http-server-middleware": "^1.0", + "laminas/laminas-inputfilter": "^2.27", + "laminas/laminas-paginator": "^2.7", + "los/uql": "^1.1", "mezzio/mezzio-hal": "^2.0", "mezzio/mezzio-helpers": "^5.6", "mezzio/mezzio-problem-details": "^1.0", - "laminas/laminas-diactoros": "^1.7 || ^2.2", - "los/uql": "^1.1", - "doctrine/coding-standard": "^9.0" + "psr/container": "^1.0 || ^2.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" }, "require-dev": { - "squizlabs/php_codesniffer": "^3.6", - "phpstan/phpstan": "^0.12", + "doctrine/coding-standard": "^12.0", "laminas/laminas-db": "^2.10", - "laminas/laminas-hydrator": "^4.0" + "laminas/laminas-hydrator": "^4.0", + "phpstan/phpstan": "^1.10", + "squizlabs/php_codesniffer": "^3.6" }, - "license": "MIT", - "autoload-dev": { + "autoload": { "psr-4": { - "LosMiddleware\\ApiServerTest\\": "test/" + "Los\\ApiServer\\": "src/" } }, - "autoload": { + "autoload-dev": { "psr-4": { - "LosMiddleware\\ApiServer\\": "src/" + "Los\\ApiServerTest\\": "test/" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true } }, - "type": "library", "scripts": { - "cs-check": "phpcs", - "cs-fix": "phpcbf", "check": [ "@cs-check", "@phpstan" ], - "phpstan": "phpstan analyse -l 4 -c phpstan.neon src" - }, - "homepage": "https://github.com/lansoweb/api-server", - "authors": [ - { - "name": "Leandro Silva", - "homepage": "http://leandrosilva.info/" - } - ], - "extra": { - "zf": { - "config-provider": "LosMiddleware\\ApiServer\\ConfigProvider" - } + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "phpstan": "phpstan analyse" } } diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index ce46b35..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - config - src - diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..1b68193 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,23 @@ + + + + Project coding standards + + + + + + ./src + + + + + + + + + + + diff --git a/phpstan.neon b/phpstan.neon deleted file mode 100644 index 309d9e3..0000000 --- a/phpstan.neon +++ /dev/null @@ -1,3 +0,0 @@ -parameters: - ignoreErrors: - - '#Method LosMiddleware\\ApiServer\\Mapper\\ZendDbMapper::findOneBy\(\) should return LosMiddleware\\ApiServer\\Entity\\EntityInterface|null but returns array|ArrayObject|null.#' diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..0f7b07e --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,10 @@ +parameters: + level: max + tmpDir: ./build/cache/phpstan + checkMissingIterableValueType: false + paths: + - ./src + ignoreErrors: + - '#no type specified#' + - '#mixed#' + - '#not specify its types#' diff --git a/src/Auth/AuthFactory.php b/src/Auth/AuthFactory.php deleted file mode 100644 index 51b6661..0000000 --- a/src/Auth/AuthFactory.php +++ /dev/null @@ -1,26 +0,0 @@ -get('config')['los']['api_server']['auth'] ?? []; - $users = $config['clients'] ?? []; - $allowedPaths = $config['allowedPaths'] ?? []; - - return new AuthMiddleware($users, $allowedPaths, $container->get(ProblemDetailsResponseFactory::class)); - } -} diff --git a/src/Auth/AuthMiddleware.php b/src/Auth/AuthMiddleware.php deleted file mode 100644 index b8365d1..0000000 --- a/src/Auth/AuthMiddleware.php +++ /dev/null @@ -1,98 +0,0 @@ -users = $users; - $this->allowedPaths = $allowedPaths; - $this->problemDetailsResponseFactory = $problemDetailsResponseFactory; - } - - /** - * @param Request $request - * @param RequestHandlerInterface $handler - * @return Response - */ - public function process(Request $request, RequestHandlerInterface $handler): Response - { - try { - $this->validate($request); - } catch (RuntimeException $ex) { - return $this->problemDetailsResponseFactory->createResponseFromThrowable($request, $ex); - } - - return $handler->handle($request); - } - - /** - * @param Request $request - */ - protected function validate(Request $request) : void - { - if (in_array($request->getUri()->getPath(), $this->allowedPaths)) { - return; - } - - if (! $request->hasHeader('authorization')) { - throw AuthorizationException::create('Missing Authorization header'); - } - - $token = $request->getHeader('authorization'); - - if (empty($token)) { - throw AuthorizationException::create('Missing Authorization header'); - } - $token = $token[0]; - - if (! preg_match('/^basic/i', $token)) { - throw AuthorizationException::create('Invalid Authorization header'); - } - - $auth = base64_decode(substr($token, 6)); - if (! $auth) { - throw AuthorizationException::create('Unable to parse Authorization header'); - } - - $tokens = explode(':', $auth); - if (count($tokens) != 2) { - throw AuthorizationException::create('Invalid Authorization header during parse'); - } - - $identity = $tokens[0]; - $credential = $tokens[1]; - if (! array_key_exists($identity, $this->users)) { - throw AuthorizationException::create('Authorization failed'); - } - - if ($this->users[$identity] != $credential && ! hash_equals($this->users[$identity], $credential)) { - throw AuthorizationException::create('Authorization failed'); - } - } -} diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 7a009c6..d156b64 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -1,9 +1,8 @@ [ - Auth\AuthMiddleware::class => Auth\AuthFactory::class, - ], + 'factories' => [], ]; } } diff --git a/src/Entity/Collection.php b/src/Entity/Collection.php index 4303767..996b76c 100644 --- a/src/Entity/Collection.php +++ b/src/Entity/Collection.php @@ -1,7 +1,8 @@ $value) { $fieldName = $filter($key); - $method = 'set'.ucfirst($fieldName); + $method = 'set' . ucfirst($fieldName); if (method_exists($this, $method)) { $this->$method($value); } elseif (property_exists($this, $fieldName)) { @@ -38,28 +51,29 @@ public function exchangeArray(array $data) /** * Return an array representation of the object + * * @see \Laminas\Stdlib\ArraySerializableInterface::getArrayCopy() */ - public function getArrayCopy() : array + public function getArrayCopy(): array { - if (empty($this->fields)) { $fields = get_object_vars($this); unset($fields['inputFilter']); unset($fields['fields']); $this->fields = array_keys($fields); } - $filter = new CamelCaseToUnderscore(); + + $filter = new CamelCaseToUnderscore(); $filterStudly = new UnderscoreToStudlyCase(); $list = []; foreach ($this->fields as $field) { - $property = $filterStudly($field); + $property = $filterStudly($field); $fieldName = extension_loaded('mbstring') ? mb_strtolower($filter($field)) : strtolower($filter($field)); - $method = 'get'.ucfirst($property); + $method = 'get' . ucfirst($property); if (method_exists($this, $method)) { $list[$fieldName] = $this->$method(); } elseif (property_exists($this, $property)) { @@ -76,32 +90,37 @@ public function getArrayCopy() : array * Returns the $data filtered by existant properties only. * * @param array $data + * * @return array */ - public function filterData(array $data) : array + public function filterData(array $data): array { $this->exchangeArray($data); + return $this->getArrayCopy(); } /** * Define which fields will be returned by getArrayCopy + * * @param array $fields */ - public function setFields(array $fields) : void + public function setFields(array $fields): void { - $this->fields = array_merge([static::IDENTIFIER_NAME], $fields); + $this->fields = array_merge([self::IDENTIFIER_NAME], $fields); } /** * @param array $data + * * @return array */ - public function prepareDataForStorage(array $data = []) : array + public function prepareDataForStorage(array $data = []): array { if (empty($data)) { $data = $this->getArrayCopy(); } + return $data; } } diff --git a/src/Entity/EntityInterface.php b/src/Entity/EntityInterface.php index 277399c..f99f0e1 100644 --- a/src/Entity/EntityInterface.php +++ b/src/Entity/EntityInterface.php @@ -1,5 +1,8 @@ status = 401; $exception->detail = $message; - $exception->type = ''; - $exception->title = ''; + $exception->type = ''; + $exception->title = ''; + return $exception; } } diff --git a/src/Exception/MethodNotAllowedException.php b/src/Exception/MethodNotAllowedException.php index 0a4a832..d3381e1 100644 --- a/src/Exception/MethodNotAllowedException.php +++ b/src/Exception/MethodNotAllowedException.php @@ -1,7 +1,8 @@ status = 405; $exception->detail = $message; - $exception->type = ''; - $exception->title = ''; + $exception->type = ''; + $exception->title = ''; + return $exception; } } diff --git a/src/Exception/NotFoundException.php b/src/Exception/NotFoundException.php index b1b943f..84f3464 100644 --- a/src/Exception/NotFoundException.php +++ b/src/Exception/NotFoundException.php @@ -1,7 +1,8 @@ status = 404; $exception->detail = $message; - $exception->type = ''; - $exception->title = ''; + $exception->type = ''; + $exception->title = ''; + return $exception; } } diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index 740564b..08a54e1 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -1,7 +1,8 @@ status = 422; - $exception->detail = 'Unprocessable Entity'; - $exception->type = ''; - $exception->title = ''; + $exception = new self('Unprocessable Entity', 422); + $exception->status = 422; + $exception->detail = 'Unprocessable Entity'; + $exception->type = ''; + $exception->title = ''; $exception->additional = ['messages' => $messages]; + return $exception; } } diff --git a/src/Handler/AbstractRestHandler.php b/src/Handler/AbstractRestHandler.php index 6e29464..844b838 100644 --- a/src/Handler/AbstractRestHandler.php +++ b/src/Handler/AbstractRestHandler.php @@ -1,72 +1,64 @@ urlHelper = $urlHelper; - $this->problemDetailsResponseFactory = $problemDetailsResponseFactory; - $this->resourceGenerator = $resourceGenerator; - $this->responseFactory = $responseFactory; } /** * Method to return the resource name for collections generation - * - * @return string */ - public function getResourceName() : string + public function getResourceName(): string { - $tokens = explode('\\', get_class($this)); + $tokens = explode('\\', static::class); $className = end($tokens); + return strtolower(str_replace('Handler', '', $className)); } - /** - * @param Request $request - * @return Response - */ public function handle(Request $request): Response { $requestMethod = strtoupper($request->getMethod()); @@ -79,36 +71,44 @@ public function handle(Request $request): Response } } - protected function handleMethods(string $requestMethod) : Response + protected function handleMethods(string $requestMethod): Response { - $id = $this->request->getAttribute(static::IDENTIFIER_NAME); + $id = $this->request->getAttribute(self::IDENTIFIER_NAME); switch ($requestMethod) { case 'GET': return isset($id) ? $this->handleFetch($id) : $this->handleFetchAll(); + case 'POST': if (isset($id)) { throw MethodNotAllowedException::create('Method Not Allowed for Entity'); } + return $this->handlePost(); + case 'PUT': return isset($id) ? $this->handleUpdate($id) : $this->handleUpdateList(); + case 'PATCH': return isset($id) ? $this->handlePatch($id) : $this->handlePatchList(); + case 'DELETE': return isset($id) ? $this->handleDelete($id) : $this->handleDeleteList(); + case 'HEAD': return $this->head(); + case 'OPTIONS': return $this->options(); + default: throw MethodNotAllowedException::create(); } @@ -117,10 +117,11 @@ protected function handleMethods(string $requestMethod) : Response /** * Call the inputfilter to filter and validate data * - * @throws ValidationException * @return array + * + * @throws ValidationException */ - protected function validateBody() : array + protected function validateBody(): array { $data = $this->request->getParsedBody(); @@ -128,11 +129,11 @@ protected function validateBody() : array $data = []; } - if ($this->entityPrototype == null || ! ($this->entityPrototype instanceof InputFilterAwareInterface)) { + if (! isset($this->entityPrototype) || ! ($this->entityPrototype instanceof InputFilterAwareInterface)) { return $data; } - if (strtoupper($this->request->getMethod()) == 'PATCH') { + if (strtoupper($this->request->getMethod()) === 'PATCH') { $this->entityPrototype->getInputFilter()->setValidationGroup(array_keys($data)); } @@ -144,32 +145,37 @@ protected function validateBody() : array $parsed = []; foreach ($values as $key => $value) { - if (array_key_exists($key, $data)) { - $parsed[$key] = $value; + if (! array_key_exists($key, $data)) { + continue; } + + $parsed[$key] = $value; } + return $parsed; } /** * Generates a proper response based on the Entity ot Collection */ - protected function generateResponse($entity, int $code = 200) : Response + protected function generateResponse($entity, int $code = 200): Response { if ($entity instanceof Entity) { $resource = $this->resourceGenerator->fromObject($entity, $this->request); + return $this->responseFactory->createResponse($this->request, $resource); } $queryParams = $this->request->getQueryParams(); $metadataMap = $this->resourceGenerator->getMetadataMap(); - /** @var RouteBasedCollectionMetadata $metadata */ - $metadata = $metadataMap->get(get_class($entity)); + $metadata = $metadataMap->get($entity::class); + assert($metadata instanceof RouteBasedCollectionMetadata); $metadataQuery = $origMetadataQuery = $metadata->getQueryStringArguments(); foreach ($queryParams as $key => $value) { $metadataQuery = array_merge($metadataQuery, [$key => $value]); } + $metadata->setQueryStringArguments($metadataQuery); $resource = $this->resourceGenerator->fromObject($entity, $this->request); @@ -188,35 +194,30 @@ protected function generateResponse($entity, int $code = 200) : Response /** * Fetch an Entity - * - * @param mixed $id - * @return Response */ - protected function handleFetch($id) : Response + protected function handleFetch(mixed $id): Response { $entity = $this->fetch($id); + return $this->generateResponse($entity); } /** * Fetch a collection - * - * @return Response */ - protected function handleFetchAll() : Response + protected function handleFetchAll(): Response { $list = $this->fetchAll(); + return $this->generateResponse($list); } /** * Create a new Entity - * - * @return Response */ - protected function handlePost() : Response + protected function handlePost(): Response { - $data = $this->validateBody(); + $data = $this->validateBody(); $entity = $this->create($data); return $this->generateResponse($entity, 201); @@ -224,13 +225,10 @@ protected function handlePost() : Response /** * Update an Entity - * - * @param mixed $id - * @return Response */ - protected function handleUpdate($id) : Response + protected function handleUpdate(mixed $id): Response { - $data = $this->validateBody(); + $data = $this->validateBody(); $entity = $this->update($id, $data); return $this->generateResponse($entity); @@ -238,10 +236,8 @@ protected function handleUpdate($id) : Response /** * Update a collection - * - * @return Response */ - protected function handleUpdateList() : Response + protected function handleUpdateList(): Response { $data = $this->validateBody(); $list = $this->updateList($data); @@ -251,13 +247,10 @@ protected function handleUpdateList() : Response /** * Update some properties from an Entity - * - * @param mixed $id - * @return Response */ - protected function handlePatch($id) : Response + protected function handlePatch(mixed $id): Response { - $data = $this->validateBody(); + $data = $this->validateBody(); $entity = $this->patch($id, $data); return $this->generateResponse($entity); @@ -265,10 +258,8 @@ protected function handlePatch($id) : Response /** * Updates some properties from a Collection - * - * @return Response */ - protected function handlePatchList() : Response + protected function handlePatchList(): Response { $data = $this->validateBody(); $list = $this->patchList($data); @@ -279,12 +270,12 @@ protected function handlePatchList() : Response /** * Delete an Entity * - * @param mixed $id * @return EmptyResponse */ - protected function handleDelete($id) : Response + protected function handleDelete(mixed $id): Response { $this->delete($id); + return new EmptyResponse(204); } @@ -293,18 +284,15 @@ protected function handleDelete($id) : Response * * @return EmptyResponse */ - protected function handleDeleteList() : Response + protected function handleDeleteList(): Response { $this->deleteList(); + return new EmptyResponse(204); } - /** - * @param mixed $id - * @param array $where - * @return EntityInterface - */ - public function fetch($id, array $where = []): EntityInterface + /** @param array $where */ + public function fetch(mixed $id, array $where = []): EntityInterface { throw MethodNotAllowedException::create(); } @@ -312,48 +300,39 @@ public function fetch($id, array $where = []): EntityInterface /** * @param array $where * @param array $options - * @return Collection */ public function fetchAll(array $where = [], array $options = []): Collection { throw MethodNotAllowedException::create(); } - /** - * @param array $data - * @return EntityInterface - */ - public function create(array $data) : EntityInterface + /** @param array $data */ + public function create(array $data): EntityInterface { throw MethodNotAllowedException::create(); } /** - * @param mixed $id * @param array $data * @param array $where - * @return EntityInterface */ - public function update($id, array $data, array $where = []): EntityInterface + public function update(mixed $id, array $data, array $where = []): EntityInterface { throw MethodNotAllowedException::create(); } - public function updateList(array $data) : Collection + public function updateList(array $data): Collection { throw MethodNotAllowedException::create(); } - /** - * @param mixed $id - * @param array $where - */ - public function delete($id, array $where = []) + /** @param array $where */ + public function delete(mixed $id, array $where = []): void { throw MethodNotAllowedException::create(); } - public function deleteList() + public function deleteList(): void { throw MethodNotAllowedException::create(); } @@ -369,17 +348,15 @@ public function options(): Response } /** - * @param mixed $id * @param array $data * @param array $where - * @return EntityInterface */ - public function patch($id, array $data, array $where = []): EntityInterface + public function patch(mixed $id, array $data, array $where = []): EntityInterface { throw MethodNotAllowedException::create(); } - public function patchList(array $data) : Collection + public function patchList(array $data): Collection { throw MethodNotAllowedException::create(); } diff --git a/src/Handler/MapperRestHandler.php b/src/Handler/MapperRestHandler.php index 641850f..e88daf6 100644 --- a/src/Handler/MapperRestHandler.php +++ b/src/Handler/MapperRestHandler.php @@ -1,52 +1,62 @@ mapper = $mapper; $this->entityPrototype = $entityPrototype; } /** * {@inheritDoc} - * @see \LosMiddleware\ApiServer\Handler\AbstractRestHandler::getResourceName() + * + * @see \Los\ApiServer\Handler\AbstractRestHandler::getResourceName() */ public function getResourceName(): string { - $tokens = explode('\\', get_class($this)); + $tokens = explode('\\', static::class); + return strtolower(str_replace('Handler', '', end($tokens))); } /** * {@inheritDoc} - * @see \LosMiddleware\ApiServer\Handler\AbstractRestHandler::create() + * + * @see \Los\ApiServer\Handler\AbstractRestHandler::create() */ public function create(array $data): EntityInterface { @@ -61,37 +71,40 @@ public function create(array $data): EntityInterface /** * {@inheritDoc} - * @see \LosMiddleware\ApiServer\Handler\AbstractRestHandler::fetch() + * + * @see \Los\ApiServer\Handler\AbstractRestHandler::fetch() */ public function fetch($id, array $where = []): EntityInterface { - $where = array_merge([static::IDENTIFIER_NAME => $id], $where); + $where = array_merge([self::IDENTIFIER_NAME => $id], $where); $entity = $this->mapper->findOneBy($where); if ($entity === null) { throw NotFoundException::create(); } - $query = $this->request->getQueryParams(); + $query = $this->request->getQueryParams(); $fields = $query['fields'] ?? ''; if (! empty($fields)) { $entity->setFields(explode(',', $fields)); } + return $entity; } /** * {@inheritDoc} - * @see \LosMiddleware\ApiServer\Handler\AbstractRestHandler::fetchAll() + * + * @see \Los\ApiServer\Handler\AbstractRestHandler::fetchAll() */ public function fetchAll(array $where = [], array $options = []): Collection { $queryParams = $this->request->getQueryParams(); - $query = array_key_exists('q', $queryParams) ? json_decode($queryParams['q'], true) : []; - $hint = array_key_exists('h', $queryParams) ? json_decode($queryParams['h'], true) : []; + $query = array_key_exists('q', $queryParams) ? json_decode($queryParams['q'], true) : []; + $hint = array_key_exists('h', $queryParams) ? json_decode($queryParams['h'], true) : []; $where = array_merge($where, $query); - $hint = array_merge($options, $hint); + $hint = array_merge($options, $hint); $collection = $this->mapper->findBy($where, $hint); @@ -110,11 +123,12 @@ public function fetchAll(array $where = [], array $options = []): Collection /** * {@inheritDoc} - * @see \LosMiddleware\ApiServer\Handler\AbstractRestHandler::delete() + * + * @see \Los\ApiServer\Handler\AbstractRestHandler::delete() */ - public function delete($id, array $where = []) + public function delete($id, array $where = []): void { - $where = array_merge([static::IDENTIFIER_NAME => $id], $where); + $where = array_merge([self::IDENTIFIER_NAME => $id], $where); $entity = $this->mapper->findOneBy($where); if ($entity === null) { @@ -126,11 +140,12 @@ public function delete($id, array $where = []) /** * {@inheritDoc} - * @see \LosMiddleware\ApiServer\Handler\AbstractRestHandler::patch() + * + * @see \Los\ApiServer\Handler\AbstractRestHandler::patch() */ public function patch($id, array $data, array $where = []): EntityInterface { - $where = array_merge([static::IDENTIFIER_NAME => $id], $where); + $where = array_merge([self::IDENTIFIER_NAME => $id], $where); $entity = $this->mapper->findOneBy($where); if ($entity === null) { @@ -147,11 +162,12 @@ public function patch($id, array $data, array $where = []): EntityInterface /** * {@inheritDoc} - * @see \LosMiddleware\ApiServer\Handler\AbstractRestHandler::update() + * + * @see \Los\ApiServer\Handler\AbstractRestHandler::update() */ public function update($id, array $data, array $where = []): EntityInterface { - $where = array_merge([static::IDENTIFIER_NAME => $id], $where); + $where = array_merge([self::IDENTIFIER_NAME => $id], $where); $entity = $this->mapper->findOneBy($where); if ($entity === null) { diff --git a/src/Mapper/MapperInterface.php b/src/Mapper/MapperInterface.php index 103a64b..cc74a12 100644 --- a/src/Mapper/MapperInterface.php +++ b/src/Mapper/MapperInterface.php @@ -1,17 +1,27 @@ table = $table; - $this->collectionClass = $collectionClass; } - /** - * @param mixed $id - * @return EntityInterface|null - */ - public function findById($id): ?EntityInterface + public function findById(mixed $id): EntityInterface|null { return $this->findOneBy(['id' => $id]); } @@ -44,9 +37,8 @@ public function findById($id): ?EntityInterface /** * @param array $where * @param array $options - * @return EntityInterface|null */ - public function findOneBy(array $where = [], array $options = []): ?EntityInterface + public function findOneBy(array $where = [], array $options = []): EntityInterface|null { $predicate = new Where(); @@ -54,14 +46,14 @@ public function findOneBy(array $where = [], array $options = []): ?EntityInterf $predicate->equalTo($key, $value); } - /** @var \Laminas\Db\ResultSet\ResultSet $resultSet */ $resultSet = $this->table->select($predicate); - if (count($resultSet) == 0) { + assert($resultSet instanceof ResultSet); + if (count($resultSet) === 0) { return null; } - /* @var EntityInterface $entity */ $entity = $resultSet->current(); + assert($entity instanceof EntityInterface); $fields = (string) ($options['fields'] ?? ''); if (! empty($fields)) { $entity->setFields(explode(',', $fields)); @@ -70,10 +62,7 @@ public function findOneBy(array $where = [], array $options = []): ?EntityInterf return $entity; } - /** - * @param array $where - * @return int - */ + /** @param array $where */ public function count(array $where = []): int { $predicate = new Where(); @@ -83,38 +72,29 @@ public function count(array $where = []): int } $resultSet = $this->table->select($predicate); + return $resultSet->count(); } - /** - * @param EntityInterface $entity - * @return bool - */ - public function insert(EntityInterface $entity) : bool + public function insert(EntityInterface $entity): bool { $data = $entity->prepareDataForStorage(); + return $this->table->insert($data) > 0; } - /** - * @param array $data - * @param EntityInterface $entity - * @return bool - */ - public function update(array $data, EntityInterface $entity) : bool + /** @param array $data */ + public function update(array $data, EntityInterface $entity): bool { $data = $entity->prepareDataForStorage($data); + return $this->table->update( $data, - [self::IDENTIFIER_NAME => $entity->getArrayCopy()[self::IDENTIFIER_NAME]] + [self::IDENTIFIER_NAME => $entity->getArrayCopy()[self::IDENTIFIER_NAME]], ) > 0; } - /** - * @param EntityInterface $entity - * @return bool - */ - public function delete(EntityInterface $entity) : bool + public function delete(EntityInterface $entity): bool { return $this->table->delete([self::IDENTIFIER_NAME => $entity->getArrayCopy()[self::IDENTIFIER_NAME]]) > 0; } @@ -122,9 +102,8 @@ public function delete(EntityInterface $entity) : bool /** * @param array $where * @param array $options - * @return Collection */ - public function findBy(array $where = [], array $options = []) : Collection + public function findBy(array $where = [], array $options = []): Collection { $sql = $this->table->getSql(); $select = $sql->select(); @@ -133,24 +112,22 @@ public function findBy(array $where = [], array $options = []) : Collection $dbAdapter = new DbSelect( $select, $sql, - $this->table->getResultSetPrototype() + $this->table->getResultSetPrototype(), ); - /** @var Collection $collection */ $collection = new $this->collectionClass($dbAdapter); + assert($collection instanceof Collection); return $collection; } - /** - * @param array $fields - */ + /** @param array $fields */ public function setFields(array $fields): void { - /** @var HydratingResultSet $resultSetPrototype */ $resultSetPrototype = $this->table->getResultSetPrototype(); - /** @var EntityInterface $entityPrototype */ + assert($resultSetPrototype instanceof HydratingResultSet); $entityPrototype = $resultSetPrototype->getObjectPrototype(); + assert($entityPrototype instanceof EntityInterface); $entityPrototype->setFields($fields); } } diff --git a/src/Paginator/MapperAdapter.php b/src/Paginator/MapperAdapter.php index 20bc3b5..1d3bf21 100644 --- a/src/Paginator/MapperAdapter.php +++ b/src/Paginator/MapperAdapter.php @@ -1,28 +1,25 @@ mapper = $mapper; - $this->where = $where; - $this->order = $order; - $this->group = $group; + public function __construct( + private MapperInterface $mapper, + private array $where = [], + private $order = null, + private $group = null, + ) { } /** * {@inheritDoc} + * * @see \Laminas\Paginator\Adapter\AdapterInterface::getItems() */ public function getItems($offset, $itemCountPerPage) @@ -37,9 +34,10 @@ public function getItems($offset, $itemCountPerPage) /** * {@inheritDoc} + * * @see Countable::count() */ - public function count() + public function count(): int { return $this->mapper->count($this->where); }