diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7a7903e3..e732e1a8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,15 +10,15 @@ on:
jobs:
testsuite:
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
- php-version: [ '7.4', '8.0', '8.1' ]
+ php-version: [ '8.1', '8.2', '8.3', '8.4' ]
db-type: [ mysql ]
prefer-lowest: ['']
include:
- - php-version: '7.4'
+ - php-version: '8.1'
db-type: 'mysql'
prefer-lowest: 'prefer-lowest'
@@ -49,7 +49,7 @@ jobs:
run: echo "::set-output name=date::$(date +'%Y-%m')"
- name: Cache composer dependencies
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}
@@ -62,27 +62,14 @@ jobs:
composer update
fi
- - name: Configure PHPUnit matcher
- if: matrix.php-version == '7.4' && matrix.db-type == 'mysql'
- uses: mheap/phpunit-matcher-action@v1
-
- name: Run PHPUnit
run: |
- if [[ ${{ matrix.db-type }} == 'mysql' && ${{ matrix.php-version }} != '7.2' ]]; then export DB_URL='mysql://root:root@127.0.0.1/cakephp?init[]=SET sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"'; fi
- if [[ ${{ matrix.db-type }} == 'mysql' && ${{ matrix.php-version }} == '7.2' ]]; then export DB_URL='mysql://root:root@127.0.0.1/cakephp?encoding=utf8'; fi
- if [[ ${{ matrix.db-type }} == 'mysql' && ${{ matrix.php-version }} == '7.4' ]]; then
- vendor/bin/phpunit --coverage-clover=coverage.xml --verbose
- else
- vendor/bin/phpunit
- fi
-
- - name: Code Coverage Report
- if: success() && matrix.php-version == '7.4' && matrix.db-type == 'mysql'
- uses: codecov/codecov-action@v3
+ export DB_URL='mysql://root:root@127.0.0.1/cakephp?init[]=SET sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"'
+ vendor/bin/phpunit --display-incomplete --display-skipped --display-warnings --display-deprecations
coding-standard:
name: Coding Standard
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
@@ -90,7 +77,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
- php-version: '7.4'
+ php-version: '8.1'
extensions: mbstring, intl, apcu
coverage: none
@@ -103,7 +90,7 @@ jobs:
run: echo "::set-output name=date::$(date +'%Y-%m')"
- name: Cache composer dependencies
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}
@@ -116,7 +103,7 @@ jobs:
static-analysis:
name: Static Analysis
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
@@ -124,7 +111,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
- php-version: '7.4'
+ php-version: '8.1'
extensions: mbstring, intl, apcu
coverage: none
tools: cs2pr
@@ -138,7 +125,7 @@ jobs:
run: echo "::set-output name=date::$(date +'%Y-%m')"
- name: Cache composer dependencies
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}
@@ -146,8 +133,5 @@ jobs:
- name: composer install
run: composer stan-setup
- - name: Run psalm
- run: vendor/bin/psalm.phar --output-format=github
-
- name: Run phpstan
run: vendor/bin/phpstan.phar analyse --error-format=checkstyle ./src | cs2pr
diff --git a/.gitignore b/.gitignore
index 5209c916..ad935574 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ vendor/
composer.lock
tmp
.phpunit.result.cache
+.phpunit.cache
diff --git a/composer.json b/composer.json
index 5fc999b4..cb6df793 100644
--- a/composer.json
+++ b/composer.json
@@ -29,17 +29,17 @@
}
],
"require": {
- "php": ">=7.4.0",
- "cakephp/cakephp": "^4.4.1",
- "friendsofcake/crud": "^6.0",
- "laravel-json-api/neomerx-json-api": "^5.0"
+ "php": ">=8.1.0",
+ "cakephp/cakephp": "^5.0.0",
+ "friendsofcake/crud": "^7.2.0",
+ "laravel-json-api/neomerx-json-api": "^5.0.2"
},
"require-dev": {
- "phpunit/phpunit": "~8.5 || ^9.3",
- "friendsofcake/cakephp-test-utilities": "^2.0.1",
- "friendsofcake/search": "^6.2.2",
- "cakephp/cakephp-codesniffer": "^4.0",
- "dms/phpunit-arraysubset-asserts": "^0.4.0"
+ "phpunit/phpunit": "^10.5.5 || ^11.1.3 || ^12.0.9",
+ "friendsofcake/cakephp-test-utilities": "^3.0.0",
+ "friendsofcake/search": "^7.0.0",
+ "cakephp/cakephp-codesniffer": "^5.1",
+ "dms/phpunit-arraysubset-asserts": "^0.5.0"
},
"autoload": {
"psr-4": {
@@ -65,13 +65,8 @@
"scripts": {
"cs-check": "phpcs -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
"cs-fix": "phpcbf --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
- "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:~1.8.0 psalm/phar:~4.26.0 && mv composer.backup composer.json",
+ "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:~2.1.17 && mv composer.backup composer.json",
"phpstan": "phpstan analyse --memory-limit=3G src/",
- "psalm": "psalm.phar --show-info=false",
- "stan": [
- "@phpstan",
- "@psalm"
- ],
"test": "phpunit"
},
"config": {
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index c8716fa2..8c2e6a5a 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -9,8 +9,3 @@ parameters:
message: "#^Access to an undefined property Cake\\\\Controller\\\\Controller\\:\\:\\$Crud\\.$#"
count: 1
path: src/Listener/PaginationListener.php
-
- -
- message: "#^Call to an undefined method Cake\\\\Datasource\\\\EntityInterface\\:\\:visibleProperties\\(\\)\\.$#"
- count: 1
- path: src/Schema/JsonApi/DynamicEntitySchema.php
diff --git a/phpstan.neon b/phpstan.neon
index e03ea496..458597eb 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -5,9 +5,10 @@ parameters:
level: 6
paths:
- src
- checkMissingIterableValueType: false
- checkGenericClassInNonGenericObjectType: false
universalObjectCratesClasses:
- Crud\Event\Subject
bootstrapFiles:
- vendor/cakephp/cakephp/src/Core/Exception/CakeException.php
+ ignoreErrors:
+ - identifier: missingType.iterableValue
+ - identifier: missingType.generics
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 69dcc090..bea061dc 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,28 +1,24 @@
+
-
-
-
- ./tests/
-
-
-
-
-
-
-
-
-
-
-
-
-
- ./src/
- ./src/
-
-
-
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ colors="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ bootstrap="./tests/bootstrap.php"
+ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
+ cacheDirectory=".phpunit.cache"
+>
+
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/
+
+
diff --git a/psalm.xml b/psalm.xml
deleted file mode 100644
index a889113f..00000000
--- a/psalm.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Action/RelationshipsAction.php b/src/Action/RelationshipsAction.php
index 93a9d6d9..6e435223 100644
--- a/src/Action/RelationshipsAction.php
+++ b/src/Action/RelationshipsAction.php
@@ -21,6 +21,8 @@
use Crud\Traits\SerializeTrait;
use Crud\Traits\ViewTrait;
use Crud\Traits\ViewVarTrait;
+use function Cake\Core\pluginSplit;
+use function Cake\I18n\__;
/**
* Class RelationshipViewAction
@@ -47,7 +49,7 @@ class RelationshipsAction extends BaseAction
*
* @var array
*/
- protected $_defaultConfig = [
+ protected array $_defaultConfig = [
'enabled' => true,
'scope' => 'entity',
'findMethod' => 'all',
@@ -150,7 +152,7 @@ public function checkAllowed(): void
protected function _findRelations(Subject $subject): EntityInterface
{
$relationName = $this->_request()->getParam('type');
- $table = $this->_table();
+ $table = $this->_controller()->fetchTable();
$association = $table->getAssociation($relationName);
$targetTable = $association->getTarget();
@@ -188,7 +190,7 @@ protected function _findRelations(Subject $subject): EntityInterface
->where(
[
$table->aliasField($primaryKey) => $foreignKeyParam,
- ]
+ ],
)
->contain([
$relationName => [
@@ -202,7 +204,7 @@ protected function _findRelations(Subject $subject): EntityInterface
'association' => $association,
'repository' => $table,
'query' => $primaryQuery,
- ]
+ ],
);
$this->_trigger('beforeFind', $subject);
$entity = $subject->query->first();
@@ -239,7 +241,7 @@ protected function _get(): void
*
* @return void
*/
- protected function _delete()
+ protected function _delete(): void
{
$subject = $this->_subject();
$request = $this->_request();
@@ -272,7 +274,7 @@ protected function _delete()
$idsToDelete = (array)Hash::extract($data, '{n}.id');
$foreignRecords = $entity->$property;
$entity->$property = [];
- foreach ($foreignRecords as $key => $foreignRecord) {
+ foreach ($foreignRecords as $foreignRecord) {
if (!in_array($foreignRecord->id, $idsToDelete, false)) {
$entity->{$property}[] = $foreignRecord;
}
@@ -284,7 +286,7 @@ protected function _delete()
$association->setSaveStrategy('replace');
}
$saveMethod = $this->saveMethod();
- if ($this->_table()->$saveMethod($entity, $this->saveOptions())) {
+ if ($this->_controller()->fetchTable()->$saveMethod($entity, $this->saveOptions())) {
$this->_success($subject);
return;
@@ -298,7 +300,7 @@ protected function _delete()
*
* @return void
*/
- protected function _post()
+ protected function _post(): void
{
$subject = $this->_subject();
$request = $this->_request();
@@ -333,7 +335,7 @@ protected function _post()
$this->_trigger('beforeSave', $subject);
$saveMethod = $this->saveMethod();
- if ($this->_table()->$saveMethod($entity, $this->saveOptions())) {
+ if ($this->_controller()->fetchTable()->$saveMethod($entity, $this->saveOptions())) {
$this->_success($subject);
return;
@@ -347,7 +349,7 @@ protected function _post()
*
* @return void
*/
- protected function _patch()
+ protected function _patch(): void
{
$subject = $this->_subject();
$request = $this->_request();
@@ -363,7 +365,6 @@ protected function _patch()
if (in_array($association->type(), [Association::MANY_TO_ONE, Association::ONE_TO_ONE], true)) {
//Set the relationship to the corresponding entity
- /** @psalm-suppress TypeDoesNotContainNull */
if (array_key_exists('id', $data)) {
$entity->{$property} = $foreignTable->get($data['id']);
} elseif ($data === null) {
@@ -382,7 +383,7 @@ protected function _patch()
$association->setSaveStrategy('replace');
}
$saveMethod = $this->saveMethod();
- if ($this->_table()->$saveMethod($entity, $this->saveOptions())) {
+ if ($this->_controller()->fetchTable()->$saveMethod($entity, $this->saveOptions())) {
$this->_success($subject);
return;
@@ -442,7 +443,7 @@ protected function getForeignRecords(array $data, Association $association): arr
->where(
[
$association->aliasField($associationPrimaryKey) . ' in' => $idsToAdd,
- ]
+ ],
)
->all();
@@ -450,13 +451,13 @@ protected function getForeignRecords(array $data, Association $association): arr
$foundIds = $foreignRecords->extract(
static function ($record) {
return $record->id;
- }
+ },
)
->toArray();
$missingIds = array_diff($idsToAdd, $foundIds);
throw new RecordNotFoundException(
- __('Not all requested records could be found. Missing IDs are {0}', implode(', ', $missingIds))
+ __('Not all requested records could be found. Missing IDs are {0}', implode(', ', $missingIds)),
);
}
diff --git a/src/Action/ViewAction.php b/src/Action/ViewAction.php
index 316d3c46..b9f726d2 100644
--- a/src/Action/ViewAction.php
+++ b/src/Action/ViewAction.php
@@ -17,7 +17,7 @@ class ViewAction extends BaseViewAction
* @return void
* @throws \Exception
*/
- protected function _handle(?string $id = null): void
+ protected function _handle(string|int|null $id = null): void
{
$request = $this->_request();
$from = $request->getParam('from');
@@ -44,16 +44,16 @@ protected function _handle(?string $id = null): void
*/
protected function _findRecordViaRelated(Subject $subject): EntityInterface
{
- $repository = $this->_table();
+ $repository = $this->_controller()->fetchTable();
- [$finder, $options] = $this->_extractFinder();
- $query = $repository->find($finder, $options);
+ [$finder] = $this->_extractFinder();
+ $query = $repository->find($finder);
$subject->set(
[
'repository' => $repository,
'query' => $query,
- ]
+ ],
);
$this->_trigger('beforeFind', $subject);
diff --git a/src/Error/JsonApiExceptionRenderer.php b/src/Error/JsonApiExceptionRenderer.php
index 0986cdc8..7ced2810 100644
--- a/src/Error/JsonApiExceptionRenderer.php
+++ b/src/Error/JsonApiExceptionRenderer.php
@@ -5,13 +5,15 @@
use Cake\Controller\Controller;
use Cake\Core\Configure;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Error\Debugger;
use Cake\Http\Response;
+use Cake\Http\ServerRequest;
use Cake\Utility\Inflector;
use Crud\Error\Exception\ValidationException;
use Crud\Error\ExceptionRenderer;
use Crud\Listener\ApiQueryLogListener;
+use Exception;
use Laminas\Diactoros\Stream;
use Neomerx\JsonApi\Encoder\Encoder;
use Neomerx\JsonApi\Schema\Error;
@@ -28,10 +30,11 @@ class JsonApiExceptionRenderer extends ExceptionRenderer
/**
* Method used for all non-validation errors.
*
- * @param string $template Name of template to use (ignored for jsonapi)
+ * @param string $template
+ * @param bool $skipControllerCheck
* @return \Cake\Http\Response
*/
- protected function _outputMessage(string $template): Response
+ protected function _outputMessage(string $template, bool $skipControllerCheck = false): Response
{
if (!$this->controller->getRequest()->accepts('application/vnd.api+json')) {
return parent::_outputMessage($template);
@@ -57,8 +60,8 @@ protected function _outputMessage(string $template): Response
$status,
$code = null,
$title,
- $detail
- )
+ $detail,
+ ),
);
$encoder = Encoder::instance();
@@ -85,7 +88,7 @@ protected function _outputMessage(string $template): Response
* Method used for rendering 422 validation used for both CakePHP entity
* validation errors and JSON API (request data) documents.
*
- * @param \Crud\Error\Exception\ValidationException $error Exception
+ * @param \Crud\Error\Exception\ValidationException $error Exception
* @return \Cake\Http\Response
*/
public function validation(ValidationException $error): Response
@@ -98,7 +101,7 @@ public function validation(ValidationException $error): Response
try {
$this->controller->setResponse($this->controller->getResponse()->withStatus($status));
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$status = 422;
$this->controller->setResponse($this->controller->getResponse()->withStatus($status));
}
@@ -131,7 +134,7 @@ public function validation(ValidationException $error): Response
* - returning cloaked collection as passed down from the Listener
* - creating a new collection from CakePHP validation errors
*
- * @param array $validationErrors CakePHP validation errors
+ * @param array $validationErrors CakePHP validation errors
* @return \Neomerx\JsonApi\Schema\ErrorCollection
*/
protected function _getNeoMerxErrorCollection(array $validationErrors): ErrorCollection
@@ -157,7 +160,7 @@ protected function _getNeoMerxErrorCollection(array $validationErrors): ErrorCol
$idx = null,
$aboutLink = null,
$code = null,
- $meta = null
+ $meta = null,
);
}
@@ -167,7 +170,7 @@ protected function _getNeoMerxErrorCollection(array $validationErrors): ErrorCol
/**
* Adds top-level `debug` node to a json encoded string
*
- * @param string $json Json encoded string
+ * @param string $json Json encoded string
* @return string Json encoded string with added debug node
*/
protected function _addDebugNode(string $json): string
@@ -185,7 +188,7 @@ protected function _addDebugNode(string $json): string
[
'format' => 'array',
'args' => false,
- ]
+ ],
);
$result = json_decode($json, true);
@@ -193,7 +196,7 @@ protected function _addDebugNode(string $json): string
try {
return (string)json_encode($result, JSON_PRETTY_PRINT);
- } catch (Exception $e) {
+ } catch (CakeException $e) {
$result['debug']['message'] = $e->getMessage();
$result['debug']['trace'] = [
'error' => 'Unable to encode stack trace',
@@ -206,7 +209,7 @@ protected function _addDebugNode(string $json): string
/**
* Add top-level `query` node if ApiQueryLogListener is loaded.
*
- * @param string $json Json encoded string
+ * @param string $json Json encoded string
* @return string Json encoded string
*/
protected function _addQueryLogsNode(string $json): string
@@ -231,7 +234,7 @@ protected function _addQueryLogsNode(string $json): string
*/
protected function _getApiQueryLogListenerObject(): ApiQueryLogListener
{
- return new ApiQueryLogListener(new Controller());
+ return new ApiQueryLogListener(new Controller(new ServerRequest()));
}
/**
@@ -242,7 +245,7 @@ protected function _getApiQueryLogListenerObject(): ApiQueryLogListener
* Note: we need this function because Cake's built-in rules don't pass
* through `_processRules()` function in the Validator.
*
- * @param array $errors CakePHP validation errors
+ * @param array $errors CakePHP validation errors
* @return array Standardized array
*/
protected function _standardizeValidationErrors(array $errors = []): array
diff --git a/src/Listener/JsonApi/DocumentRelationshipValidator.php b/src/Listener/JsonApi/DocumentRelationshipValidator.php
index 37b156de..d58b4f7f 100644
--- a/src/Listener/JsonApi/DocumentRelationshipValidator.php
+++ b/src/Listener/JsonApi/DocumentRelationshipValidator.php
@@ -67,7 +67,7 @@ protected function _primaryDataMayBeNullEmptyArrayObjectOrArray(): bool
$detail = "Related records are missing member 'type' or 'id'",
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/' . $about)
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/' . $about),
);
return false;
diff --git a/src/Listener/JsonApi/DocumentValidator.php b/src/Listener/JsonApi/DocumentValidator.php
index 16ce452c..7e1d789b 100644
--- a/src/Listener/JsonApi/DocumentValidator.php
+++ b/src/Listener/JsonApi/DocumentValidator.php
@@ -28,25 +28,25 @@ class DocumentValidator extends stdClass
*
* @var array $_document
*/
- protected $_document;
+ protected array $_document;
/**
* @var \Neomerx\JsonApi\Schema\ErrorCollection
*/
- protected $_errorCollection;
+ protected ErrorCollection $_errorCollection;
/**
* JsonApiListener config() options
*
* @var array
*/
- protected $_config;
+ protected array $_config;
/**
* Constructor
*
- * @param array $documentArray Decoded JSON API document
- * @param array $listenerConfig JsonApiListener config() options
+ * @param array $documentArray Decoded JSON API document
+ * @param array $listenerConfig JsonApiListener config() options
* @return void
*/
public function __construct(array $documentArray, array $listenerConfig)
@@ -127,8 +127,8 @@ protected function _documentMustHavePrimaryData(): bool
$detail = "Document does not contain top-level member 'data'",
$source = [
'pointer' => '',
- ]
- )
+ ],
+ ),
);
throw new ValidationException($this->_getErrorCollectionEntity());
@@ -139,7 +139,7 @@ protected function _documentMustHavePrimaryData(): bool
*
* @return bool
*/
- protected function _primaryDataMustHaveType()
+ protected function _primaryDataMustHaveType(): bool
{
$path = $this->_getPathObject('data.type');
@@ -149,7 +149,7 @@ protected function _primaryDataMustHaveType()
$detail = "Primary data does not contain member 'type'",
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating')
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating'),
);
return false;
@@ -166,7 +166,7 @@ protected function _primaryDataMustHaveType()
$details = "Primary data member 'type' is not a string",
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#document-resource-object-identification')
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#document-resource-object-identification'),
);
return false;
@@ -187,7 +187,7 @@ protected function _primaryDataMustHaveId(): bool
$detail = "Primary data does not contain member 'id'",
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-updating')
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-updating'),
);
return false;
@@ -204,7 +204,7 @@ protected function _primaryDataMustHaveId(): bool
$details = "Primary data member 'id' is not a string",
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#document-resource-object-identification')
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#document-resource-object-identification'),
);
return false;
@@ -234,7 +234,7 @@ protected function _primaryDataMayHaveUuid(): bool
$details = "Primary data member 'id' is not a valid UUID",
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating-client-ids')
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating-client-ids'),
);
return false;
@@ -262,7 +262,7 @@ protected function _primaryDataMayHaveRelationships(): bool
$detail = 'Relationships object does not contain any members',
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating')
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating'),
);
return false;
@@ -304,10 +304,10 @@ protected function _primaryDataMayHaveRelationships(): bool
/**
* Ensures a relationship object has a 'data' member.
*
- * @param string|\stdClass $path Dot separated path of relationship object or path object
+ * @param \stdClass|string $path Dot separated path of relationship object or path object
* @return bool
*/
- protected function _relationshipMustHaveData($path): bool
+ protected function _relationshipMustHaveData(string|stdClass $path): bool
{
$path = $this->_getPathObject($path);
@@ -321,7 +321,7 @@ protected function _relationshipMustHaveData($path): bool
$detail = "Relationships object does not contain member 'data'",
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating')
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating'),
);
return false;
@@ -331,10 +331,10 @@ protected function _relationshipMustHaveData($path): bool
* Checks if relationship object has 'data' member set to null which is
* allowed by the JSON API spec.
*
- * @param string|\stdClass $path Dot separated path of relationship object or path object
+ * @param \stdClass|string $path Dot separated path of relationship object or path object
* @return bool
*/
- protected function _relationshipDataIsNull($path): bool
+ protected function _relationshipDataIsNull(string|stdClass $path): bool
{
$path = $this->_getPathObject($path);
@@ -344,11 +344,11 @@ protected function _relationshipDataIsNull($path): bool
/**
* Ensures a relationship data has a 'type' member.
*
- * @param string $relationship Singular or plural relationship name
- * @param string|\stdClass $path Dot separated path of relationship object or path object
+ * @param string $relationship Singular or plural relationship name
+ * @param \stdClass|string $path Dot separated path of relationship object or path object
* @return bool
*/
- protected function _relationshipDataMustHaveType($relationship, $path): bool
+ protected function _relationshipDataMustHaveType(string $relationship, string|stdClass $path): bool
{
$path = $this->_getPathObject($path);
@@ -372,7 +372,7 @@ protected function _relationshipDataMustHaveType($relationship, $path): bool
$detail = "Relationship data does not contain member 'type'",
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating')
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating'),
);
return false;
@@ -388,7 +388,7 @@ protected function _relationshipDataMustHaveType($relationship, $path): bool
$detail = "Relationship data member 'type' is not a string",
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating')
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating'),
);
return false;
@@ -400,11 +400,11 @@ protected function _relationshipDataMustHaveType($relationship, $path): bool
/**
* Ensures relationship data has an 'id' member.
*
- * @param string $relationship Singular or plural relationship name
- * @param string|\stdClass $path Dot separated path of relationship object or path object
+ * @param string $relationship Singular or plural relationship name
+ * @param \stdClass|string $path Dot separated path of relationship object or path object
* @return bool
*/
- protected function _relationshipDataMustHaveId(string $relationship, $path): bool
+ protected function _relationshipDataMustHaveId(string $relationship, string|stdClass $path): bool
{
$path = $this->_getPathObject($path);
@@ -428,7 +428,7 @@ protected function _relationshipDataMustHaveId(string $relationship, $path): boo
$detail = "Relationship data does not contain member 'id'",
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating')
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating'),
);
return false;
@@ -444,7 +444,7 @@ protected function _relationshipDataMustHaveId(string $relationship, $path): boo
$detail = "Relationship data member 'type' is not a string",
$status = null,
$idx = null,
- $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating')
+ $aboutLink = $this->_getAboutLink('http://jsonapi.org/format/#crud-creating'),
);
return false;
@@ -456,10 +456,10 @@ protected function _relationshipDataMustHaveId(string $relationship, $path): boo
/**
* Checks if a document property is a string.
*
- * @param string|\stdClass $path Dot separated path of the property
+ * @param \stdClass|string $path Dot separated path of the property
* @return bool
*/
- protected function _isString($path): bool
+ protected function _isString(string|stdClass $path): bool
{
$path = $this->_getPathObject($path);
@@ -479,11 +479,11 @@ protected function _isString($path): bool
/**
* Checks if a document property is a valid UUID.
*
- * @param string|\stdClass $path Dot separated path of the property
+ * @param \stdClass|string $path Dot separated path of the property
* @return bool
* @throws \Crud\Error\Exception\CrudException
*/
- protected function _isUuid($path): bool
+ protected function _isUuid(string|stdClass $path): bool
{
$path = $this->_getPathObject($path);
@@ -504,10 +504,10 @@ protected function _isUuid($path): bool
* Checks if document contains a given property (even when value
* is `false` or `null`).
*
- * @param string|\stdClass $path Dot separated path of the property or a path object
+ * @param \stdClass|string $path Dot separated path of the property or a path object
* @return bool
*/
- protected function _hasProperty($path): bool
+ protected function _hasProperty(string|stdClass $path): bool
{
if (is_a($path, 'stdClass')) {
$path = $path->dotted;
@@ -530,11 +530,11 @@ protected function _hasProperty($path): bool
/**
* Returns the value for a given document property.
*
- * @param string|\stdClass $path Dot separated path of the property or path object
+ * @param \stdClass|string $path Dot separated path of the property or path object
* @throws \Crud\Error\Exception\CrudException
* @return mixed
*/
- protected function _getProperty($path)
+ protected function _getProperty(string|stdClass $path): mixed
{
if (is_a($path, 'stdClass')) {
$path = $path->dotted;
@@ -560,10 +560,10 @@ protected function _getProperty($path)
* Helper method to create an object with consistent path strings from
* given dot separated path.
*
- * @param string|\stdClass $path Dot separated path or stdClass $path object
+ * @param \stdClass|string $path Dot separated path or stdClass $path object
* @return \stdClass
*/
- protected function _getPathObject($path): \stdClass
+ protected function _getPathObject(string|stdClass $path): stdClass
{
// return as-is if parameter is
if (is_a($path, 'stdClass')) {
@@ -594,7 +594,7 @@ protected function _getPathObject($path): \stdClass
/**
* Helper method that displays aboutLink only if enabled in Listener config.
*
- * @param string $url URL
+ * @param string $url URL
* @return \Neomerx\JsonApi\Schema\Link|null
*/
protected function _getAboutLink(string $url): ?Link
@@ -623,7 +623,7 @@ protected function _getErrorCollectionEntity(): Entity
'CrudJsonApiListener' => [
'NeoMerxErrorCollection' => $this->_errorCollection,
],
- ]
+ ],
);
return $entity;
@@ -632,10 +632,10 @@ protected function _getErrorCollectionEntity(): Entity
/**
* Helper function to determine if string is singular or plural.
*
- * @param string $string Preferably a CakePHP generated name.
+ * @param string $string Preferably a CakePHP generated name.
* @return bool
*/
- protected function _stringIsSingular($string): bool
+ protected function _stringIsSingular(string $string): bool
{
return Inflector::singularize($string) === $string;
}
diff --git a/src/Listener/JsonApiListener.php b/src/Listener/JsonApiListener.php
index 1609c4bd..e994e296 100644
--- a/src/Listener/JsonApiListener.php
+++ b/src/Listener/JsonApiListener.php
@@ -6,7 +6,6 @@
use Cake\Collection\CollectionInterface;
use Cake\Datasource\EntityInterface;
use Cake\Datasource\RepositoryInterface;
-use Cake\Datasource\ResultSetDecorator;
use Cake\Datasource\ResultSetInterface;
use Cake\Event\EventInterface;
use Cake\Http\Exception\BadRequestException;
@@ -15,7 +14,7 @@
use Cake\Http\Response;
use Cake\ORM\Association;
use Cake\ORM\Locator\LocatorAwareTrait;
-use Cake\ORM\Query;
+use Cake\ORM\Query\SelectQuery;
use Cake\ORM\ResultSet;
use Cake\ORM\Table;
use Cake\Utility\Hash;
@@ -27,6 +26,7 @@
use CrudJsonApi\Listener\JsonApi\DocumentRelationshipValidator;
use CrudJsonApi\Listener\JsonApi\DocumentValidator;
use InvalidArgumentException;
+use function Cake\Core\pluginSplit;
/**
* Extends Crud ApiListener to respond in JSON API format.
@@ -46,7 +46,7 @@ class JsonApiListener extends ApiListener
*
* @var array
*/
- protected $_defaultConfig = [
+ protected array $_defaultConfig = [
'detectors' => [
'jsonapi' => ['ext' => false, 'accept' => [self::MIME_TYPE]],
],
@@ -81,7 +81,7 @@ class JsonApiListener extends ApiListener
*
* @var bool
*/
- protected $_ControllerHasSetContain;
+ protected bool $_ControllerHasSetContain = false;
/**
* Returns a list of all events that will fire in the controller during its lifecycle.
@@ -121,7 +121,7 @@ public function implementedEvents(): array
*
* Called before the crud action is executed.
*
- * @param \Cake\Event\EventInterface $event Event
+ * @param \Cake\Event\EventInterface $event Event
* @return void
*/
public function beforeHandle(EventInterface $event): void
@@ -136,27 +136,19 @@ public function beforeHandle(EventInterface $event): void
* a single primary resource. Does NOT execute when either a Controller has set `contain` or the
* `?include=` query parameter was passed because that would override/break previously generated data.
*
- * @param \Cake\Event\EventInterface $event Event
- * @return null
+ * @param \Cake\Event\EventInterface $event Event
+ * @return void
*/
- public function afterFind(EventInterface $event)
+ public function afterFind(EventInterface $event): void
{
if (!$this->_request()->is('get')) {
- return null;
+ return;
}
// set property so we can check inside `_renderWithResources()`
if (!empty($event->getSubject()->query->getContain())) {
$this->_ControllerHasSetContain = true;
-
- return null;
- }
-
- if ($this->getConfig('include')) {
- return null;
}
-
- return null;
}
/**
@@ -164,7 +156,7 @@ public function afterFind(EventInterface $event)
* to prevent them from sending `hasMany` relationships not belonging to this primary resource
* when PATCHing.
*
- * @param \Cake\Event\EventInterface $event Event
+ * @param \Cake\Event\EventInterface $event Event
* @return void
* @throws \Cake\Http\Exception\BadRequestException
*/
@@ -195,23 +187,19 @@ public function beforeSave(EventInterface $event): void
if ($this->_request()->getMethod() === 'POST') {
throw new BadRequestException(
'JSON API 1.1 does not support sideposting ' .
- '(hasMany relationships detected in the request body)'
+ '(hasMany relationships detected in the request body)',
);
}
// hasMany found in the entity, extract ids from the request data
$primaryResourceId = $this->_controller()->getRequest()->getData('id');
- /**
- * @var array $hasManyIds
-*/
+ /** @var array $hasManyIds */
$hasManyIds = Hash::extract($this->_controller()->getRequest()->getData($key), '{n}.id');
$hasManyTable = $this->getTableLocator()->get($associationName);
// query database only for hasMany that match both passed id and the id of the primary resource
- /**
- * @var string $entityForeignKey
-*/
+ /** @var string $entityForeignKey */
$entityForeignKey = $hasManyTable->getAssociation($entity->getSource())->getForeignKey();
$primaryKey = current((array)$hasManyTable->getPrimaryKey());
$query = $hasManyTable->find()
@@ -220,14 +208,14 @@ public function beforeSave(EventInterface $event): void
[
$entityForeignKey => $primaryResourceId,
$primaryKey . ' IN' => $hasManyIds,
- ]
+ ],
);
// throw an exception if number of database records does not exactly matches passed ids
if (count($hasManyIds) !== $query->count()) {
throw new BadRequestException(
"One or more of the provided relationship ids for
- $associationName do not exist in the database"
+ $associationName do not exist in the database",
);
}
@@ -243,18 +231,22 @@ public function beforeSave(EventInterface $event): void
/**
* afterSave() event.
*
- * @param \Cake\Event\EventInterface $event Event
- * @return bool|null
+ * @param \Cake\Event\EventInterface $event Event
+ * @return void
*/
- public function afterSave(EventInterface $event): ?bool
+ public function afterSave(EventInterface $event): void
{
if (!$event->getSubject()->success) {
- return false;
+ $event->setResult(false);
+
+ return;
}
// `created` will be set for add actions, `id` for edit actions
if (!$event->getSubject()->created && !$event->getSubject()->id) {
- return false;
+ $event->setResult(false);
+
+ return;
}
// The `add`action (new Resource) MUST respond with HTTP Status Code 201,
@@ -264,12 +256,10 @@ public function afterSave(EventInterface $event): ?bool
}
/**
- * @var \Crud\Event\Subject $subject
-*/
+ * @var \Crud\Event\Subject $subject
+ */
$subject = $event->getSubject();
$this->render($subject);
-
- return null;
}
/**
@@ -279,27 +269,27 @@ public function afterSave(EventInterface $event): ?bool
* only meta node after a successful delete as well but this has not
* been implemented here yet. http://jsonapi.org/format/#crud-deleting
*
- * @param \Cake\Event\EventInterface $event Event
- * @return bool|null
+ * @param \Cake\Event\EventInterface $event Event
+ * @return void
*/
- public function afterDelete(EventInterface $event)
+ public function afterDelete(EventInterface $event): void
{
if (!$event->getSubject()->success) {
- return false;
+ $event->setResult(false);
+
+ return;
}
$this->_controller()->setResponse($this->_controller()->getResponse()->withStatus(204));
-
- return null;
}
/**
* beforeRedirect() event used to stop the event and thus redirection.
*
- * @param \Cake\Event\EventInterface $event Event
+ * @param \Cake\Event\EventInterface $event Event
* @return void
*/
- public function beforeRedirect(EventInterface $event)
+ public function beforeRedirect(EventInterface $event): void
{
$event->stopPropagation();
}
@@ -318,11 +308,11 @@ protected function _checkIsRelationshipsRequest(): bool
}
/**
- * @param \Cake\ORM\Table $repository Repository
- * @param string $include The association include path
+ * @param \Cake\ORM\Table $repository Repository
+ * @param string $include The association include path
* @return \Cake\ORM\Association|null
*/
- protected function _getAssociation(Table $repository, $include): ?Association
+ protected function _getAssociation(Table $repository, string $include): ?Association
{
//We refer to associations by their property names, so we try that first
$propertyName = Inflector::underscore($include);
@@ -355,16 +345,21 @@ protected function _getAssociation(Table $repository, $include): ?Association
/**
* Takes a "include" string and converts it into a correct CakePHP ORM association alias
*
- * @param array $includes The relationships to include
- * @param array|bool $denyList Denied includes
- * @param array|bool $allowList Allowed includes
- * @param \Cake\ORM\Table|null $repository The repository
- * @param array $path Include path
+ * @param array $includes The relationships to include
+ * @param array|bool $denyList Denied includes
+ * @param array|bool $allowList Allowed includes
+ * @param \Cake\ORM\Table|null $repository The repository
+ * @param array $path Include path
* @return array
* @throws \Cake\Http\Exception\BadRequestException
*/
- protected function _parseIncludes($includes, $denyList, $allowList, ?Table $repository = null, $path = []): array
- {
+ protected function _parseIncludes(
+ array $includes,
+ array|bool $denyList,
+ array|bool $allowList,
+ ?Table $repository = null,
+ array $path = [],
+ ): array {
$wildcard = implode('.', array_merge($path, ['*']));
$wildcardAllowList = Hash::get((array)$allowList, $wildcard);
$wildcardDenyList = Hash::get((array)$denyList, $wildcard);
@@ -396,7 +391,7 @@ protected function _parseIncludes($includes, $denyList, $allowList, ?Table $repo
$association = $this->_getAssociation($repository, $include);
if ($association === null) {
throw new BadRequestException(
- "Invalid relationship path '{$includeDotPath}' supplied in include parameter"
+ "Invalid relationship path '{$includeDotPath}' supplied in include parameter",
);
}
}
@@ -407,7 +402,7 @@ protected function _parseIncludes($includes, $denyList, $allowList, ?Table $repo
$denyList,
$allowList,
$association ? $association->getTarget() : null,
- $includePath
+ $includePath,
);
}
@@ -428,12 +423,12 @@ protected function _parseIncludes($includes, $denyList, $allowList, ?Table $repo
*
* Supported options is "allowList" and "Blacklist"
*
- * @param string|array $includes The query data
- * @param \Crud\Event\Subject $subject The subject
- * @param array $options Array of options for includes.
+ * @param array|string $includes The query data
+ * @param \Crud\Event\Subject $subject The subject
+ * @param array $options Array of options for includes.
* @return void
*/
- protected function _includeParameter($includes, Subject $subject, $options): void
+ protected function _includeParameter(string|array $includes, Subject $subject, array $options): void
{
if (is_string($includes)) {
$includes = explode(',', $includes);
@@ -471,15 +466,15 @@ protected function _includeParameter($includes, Subject $subject, $options): voi
/**
* Parses out fields query parameter and apply it to the query
*
- * @param string|array|null $fieldSets The query data
- * @param \Crud\Event\Subject $subject The subject
- * @param array $options Array of options for includes.
+ * @param array|string|null $fieldSets The query data
+ * @param \Crud\Event\Subject $subject The subject
+ * @param array $options Array of options for includes.
* @return void
*/
- protected function _fieldSetsParameter($fieldSets, Subject $subject, $options): void
+ protected function _fieldSetsParameter(string|array|null $fieldSets, Subject $subject, array $options): void
{
// could be null for e.g. using integration tests
- if ($fieldSets === null) {
+ if (empty($fieldSets)) {
return;
}
@@ -489,7 +484,7 @@ protected function _fieldSetsParameter($fieldSets, Subject $subject, $options):
static function ($val) {
return explode(',', $val);
},
- (array)$fieldSets
+ (array)$fieldSets,
);
$repository = $subject->query->getRepository();
@@ -518,7 +513,7 @@ static function ($val) use ($repository, $columns) {
return $repository->aliasField($val);
},
- $fields
+ $fields,
);
$selectFields[] = array_filter($aliasFields);
}
@@ -543,7 +538,7 @@ static function ($val) use ($repository, $columns) {
/**
* BeforeFind event listener to parse any supplied query parameters
*
- * @param \Cake\Event\EventInterface $event Event
+ * @param \Cake\Event\EventInterface $event Event
* @return void
*/
public function beforeFind(EventInterface $event): void
@@ -552,16 +547,16 @@ public function beforeFind(EventInterface $event): void
$queryParameters = Hash::merge(
$this->getConfig('queryParameters'),
[
- 'sort' => [
- 'callable' => [$this, '_sortParameter'],
- ],
- 'include' => [
- 'callable' => [$this, '_includeParameter'],
- ],
- 'fields' => [
- 'callable' => [$this, '_fieldSetsParameter'],
+ 'sort' => [
+ 'callable' => [$this, '_sortParameter'],
+ ],
+ 'include' => [
+ 'callable' => [$this, '_includeParameter'],
+ ],
+ 'fields' => [
+ 'callable' => [$this, '_fieldSetsParameter'],
+ ],
],
- ]
);
/** @var \Crud\Event\Subject $subject */
@@ -578,7 +573,7 @@ public function beforeFind(EventInterface $event): void
throw new InvalidArgumentException('Invalid callable supplied for query parameter ' . $parameter);
}
- $options['callable']($this->_request()->getQuery($parameter), $subject, $options);
+ $options['callable']($this->_request()->getQuery($parameter, ''), $subject, $options);
}
$this->_fetchRelated($subject);
@@ -591,7 +586,7 @@ public function beforeFind(EventInterface $event): void
*/
protected function checkValidReverseAssociation(
Association $forwardAssociation,
- Association $reverseAssociation
+ Association $reverseAssociation,
): bool {
$reverseAssociationTarget = $reverseAssociation->getTarget();
$forwardAssociationSource = $forwardAssociation->getSource();
@@ -704,14 +699,14 @@ protected function _fetchRelated(Subject $subject): void
$associationKeys = $repository->associations()->keys();
$subject->query
- ->matching($reverseAssociation->getName(), static function (Query $query) use (
+ ->matching($reverseAssociation->getName(), static function (SelectQuery $query) use (
$reverseAssociation,
- $foreignKeyValue
+ $foreignKeyValue,
) {
return $query
->where([
$reverseAssociation->aliasField(
- current((array)$reverseAssociation->getPrimaryKey())
+ current((array)$reverseAssociation->getPrimaryKey()),
) => $foreignKeyValue,
]);
})
@@ -730,13 +725,13 @@ protected function _fetchRelated(Subject $subject): void
/**
* Add 'sort' capability
*
- * @see http://jsonapi.org/format/#fetching-sorting
- * @param string|array $sortFields Field sort request
- * @param \Crud\Event\Subject $subject The subject
- * @param array $options Array of options for includes.
+ * @see http://jsonapi.org/format/#fetching-sorting
+ * @param array|string $sortFields Field sort request
+ * @param \Crud\Event\Subject $subject The subject
+ * @param array $options Array of options for includes.
* @return void
*/
- protected function _sortParameter($sortFields, Subject $subject, $options): void
+ protected function _sortParameter(string|array $sortFields, Subject $subject, array $options): void
{
if (is_string($sortFields)) {
$sortFields = explode(',', $sortFields);
@@ -783,7 +778,7 @@ protected function _sortParameter($sortFields, Subject $subject, $options): void
],
'strategy' => 'select',
],
- ]
+ ],
);
$subject->query->leftJoinWith($association->getAlias());
@@ -794,13 +789,13 @@ protected function _sortParameter($sortFields, Subject $subject, $options): void
$order[$repository->aliasField($sortField)] = $direction;
}
}
- $subject->query->order($order);
+ $subject->query->orderBy($order);
}
/**
* Set required viewVars before rendering the JsonApiView.
*
- * @param \Crud\Event\Subject $subject Subject
+ * @param \Crud\Event\Subject $subject Subject
* @return \Cake\Http\Response
*/
public function render(Subject $subject): Response
@@ -866,7 +861,7 @@ protected function _renderWithIdentifiers(Subject $subject): Response
'serialize' => true,
'association' => $association,
'inflect' => $this->getConfig('inflect'),
- ]
+ ],
);
return $this->_controller()
@@ -876,12 +871,12 @@ protected function _renderWithIdentifiers(Subject $subject): Response
/**
* Renders a JSON API response with top-level data node holding resource(s).
*
- * @param \Crud\Event\Subject $subject Subject
+ * @param \Crud\Event\Subject $subject Subject
* @return \Cake\Http\Response
*/
protected function _renderWithResources(Subject $subject): Response
{
- $repository = $this->_controller()->loadModel(); // Default model class
+ $repository = $this->_controller()->fetchTable(); // Default model class
$usedAssociations = [];
if (isset($subject->query)) {
@@ -921,7 +916,7 @@ protected function _renderWithResources(Subject $subject): Response
'include' => $include,
'repositories' => $this->_getRepositoryList(
$repository,
- $usedAssociations
+ $usedAssociations,
),
]);
$this->_controller()->set([
@@ -945,7 +940,7 @@ protected function _validateConfigOptions(): void
!is_array($this->getConfig('withJsonApiVersion'))
) {
throw new CrudException(
- 'JsonApiListener configuration option `withJsonApiVersion` only accepts a boolean or an array'
+ 'JsonApiListener configuration option `withJsonApiVersion` only accepts a boolean or an array',
);
}
}
@@ -956,7 +951,7 @@ protected function _validateConfigOptions(): void
if (!is_bool($this->getConfig('absoluteLinks'))) {
throw new CrudException(
- 'JsonApiListener configuration option `absoluteLinks` only accepts a boolean'
+ 'JsonApiListener configuration option `absoluteLinks` only accepts a boolean',
);
}
@@ -999,7 +994,7 @@ protected function _checkRequestMethods(): void
if ($this->_request()->contentType() !== self::MIME_TYPE) {
throw new BadRequestException(
- 'JSON API requests with data require the "' . self::MIME_TYPE . '" Content-Type header'
+ 'JSON API requests with data require the "' . self::MIME_TYPE . '" Content-Type header',
);
}
}
@@ -1007,10 +1002,10 @@ protected function _checkRequestMethods(): void
/**
* Deduplicate resultset from rows that might have come from joins
*
- * @param \Crud\Event\Subject $subject Subject
+ * @param \Crud\Event\Subject $subject Subject
* @return \Cake\Datasource\ResultSetInterface
*/
- protected function _deduplicateResultSet($subject): ResultSetInterface
+ protected function _deduplicateResultSet(Subject $subject): ResultSetInterface
{
$ids = [];
$entities = [];
@@ -1023,24 +1018,17 @@ protected function _deduplicateResultSet($subject): ResultSetInterface
}
}
- if ($subject->entities instanceof ResultSet) {
- $resultSet = clone $subject->entities;
- $resultSet->unserialize(serialize($entities));
- } else {
- $resultSet = new ResultSetDecorator($entities);
- }
-
- return $resultSet;
+ return new ResultSet($entities);
}
/**
* Helper function to easily retrieve `find()` result from Crud subject
* regardless of current action.
*
- * @param \Crud\Event\Subject $subject Subject
- * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\EntityInterface[]|\Cake\ORM\ResultSet Single Entity or ORM\ResultSet
+ * @param \Crud\Event\Subject $subject Subject
+ * @return \Cake\Datasource\EntityInterface|\Cake\Datasource\ResultSetInterface|array<\Cake\Datasource\EntityInterface>|string Single Entity or ORM\ResultSet
*/
- protected function _getFindResult(Subject $subject)
+ protected function _getFindResult(Subject $subject): EntityInterface|ResultSetInterface|array|string
{
if (!empty($subject->entities)) {
if (isset($subject->query)) {
@@ -1057,13 +1045,12 @@ protected function _getFindResult(Subject $subject)
* Helper function to easily retrieve a single entity from Crud subject
* find result regardless of current action.
*
- * @param \Crud\Event\Subject $subject Subject
+ * @param \Crud\Event\Subject $subject Subject
* @return \Cake\Datasource\EntityInterface|null
*/
protected function _getSingleEntity(Subject $subject): ?EntityInterface
{
- if (!empty($subject->entities) && $subject->entities instanceof Query) {
- /** @psalm-suppress InvalidReturnStatement */
+ if (!empty($subject->entities) && $subject->entities instanceof SelectQuery) {
return (clone $subject->entities)->first();
}
@@ -1078,14 +1065,14 @@ protected function _getSingleEntity(Subject $subject): ?EntityInterface
return $subject->entities->first();
}
- return $subject->entity;
+ return $subject->entity ?? null;
}
/**
* Creates a nested array of all associations used in the query
*
- * @param \Cake\Datasource\RepositoryInterface $repository Repository
- * @param array $contains Array of contained associations
+ * @param \Cake\Datasource\RepositoryInterface $repository Repository
+ * @param array $contains Array of contained associations
* @return array Array with \Cake\ORM\AssociationCollection
*/
protected function _getContainedAssociations(RepositoryInterface $repository, array $contains): array
@@ -1118,7 +1105,7 @@ protected function _getContainedAssociations(RepositoryInterface $repository, ar
if (!empty($nestedContains)) {
$associations[$associationKey]['children'] = $this->_getContainedAssociations(
$association->getTarget(),
- $nestedContains
+ $nestedContains,
);
}
}
@@ -1131,8 +1118,8 @@ protected function _getContainedAssociations(RepositoryInterface $repository, ar
* query) in the find result from the entity's AssociationCollection to
* prevent `null` entries appearing in the json api `relationships` node.
*
- * @param \Cake\Datasource\RepositoryInterface $repository Repository
- * @param \Cake\Datasource\EntityInterface $entity Entity
+ * @param \Cake\Datasource\RepositoryInterface $repository Repository
+ * @param \Cake\Datasource\EntityInterface $entity Entity
* @return array
*/
protected function _extractEntityAssociations(RepositoryInterface $repository, EntityInterface $entity): array
@@ -1163,8 +1150,8 @@ protected function _extractEntityAssociations(RepositoryInterface $repository, E
* Get a flat list of all repositories indexed by their registry alias.
*
* @param \Cake\Datasource\RepositoryInterface $repository Current repository
- * @param array $associations Nested associations to get repository from
- * @return array Used repositories indexed by registry alias
+ * @param array $associations Nested associations to get repository from
+ * @return array Used repositories indexed by registry alias
* @internal
*/
protected function _getRepositoryList(RepositoryInterface $repository, array $associations): array
@@ -1197,8 +1184,8 @@ protected function _getRepositoryList(RepositoryInterface $repository, array $as
* `included` node in the json response UNLESS user has specified listener
* config option 'include'.
*
- * @param array $associations Array with \Cake\ORM\AssociationCollection(s)
- * @param bool $last Is this the "top-level"/entry point for the recursive function
+ * @param array $associations Array with \Cake\ORM\AssociationCollection(s)
+ * @param bool $last Is this the "top-level"/entry point for the recursive function
* @return array
* @throws \InvalidArgumentException
*/
@@ -1257,7 +1244,7 @@ protected function _checkRequestData(): void
throw new BadRequestException(
'Missing request data required for POST and PATCH methods, ' .
'as well as DELETE methods to relationship endpoints. ' .
- 'Make sure that you are sending a request body and that it is valid JSON.'
+ 'Make sure that you are sending a request body and that it is valid JSON.',
);
}
@@ -1271,7 +1258,6 @@ protected function _checkRequestData(): void
$validator->validateCreateDocument();
}
- /** @psalm-suppress TypeDoesNotContainType */
if ($requestMethod === 'PATCH' || $requestMethod === 'DELETE') {
$validator->validateUpdateDocument();
}
@@ -1289,7 +1275,7 @@ protected function _checkRequestData(): void
if ($exception) {
throw new BadRequestException(
- 'URL id does not match request data id as required for JSON API PATCH actions'
+ 'URL id does not match request data id as required for JSON API PATCH actions',
);
}
@@ -1300,16 +1286,13 @@ protected function _checkRequestData(): void
* Returns a flat array list with the names of all associations for the given
* repository (Or the default for the controller), optionally limited to only matching associationTypes.
*
- * @param \Cake\ORM\Table|null $table Table
- * @param array $associationTypes Array with any combination of Cake\ORM\Association types
+ * @param \Cake\ORM\Table|null $table Table
+ * @param array $associationTypes Array with any combination of Cake\ORM\Association types
* @return array
*/
protected function _getAssociationsList(?Table $table, array $associationTypes = []): array
{
- $table = $table ?: $this->_table();
- if (!$table instanceof Table) {
- return [];
- }
+ $table = $table ?: $this->_controller()->fetchTable();
$associations = $table->associations();
@@ -1335,7 +1318,7 @@ protected function _getAssociationsList(?Table $table, array $associationTypes =
*
* Please note that decoding hasMany relationships has not yet been implemented.
*
- * @param array $document Request data document array
+ * @param array $document Request data document array
* @return array
*/
protected function _convertJsonApiDocumentArray(array $document): array
diff --git a/src/Listener/PaginationListener.php b/src/Listener/PaginationListener.php
index 1028150e..b9c1fd94 100644
--- a/src/Listener/PaginationListener.php
+++ b/src/Listener/PaginationListener.php
@@ -3,6 +3,7 @@
namespace CrudJsonApi\Listener;
+use Cake\Datasource\Paging\PaginatedInterface;
use Cake\Event\EventInterface;
use Cake\Routing\Router;
use Crud\Listener\ApiPaginationListener as BaseListener;
@@ -38,29 +39,37 @@ public function implementedEvents(): array
/**
* Appends the pagination information to the JSON or XML output
*
- * @param \Cake\Event\EventInterface $event Event
+ * @param \Cake\Event\EventInterface $event Event
* @return void
*/
public function beforeRender(EventInterface $event): void
{
- $paging = $this->_request()->getAttribute('paging');
+ $viewVar = 'data';
+ $action = $this->_action();
- if (empty($paging)) {
- return;
+ if (method_exists($action, 'viewVar')) {
+ $viewVar = $action->viewVar();
}
- $pagination = current($paging);
- if (empty($pagination)) {
+ $paginatedResultset = $this
+ ->_controller()
+ ->viewBuilder()
+ ->getVar($viewVar);
+
+ if (!$paginatedResultset instanceof PaginatedInterface) {
return;
}
- $this->_controller->viewBuilder()->setOption('pagination', $this->_getJsonApiPaginationResponse($pagination));
+ $this
+ ->_controller()
+ ->viewBuilder()
+ ->setOption('pagination', $this->_getJsonApiPaginationResponse($paginatedResultset->pagingParams()));
}
/**
* Generates pagination viewVars with JSON API compatible hyperlinks.
*
- * @param array $pagination CakePHP pagination result
+ * @param array $pagination CakePHP pagination result
* @return array
*/
protected function _getJsonApiPaginationResponse(array $pagination): array
@@ -72,7 +81,7 @@ protected function _getJsonApiPaginationResponse(array $pagination): array
'page' => null,
'limit' => null,
],
- $pagination
+ $pagination,
);
$request = $this->_request();
@@ -86,7 +95,6 @@ protected function _getJsonApiPaginationResponse(array $pagination): array
$query['sort'] = $request->getQuery('sort');
}
- /** @psalm-suppress UndefinedMagicPropertyFetch */
$fullBase = (bool)$this->_controller()->Crud->getConfig('listeners.jsonApi.absoluteLinks');
$baseUrl = $request->getAttributes()['params'];
@@ -94,42 +102,42 @@ protected function _getJsonApiPaginationResponse(array $pagination): array
$self = Router::url(
$baseUrl + [
- '?' => ['page' => $pagination['page']] + $query,
+ '?' => ['page' => $pagination['currentPage']] + $query,
],
- $fullBase
+ $fullBase,
);
$first = Router::url(
$baseUrl + [
'?' => ['page' => 1] + $query,
],
- $fullBase
+ $fullBase,
);
$last = Router::url(
$baseUrl + [
'?' => ['page' => $pagination['pageCount']] + $query,
],
- $fullBase
+ $fullBase,
);
$prev = null;
- if ($pagination['prevPage']) {
+ if ($pagination['hasPrevPage']) {
$prev = Router::url(
$baseUrl + [
- '?' => ['page' => $pagination['page'] - 1] + $query,
+ '?' => ['page' => $pagination['currentPage'] - 1] + $query,
],
- $fullBase
+ $fullBase,
);
}
$next = null;
- if ($pagination['nextPage']) {
+ if ($pagination['hasNextPage']) {
$next = Router::url(
$baseUrl + [
- '?' => ['page' => $pagination['page'] + 1] + $query,
+ '?' => ['page' => $pagination['currentPage'] + 1] + $query,
],
- $fullBase
+ $fullBase,
);
}
@@ -139,7 +147,7 @@ protected function _getJsonApiPaginationResponse(array $pagination): array
'last' => $last,
'prev' => $prev,
'next' => $next,
- 'record_count' => $pagination['count'],
+ 'record_count' => $pagination['totalCount'],
'page_count' => $pagination['pageCount'],
'page_limit' => $pagination['limit'],
];
diff --git a/src/Listener/SearchListener.php b/src/Listener/SearchListener.php
index a91c001d..fd6f1b41 100644
--- a/src/Listener/SearchListener.php
+++ b/src/Listener/SearchListener.php
@@ -4,7 +4,6 @@
namespace CrudJsonApi\Listener;
use Cake\Event\EventInterface;
-use Cake\ORM\Table;
use Crud\Listener\BaseListener;
use RuntimeException;
@@ -15,7 +14,7 @@ class SearchListener extends BaseListener
*
* @var array
*/
- protected $_defaultConfig = [
+ protected array $_defaultConfig = [
'enabled' => [
'Crud.beforeLookup',
'Crud.beforePaginate',
@@ -40,7 +39,7 @@ public function implementedEvents(): array
/**
* Inject search conditions into the query object.
*
- * @param \Cake\Event\EventInterface $event Event
+ * @param \Cake\Event\EventInterface $event Event
* @return void
*/
public function injectSearch(EventInterface $event): void
@@ -49,13 +48,13 @@ public function injectSearch(EventInterface $event): void
return;
}
- $repository = $this->_table();
- if ($repository instanceof Table && !$repository->behaviors()->has('Search')) {
+ $repository = $this->_controller()->fetchTable();
+ if (!$repository->behaviors()->has('Search')) {
throw new RuntimeException(
sprintf(
'Missing Search.Search behavior on %s',
- get_class($repository)
- )
+ get_class($repository),
+ ),
);
}
diff --git a/src/Plugin.php b/src/Plugin.php
index 036dff65..dd08c99f 100644
--- a/src/Plugin.php
+++ b/src/Plugin.php
@@ -13,7 +13,7 @@ class Plugin extends BasePlugin
/**
* Plugin name
*
- * @var string
+ * @var ?string
*/
- protected $name = 'CrudJsonApi';
+ protected ?string $name = 'CrudJsonApiPlugin';
}
diff --git a/src/Route/JsonApiRoutes.php b/src/Route/JsonApiRoutes.php
index 6a1c4e76..274e5bce 100644
--- a/src/Route/JsonApiRoutes.php
+++ b/src/Route/JsonApiRoutes.php
@@ -10,6 +10,8 @@
use Cake\Routing\RouteBuilder;
use Cake\Utility\Hash;
use Cake\Utility\Inflector;
+use function Cake\Core\pluginSplit;
+use function in_array;
/**
* Class RouteBuilder
@@ -38,7 +40,7 @@ private static function inflect(string $string): string
private static function buildRelationshipLink(
RouteBuilder $routeBuilder,
Association $association,
- array $options
+ array $options,
): void {
$type = $association->getName();
@@ -85,7 +87,7 @@ private static function buildRelationshipLink(
$routeBuilder->connect(
'/relationships/' . $path,
- $base + ['_method' => $method]
+ $base + ['_method' => $method],
);
}
}
@@ -96,8 +98,11 @@ private static function buildRelationshipLink(
* @param array $options Array of options
* @return void
*/
- private static function buildAssociationLinks(RouteBuilder $routeBuilder, Association $association, array $options)
- {
+ private static function buildAssociationLinks(
+ RouteBuilder $routeBuilder,
+ Association $association,
+ array $options,
+ ): void {
$name = $association->getName();
if (in_array($name, $options['ignoredAssociations'], true)) {
@@ -111,10 +116,10 @@ private static function buildAssociationLinks(RouteBuilder $routeBuilder, Associ
$from = $association->getSource()->getRegistryAlias();
$plugin = $routeBuilder->params()['plugin'] ?? null;
- $isOne = \in_array(
+ $isOne = in_array(
$association->type(),
[Association::MANY_TO_ONE, Association::ONE_TO_ONE],
- true
+ true,
);
$pathName = self::inflect($association->getProperty());
@@ -133,8 +138,8 @@ static function (RouteBuilder $routeBuilder) use (
$name,
$isOne,
$from,
- $controller
- ) {
+ $controller,
+ ): void {
$routeBuilder->connect(
'/' . $pathName,
[
@@ -146,9 +151,9 @@ static function (RouteBuilder $routeBuilder) use (
],
[
'_name' => "CrudJsonApi.{$from}:{$name}",
- ]
+ ],
);
- }
+ },
);
return;
@@ -165,7 +170,7 @@ static function (RouteBuilder $routeBuilder) use (
],
[
'_name' => "CrudJsonApi.{$from}:{$name}",
- ]
+ ],
);
}
@@ -181,7 +186,7 @@ public static function mapModels(array $models, RouteBuilder $routeBuilder): voi
$plugin = $routeBuilder->params()['plugin'] ?? null;
- $routeBuilder->scope('/', function (RouteBuilder $routeBuilder) use ($models, $plugin, $locator) {
+ $routeBuilder->scope('/', function (RouteBuilder $routeBuilder) use ($models, $plugin, $locator): void {
$routeBuilder->namePrefix('');
foreach ($models as $model => $options) {
@@ -210,7 +215,7 @@ public static function mapModels(array $models, RouteBuilder $routeBuilder): voi
$associations = $tableObject->associations();
if ($options['allowedAssociations'] !== false) {
- $callback = function (RouteBuilder $routeBuilder) use ($associations, $options) {
+ $callback = function (RouteBuilder $routeBuilder) use ($associations, $options): void {
/** @var \Cake\ORM\Association $association */
foreach ($associations as $association) {
self::buildAssociationLinks($routeBuilder, $association, $options);
@@ -223,7 +228,7 @@ public static function mapModels(array $models, RouteBuilder $routeBuilder): voi
$routeBuilder->resources(
$model,
$options,
- $callback
+ $callback,
);
}
});
diff --git a/src/Schema/JsonApi/DynamicEntitySchema.php b/src/Schema/JsonApi/DynamicEntitySchema.php
index 1064f8c5..623564de 100644
--- a/src/Schema/JsonApi/DynamicEntitySchema.php
+++ b/src/Schema/JsonApi/DynamicEntitySchema.php
@@ -5,6 +5,7 @@
use Cake\Core\App;
use Cake\Datasource\EntityInterface;
+use Cake\Datasource\RepositoryInterface;
use Cake\ORM\Association;
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\ORM\Table;
@@ -20,6 +21,9 @@
use Neomerx\JsonApi\Contracts\Schema\LinkInterface;
use Neomerx\JsonApi\Schema\BaseSchema;
use Neomerx\JsonApi\Schema\Identifier;
+use RuntimeException;
+use function Cake\Core\pluginSplit;
+use function in_array;
/**
* Licensed under The MIT License
@@ -35,11 +39,11 @@ class DynamicEntitySchema extends BaseSchema
*
* @var \Cake\View\View
*/
- protected $view;
+ protected View $view;
/**
* @var \Cake\ORM\Table
*/
- protected $repository;
+ protected Table $repository;
/**
* Class constructor
@@ -51,7 +55,7 @@ class DynamicEntitySchema extends BaseSchema
public function __construct(
FactoryInterface $factory,
View $view,
- Table $repository
+ Table $repository,
) {
$this->view = $view;
$this->repository = $repository;
@@ -63,7 +67,7 @@ public function __construct(
* @param \Cake\ORM\Table $repository The repository object
* @return mixed
*/
- protected function getTypeFromRepository(Table $repository)
+ protected function getTypeFromRepository(Table $repository): mixed
{
$repositoryName = App::shortName(get_class($repository), 'Model/Table', 'Table');
[, $entityName] = pluginSplit($repositoryName);
@@ -82,26 +86,26 @@ public function getType(): string
/**
* Get resource id.
*
- * @param \Cake\ORM\Entity $resource Entity
+ * @param \Cake\ORM\Entity $resource Entity
* @return string
- * @psalm-suppress MoreSpecificImplementedParamType
+ * @phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
public function getId($resource): ?string
{
$primaryKey = $this->repository->getPrimaryKey();
if (is_array($primaryKey)) {
- throw new \RuntimeException('Crud-Json-Api does not support composite keys out of the box.');
+ throw new RuntimeException('Crud-Json-Api does not support composite keys out of the box.');
}
return (string)$resource->get($primaryKey);
}
/**
- * @param \Cake\Datasource\EntityInterface $entity Entity
+ * @param \Cake\Datasource\EntityInterface $entity Entity
* @return \Cake\ORM\Table
*/
- protected function getRepository($entity = null): Table
+ protected function getRepository(?EntityInterface $entity = null): Table
{
if (!$entity) {
return $this->repository;
@@ -118,16 +122,13 @@ protected function getRepository($entity = null): Table
*
* This method will ignore any properties that are entities.
*
- * @param \Cake\Datasource\EntityInterface $entity Entity
+ * @param \Cake\Datasource\EntityInterface $entity Entity
* @return array
*/
- protected function entityToShallowArray(EntityInterface $entity)
+ protected function entityToShallowArray(EntityInterface $entity): array
{
$result = [];
- /** @psalm-suppress UndefinedInterfaceMethod */
- $properties = method_exists($entity, 'getVisible')
- ? $entity->getVisible()
- : $entity->visibleProperties();
+ $properties = $entity->getVisible();
foreach ($properties as $property) {
if ($property[0] === '_') {
continue;
@@ -156,7 +157,7 @@ protected function entityToShallowArray(EntityInterface $entity)
* @param \Cake\Datasource\EntityInterface $resource Entity
* @param \Neomerx\JsonApi\Contracts\Schema\ContextInterface $context The Context
* @return array
- * @psalm-suppress MoreSpecificImplementedParamType
+ * @phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
public function getAttributes($resource, ContextInterface $context): iterable
{
@@ -197,10 +198,10 @@ public function getAttributes($resource, ContextInterface $context): iterable
*
* JSON API optional `related` links not implemented yet.
*
- * @param \Cake\Datasource\EntityInterface $resource Entity object
+ * @param \Cake\Datasource\EntityInterface $resource Entity object
* @param \Neomerx\JsonApi\Contracts\Schema\ContextInterface $context The Context
* @return array
- * @psalm-suppress MoreSpecificImplementedParamType
+ * @phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
public function getRelationships($resource, ContextInterface $context): iterable
{
@@ -258,7 +259,7 @@ public function getRelationships($resource, ContextInterface $context): iterable
if (!$data && !is_array($foreignKey)) {
$data = new Identifier(
(string)$resource->get($foreignKey),
- $this->getTypeFromRepository($association->getTarget())
+ $this->getTypeFromRepository($association->getTarget()),
);
}
@@ -275,9 +276,9 @@ public function getRelationships($resource, ContextInterface $context): iterable
/**
* NeoMerx override used to generate `self` links
*
- * @param \Cake\ORM\Entity|null $resource Entity, null only to be compatible with the Neomerx method
+ * @param \Cake\ORM\Entity|null $resource Entity, null only to be compatible with the Neomerx method
* @return string
- * @psalm-suppress MoreSpecificImplementedParamType
+ * @phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
public function getSelfSubUrl($resource = null): string
{
@@ -292,7 +293,7 @@ public function getSelfSubUrl($resource = null): string
'_method' => 'GET',
'action' => 'view',
],
- $this->view->getConfig('absoluteLinks', false)
+ $this->view->getConfig('absoluteLinks', false),
);
}
@@ -321,14 +322,14 @@ protected function getAssociationByProperty(string $name): ?Association
* @param \Cake\Datasource\EntityInterface $resource Entity
* @param string $name Relationship name in lowercase singular or plural
* @return \Neomerx\JsonApi\Contracts\Schema\LinkInterface
- * @psalm-suppress MoreSpecificImplementedParamType
+ * @phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
public function getRelationshipSelfLink($resource, string $name): LinkInterface
{
$association = $this->getAssociationByProperty($name);
if (!$association) {
throw new InvalidArgumentException(
- sprintf('Invalid association for resource %s: %s', get_class($resource), $name)
+ sprintf('Invalid association for resource %s: %s', get_class($resource), $name),
);
}
@@ -346,7 +347,7 @@ public function getRelationshipSelfLink($resource, string $name): LinkInterface
'from' => $from,
'type' => $type,
],
- $this->view->getConfig('absoluteLinks', false)
+ $this->view->getConfig('absoluteLinks', false),
);
return $this->getFactory()->createLink(false, $url, false);
@@ -361,7 +362,7 @@ public function getRelationshipSelfLink($resource, string $name): LinkInterface
* @param \Cake\Datasource\EntityInterface $resource Entity
* @param string $name Relationship name in lowercase singular or plural
* @return \Neomerx\JsonApi\Contracts\Schema\LinkInterface
- * @psalm-suppress MoreSpecificImplementedParamType
+ * @phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
public function getRelationshipRelatedLink($resource, string $name): LinkInterface
{
@@ -375,10 +376,10 @@ public function getRelationshipRelatedLink($resource, string $name): LinkInterfa
['controller' => $controllerName] = $this->_getRepositoryRoutingParameters($this->getRepository());
$sourceName = Inflector::underscore(Inflector::singularize($controllerName));
- $isOne = \in_array(
+ $isOne = in_array(
$association->type(),
[Association::MANY_TO_ONE, Association::ONE_TO_ONE],
- true
+ true,
);
$baseRoute = $this->_getRepositoryRoutingParameters($relatedRepository) + [
@@ -399,7 +400,7 @@ public function getRelationshipRelatedLink($resource, string $name): LinkInterfa
try {
$url = Router::url(
$route,
- $this->view->getConfig('absoluteLinks', false)
+ $this->view->getConfig('absoluteLinks', false),
);
} catch (MissingRouteException $e) {
//This means that the JSON:API recommended route is missing. We need to try something else.
@@ -424,7 +425,7 @@ public function getRelationshipRelatedLink($resource, string $name): LinkInterfa
$url = Router::url(
$baseRoute + $keys,
- $this->view->getConfig('absoluteLinks', false)
+ $this->view->getConfig('absoluteLinks', false),
);
}
@@ -436,10 +437,10 @@ public function getRelationshipRelatedLink($resource, string $name): LinkInterfa
* Parses the name of an Entity class to build a lowercase plural
* controller name to be used in links.
*
- * @param \Cake\Datasource\RepositoryInterface $repository Repository
+ * @param \Cake\Datasource\RepositoryInterface $repository Repository
* @return array Array holding lowercase controller name as the value
*/
- protected function _getRepositoryRoutingParameters($repository)
+ protected function _getRepositoryRoutingParameters(RepositoryInterface $repository): array
{
$repositoryName = App::shortName(get_class($repository), 'Model/Table', 'Table');
[$pluginName, $controllerName] = pluginSplit($repositoryName);
diff --git a/src/View/JsonApiView.php b/src/View/JsonApiView.php
index c81d56ad..0e66bbc1 100644
--- a/src/View/JsonApiView.php
+++ b/src/View/JsonApiView.php
@@ -5,6 +5,7 @@
use Cake\Core\App;
use Cake\Core\Configure;
+use Cake\Core\InstanceConfigTrait;
use Cake\Event\EventManager;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
@@ -17,11 +18,16 @@
use Neomerx\JsonApi\Contracts\Schema\LinkInterface;
use Neomerx\JsonApi\Encoder\Encoder;
use Neomerx\JsonApi\Schema\Link;
+use RuntimeException;
+use function Cake\Core\pluginSplit;
class JsonApiView extends View
{
use InflectTrait;
+ # For BC, re-use trait since Cake 5.0.0 declares `getConfig()` protected in its `View::class`.
+ use InstanceConfigTrait;
+
/**
* Constructor
*
@@ -34,7 +40,7 @@ public function __construct(
?ServerRequest $request = null,
?Response $response = null,
?EventManager $eventManager = null,
- array $viewOptions = []
+ array $viewOptions = [],
) {
parent::__construct($request, $response, $eventManager, $viewOptions);
@@ -73,11 +79,11 @@ protected function _getSpecialVars(): array
* - with empty body
* - with body containing only the meta node
*
- * @param string|null $template Name of view file to use
- * @param string|false|null $layout Layout to use.
+ * @param string|null $template Name of view file to use
+ * @param string|false|null $layout Layout to use.
* @return string
*/
- public function render(?string $template = null, $layout = null): string
+ public function render(?string $template = null, string|false|null $layout = null): string
{
if ($this->getConfig('association')) {
$json = $this->_encodeWithIdentifiers();
@@ -130,8 +136,8 @@ function ($link) {
return new Link(false, $link, false);
},
- $links
- )
+ $links,
+ ),
);
}
@@ -141,7 +147,7 @@ function ($link) {
/**
* Generates a JSON API string without resource(s).
*
- * @return null|string
+ * @return string|null
*/
protected function _encodeWithoutSchemas(): ?string
{
@@ -260,7 +266,7 @@ protected function _encodeWithSchemas(): string
* 2. custom dynamic schema
* 3. Crud's dynamic schema
*
- * @param \Cake\ORM\Table[] $repositories List holding repositories used to map entities to schema classes
+ * @param array<\Cake\ORM\Table> $repositories List holding repositories used to map entities to schema classes
* @throws \Crud\Error\Exception\CrudException
* @return array A list with Entity class names as key holding NeoMerx Closure object
*/
@@ -278,7 +284,7 @@ protected function _entitiesToNeoMerxSchema(array $repositories): array
throw new CrudException(sprintf(
'Entity classes must not be the generic "%s" class for repository "%s"',
$entityClass,
- $repositoryName
+ $repositoryName,
));
}
@@ -313,7 +319,7 @@ protected function _entitiesToNeoMerxSchema(array $repositories): array
//Otherwise something is horribly wrong
if (!$schemaClass) {
- throw new \RuntimeException('No valid schema classes found');
+ throw new RuntimeException('No valid schema classes found');
}
// Uses NeoMerx createSchemaFromClosure()` to generate Closure
@@ -332,10 +338,10 @@ protected function _entitiesToNeoMerxSchema(array $repositories): array
/**
* Returns an array with NeoMerx Link objects to be used for pagination.
*
- * @param array $pagination ApiPaginationListener pagination response
+ * @param array $pagination ApiPaginationListener pagination response
* @return array
*/
- protected function _getPaginationLinks($pagination): array
+ protected function _getPaginationLinks(array $pagination): array
{
$links = [];
@@ -365,23 +371,23 @@ protected function _getPaginationLinks($pagination): array
/**
* Returns data to be serialized.
*
- * @param array|string|bool|object $serialize The name(s) of the view variable(s) that
+ * @param object|array|string|bool $serialize The name(s) of the view variable(s) that
* need(s) to be serialized. If true all available view variables will be used.
* @return mixed The data to serialize.
*/
- protected function _getDataToSerializeFromViewVars($serialize = true)
+ protected function _getDataToSerializeFromViewVars(array|string|bool|object $serialize = true): mixed
{
if (is_object($serialize)) {
throw new CrudException(
'Assigning an object to JsonApiListener "serialize" is deprecated, ' .
- 'assign the object to its own variable and assign "serialize" = true instead.'
+ 'assign the object to its own variable and assign "serialize" = true instead.',
);
}
if ($serialize === true) {
$viewVars = array_diff(
$this->getVars(),
- $this->_getSpecialVars()
+ $this->_getSpecialVars(),
);
if (empty($viewVars)) {
@@ -404,7 +410,7 @@ protected function _getDataToSerializeFromViewVars($serialize = true)
*
* @return int Flag holding json options
*/
- protected function _jsonOptions()
+ protected function _jsonOptions(): int
{
$jsonOptions = 0;
@@ -434,7 +440,7 @@ protected function _jsonOptions()
*
* @return void
*/
- protected function _inflectIncludesViewVar()
+ protected function _inflectIncludesViewVar(): void
{
$inflect = $this->getConfig('inflect');
$include = $this->getConfig('include');
diff --git a/tests/Fixture/CountriesFixture.php b/tests/Fixture/CountriesFixture.php
index 11a5da08..3c047dc9 100644
--- a/tests/Fixture/CountriesFixture.php
+++ b/tests/Fixture/CountriesFixture.php
@@ -5,18 +5,7 @@
class CountriesFixture extends TestFixture
{
- public $fields = [
- 'id' => ['type' => 'integer'],
- 'code' => ['type' => 'string', 'length' => 2, 'null' => false],
- 'name' => ['type' => 'string', 'length' => 255, 'null' => false],
- 'dummy_counter' => ['type' => 'integer'],
- 'currency_id' => ['type' => 'integer', 'null' => true],
- 'national_capital_id' => ['type' => 'integer', 'null' => true],
- 'supercountry_id' => ['type' => 'integer', 'null' => true],
- '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
- ];
-
- public $records = [
+ public array $records = [
['code' => 'NL', 'name' => 'The Netherlands', 'dummy_counter' => 11111, 'currency_id' => 1, 'national_capital_id' => 1],
['code' => 'BG', 'name' => 'Bulgaria', 'dummy_counter' => 22222, 'currency_id' => 1, 'national_capital_id' => 2],
['code' => 'IT', 'name' => 'Italy', 'dummy_counter' => 33333, 'currency_id' => 1, 'national_capital_id' => 4],
diff --git a/tests/Fixture/CountriesLanguagesFixture.php b/tests/Fixture/CountriesLanguagesFixture.php
index 3e80c03e..cde5e67f 100644
--- a/tests/Fixture/CountriesLanguagesFixture.php
+++ b/tests/Fixture/CountriesLanguagesFixture.php
@@ -5,14 +5,7 @@
class CountriesLanguagesFixture extends TestFixture
{
- public $fields = [
- 'id' => ['type' => 'integer'],
- 'country_id' => ['type' => 'integer', 'length' => 3, 'null' => false],
- 'language_id' => ['type' => 'integer', 'length' => 100, 'null' => false],
- '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
- ];
-
- public $records = [
+ public array $records = [
['country_id' => 1, 'language_id' => 1],
['country_id' => 1, 'language_id' => 2],
['country_id' => 2, 'language_id' => 4],
diff --git a/tests/Fixture/CulturesFixture.php b/tests/Fixture/CulturesFixture.php
index 4258d5e9..857f0ba2 100644
--- a/tests/Fixture/CulturesFixture.php
+++ b/tests/Fixture/CulturesFixture.php
@@ -5,16 +5,7 @@
class CulturesFixture extends TestFixture
{
- public $fields = [
- 'id' => ['type' => 'integer'],
- 'code' => ['type' => 'string', 'length' => 5, 'null' => false],
- 'name' => ['type' => 'string', 'length' => 100, 'null' => false],
- 'another_dummy_counter' => ['type' => 'integer'],
- 'country_id' => ['type' => 'integer', 'null' => false],
- '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
- ];
-
- public $records = [
+ public array $records = [
['code' => 'nl-NL', 'name' => 'Dutch', 'another_dummy_counter' => 11111, 'country_id' => 1],
['code' => 'bg-BG', 'name' => 'Bulgarian', 'another_dummy_counter' => 22222, 'country_id' => 2],
['code' => 'tr-BG', 'name' => 'Turkish (Bulgarian)', 'another_dummy_counter' => 22222, 'country_id' => 2],
diff --git a/tests/Fixture/CurrenciesFixture.php b/tests/Fixture/CurrenciesFixture.php
index 1f720cf5..dfbeaf54 100644
--- a/tests/Fixture/CurrenciesFixture.php
+++ b/tests/Fixture/CurrenciesFixture.php
@@ -5,14 +5,7 @@
class CurrenciesFixture extends TestFixture
{
- public $fields = [
- 'id' => ['type' => 'integer'],
- 'code' => ['type' => 'string', 'length' => 3, 'null' => false],
- 'name' => ['type' => 'string', 'length' => 100, 'null' => false],
- '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
- ];
-
- public $records = [
+ public array $records = [
['code' => 'EUR', 'name' => 'Euro'],
['code' => 'USD', 'name' => 'US Dollar'],
];
diff --git a/tests/Fixture/JsonApiResponseBodies/Errors/404-error-for-collection-in-debug-mode.json b/tests/Fixture/JsonApiResponseBodies/Errors/404-error-for-collection-in-debug-mode.json
index 3bbe68bf..35a306fa 100644
--- a/tests/Fixture/JsonApiResponseBodies/Errors/404-error-for-collection-in-debug-mode.json
+++ b/tests/Fixture/JsonApiResponseBodies/Errors/404-error-for-collection-in-debug-mode.json
@@ -3,7 +3,7 @@
{
"status": "404",
"title": "Not Found",
- "detail": "A route matching \"\/nonexistents\" could not be found."
+ "detail": "A route matching `\/nonexistents` could not be found."
}
],
"debug": {}
diff --git a/tests/Fixture/LanguagesFixture.php b/tests/Fixture/LanguagesFixture.php
index d3a72b4e..38806110 100644
--- a/tests/Fixture/LanguagesFixture.php
+++ b/tests/Fixture/LanguagesFixture.php
@@ -5,14 +5,7 @@
class LanguagesFixture extends TestFixture
{
- public $fields = [
- 'id' => ['type' => 'integer'],
- 'code' => ['type' => 'string', 'length' => 2, 'null' => false],
- 'name' => ['type' => 'string', 'length' => 100, 'null' => false],
- '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
- ];
-
- public $records = [
+ public array $records = [
['id' => 1, 'code' => 'en', 'name' => 'English'],
['id' => 2, 'code' => 'nl', 'name' => 'Dutch'],
['id' => 3, 'code' => 'it', 'name' => 'Italian'],
diff --git a/tests/Fixture/NationalCapitalsFixture.php b/tests/Fixture/NationalCapitalsFixture.php
index 98549c15..27d34868 100644
--- a/tests/Fixture/NationalCapitalsFixture.php
+++ b/tests/Fixture/NationalCapitalsFixture.php
@@ -5,14 +5,7 @@
class NationalCapitalsFixture extends TestFixture
{
- public $fields = [
- 'id' => ['type' => 'integer'],
- 'name' => ['type' => 'string', 'length' => 100, 'null' => false],
- 'description' => ['type' => 'string', 'length' => 255, 'null' => false],
- '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
- ];
-
- public $records = [
+ public array $records = [
['name' => 'Amsterdam', 'description' => 'National capital of the Netherlands'],
['name' => 'Sofia', 'description' => 'National capital of Bulgaria'],
['name' => 'Wellington', 'description' => 'National capital of New Zealand'],
diff --git a/tests/Fixture/NationalCitiesFixture.php b/tests/Fixture/NationalCitiesFixture.php
index f476fb68..29bfc9ef 100644
--- a/tests/Fixture/NationalCitiesFixture.php
+++ b/tests/Fixture/NationalCitiesFixture.php
@@ -5,14 +5,7 @@
class NationalCitiesFixture extends TestFixture
{
- public $fields = [
- 'id' => ['type' => 'integer'],
- 'name' => ['type' => 'string', 'length' => 100, 'null' => false],
- 'country_id' => ['type' => 'integer', 'null' => false],
- '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
- ];
-
- public $records = [
+ public array $records = [
['name' => 'Amsterdam', 'country_id' => 1],
['name' => 'Rotterdam', 'country_id' => 1],
['name' => 'Sofia', 'country_id' => 2],
diff --git a/tests/Fixture/schema.php b/tests/Fixture/schema.php
new file mode 100644
index 00000000..2cb3b8a9
--- /dev/null
+++ b/tests/Fixture/schema.php
@@ -0,0 +1,85 @@
+ [
+ 'columns' => [
+ 'id' => ['type' => 'integer'],
+ 'code' => ['type' => 'string', 'length' => 2, 'null' => false],
+ 'name' => ['type' => 'string', 'length' => 255, 'null' => false],
+ 'dummy_counter' => ['type' => 'integer'],
+ 'currency_id' => ['type' => 'integer', 'null' => true],
+ 'national_capital_id' => ['type' => 'integer', 'null' => true],
+ 'supercountry_id' => ['type' => 'integer', 'null' => true],
+ ],
+ 'constraints' => [
+ 'primary' => ['type' => 'primary', 'columns' => ['id']],
+ ],
+ ],
+ 'countries_languages' => [
+ 'columns' => [
+ 'id' => ['type' => 'integer'],
+ 'country_id' => ['type' => 'integer', 'length' => 3, 'null' => false],
+ 'language_id' => ['type' => 'integer', 'length' => 100, 'null' => false],
+ ],
+ 'constraints' => [
+ 'primary' => ['type' => 'primary', 'columns' => ['id']],
+ ],
+ ],
+ 'cultures' => [
+ 'columns' => [
+ 'id' => ['type' => 'integer'],
+ 'code' => ['type' => 'string', 'length' => 5, 'null' => false],
+ 'name' => ['type' => 'string', 'length' => 100, 'null' => false],
+ 'another_dummy_counter' => ['type' => 'integer'],
+ 'country_id' => ['type' => 'integer', 'null' => false],
+ ],
+ 'constraints' => [
+ 'primary' => ['type' => 'primary', 'columns' => ['id']],
+ ],
+ ],
+ 'currencies' => [
+ 'columns' => [
+ 'id' => ['type' => 'integer'],
+ 'code' => ['type' => 'string', 'length' => 3, 'null' => false],
+ 'name' => ['type' => 'string', 'length' => 100, 'null' => false],
+ ],
+ 'constraints' => [
+ 'primary' => ['type' => 'primary', 'columns' => ['id']],
+ ],
+ ],
+ 'languages' => [
+ 'columns' => [
+ 'id' => ['type' => 'integer'],
+ 'code' => ['type' => 'string', 'length' => 2, 'null' => false],
+ 'name' => ['type' => 'string', 'length' => 100, 'null' => false],
+ ],
+ 'constraints' => [
+ 'primary' => ['type' => 'primary', 'columns' => ['id']],
+ ],
+ ],
+ 'national_capitals' => [
+ 'columns' => [
+ 'id' => ['type' => 'integer'],
+ 'name' => ['type' => 'string', 'length' => 100, 'null' => false],
+ 'description' => ['type' => 'string', 'length' => 255, 'null' => false],
+ ],
+ 'constraints' => [
+ 'primary' => ['type' => 'primary', 'columns' => ['id']],
+ ],
+ ],
+ 'national_cities' => [
+ 'columns' => [
+ 'id' => ['type' => 'integer'],
+ 'name' => ['type' => 'string', 'length' => 100, 'null' => false],
+ 'country_id' => ['type' => 'integer', 'null' => false],
+ ],
+ 'constraints' => [
+ 'primary' => ['type' => 'primary', 'columns' => ['id']],
+ ],
+ ],
+];
diff --git a/tests/TestCase/Error/JsonApiExceptionRendererTest.php b/tests/TestCase/Error/JsonApiExceptionRendererTest.php
index 4bb6aecb..84ce22a1 100644
--- a/tests/TestCase/Error/JsonApiExceptionRendererTest.php
+++ b/tests/TestCase/Error/JsonApiExceptionRendererTest.php
@@ -5,15 +5,15 @@
use Cake\Controller\Controller;
use Cake\Core\Configure;
-use Cake\Core\Exception\Exception;
+use Cake\Core\Exception\CakeException;
use Cake\Core\Plugin;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
-use Cake\ORM\TableRegistry;
use Crud\Error\Exception\ValidationException;
use Crud\TestSuite\TestCase;
use CrudJsonApi\Error\JsonApiExceptionRenderer;
use Neomerx\JsonApi\Schema\ErrorCollection;
+use PHPUnit\Framework\MockObject\MockObject;
class JsonApiExceptionRendererTest extends TestCase
{
@@ -22,14 +22,14 @@ class JsonApiExceptionRendererTest extends TestCase
*
* @var
*/
- protected $_JsonApiResponseBodyFixtures;
+ protected string $_JsonApiResponseBodyFixtures;
/**
* fixtures property
*
* @var array
*/
- public $fixtures = [
+ public array $fixtures = [
'plugin.CrudJsonApi.Countries',
];
@@ -52,10 +52,11 @@ public function setUp(): void
*/
public function testRenderWithNonValidationError()
{
- $exception = new Exception('Hello World');
+ $exception = new CakeException('Hello World');
$controller = $this->getMockBuilder(Controller::class)
->onlyMethods(['render'])
+ ->setConstructorArgs([new ServerRequest()])
->getMock();
$controller->setRequest(new ServerRequest([
'environment' => [
@@ -102,7 +103,7 @@ public function testRenderWithNonValidationError()
*/
public function testRenderWithValidationError()
{
- $countries = TableRegistry::get('Countries');
+ $countries = $this->fetchTable('Countries');
$invalidCountry = $countries->newEntity([
'code' => 'not-all-uppercase',
@@ -111,17 +112,22 @@ public function testRenderWithValidationError()
$exception = new ValidationException($invalidCountry);
$controller = $this->getMockBuilder('Cake\Controller\Controller')
- ->setMethods(['render'])
+ ->onlyMethods(['render'])
+ ->setConstructorArgs([new ServerRequest()])
->getMock();
- $controller->request = new ServerRequest([
+
+ $request = new ServerRequest([
'environment' => [
'HTTP_ACCEPT' => 'application/vnd.api+json',
],
]);
- $controller->response = new Response();
+
+ $controller
+ ->setRequest($request)
+ ->setResponse(new Response());
$renderer = $this->getMockBuilder('CrudJsonApi\Error\JsonApiExceptionRenderer')
- ->setMethods(['_getController'])
+ ->onlyMethods(['_getController'])
->disableOriginalConstructor()
->getMock();
$renderer
@@ -151,49 +157,68 @@ public function testRenderWithValidationError()
*/
public function testValidationExceptionsFallBackToStatusCode422()
{
- $countries = TableRegistry::get('Countries');
+ $countries = $this->fetchTable('Countries');
$invalidCountry = $countries->newEntity([]);
$exception = new ValidationException($invalidCountry);
$controller = $this->getMockBuilder('Cake\Controller\Controller')
- ->setMethods(['render'])
+ ->onlyMethods(['render'])
+ ->setConstructorArgs([new ServerRequest()])
->getMock();
- $controller->request = new ServerRequest([
+ $request = new ServerRequest([
'environment' => [
'HTTP_ACCEPT' => 'application/vnd.api+json',
],
]);
- $res = new Response();
+ $controller->setRequest($request);
$response = $this->getMockBuilder('Cake\Http\Response')
- ->setMethods(['withStatus'])
+ ->onlyMethods(['withStatus', 'getStatusCode', 'withType', 'withBody'])
->getMock();
$response
- ->expects($this->at(0))
+ ->expects($this->exactly(2))
->method('withStatus')
- ->will($this->throwException(new Exception('woot')));
+ ->willReturnCallback(function () use (&$callCount, $response): MockObject {
+ $callCount++;
+
+ // First call should throw an exception.
+ if ($callCount === 1) {
+ throw new CakeException('woot');
+ }
+
+ // Second call should succeed and return response with status.
+ return $response;
+ });
+
+ // Mock getStatusCode to return 422 after the successful withStatus call.
$response
- ->expects($this->at(1))
- ->method('withStatus')
- ->will($this->returnCallback(function ($input) use ($res) {
- return $res->withStatus($input);
- }));
+ ->method('getStatusCode')
+ ->willReturn(422);
+
+ // Mock the other methods that are called in the validation method.
+ $response
+ ->method('withType')
+ ->willReturn($response);
+
+ $response
+ ->method('withBody')
+ ->willReturn($response);
- $controller->response = $response;
+ $controller->setResponse($response);
$renderer = $this->getMockBuilder('CrudJsonApi\Error\JsonApiExceptionRenderer')
- ->setMethods(['_getController'])
+ ->onlyMethods(['_getController'])
->disableOriginalConstructor()
->getMock();
$renderer
->expects($this->once())
->method('_getController')
->with()
- ->will($this->returnValue($controller));
+ ->willReturn($controller);
$renderer->__construct($exception);
$result = $renderer->render();
@@ -227,7 +252,7 @@ public function testStandardizeValidationErrors()
];
$renderer = $this->getMockBuilder('CrudJsonApi\Error\JsonApiExceptionRenderer')
- ->setMethods(null)
+ ->onlyMethods([])
->disableOriginalConstructor()
->getMock();
@@ -271,7 +296,7 @@ public function testStandardizeValidationErrors()
public function testGetNeoMerxErrorCollection()
{
$renderer = $this->getMockBuilder('CrudJsonApi\Error\JsonApiExceptionRenderer')
- ->setMethods(null)
+ ->onlyMethods([])
->disableOriginalConstructor()
->getMock();
@@ -334,19 +359,14 @@ public function testAddQueryLogs()
->disableOriginalConstructor()
->getMock();
$apiQueryLogListener
- ->expects($this->at(0))
- ->method('getQueryLogs')
- ->with()
- ->willReturn([]);
- $apiQueryLogListener
- ->expects($this->at(1))
+ ->expects($this->exactly(2))
->method('getQueryLogs')
->with()
- ->willReturn(
- [
- 'dummy' => 'log-entry',
- ]
- );
+ ->willReturnCallback(function () use (&$callCount): array {
+ $callCount++;
+
+ return $callCount === 1 ? [] : ['dummy' => 'log-entry'];
+ });
$renderer = $this->getMockBuilder('CrudJsonApi\Error\JsonApiExceptionRenderer')
->onlyMethods(['_getApiQueryLogListenerObject'])
diff --git a/tests/TestCase/Integration/JsonApi/CreatingResourcesIntegrationTest.php b/tests/TestCase/Integration/JsonApi/CreatingResourcesIntegrationTest.php
index 63525316..d8e0b407 100644
--- a/tests/TestCase/Integration/JsonApi/CreatingResourcesIntegrationTest.php
+++ b/tests/TestCase/Integration/JsonApi/CreatingResourcesIntegrationTest.php
@@ -27,8 +27,8 @@ public function testSidePostingException()
$this->post(
'/countries',
$this->_getJsonApiRequestBody(
- 'CreatingResources' . DS . 'create-country-throw-side-posting-exception.json'
- )
+ 'CreatingResources' . DS . 'create-country-throw-side-posting-exception.json',
+ ),
);
$this->assertResponseCode(400); // bad request
$responseBodyArray = json_decode((string)$this->_response->getBody(), true);
@@ -52,7 +52,7 @@ public function testSidePostingException()
*
* @return array
*/
- public function createResourceProvider()
+ public static function createResourceProvider(): array
{
return [
'create-single-word-resource-no-relationships' => [
diff --git a/tests/TestCase/Integration/JsonApi/FetchingCollectionsIntegrationTest.php b/tests/TestCase/Integration/JsonApi/FetchingCollectionsIntegrationTest.php
index 5d722b36..bcc55d61 100644
--- a/tests/TestCase/Integration/JsonApi/FetchingCollectionsIntegrationTest.php
+++ b/tests/TestCase/Integration/JsonApi/FetchingCollectionsIntegrationTest.php
@@ -12,7 +12,7 @@ class FetchingCollectionsIntegrationTest extends JsonApiBaseTestCase
*
* @return array
*/
- public function getProvider()
+ public static function getProvider(): array
{
return [
# Test fetching a single-word collection.
diff --git a/tests/TestCase/Integration/JsonApi/FetchingResourcesIntegrationTest.php b/tests/TestCase/Integration/JsonApi/FetchingResourcesIntegrationTest.php
index 46fb991c..852be0bc 100644
--- a/tests/TestCase/Integration/JsonApi/FetchingResourcesIntegrationTest.php
+++ b/tests/TestCase/Integration/JsonApi/FetchingResourcesIntegrationTest.php
@@ -13,7 +13,7 @@ class FetchingResourcesIntegrationTest extends JsonApiBaseTestCase
*
* @return array
*/
- public function fetchResourceProvider()
+ public static function fetchResourceProvider(): array
{
return [
'fetch-single-word-resource-with-no-relationships' => [
@@ -67,7 +67,7 @@ public function testFetchResource($url, $expectedResponseFile)
*
* @return array
*/
- public function fetchNestedResourceProvider()
+ public static function fetchNestedResourceProvider(): array
{
return [
'fetch-one-to-many-relation' => [
@@ -100,7 +100,7 @@ public function testFetchNestedResource($url, $expectedResponseFile)
'headers' => [
'Accept' => 'application/vnd.api+json',
],
- ]
+ ],
);
# execute the GET request
diff --git a/tests/TestCase/Integration/JsonApi/FilteringIntegrationTest.php b/tests/TestCase/Integration/JsonApi/FilteringIntegrationTest.php
index bb4f125b..d982f308 100644
--- a/tests/TestCase/Integration/JsonApi/FilteringIntegrationTest.php
+++ b/tests/TestCase/Integration/JsonApi/FilteringIntegrationTest.php
@@ -10,7 +10,7 @@ class FilteringIntegrationTest extends JsonApiBaseTestCase
/**
* @return array
*/
- public function filterProvider()
+ public static function filterProvider(): array
{
return [
// assert single-field searches (case sensitive for now or
diff --git a/tests/TestCase/Integration/JsonApi/InclusionIntegrationTest.php b/tests/TestCase/Integration/JsonApi/InclusionIntegrationTest.php
index 2ef789ef..55cf91fc 100644
--- a/tests/TestCase/Integration/JsonApi/InclusionIntegrationTest.php
+++ b/tests/TestCase/Integration/JsonApi/InclusionIntegrationTest.php
@@ -12,7 +12,7 @@ class InclusionIntegrationTest extends JsonApiBaseTestCase
/**
* @return array
*/
- public function inclusionProvider()
+ public static function inclusionProvider(): array
{
return [
// assert single-word associations
diff --git a/tests/TestCase/Integration/JsonApi/RelationshipsIntegrationTest.php b/tests/TestCase/Integration/JsonApi/RelationshipsIntegrationTest.php
index b60219f3..f4205e27 100644
--- a/tests/TestCase/Integration/JsonApi/RelationshipsIntegrationTest.php
+++ b/tests/TestCase/Integration/JsonApi/RelationshipsIntegrationTest.php
@@ -13,7 +13,7 @@ class RelationshipsIntegrationTest extends JsonApiBaseTestCase
/**
* @return array
*/
- public function getProvider()
+ public static function getProvider(): array
{
return [
// 'one-to-many: get cultures for country' => [
@@ -54,7 +54,7 @@ public function testGet($url, $expectedFile): void
/**
* @return array
*/
- public function postProvider()
+ public static function postProvider(): array
{
return [
'one-to-many: add culture relationship for country' => [
@@ -91,7 +91,7 @@ public function testPost($url, $requestBodyFile, $expectedResponseFile)
'Content-Type' => 'application/vnd.api+json',
],
'input' => $this->_getJsonApiRequestBody('Relationships' . DS . $requestBodyFile),
- ]
+ ],
);
// execute the POST request
@@ -107,7 +107,7 @@ public function testPost($url, $requestBodyFile, $expectedResponseFile)
/**
* @return \string[][]
*/
- public function toOne()
+ public static function toOne(): array
{
return [
'POST' => ['post'],
@@ -134,9 +134,9 @@ public function testNoToOne($method)
'type' => 'currencies',
'id' => 1,
],
- ]
+ ],
),
- ]
+ ],
);
// execute the POST request
@@ -151,7 +151,7 @@ public function testNoToOne($method)
/**
* @return \string[][]
*/
- public function missingRecordProvider()
+ public static function missingRecordProvider(): array
{
return [
'POST' => ['post'],
@@ -183,9 +183,9 @@ public function testMissingRecords($method)
'id' => 10,
],
],
- ]
+ ],
),
- ]
+ ],
);
// execute the POST request
@@ -201,7 +201,7 @@ public function testMissingRecords($method)
/**
* @return array
*/
- public function patchProvider()
+ public static function patchProvider(): array
{
return [
'one-to-many: replace culture relationship for country' => [
@@ -243,7 +243,7 @@ public function testPatch($url, $requestBodyFile, $expectedResponseFile)
'Content-Type' => 'application/vnd.api+json',
],
'input' => $this->_getJsonApiRequestBody('Relationships' . DS . $requestBodyFile),
- ]
+ ],
);
$this->disableErrorHandlerMiddleware();
@@ -260,7 +260,7 @@ public function testPatch($url, $requestBodyFile, $expectedResponseFile)
/**
* @return array
*/
- public function deleteProvider()
+ public static function deleteProvider(): array
{
return [
'one-to-many: delete culture relationship for country' => [
@@ -297,7 +297,7 @@ public function testDelete($url, $requestBodyFile, $expectedResponseFile)
'Content-Type' => 'application/vnd.api+json',
],
'input' => $this->_getJsonApiRequestBody('Relationships' . DS . $requestBodyFile),
- ]
+ ],
);
// execute the DELETE request
diff --git a/tests/TestCase/Integration/JsonApi/SelfReferencedAssociationIntegrationTest.php b/tests/TestCase/Integration/JsonApi/SelfReferencedAssociationIntegrationTest.php
index f8ccedaf..46c5eca5 100644
--- a/tests/TestCase/Integration/JsonApi/SelfReferencedAssociationIntegrationTest.php
+++ b/tests/TestCase/Integration/JsonApi/SelfReferencedAssociationIntegrationTest.php
@@ -10,7 +10,7 @@ class SelfReferencedAssociationIntegrationTest extends JsonApiBaseTestCase
/**
* @return array
*/
- public function viewProvider()
+ public static function viewProvider(): array
{
return [
'get supercountry with subcountries' => [
diff --git a/tests/TestCase/Integration/JsonApi/SortingIntegrationTest.php b/tests/TestCase/Integration/JsonApi/SortingIntegrationTest.php
index 12a8e507..037979d7 100644
--- a/tests/TestCase/Integration/JsonApi/SortingIntegrationTest.php
+++ b/tests/TestCase/Integration/JsonApi/SortingIntegrationTest.php
@@ -11,7 +11,7 @@ class SortingIntegrationTest extends JsonApiBaseTestCase
/**
* @return array
*/
- public function sortProvider()
+ public static function sortProvider(): array
{
return [
'unsorted' => [
@@ -92,7 +92,7 @@ public function sortProvider()
/**
* @return array
*/
- public function paginationUrlProvider()
+ public static function paginationUrlProvider(): array
{
return [
'pagination' => [
diff --git a/tests/TestCase/Integration/JsonApi/SparseFieldsetsIntegrationTest.php b/tests/TestCase/Integration/JsonApi/SparseFieldsetsIntegrationTest.php
index 7de306d0..596aff51 100644
--- a/tests/TestCase/Integration/JsonApi/SparseFieldsetsIntegrationTest.php
+++ b/tests/TestCase/Integration/JsonApi/SparseFieldsetsIntegrationTest.php
@@ -11,7 +11,7 @@ class SparseFieldsetsIntegrationTest extends JsonApiBaseTestCase
/**
* @return array
*/
- public function viewProvider()
+ public static function viewProvider(): array
{
return [
// assert "single-field" sparse for index actions
@@ -101,7 +101,7 @@ public function viewProvider()
/**
* @return array
*/
- public function paginationUrlProvider()
+ public static function paginationUrlProvider(): array
{
return [
'pagination' => [
diff --git a/tests/TestCase/Integration/JsonApi/UpdatingResourcesIntegrationTest.php b/tests/TestCase/Integration/JsonApi/UpdatingResourcesIntegrationTest.php
index 6a3328a8..e8d08ace 100644
--- a/tests/TestCase/Integration/JsonApi/UpdatingResourcesIntegrationTest.php
+++ b/tests/TestCase/Integration/JsonApi/UpdatingResourcesIntegrationTest.php
@@ -3,7 +3,6 @@
namespace CrudJsonApi\Test\TestCase\Integration\JsonApi;
-use Cake\ORM\TableRegistry;
use Cake\Utility\Inflector;
use CrudJsonApi\Test\TestCase\Integration\JsonApiBaseTestCase;
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
@@ -20,7 +19,7 @@ class UpdatingResourcesIntegrationTest extends JsonApiBaseTestCase
*
* @return array
*/
- public function updateResourceProvider()
+ public static function updateResourceProvider(): array
{
return [
# changing USD to RUB
@@ -179,7 +178,7 @@ public function testUpdateResource($url, $requestBodyFile, $expectedResponseFile
$recordId = $matches[2];
# assert the database record got updated like expected
- $table = TableRegistry::get($tableName);
+ $table = $this->fetchTable($tableName);
$record = $table->get($recordId)->toArray();
$this->assertArraySubset($expectedRecordSubset, $record);
diff --git a/tests/TestCase/Integration/JsonApiBaseTestCase.php b/tests/TestCase/Integration/JsonApiBaseTestCase.php
index 151ea8d2..06e9ea23 100644
--- a/tests/TestCase/Integration/JsonApiBaseTestCase.php
+++ b/tests/TestCase/Integration/JsonApiBaseTestCase.php
@@ -37,7 +37,7 @@ abstract class JsonApiBaseTestCase extends TestCase
*
* @var array
*/
- public $fixtures = [
+ public array $fixtures = [
'plugin.CrudJsonApi.Countries',
'plugin.CrudJsonApi.Currencies',
'plugin.CrudJsonApi.Cultures',
diff --git a/tests/TestCase/Listener/JsonApi/DocumentValidatorTest.php b/tests/TestCase/Listener/JsonApi/DocumentValidatorTest.php
index ab0acf14..10d84322 100644
--- a/tests/TestCase/Listener/JsonApi/DocumentValidatorTest.php
+++ b/tests/TestCase/Listener/JsonApi/DocumentValidatorTest.php
@@ -580,9 +580,9 @@ public function testGetPathObject()
$obj = $this->callProtectedMethod('_getPathObject', ['data'], $this->_validator);
$this->assertTrue(is_a($obj, 'stdClass'));
- $this->assertObjectHasAttribute('dotted', $obj);
- $this->assertObjectHasAttribute('toKey', $obj);
- $this->assertObjectHasAttribute('key', $obj);
+ $this->assertObjectHasProperty('dotted', $obj);
+ $this->assertObjectHasProperty('toKey', $obj);
+ $this->assertObjectHasProperty('key', $obj);
// assert single-level path
$this->assertEquals('data', $obj->dotted);
diff --git a/tests/TestCase/Listener/JsonApiListenerTest.php b/tests/TestCase/Listener/JsonApiListenerTest.php
index 382f4387..9f263a72 100644
--- a/tests/TestCase/Listener/JsonApiListenerTest.php
+++ b/tests/TestCase/Listener/JsonApiListenerTest.php
@@ -7,7 +7,6 @@
use Cake\Core\Plugin;
use Cake\Datasource\ResultSetDecorator;
use Cake\Event\Event;
-use Cake\Filesystem\File;
use Cake\Http\Exception\BadRequestException;
use Cake\Http\Exception\MethodNotAllowedException;
use Cake\Http\Response;
@@ -15,13 +14,13 @@
use Cake\ORM\Entity;
use Cake\ORM\Query;
use Cake\ORM\ResultSet;
-use Cake\ORM\TableRegistry;
use Cake\Routing\RouteBuilder;
use Cake\Routing\Router;
use Crud\Event\Subject;
use Crud\TestSuite\TestCase;
use CrudJsonApi\Listener\JsonApiListener;
use CrudJsonApi\Test\App\Model\Entity\Country;
+use function file_get_contents;
/**
* Licensed under The MIT License
@@ -41,7 +40,7 @@ class JsonApiListenerTest extends TestCase
*
* @var array
*/
- public $fixtures = [
+ public array $fixtures = [
'plugin.CrudJsonApi.Countries',
'plugin.CrudJsonApi.Cultures',
'plugin.CrudJsonApi.Currencies',
@@ -60,7 +59,7 @@ public function setUp(): void
'/',
static function (RouteBuilder $routeBuilder) {
$routeBuilder->fallbacks();
- }
+ },
);
$this->_JsonApiDecoderFixtures = Plugin::path('CrudJsonApi') . 'tests' . DS . 'Fixture' . DS . 'JsonApiDecoder';
@@ -71,7 +70,7 @@ static function (RouteBuilder $routeBuilder) {
*/
public function testDefaultConfig()
{
- $listener = new JsonApiListener(new Controller());
+ $listener = new JsonApiListener(new Controller(new ServerRequest()));
$expected = [
'detectors' => [
@@ -120,14 +119,14 @@ public function testImplementedEvents()
->getMock();
$listener
- ->expects($this->at(1))
+ ->expects($this->exactly(2))
->method('_checkRequestType')
- ->willReturn(false); // for asserting missing JSON API Accept header
+ ->willReturnCallback(function () use (&$callCount): bool {
+ $callCount++;
- $listener
- ->expects($this->at(3))
- ->method('_checkRequestType')
- ->willReturn(true); // for asserting valid JSON API Accept header
+ // First call asserts missing JSON API Accept header; second call asserts valid JSON API Accept header.
+ return $callCount === 2;
+ });
// assert that listener does nothing if JSON API Accept header is missing
$result = $listener->implementedEvents();
@@ -212,6 +211,7 @@ public function testAfterSave()
$controller = $this
->getMockBuilder(Controller::class)
->onlyMethods([])
+ ->setConstructorArgs([new ServerRequest()])
->getMock();
$response = $this
@@ -252,16 +252,18 @@ public function testAfterSave()
// assert nothing happens if `success` is false
$event->getSubject()->success = false;
- $this->assertFalse($this->callProtectedMethod('afterSave', [$event], $listener));
+ $this->callProtectedMethod('afterSave', [$event], $listener);
+ $this->assertFalse($event->getResult());
// assert nothing happens if `success` is true but both `created` and `id` are false
$event->getSubject()->success = true;
$event->getSubject()->created = false;
$event->getSubject()->id = false;
- $this->assertFalse($this->callProtectedMethod('afterSave', [$event], $listener));
+ $this->callProtectedMethod('afterSave', [$event], $listener);
+ $this->assertFalse($event->getResult());
// assert success
- $table = TableRegistry::get('Countries');
+ $table = $this->fetchTable('Countries');
$entity = $table->find()->first();
$subject->entity = $entity;
@@ -301,7 +303,7 @@ public function testAfterDelete()
->onlyMethods([])
->getMock();
- $controller->response = $response;
+ $controller->setResponse($response);
$listener
->method('_response')
@@ -331,7 +333,8 @@ public function testAfterDelete()
// assert nothing happens if `success` is false
$event->getSubject()->success = false;
- $this->assertFalse($this->callProtectedMethod('afterDelete', [$event], $listener));
+ $this->callProtectedMethod('afterDelete', [$event], $listener);
+ $this->assertFalse($event->getResult());
$event->getSubject()->success = true;
$this->assertNull($this->callProtectedMethod('afterDelete', [$event], $listener));
@@ -362,7 +365,7 @@ public function testRenderWithResources()
->getMockBuilder(Controller::class)
->onlyMethods([])
->enableOriginalConstructor()
- ->setConstructorArgs([null, null, 'Countries'])
+ ->setConstructorArgs([new ServerRequest(), 'Countries'])
->getMock();
$listener = $this
@@ -395,6 +398,7 @@ public function testRenderWithoutResources()
->getMockBuilder(Controller::class)
->onlyMethods([])
->enableOriginalConstructor()
+ ->setConstructorArgs([new ServerRequest()])
->getMock();
$listener = $this
@@ -695,8 +699,7 @@ public function testCheckRequestMethodsSuccess()
{
$request = new ServerRequest();
$request = $request->withEnv('HTTP_ACCEPT', 'application/vnd.api+json');
- $response = new Response();
- $controller = new Controller($request, $response);
+ $controller = new Controller($request);
$listener = new JsonApiListener($controller);
$listener->setupDetectors();
@@ -706,8 +709,7 @@ public function testCheckRequestMethodsSuccess()
$request = new ServerRequest();
$request = $request->withEnv('HTTP_ACCEPT', 'application/vnd.api+json')
->withEnv('CONTENT_TYPE', 'application/vnd.api+json');
- $response = new Response();
- $controller = new Controller($request, $response);
+ $controller = new Controller($request);
$listener = new JsonApiListener($controller);
$listener->setupDetectors();
@@ -726,8 +728,7 @@ public function testCheckRequestMethodsFailContentHeader()
$request = new ServerRequest();
$request = $request->withEnv('HTTP_ACCEPT', 'application/vnd.api+json')
->withEnv('CONTENT_TYPE', 'application/json');
- $response = new Response();
- $controller = new Controller($request, $response);
+ $controller = new Controller($request);
$listener = new JsonApiListener($controller);
$listener->setupDetectors();
@@ -746,8 +747,7 @@ public function testCheckRequestMethodsFailOnPutMethod()
$request = new ServerRequest();
$request = $request->withEnv('HTTP_ACCEPT', 'application/vnd.api+json')
->withEnv('REQUEST_METHOD', 'PUT');
- $response = new Response();
- $controller = new Controller($request, $response);
+ $controller = new Controller($request);
$listener = new JsonApiListener($controller);
$listener->setupDetectors();
@@ -793,6 +793,7 @@ public function testGetSingleEntity()
->getMockBuilder(Controller::class)
->onlyMethods([])
->enableOriginalConstructor()
+ ->setConstructorArgs([new ServerRequest()])
->getMock();
$listener = $this
@@ -851,6 +852,7 @@ public function testGetSingleEntityForEmptyResultSet()
->getMockBuilder(Controller::class)
->onlyMethods([])
->enableOriginalConstructor()
+ ->setConstructorArgs([new ServerRequest()])
->getMock();
$listener = $this
@@ -864,8 +866,6 @@ public function testGetSingleEntityForEmptyResultSet()
->method('_controller')
->willReturn($controller);
- $entity = new Entity();
-
$subject = $this
->getMockBuilder(Subject::class)
->getMock();
@@ -888,7 +888,7 @@ public function testGetSingleEntityForEmptyResultSet()
$subject->query = $query;
$subject->query
->method('getRepository')
- ->willReturn(TableRegistry::get('Countries'));
+ ->willReturn($this->fetchTable('Countries'));
$this->setReflectionClassInstance($listener);
$result = $this->callProtectedMethod('_getSingleEntity', [$subject], $listener);
@@ -905,9 +905,7 @@ public function testGetSingleEntityForEmptyResultSet()
*/
public function testGetContainedAssociations()
{
- $table = TableRegistry::get('Countries');
- $table->belongsTo('Currencies');
- $table->hasMany('Cultures');
+ $table = $this->fetchTable('Countries');
// make sure expected associations are there
$associationsBefore = $table->associations();
@@ -924,7 +922,7 @@ public function testGetContainedAssociations()
$this->assertNull($entity->cultures);
// make sure cultures are removed from AssociationCollection
- $listener = new JsonApiListener(new Controller());
+ $listener = new JsonApiListener(new Controller(new ServerRequest()));
$this->setReflectionClassInstance($listener);
$associationsAfter = $this->callProtectedMethod('_getContainedAssociations', [$table, $query->getContain()], $listener);
@@ -940,21 +938,7 @@ public function testGetContainedAssociations()
*/
public function testGetRepositoryList()
{
- $table = TableRegistry::get('Countries');
- $table->belongsTo('Currencies');
- $table->belongsTo('NationalCapitals');
- $table->hasMany('Cultures');
- $table->hasMany('NationalCities');
-
- $table->hasMany('SubCountries', [
- 'className' => 'Countries',
- 'propertyName' => 'subcountry',
- ]);
-
- $table->belongsTo('SuperCountries', [
- 'className' => 'Countries',
- 'propertyName' => 'supercountry',
- ]);
+ $table = $this->fetchTable('Countries');
$associations = [];
foreach ($table->associations() as $association) {
@@ -973,7 +957,7 @@ public function testGetRepositoryList()
$this->assertArrayHasKey('currencies', $associations);
$this->assertArrayHasKey('cultures', $associations);
- $listener = new JsonApiListener(new Controller());
+ $listener = new JsonApiListener(new Controller(new ServerRequest()));
$this->setReflectionClassInstance($listener);
$result = $this->callProtectedMethod('_getRepositoryList', [$table, $associations], $listener);
@@ -1011,7 +995,7 @@ public function testGetIncludeList()
// hasMany relations (if listener config option `include` is not set)
$this->assertEmpty($listener->getConfig('include'));
- $table = TableRegistry::get('Countries');
+ $table = $this->fetchTable('Countries');
$associations = [];
foreach ($table->associations() as $association) {
$associations[strtolower($association->getName())] = [
@@ -1076,7 +1060,7 @@ public function testCheckRequestData()
$this->expectException(BadRequestException::class);
$this->expectExceptionMessage(
'Missing request data required for POST and PATCH methods, as well as DELETE methods to relationship endpoints. ' .
- 'Make sure that you are sending a request body and that it is valid JSON.'
+ 'Make sure that you are sending a request body and that it is valid JSON.',
);
$controller = $this
->getMockBuilder(Controller::class)
@@ -1091,24 +1075,17 @@ public function testCheckRequestData()
->getMock();
$request
- ->expects($this->at(0))
- ->method('getMethod')
- ->willReturn('GET');
-
- $request
- ->expects($this->at(1))
- ->method('getMethod')
- ->willReturn('POST');
-
- $request
- ->expects($this->at(2))
+ ->expects($this->exactly(4))
->method('getMethod')
- ->will($this->returnValue('POST'));
+ ->willReturnCallback(function () use (&$callCount): string {
+ $callCount++;
- $request
- ->expects($this->at(3))
- ->method('getMethod')
- ->willReturn('PATCH');
+ return match ($callCount) {
+ 1 => 'GET',
+ 2, 3 => 'POST',
+ default => 'PATCH',
+ };
+ });
$controller->setRequest($request);
@@ -1164,7 +1141,7 @@ public function testCheckRequestData()
*/
public function testConvertJsonApiDataArray()
{
- $listener = new JsonApiListener(new Controller());
+ $listener = new JsonApiListener(new Controller(new ServerRequest()));
$this->setReflectionClassInstance($listener);
// assert posted id attribute gets processed as expected
@@ -1181,8 +1158,11 @@ public function testConvertJsonApiDataArray()
$this->assertSame($expected, $result);
// assert success (single entity, no relationships)
- $jsonApiFixture = new File($this->_JsonApiDecoderFixtures . DS . 'incoming-country-no-relationships.json');
- $jsonApiArray = json_decode($jsonApiFixture->read(), true);
+ $jsonApiFixture = file_get_contents(
+ $this->_JsonApiDecoderFixtures . DS . 'incoming-country-no-relationships.json',
+ );
+
+ $jsonApiArray = json_decode($jsonApiFixture, true);
$expected = [
'code' => 'NL',
'name' => 'The Netherlands',
@@ -1192,8 +1172,11 @@ public function testConvertJsonApiDataArray()
$this->assertSame($expected, $result);
// assert success (single entity, multiple relationships, hasMany ignored for now)
- $jsonApiFixture = new File($this->_JsonApiDecoderFixtures . DS . 'incoming-country-mixed-relationships.json');
- $jsonApiArray = json_decode($jsonApiFixture->read(), true);
+ $jsonApiFixture = file_get_contents(
+ $this->_JsonApiDecoderFixtures . DS . 'incoming-country-mixed-relationships.json',
+ );
+
+ $jsonApiArray = json_decode($jsonApiFixture, true);
$expected = [
'code' => 'NL',
'name' => 'The Netherlands',
@@ -1211,8 +1194,11 @@ public function testConvertJsonApiDataArray()
$this->assertSame($expected, $result);
// assert success for relationships with null/empty data
- $jsonApiFixture = new File($this->_JsonApiDecoderFixtures . DS . 'incoming-country-mixed-relationships.json');
- $jsonApiArray = json_decode($jsonApiFixture->read(), true);
+ $jsonApiFixture = file_get_contents(
+ $this->_JsonApiDecoderFixtures . DS . 'incoming-country-mixed-relationships.json',
+ );
+
+ $jsonApiArray = json_decode($jsonApiFixture, true);
$jsonApiArray['data']['relationships']['cultures']['data'] = null;
$jsonApiArray['data']['relationships']['currency']['data'] = null;
@@ -1225,7 +1211,7 @@ public function testConvertJsonApiDataArray()
$this->assertSame($expected, $result);
}
- public function includeQueryProvider()
+ public static function includeQueryProvider(): array
{
return [
'standard' => [
@@ -1337,7 +1323,7 @@ public function includeQueryProvider()
*/
public function testIncludeQuery($include, $options, $expectedContain, $expectedInclude)
{
- $listener = new JsonApiListener(new Controller());
+ $listener = new JsonApiListener(new Controller(new ServerRequest()));
$this->setReflectionClassInstance($listener);
$subject = new Subject();
@@ -1350,13 +1336,13 @@ public function testIncludeQuery($include, $options, $expectedContain, $expected
$subject->query = $query;
$subject->query
->method('getRepository')
- ->willReturn(TableRegistry::get('Countries'));
+ ->willReturn($this->fetchTable('Countries'));
$this->callProtectedMethod('_includeParameter', [$include, $subject, $options], $listener);
$this->assertSame($expectedInclude, $listener->getConfig('include'));
}
- public function includeQueryBadRequestProvider()
+ public static function includeQueryBadRequestProvider(): array
{
return [
'denyList everything' => [
@@ -1384,7 +1370,7 @@ public function includeQueryBadRequestProvider()
public function testIncludeQueryBadRequest($include, $options, $expectedContain, $expectedInclude)
{
$this->expectException('Cake\Http\Exception\BadRequestException');
- $listener = new JsonApiListener(new Controller());
+ $listener = new JsonApiListener(new Controller(new ServerRequest()));
$this->setReflectionClassInstance($listener);
$subject = new Subject();
@@ -1400,8 +1386,8 @@ public function testIncludeQueryBadRequest($include, $options, $expectedContain,
->method('contain')
->with($expectedContain);
$subject->query
- ->method('repository')
- ->willReturn(TableRegistry::get('Countries'));
+ ->method('getRepository')
+ ->willReturn($this->fetchTable('Countries'));
$this->callProtectedMethod('_includeParameter', [$include, $subject, $options], $listener);
$this->assertSame($expectedInclude, $listener->getConfig('include'));
@@ -1416,7 +1402,7 @@ public function testIncludeQueryBadRequest($include, $options, $expectedContain,
*/
public function testSortingNotAppliedToAllTables()
{
- $listener = new JsonApiListener(new Controller());
+ $listener = new JsonApiListener(new Controller(new ServerRequest()));
$this->setReflectionClassInstance($listener);
$subject = new Subject();
@@ -1432,7 +1418,7 @@ public function testSortingNotAppliedToAllTables()
->method('contain');
$subject->query
->method('getRepository')
- ->willReturn(TableRegistry::get('Countries'));
+ ->willReturn($this->fetchTable('Countries'));
$sort = 'code,currency.code';
$listener->setConfig('include', ['currency', 'national_capitals']);
diff --git a/tests/TestCase/Schema/DynamicEntitySchemaTest.php b/tests/TestCase/Schema/DynamicEntitySchemaTest.php
index 88c66229..2fbd0e7b 100644
--- a/tests/TestCase/Schema/DynamicEntitySchemaTest.php
+++ b/tests/TestCase/Schema/DynamicEntitySchemaTest.php
@@ -4,10 +4,11 @@
namespace CrudJsonApi\Test\TestCase\Schema\JsonApi;
use Cake\Controller\Controller;
-use Cake\View\View;
+use Cake\Http\ServerRequest;
use Crud\TestSuite\TestCase;
use CrudJsonApi\Listener\JsonApiListener;
use CrudJsonApi\Schema\JsonApi\DynamicEntitySchema;
+use CrudJsonApi\View\JsonApiView;
use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
use Neomerx\JsonApi\Contracts\Schema\SchemaInterface;
@@ -25,7 +26,7 @@ class DynamicEntitySchemaTest extends TestCase
*
* @var array
*/
- public $fixtures = [
+ public array $fixtures = [
'plugin.CrudJsonApi.Countries',
'plugin.CrudJsonApi.Cultures',
'plugin.CrudJsonApi.Currencies',
@@ -75,14 +76,14 @@ public function testGetAttributes()
$this->assertSame($expectedSecondCultureId, $entity['cultures'][1]['id']);
// get required AssociationsCollection
- $listener = new JsonApiListener(new Controller());
+ $listener = new JsonApiListener(new Controller(new ServerRequest()));
$this->setReflectionClassInstance($listener);
$associations = $this->callProtectedMethod('_getContainedAssociations', [$table, $query->getContain()], $listener);
$repositories = $this->callProtectedMethod('_getRepositoryList', [$table, $associations], $listener);
// make view return associations on get('_associations') call
$view = $this
- ->getMockBuilder(View::class)
+ ->getMockBuilder(JsonApiView::class)
->onlyMethods(['get'])
->disableOriginalConstructor()
->getMock();
@@ -155,13 +156,13 @@ public function testRelationships()
$this->assertSame($expectedSecondCultureId, $entity['cultures'][1]['id']);
// get required AssociationsCollection
- $listener = new JsonApiListener(new Controller());
+ $listener = new JsonApiListener(new Controller(new ServerRequest()));
$this->setReflectionClassInstance($listener);
$associations = $this->callProtectedMethod('_getContainedAssociations', [$table, $query->getContain()], $listener);
$repositories = $this->callProtectedMethod('_getRepositoryList', [$table, $associations], $listener);
// make view return associations on get('_associations') call
- $view = new View();
+ $view = new JsonApiView();
$view->setConfig('repositories', $repositories);
$view->setConfig('absoluteLinks', false); // test relative links (listener default)
diff --git a/tests/TestCase/View/JsonApiViewTest.php b/tests/TestCase/View/JsonApiViewTest.php
index 51d67ea3..f984d485 100644
--- a/tests/TestCase/View/JsonApiViewTest.php
+++ b/tests/TestCase/View/JsonApiViewTest.php
@@ -7,7 +7,6 @@
use Cake\Core\Configure;
use Cake\Core\Plugin;
use Cake\Event\Event;
-use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Cake\ORM\Entity;
use Cake\ORM\ResultSet;
@@ -34,7 +33,7 @@ class JsonApiViewTest extends TestCase
*
* @var array
*/
- public $fixtures = [
+ public array $fixtures = [
'plugin.CrudJsonApi.Countries',
'plugin.CrudJsonApi.Currencies',
'plugin.CrudJsonApi.Cultures',
@@ -63,7 +62,7 @@ public function setUp(): void
require CONFIG . 'routes.php';
- $listener = new JsonApiListener(new Controller());
+ $listener = new JsonApiListener(new Controller(new ServerRequest()));
$this->_defaultOptions = [
'urlPrefix' => $listener->getConfig('urlPrefix'),
@@ -130,8 +129,7 @@ protected function _getView(?string $tableName, array $viewVars = [], array $opt
// create required (but non user configurable) viewVars next
$request = new ServerRequest();
- $response = new Response();
- $controller = new Controller($request, $response, $tableName);
+ $controller = new Controller($request);
$builder = $controller->viewBuilder();
$builder
@@ -147,7 +145,7 @@ protected function _getView(?string $tableName, array $viewVars = [], array $opt
// still here, create view with viewVars for response with resource(s)
$controller->setName($tableName); // e.g. Countries
- $table = $controller->loadModel(); // table object
+ $table = $controller->fetchTable($tableName); // table object
// fetch data from test viewVar normally found in subject
$subject = new Subject(['event' => new Event('Crud.beforeHandle')]);
@@ -157,7 +155,7 @@ protected function _getView(?string $tableName, array $viewVars = [], array $opt
} else {
$subject->entity = $findResult;
}
- $subject->query = $table->query();
+ $subject->query = $table->selectQuery();
// create required '_entities' and '_associations' viewVars normally
// produced and set by the JsonApiListener
@@ -242,7 +240,7 @@ public function testEncodeWithDynamicSchemas(): void
$this->assertSameAsFile(
$this->_JsonApiResponseBodyFixtures . DS . 'FetchingCollections' . DS . 'get-countries-without-pagination.json',
- $view->render()
+ $view->render(),
);
// test single entity without relationships
@@ -253,7 +251,7 @@ public function testEncodeWithDynamicSchemas(): void
$this->assertSameAsFile(
$this->_JsonApiResponseBodyFixtures . DS . 'FetchingResources' . DS . 'get-country-no-relationships.json',
- $view->render()
+ $view->render(),
);
}
@@ -279,7 +277,7 @@ public function testEncodeWithoutSchemas(): void
$this->assertSameAsFile(
$this->_JsonApiResponseBodyFixtures . DS . 'MetaInformation' . DS . 'meta-only.json',
- $view->render()
+ $view->render(),
);
}
@@ -297,11 +295,7 @@ public function testOptionalWithJsonApiVersion(): void
], [
'withJsonApiVersion' => true,
]);
- $expectedVersionArray = [
- 'jsonapi' => [
- 'version' => '1.1',
- ],
- ];
+
$jsonApi = json_decode($view->render(), true);
$this->assertArrayHasKey('jsonapi', $jsonApi);
$this->assertSame(['version' => '1.1'], $jsonApi['jsonapi']);
@@ -316,15 +310,7 @@ public function testOptionalWithJsonApiVersion(): void
'meta-key-2' => 'meta-val-2',
],
]);
- $expectedVersionArray = [
- 'jsonapi' => [
- 'version' => '1.1',
- 'meta' => [
- 'meta-key-1' => 'meta-val-1',
- 'meta-key-2' => 'meta-val-2',
- ],
- ],
- ];
+
$this->assertArrayHasKey('jsonapi', json_decode($view->render(), true));
$this->assertSame(['version' => '1.1', 'meta' => ['meta-key-1' => 'meta-val-1', 'meta-key-2' => 'meta-val-2']], json_decode($view->render(), true)['jsonapi']);
@@ -352,11 +338,7 @@ public function testOptionalMeta(): void
'author' => 'bravo-kernel',
],
]);
- $expectedMetaArray = [
- 'meta' => [
- 'author' => 'bravo-kernel',
- ],
- ];
+
$this->assertArrayHasKey('meta', json_decode($view->render(), true));
$this->assertSame(['author' => 'bravo-kernel'], json_decode($view->render(), true)['meta']);
@@ -405,7 +387,7 @@ public function testOptionalDebugPrettyPrint(): void
$this->assertSameAsFile(
$this->_JsonApiResponseBodyFixtures . DS . 'FetchingResources' . DS . 'get-country-no-relationships.json',
- $view->render()
+ $view->render(),
);
// make sure we can produce non-pretty in debug mode as well
@@ -422,7 +404,7 @@ public function testOptionalDebugPrettyPrint(): void
$this->assertSame(
'{"data":{"type":"countries","id":"1","attributes":{"code":"NL","dummyCounter":11111,"name":"The Netherlands"},"relationships":{"currency":{"links":{"self":"\/countries\/1\/relationships\/currency","related":"\/countries\/1\/currency"},"data":{"type":"currencies","id":"1"}},"nationalCapital":{"links":{"self":"\/countries\/1\/relationships\/nationalCapital","related":"\/countries\/1\/nationalCapital"},"data":{"type":"nationalCapitals","id":"1"}},"cultures":{"links":{"self":"\/countries\/1\/relationships\/cultures","related":"\/countries\/1\/cultures"}},"nationalCities":{"links":{"self":"\/countries\/1\/relationships\/nationalCities","related":"\/countries\/1\/nationalCities"}},"subcountries":{"links":{"self":"\/countries\/1\/relationships\/subcountries","related":"\/countries\/1\/subcountries"}},"supercountry":{"links":{"self":"\/countries\/1\/relationships\/supercountry","related":"\/countries\/1\/supercountry"}},"languages":{"links":{"self":"\/countries\/1\/relationships\/languages","related":"\/countries\/1\/languages"}}},"links":{"self":"\/countries\/1"}}}',
- $view->render()
+ $view->render(),
);
}
@@ -453,7 +435,7 @@ public function testGetDataToSerializeFromViewVarsSuccess(): void
$view = $this
->getMockBuilder(JsonApiView::class)
->disableOriginalConstructor()
- ->setMethods(null)
+ ->onlyMethods([])
->getMock();
$this->setReflectionClassInstance($view);
@@ -465,7 +447,7 @@ public function testGetDataToSerializeFromViewVarsSuccess(): void
$this->assertSame(
'dummy-would-normally-be-an-entity-or-resultset',
- $this->callProtectedMethod('_getDataToSerializeFromViewVars', [], $view)
+ $this->callProtectedMethod('_getDataToSerializeFromViewVars', [], $view),
);
// make sure null is returned when no data is found (which would mean
@@ -489,7 +471,7 @@ public function testGetDataToSerializeFromViewVarsSuccess(): void
$this->assertSame(
'dummy-country-would-normally-be-an-entity-or-resultset',
- $this->callProtectedMethod('_getDataToSerializeFromViewVars', [$parameters], $view)
+ $this->callProtectedMethod('_getDataToSerializeFromViewVars', [$parameters], $view),
);
// In this case the first entity in the _serialize array does not have
@@ -515,7 +497,7 @@ public function testGetDataToSerializeFromViewVarsObjectExcecption(): void
$this->expectException(CrudException::class);
$this->expectExceptionMessage(
'Assigning an object to JsonApiListener "serialize" is deprecated, ' .
- 'assign the object to its own variable and assign "serialize" = true instead.'
+ 'assign the object to its own variable and assign "serialize" = true instead.',
);
$view = $this
->getMockBuilder(JsonApiView::class)
@@ -536,7 +518,7 @@ public function testJsonOptions(): void
$view = $this
->getMockBuilder(JsonApiView::class)
->disableOriginalConstructor()
- ->setMethods(null)
+ ->onlyMethods([])
->getMock();
$this->setReflectionClassInstance($view);
@@ -550,7 +532,7 @@ public function testJsonOptions(): void
[
JSON_HEX_AMP, // 2
JSON_HEX_QUOT, // 8
- ]
+ ],
);
$this->assertEquals(10, $this->callProtectedMethod('_jsonOptions', [], $view));
@@ -563,7 +545,7 @@ public function testJsonOptions(): void
[
JSON_HEX_AMP, // 2
JSON_HEX_QUOT, // 8
- ]
+ ],
);
$this->assertEquals(138, $this->callProtectedMethod('_jsonOptions', [], $view));
@@ -577,7 +559,7 @@ public function testJsonOptions(): void
[
JSON_HEX_AMP, // 2
JSON_HEX_QUOT, // 8
- ]
+ ],
);
$this->assertEquals(10, $this->callProtectedMethod('_jsonOptions', [], $view));
@@ -590,7 +572,7 @@ public function testJsonOptions(): void
[
JSON_HEX_AMP, // 2
JSON_HEX_QUOT, // 8
- ]
+ ],
);
$this->assertEquals(10, $this->callProtectedMethod('_jsonOptions', [], $view));
}
@@ -663,7 +645,7 @@ public function testGetPaginationLinks(): void
$view = $this
->getMockBuilder(JsonApiView::class)
->disableOriginalConstructor()
- ->setMethods(null)
+ ->onlyMethods([])
->getMock();
$this->setReflectionClassInstance($view);
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 3880bec9..4974583f 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -1,7 +1,8 @@
create(TMP . 'cache/models', 0777);
-$TMP->create(TMP . 'cache/persistent', 0777);
-$TMP->create(TMP . 'cache/views', 0777);
+$tmps = ['models', 'persistent', 'views'];
+foreach ($tmps as $tmp) {
+ if (!is_dir(sprintf('%scache/%s', TMP, $tmp))) {
+ mkdir(sprintf('%scache/%s', TMP, $tmp), 0777, true);
+ }
+}
$cache = [
'default' => [
'engine' => 'File'
],
+ # This config key wasn't deprecated until CakePHP 5.1.
+ # @link https://book.cakephp.org/5/en/core-libraries/caching.html#configuring-cache-engines
'_cake_core_' => [
'className' => 'File',
'prefix' => 'crud_myapp_cake_core_',
@@ -68,6 +73,13 @@
'serialize' => true,
'duration' => '+10 seconds'
],
+ '_cake_translations_' => [
+ 'className' => 'File',
+ 'prefix' => 'crud_myapp_cake_translations_',
+ 'path' => CACHE . 'persistent/',
+ 'serialize' => true,
+ 'duration' => '+10 seconds'
+ ],
'_cake_model_' => [
'className' => 'File',
'prefix' => 'crud_my_app_cake_model_',
@@ -90,17 +102,18 @@
putenv('DB_URL=sqlite:///:memory:');
}
-Cake\Datasource\ConnectionManager::setConfig(
- 'test',
- [
- 'url' => getenv('DB_URL'),
- 'timezone' => 'UTC'
- ]
-);
+// Configure both 'default' and 'test' datasources.
+$dbConfig = [
+ 'url' => getenv('DB_URL'),
+ 'timezone' => 'UTC'
+];
-Plugin::getCollection()->add(new \Crud\Plugin());
-Plugin::getCollection()->add(new \CrudJsonApi\Plugin());
+ConnectionManager::setConfig('default', $dbConfig);
+ConnectionManager::setConfig('test', $dbConfig);
+ConnectionManager::alias('test', 'default');
+
+$loader = new SchemaLoader();
+$loader->loadInternalFile(ROOT . '/tests/Fixture/schema.php');
-Configure::write('Error.ignoredDeprecationPaths', [
- 'vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php',
-]);
+Plugin::getCollection()->add(new \Crud\CrudPlugin());
+Plugin::getCollection()->add(new \CrudJsonApi\Plugin());
diff --git a/tests/test_app/config/routes.php b/tests/test_app/config/routes.php
index 035ae314..cbf5ff30 100644
--- a/tests/test_app/config/routes.php
+++ b/tests/test_app/config/routes.php
@@ -17,5 +17,8 @@
'Countries',
'Currencies',
'Cultures',
+ 'Languages',
+ 'NationalCapitals',
+ 'NationalCities',
], $routes);
});
diff --git a/tests/test_app/src/Controller/CountriesController.php b/tests/test_app/src/Controller/CountriesController.php
index 23d3db75..c3075a38 100644
--- a/tests/test_app/src/Controller/CountriesController.php
+++ b/tests/test_app/src/Controller/CountriesController.php
@@ -13,13 +13,12 @@ class CountriesController extends Controller
{
use ControllerTrait;
- public $paginate = ['limit' => 3];
+ public array $paginate = ['limit' => 3];
public function initialize(): void
{
parent::initialize();
- $this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent(
'Crud.Crud',
@@ -37,7 +36,7 @@ public function initialize(): void
'CrudJsonApi.Pagination',
'Crud.Search',
],
- ]
+ ],
);
}
}
diff --git a/tests/test_app/src/Controller/CurrenciesController.php b/tests/test_app/src/Controller/CurrenciesController.php
index e7c108a5..f4bb2137 100644
--- a/tests/test_app/src/Controller/CurrenciesController.php
+++ b/tests/test_app/src/Controller/CurrenciesController.php
@@ -13,13 +13,12 @@ class CurrenciesController extends Controller
{
use ControllerTrait;
- public $paginate = ['limit' => 3];
+ public array $paginate = ['limit' => 3];
public function initialize(): void
{
parent::initialize();
- $this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent(
'Crud.Crud',
@@ -35,7 +34,7 @@ public function initialize(): void
'listeners' => [
'CrudJsonApi.JsonApi',
],
- ]
+ ],
);
}
}
diff --git a/tests/test_app/src/Controller/LanguagesController.php b/tests/test_app/src/Controller/LanguagesController.php
index 506f180b..1d2b0a4c 100644
--- a/tests/test_app/src/Controller/LanguagesController.php
+++ b/tests/test_app/src/Controller/LanguagesController.php
@@ -13,13 +13,12 @@ class LanguagesController extends Controller
{
use ControllerTrait;
- public $paginate = ['limit' => 3];
+ public array $paginate = ['limit' => 3];
public function initialize(): void
{
parent::initialize();
- $this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent(
'Crud.Crud',
@@ -35,7 +34,7 @@ public function initialize(): void
'listeners' => [
'CrudJsonApi.JsonApi',
],
- ]
+ ],
);
}
}
diff --git a/tests/test_app/src/Controller/NationalCapitalsController.php b/tests/test_app/src/Controller/NationalCapitalsController.php
index cd2a3378..25a5aa35 100644
--- a/tests/test_app/src/Controller/NationalCapitalsController.php
+++ b/tests/test_app/src/Controller/NationalCapitalsController.php
@@ -17,7 +17,6 @@ public function initialize(): void
{
parent::initialize();
- $this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent(
'Crud.Crud',
@@ -34,7 +33,7 @@ public function initialize(): void
'CrudJsonApi.JsonApi',
'CrudJsonApi.Pagination',
],
- ]
+ ],
);
}
}
diff --git a/tests/test_app/src/Controller/NationalCitiesController.php b/tests/test_app/src/Controller/NationalCitiesController.php
index 479bac22..4cd22227 100644
--- a/tests/test_app/src/Controller/NationalCitiesController.php
+++ b/tests/test_app/src/Controller/NationalCitiesController.php
@@ -18,7 +18,6 @@ public function initialize(): void
{
parent::initialize();
- $this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent(
'Crud.Crud',
@@ -35,7 +34,7 @@ public function initialize(): void
'CrudJsonApi.JsonApi',
'CrudJsonApi.Pagination',
],
- ]
+ ],
);
}
diff --git a/tests/test_app/src/Model/Table/CountriesTable.php b/tests/test_app/src/Model/Table/CountriesTable.php
index 00090708..dfa223af 100644
--- a/tests/test_app/src/Model/Table/CountriesTable.php
+++ b/tests/test_app/src/Model/Table/CountriesTable.php
@@ -32,7 +32,9 @@ public function initialize(array $config): void
'propertyName' => 'supercountry',
]);
- $this->belongsToMany('Languages');
+ $this
+ ->belongsToMany('Languages')
+ ->setThrough('CountriesLanguages');
}
public function validationDefault(Validator $validator): Validator
diff --git a/tests/test_app/src/Model/Table/LanguagesTable.php b/tests/test_app/src/Model/Table/LanguagesTable.php
index 7467c6df..768b7f53 100644
--- a/tests/test_app/src/Model/Table/LanguagesTable.php
+++ b/tests/test_app/src/Model/Table/LanguagesTable.php
@@ -12,6 +12,8 @@ class LanguagesTable extends Table
{
public function initialize(array $config): void
{
- $this->belongsToMany('Countries');
+ $this
+ ->belongsToMany('Countries')
+ ->setThrough('CountriesLanguages');
}
}