From aeead1a07dba3b9f21e103b215375952d5a64947 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Mon, 29 Dec 2025 15:14:38 +0100 Subject: [PATCH 1/8] fixes for phpcs bonus levels --- examples/bootstrap_examples.php | 2 +- examples/collection.php | 2 +- phpcs.bonus.xml | 4 +++- phpstan.bonus.neon | 4 ++-- src/objects/LinkObject.php | 14 ++++++++------ tests/example_output/collection/collection.php | 2 +- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/bootstrap_examples.php b/examples/bootstrap_examples.php index acf810cc..92348d3b 100644 --- a/examples/bootstrap_examples.php +++ b/examples/bootstrap_examples.php @@ -59,7 +59,7 @@ class ExampleDataset { ]; public static function getRecord($type, $id) { - if (!isset(self::$records[$type][$id])) { + if (isset(self::$records[$type][$id]) === false) { throw new \Exception('sorry, we have a limited dataset'); } diff --git a/examples/collection.php b/examples/collection.php index cdd5a38c..ab11fc8a 100644 --- a/examples/collection.php +++ b/examples/collection.php @@ -18,7 +18,7 @@ foreach ($users as $user) { $resource = ResourceObject::fromObject($user, type: 'user', id: $user->id); - if ($user->id == 42) { + if ($user->id === 42) { $ship = new ResourceObject('ship', 5); $ship->add('name', 'Heart of Gold'); $resource->addRelationship('ship', $ship); diff --git a/phpcs.bonus.xml b/phpcs.bonus.xml index ee7fe9e0..5cb3deca 100644 --- a/phpcs.bonus.xml +++ b/phpcs.bonus.xml @@ -21,5 +21,7 @@ - + + + diff --git a/phpstan.bonus.neon b/phpstan.bonus.neon index ae9db4e9..169fed18 100644 --- a/phpstan.bonus.neon +++ b/phpstan.bonus.neon @@ -4,9 +4,9 @@ includes: parameters: level: 10 - + treatPhpDocTypesAsCertain: true - + # @see https://github.com/phpstan/phpstan-strict-rules strictRules: allRules: true diff --git a/src/objects/LinkObject.php b/src/objects/LinkObject.php index 5a13d2fb..7fbb4fdb 100644 --- a/src/objects/LinkObject.php +++ b/src/objects/LinkObject.php @@ -152,12 +152,7 @@ public function toArray(): array { $array['type'] = $this->type; } if ($this->hreflang !== []) { - if (count($this->hreflang) === 1) { - $array['hreflang'] = $this->hreflang[0]; - } - else { - $array['hreflang'] = $this->hreflang; - } + $array['hreflang'] = $this->getHrefLanguages(); } if (isset($this->describedby) && $this->describedby->isEmpty() === false) { $array['describedby'] = $this->describedby->toArray(); @@ -168,4 +163,11 @@ public function toArray(): array { return $array; } + + /** + * @return string|string[] + */ + private function getHrefLanguages(): string|array { + return (count($this->hreflang) === 1) ? $this->hreflang[0] : $this->hreflang; + } } diff --git a/tests/example_output/collection/collection.php b/tests/example_output/collection/collection.php index 991fbaee..a3a02659 100644 --- a/tests/example_output/collection/collection.php +++ b/tests/example_output/collection/collection.php @@ -29,7 +29,7 @@ public static function createJsonapiDocument() { foreach ($users as $user) { $resource = ResourceObject::fromObject($user, 'user', $user->id); - if ($user->id == 42) { + if ($user->id === 42) { $ship = new ResourceObject('ship', 5); $ship->add('name', 'Heart of Gold'); $resource->addRelationship('ship', $ship); From 7452fffadd739ba400ee532b53d199e14c92f754 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Mon, 29 Dec 2025 15:14:52 +0100 Subject: [PATCH 2/8] fix phpstan performance issues --- .../profiles/CursorPaginationProfileTest.php | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/tests/profiles/CursorPaginationProfileTest.php b/tests/profiles/CursorPaginationProfileTest.php index 42250a6d..9d67e331 100644 --- a/tests/profiles/CursorPaginationProfileTest.php +++ b/tests/profiles/CursorPaginationProfileTest.php @@ -76,21 +76,30 @@ public function test_WithRelationship(): void { parent::assertArrayHasKey('data', $array); parent::assertArrayHasKey('relationships', $array['data']); parent::assertArrayHasKey('people', $array['data']['relationships']); - parent::assertArrayHasKey('links', $array['data']['relationships']['people']); - parent::assertArrayHasKey('data', $array['data']['relationships']['people']); - parent::assertArrayHasKey('meta', $array['data']['relationships']['people']); - parent::assertArrayHasKey('prev', $array['data']['relationships']['people']['links']); - parent::assertArrayHasKey('next', $array['data']['relationships']['people']['links']); - parent::assertArrayHasKey('page', $array['data']['relationships']['people']['meta']); - parent::assertArrayHasKey('href', $array['data']['relationships']['people']['links']['prev']); - parent::assertArrayHasKey('href', $array['data']['relationships']['people']['links']['next']); - parent::assertArrayHasKey('total', $array['data']['relationships']['people']['meta']['page']); - parent::assertArrayHasKey('estimatedTotal', $array['data']['relationships']['people']['meta']['page']); - parent::assertArrayHasKey('bestGuess', $array['data']['relationships']['people']['meta']['page']['estimatedTotal']); - parent::assertCount(3, $array['data']['relationships']['people']['data']); - parent::assertArrayHasKey('meta', $array['data']['relationships']['people']['data'][0]); - parent::assertArrayHasKey('page', $array['data']['relationships']['people']['data'][0]['meta']); - parent::assertArrayHasKey('cursor', $array['data']['relationships']['people']['data'][0]['meta']['page']); + + // re-map nested arrays to variables to speed up phpstan + // without it, this file takes 10 seconds (!) more to process + + $people = $array['data']['relationships']['people']; + parent::assertArrayHasKey('links', $people); + parent::assertArrayHasKey('data', $people); + parent::assertArrayHasKey('meta', $people); + parent::assertArrayHasKey('prev', $people['links']); + parent::assertArrayHasKey('next', $people['links']); + parent::assertArrayHasKey('page', $people['meta']); + parent::assertArrayHasKey('href', $people['links']['prev']); + parent::assertArrayHasKey('href', $people['links']['next']); + + $peopleMeta = $people['meta']; + parent::assertArrayHasKey('total', $peopleMeta['page']); + parent::assertArrayHasKey('estimatedTotal', $peopleMeta['page']); + parent::assertArrayHasKey('bestGuess', $peopleMeta['page']['estimatedTotal']); + parent::assertCount(3, $people['data']); + + $firstPerson = $people['data'][0]; + parent::assertArrayHasKey('meta', $firstPerson); + parent::assertArrayHasKey('page', $firstPerson['meta']); + parent::assertArrayHasKey('cursor', $firstPerson['meta']['page']); } public function testSetLinksFirstPage_HappyPath(): void { From 0a998dc0b6ef10465104fd489b3f507569859c7a Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Mon, 29 Dec 2025 15:19:27 +0100 Subject: [PATCH 3/8] cleanup relaxed type checking by phpdoc --- examples/bootstrap_examples.php | 13 +++++-------- phpstan.bonus.neon | 2 -- phpstan.neon | 4 ++-- src/helpers/RequestParser.php | 2 +- src/objects/RelationshipObject.php | 1 + tests/example_output/ExampleTimestampsProfile.php | 13 +++++-------- 6 files changed, 14 insertions(+), 21 deletions(-) diff --git a/examples/bootstrap_examples.php b/examples/bootstrap_examples.php index 92348d3b..c18cf55c 100644 --- a/examples/bootstrap_examples.php +++ b/examples/bootstrap_examples.php @@ -151,14 +151,11 @@ public function getOfficialLink(): string { * optionally helpers for the specific profile */ - /** - * @param ResourceInterface&HasAttributesInterface $resource - */ - public function setTimestamps(ResourceInterface $resource, ?\DateTimeInterface $created=null, ?\DateTimeInterface $updated=null) { - if ($resource instanceof HasAttributesInterface === false) { - throw new \Exception('cannot add attributes to identifier objects'); - } - + public function setTimestamps( + ResourceInterface & HasAttributesInterface $resource, + ?\DateTimeInterface $created=null, + ?\DateTimeInterface $updated=null, + ) { $timestamps = []; if ($created !== null) { $timestamps['created'] = $created->format(\DateTime::ISO8601); diff --git a/phpstan.bonus.neon b/phpstan.bonus.neon index 169fed18..ddc4b487 100644 --- a/phpstan.bonus.neon +++ b/phpstan.bonus.neon @@ -5,8 +5,6 @@ includes: parameters: level: 10 - treatPhpDocTypesAsCertain: true - # @see https://github.com/phpstan/phpstan-strict-rules strictRules: allRules: true diff --git a/phpstan.neon b/phpstan.neon index a5a5f410..d0cf43ae 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,11 +5,11 @@ parameters: - src/ - tests/ - examples/ - + typeAliases: PHPStanTypeAlias_InternalOptions: 'array' - treatPhpDocTypesAsCertain: false + treatPhpDocTypesAsCertain: true strictRules: allRules: false diff --git a/src/helpers/RequestParser.php b/src/helpers/RequestParser.php index 0863c306..24918ca8 100644 --- a/src/helpers/RequestParser.php +++ b/src/helpers/RequestParser.php @@ -112,7 +112,7 @@ public function hasIncludePaths(): bool { * the raw format allows for custom processing * * @param PHPStanTypeAlias_InternalOptions $options {@see RequestParser::$defaults} - * @return string[]|array + * @return array|array */ public function getIncludePaths(array $options=[]): array { if ($this->queryParameters['include'] === '') { diff --git a/src/objects/RelationshipObject.php b/src/objects/RelationshipObject.php index e44ece9a..51668997 100644 --- a/src/objects/RelationshipObject.php +++ b/src/objects/RelationshipObject.php @@ -316,6 +316,7 @@ public function getNestedContainedResourceObjects(): array { $resourceObjects = []; foreach ($resources as $resource) { + // @phpstan-ignore instanceof.alwaysTrue, identical.alwaysFalse (we _can_ have both ResourceObject and ResourceIdentifierObject here) if ($resource->getResource() instanceof ResourceObject === false) { continue; } diff --git a/tests/example_output/ExampleTimestampsProfile.php b/tests/example_output/ExampleTimestampsProfile.php index 210af099..4129a35f 100644 --- a/tests/example_output/ExampleTimestampsProfile.php +++ b/tests/example_output/ExampleTimestampsProfile.php @@ -14,14 +14,11 @@ public function getOfficialLink(): string { return 'https://jsonapi.org/recommendations/#authoring-profiles'; } - /** - * @param ResourceInterface&HasAttributesInterface $resource - */ - public function setTimestamps(ResourceInterface $resource, ?\DateTimeInterface $created=null, ?\DateTimeInterface $updated=null) { - if ($resource instanceof HasAttributesInterface === false) { - throw new InputException('cannot add attributes to identifier objects'); - } - + public function setTimestamps( + ResourceInterface & HasAttributesInterface $resource, + ?\DateTimeInterface $created=null, + ?\DateTimeInterface $updated=null, + ) { $timestamps = []; if ($created !== null) { $timestamps['created'] = $created->format(\DateTime::ISO8601); From 7d4b8515a566df31bfca36cddc5a3ab33c200707 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Mon, 29 Dec 2025 15:53:12 +0100 Subject: [PATCH 4/8] improve test coverage --- src/Document.php | 4 -- src/objects/RelationshipObject.php | 5 -- src/objects/ResourceIdentifierObject.php | 2 +- tests/ResourceDocumentTest.php | 61 +++++++++++++++++++ tests/helpers/RequestParserTest.php | 7 +-- .../objects/ResourceIdentifierObjectTest.php | 48 +++++++++++++++ 6 files changed, 112 insertions(+), 15 deletions(-) diff --git a/src/Document.php b/src/Document.php index 0c2dfcdd..f1d599bb 100644 --- a/src/Document.php +++ b/src/Document.php @@ -140,7 +140,6 @@ public function setDescribedByLink(string $href, array $meta=[]): void { } /** - * @throws InputException if the $level is unknown * @throws InputException if the $level is DocumentLevelEnum::Resource */ public function addMeta(string $key, mixed $value, DocumentLevelEnum $level=DocumentLevelEnum::Root): void { @@ -163,9 +162,6 @@ public function addMeta(string $key, mixed $value, DocumentLevelEnum $level=Docu case DocumentLevelEnum::Resource: throw new InputException('level "resource" can only be set on a ResourceDocument'); - - default: - throw new InputException('unknown level "'.$level->value.'"'); } } diff --git a/src/objects/RelationshipObject.php b/src/objects/RelationshipObject.php index 51668997..6d72df4b 100644 --- a/src/objects/RelationshipObject.php +++ b/src/objects/RelationshipObject.php @@ -45,8 +45,6 @@ public function __construct( * @param CollectionDocument|ResourceInterface|ResourceInterface[]|null $relation * @param array $links * @param array $meta - * - * @throws InputException if $relation is not one of the supported formats */ public static function fromAnything( array|CollectionDocument|ResourceInterface|null $relation, @@ -66,9 +64,6 @@ public static function fromAnything( elseif ($relation === null) { $relationshipObject = new RelationshipObject(RelationshipTypeEnum::ToOne); } - else { - throw new InputException('unknown format of relation "'.get_debug_type($relation).'"'); - } return $relationshipObject; } diff --git a/src/objects/ResourceIdentifierObject.php b/src/objects/ResourceIdentifierObject.php index 5f09cd4d..b45be053 100644 --- a/src/objects/ResourceIdentifierObject.php +++ b/src/objects/ResourceIdentifierObject.php @@ -112,7 +112,7 @@ public function setMetaObject(MetaObject $metaObject): void { */ public static function fromResourceObject(ResourceObject $resourceObject): static { if ($resourceObject->hasIdentification() === false) { - throw new InputException('resource has no identification yet<'); + throw new InputException('resource has no identification yet'); } $resourceIdentifierObject = new static($resourceObject->type, $resourceObject->primaryId()); diff --git a/tests/ResourceDocumentTest.php b/tests/ResourceDocumentTest.php index 144d574c..f9a2276c 100644 --- a/tests/ResourceDocumentTest.php +++ b/tests/ResourceDocumentTest.php @@ -6,6 +6,7 @@ use alsvanzelf\jsonapi\ResourceDocument; use alsvanzelf\jsonapi\enums\DocumentLevelEnum; +use alsvanzelf\jsonapi\enums\RelationshipTypeEnum; use alsvanzelf\jsonapi\exceptions\Exception; use alsvanzelf\jsonapi\exceptions\InputException; use alsvanzelf\jsonapi\interfaces\ExtensionInterface; @@ -100,6 +101,66 @@ public function testAddRelationship_DoNotIncludeContainedResources(): void { parent::assertArrayNotHasKey('included', $array); } + public function testAddRelationship_IdentifierOnlyObject(): void { + $document = new ResourceDocument(); + $document->setPrimaryResource(new ResourceIdentifierObject('user', 42)); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('the resource is an identifier-only object'); + + $document->addRelationship('foo', null); + } + + public function testAddLink_IdentifierOnlyObject(): void { + $document = new ResourceDocument(); + $document->setPrimaryResource(new ResourceIdentifierObject('user', 42)); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('the resource is an identifier-only object'); + + $document->addLink('foo', null); + } + + public function testSetSelfLink_IdentifierOnlyObject(): void { + $document = new ResourceDocument(); + $document->setPrimaryResource(new ResourceIdentifierObject('user', 42)); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('the resource is an identifier-only object'); + + $document->setSelfLink('https://jsonapi.org'); + } + + public function testSetAttributesObject_IdentifierOnlyObject(): void { + $document = new ResourceDocument(); + $document->setPrimaryResource(new ResourceIdentifierObject('user', 42)); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('the resource is an identifier-only object'); + + $document->setAttributesObject(new AttributesObject()); + } + + public function testAddRelationshipObject_IdentifierOnlyObject(): void { + $document = new ResourceDocument(); + $document->setPrimaryResource(new ResourceIdentifierObject('user', 42)); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('the resource is an identifier-only object'); + + $document->addRelationshipObject('foo', new RelationshipObject(RelationshipTypeEnum::ToOne)); + } + + public function testSetRelationshipsObject_IdentifierOnlyObject(): void { + $document = new ResourceDocument(); + $document->setPrimaryResource(new ResourceIdentifierObject('user', 42)); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('the resource is an identifier-only object'); + + $document->setRelationshipsObject(new RelationshipsObject()); + } + public function testAddMeta_HappyPath(): void { $document = new ResourceDocument(); $document->addMeta('foo', 'root', DocumentLevelEnum::Root); diff --git a/tests/helpers/RequestParserTest.php b/tests/helpers/RequestParserTest.php index 2f03689c..fa2120f9 100644 --- a/tests/helpers/RequestParserTest.php +++ b/tests/helpers/RequestParserTest.php @@ -87,12 +87,9 @@ public function testFromSuperglobals_WithPhpInputStream(): void { $_SERVER['REQUEST_URI'] = '/'; $_SERVER['CONTENT_TYPE'] = ContentTypeEnum::Official->value; + // empty $_POST so we get a bit more test coverage for input stream processing $_GET = []; - $_POST = [ - 'meta' => [ - 'foo' => 'bar', - ], - ]; + $_POST = []; $requestParser = RequestParser::fromSuperglobals(); diff --git a/tests/objects/ResourceIdentifierObjectTest.php b/tests/objects/ResourceIdentifierObjectTest.php index 64e65ff2..89539196 100644 --- a/tests/objects/ResourceIdentifierObjectTest.php +++ b/tests/objects/ResourceIdentifierObjectTest.php @@ -6,8 +6,10 @@ use alsvanzelf\jsonapi\exceptions\DuplicateException; use alsvanzelf\jsonapi\exceptions\Exception; +use alsvanzelf\jsonapi\exceptions\InputException; use alsvanzelf\jsonapi\interfaces\ExtensionInterface; use alsvanzelf\jsonapi\objects\ResourceIdentifierObject; +use alsvanzelf\jsonapi\objects\ResourceObject; use PHPUnit\Framework\TestCase; class ResourceIdentifierObjectTest extends TestCase { @@ -55,6 +57,35 @@ public function testSetLocalId_WithIdAlreadySet(): void { $resourceIdentifierObject->setLocalId('uuid-1'); } + public function testFromResourceObject_HappyPath(): void { + $resource = new ResourceObject('test', 1); + $resource->addAttribute('foo', 'bar'); + + $array = $resource->toArray(); + + parent::assertSame('test', $array['type']); + parent::assertSame('1', $array['id']); + parent::assertArrayHasKey('attributes', $array); + + $resourceIdentifierObject = ResourceIdentifierObject::fromResourceObject($resource); + + $array = $resourceIdentifierObject->toArray(); + + parent::assertSame('test', $array['type']); + parent::assertSame('1', $array['id']); + parent::assertArrayNotHasKey('attributes', $array); + } + + public function testFromResourceObject_NoFullIdentification(): void { + $resource = new ResourceObject(); + $array = $resource->toArray(); + + $this->expectException(InputException::class); + $this->expectExceptionMessage('resource has no identification yet'); + + ResourceIdentifierObject::fromResourceObject($resource); + } + public function testEquals_HappyPath(): void { $one = new ResourceIdentifierObject('test', 1); $two = new ResourceIdentifierObject('test', 2); @@ -168,6 +199,13 @@ public function testGetIdentificationKey_NoFullIdentification(): void { $resourceIdentifierObject->getIdentificationKey(); } + public function testIsEmpty_IdWithoutType(): void { + $resourceIdentifierObject = new ResourceIdentifierObject(); + $resourceIdentifierObject->setId(42); + + parent::assertFalse($resourceIdentifierObject->isEmpty()); + } + public function testIsEmpty_WithAtMembers(): void { $resourceIdentifierObject = new ResourceIdentifierObject(); @@ -190,4 +228,14 @@ public function testIsEmpty_WithExtensionMembers(): void { parent::assertFalse($resourceIdentifierObject->isEmpty()); } + + public function testPrimaryId_NoFullIdentification(): void { + $resourceIdentifierObject = new ResourceIdentifierObject(); + $primaryIdMethod = new \ReflectionMethod($resourceIdentifierObject, 'primaryId'); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('resource has no identification yet'); + + $primaryIdMethod->invoke($resourceIdentifierObject); + } } From c99963d50dea0cda33e66b336e207f2fc265b7c8 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Fri, 2 Jan 2026 23:18:48 +0100 Subject: [PATCH 5/8] fix issues for phpstan level 5 --- examples/output.php | 6 +----- examples/relationships.php | 9 --------- phpstan.neon | 5 +++-- src/profiles/CursorPaginationProfile.php | 24 ++++++++++++------------ 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/examples/output.php b/examples/output.php index 1cb9d4ab..a0ac0592 100644 --- a/examples/output.php +++ b/examples/output.php @@ -69,10 +69,6 @@ * send json response */ -$options = ['prettyPrint' => true, 'contentType' => 'text/html']; echo '

Send json response

'; echo '
$document->sendResponse();
'; -echo '
';
-$document->sendResponse($options);
-echo '
'; -echo '

Also sends http status code ('.$document->getHttpStatusCode().') and headers: [Content-Type: '.ContentTypeEnum::Official->value.']

'; +echo '

Echo\'s the result of $document->json() and sends http status code ('.$document->getHttpStatusCode().') and headers: [Content-Type: '.ContentTypeEnum::Official->value.']

'; diff --git a/examples/relationships.php b/examples/relationships.php index 73db5dc8..5f0cb5e3 100644 --- a/examples/relationships.php +++ b/examples/relationships.php @@ -74,15 +74,6 @@ $document->addRelationshipObject('one-by-one-neighbours', $relationshipObject); -/** - * custom - */ -$jsonapi = new ResourceDocument('user', 1); -$customRelation = [ - 'data' => ['cus' => 'tom'], -]; -$jsonapi->addRelationship('custom', $customRelation); - /** * sending the response */ diff --git a/phpstan.neon b/phpstan.neon index d0cf43ae..303d0a25 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,13 +1,14 @@ parameters: # slowly increase - level: 4 + level: 5 paths: - src/ - tests/ - examples/ typeAliases: - PHPStanTypeAlias_InternalOptions: 'array' + PHPStanTypeAlias_InternalOptions: 'array' + PHPStanTypeAlias_Document: 'array' treatPhpDocTypesAsCertain: true diff --git a/src/profiles/CursorPaginationProfile.php b/src/profiles/CursorPaginationProfile.php index 75825210..399b5f86 100644 --- a/src/profiles/CursorPaginationProfile.php +++ b/src/profiles/CursorPaginationProfile.php @@ -56,10 +56,10 @@ class CursorPaginationProfile implements ProfileInterface { /** * set links to paginate the data using cursors of the paginated data * - * @param PaginableInterface $paginable a CollectionDocument or RelationshipObject + * @param PaginableInterface & HasLinksInterface $paginable a CollectionDocument or RelationshipObject */ public function setLinks( - PaginableInterface $paginable, + PaginableInterface & HasLinksInterface $paginable, string $baseOrCurrentUrl, string $firstCursor, string $lastCursor, @@ -71,32 +71,32 @@ public function setLinks( } /** - * @param PaginableInterface $paginable a CollectionDocument or RelationshipObject + * @param PaginableInterface & HasLinksInterface $paginable a CollectionDocument or RelationshipObject */ - public function setLinksFirstPage(PaginableInterface $paginable, string $baseOrCurrentUrl, string $lastCursor): void { + public function setLinksFirstPage(PaginableInterface & HasLinksInterface $paginable, string $baseOrCurrentUrl, string $lastCursor): void { $this->setPaginationLinkObjectsWithoutPrevious($paginable, $baseOrCurrentUrl, $lastCursor); } /** - * @param PaginableInterface $paginable a CollectionDocument or RelationshipObject + * @param PaginableInterface & HasLinksInterface $paginable a CollectionDocument or RelationshipObject */ - public function setLinksLastPage(PaginableInterface $paginable, string $baseOrCurrentUrl, string $firstCursor): void { + public function setLinksLastPage(PaginableInterface & HasLinksInterface $paginable, string $baseOrCurrentUrl, string $firstCursor): void { $this->setPaginationLinkObjectsWithoutNext($paginable, $baseOrCurrentUrl, $firstCursor); } /** * set the cursor of a specific resource to allow pagination after or before this resource */ - public function setCursor(ResourceInterface $resource, string $cursor): void { + public function setCursor(ResourceInterface & HasMetaInterface $resource, string $cursor): void { $this->setItemMeta($resource, $cursor); } /** * set count(s) to tell about the (estimated) total size * - * @param PaginableInterface $paginable a CollectionDocument or RelationshipObject + * @param PaginableInterface & HasMetaInterface $paginable a CollectionDocument or RelationshipObject */ - public function setCount(PaginableInterface $paginable, ?int $exactTotal=null, ?int $bestGuessTotal=null) { + public function setCount(PaginableInterface & HasMetaInterface $paginable, ?int $exactTotal=null, ?int $bestGuessTotal=null) { $this->setPaginationMeta($paginable, $exactTotal, $bestGuessTotal); } @@ -137,15 +137,15 @@ public function setPaginationLinkObjects( $paginable->addLinkObject('next', $nextLinkObject); } - public function setPaginationLinkObjectsWithoutNext(PaginableInterface $paginable, string $baseOrCurrentUrl, string $firstCursor): void { + public function setPaginationLinkObjectsWithoutNext(PaginableInterface & HasLinksInterface $paginable, string $baseOrCurrentUrl, string $firstCursor): void { $this->setPaginationLinkObjects($paginable, new LinkObject($this->generatePreviousLink($baseOrCurrentUrl, $firstCursor)), new LinkObject()); } - public function setPaginationLinkObjectsWithoutPrevious(PaginableInterface $paginable, string $baseOrCurrentUrl, string $lastCursor): void { + public function setPaginationLinkObjectsWithoutPrevious(PaginableInterface & HasLinksInterface $paginable, string $baseOrCurrentUrl, string $lastCursor): void { $this->setPaginationLinkObjects($paginable, new LinkObject(), new LinkObject($this->generateNextLink($baseOrCurrentUrl, $lastCursor))); } - public function setPaginationLinkObjectsExplicitlyEmpty(PaginableInterface $paginable): void { + public function setPaginationLinkObjectsExplicitlyEmpty(PaginableInterface & HasLinksInterface $paginable): void { $this->setPaginationLinkObjects($paginable, new LinkObject(), new LinkObject()); } From 17579ff7753e61d065d7e1e3c514c562160804c9 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Fri, 2 Jan 2026 23:50:32 +0100 Subject: [PATCH 6/8] improve type linting for options --- phpstan.neon | 12 +++++++-- src/CollectionDocument.php | 8 +++--- src/Document.php | 8 +++--- src/ErrorsDocument.php | 12 ++++----- src/ResourceDocument.php | 30 +++++++++++------------ src/helpers/RequestParser.php | 12 ++++----- src/helpers/Validator.php | 8 +++--- src/interfaces/DocumentInterface.php | 4 +-- src/interfaces/HasAttributesInterface.php | 4 +-- src/objects/ErrorObject.php | 8 +++--- src/objects/ResourceObject.php | 20 +++++++-------- 11 files changed, 67 insertions(+), 59 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 303d0a25..bbd71f3d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,8 +7,16 @@ parameters: - examples/ typeAliases: - PHPStanTypeAlias_InternalOptions: 'array' - PHPStanTypeAlias_Document: 'array' + PHPStanTypeAlias_CollectionDocumentOptions: 'array{includeContainedResources?: bool}' + PHPStanTypeAlias_DocumentOptions: 'array{encodeOptions?: int, prettyPrint?: bool, contentType?: \alsvanzelf\jsonapi\enums\ContentTypeEnum, array?: ?PHPStanTypeAlias_DocumentArray, json?: ?string, jsonpCallback?: ?string}' + PHPStanTypeAlias_ErrorsDocumentOptions: 'array{includeExceptionTrace?: bool, includeExceptionPrevious?: bool}' + PHPStanTypeAlias_ResourceDocumentOptions: 'array{includeContainedResources?: bool}' + PHPStanTypeAlias_ResourceDocumentAndValidatorOptions: 'array{includeContainedResources?: bool, enforceTypeFieldNamespace?: bool}' + PHPStanTypeAlias_ValidatorOptions: 'array{enforceTypeFieldNamespace?: bool}' + PHPStanTypeAlias_RequestParserOptions: 'array{useNestedIncludePaths?: bool, useAnnotatedSortFields?: bool}' + PHPStanTypeAlias_ErrorObjectOptions: 'array{includeExceptionTrace?: bool, stripExceptionBasePath?: ?string}' + PHPStanTypeAlias_ErrorsDocumentAndErrorObjectOptions: 'array{includeExceptionTrace?: bool, includeExceptionPrevious?: bool, stripExceptionBasePath?: ?string}' + PHPStanTypeAlias_DocumentArray: 'array' treatPhpDocTypesAsCertain: true diff --git a/src/CollectionDocument.php b/src/CollectionDocument.php index d3663039..9262966b 100644 --- a/src/CollectionDocument.php +++ b/src/CollectionDocument.php @@ -21,8 +21,8 @@ class CollectionDocument extends DataDocument implements PaginableInterface, ResourceContainerInterface { /** @var ResourceInterface[] */ protected array $resources = []; - /** @var PHPStanTypeAlias_InternalOptions */ - protected static array $defaults = [ + /** @var PHPStanTypeAlias_CollectionDocumentOptions */ + protected static array $collectionDocumentDefaults = [ /** * add resources inside relationships to /included when adding resources to the collection */ @@ -89,7 +89,7 @@ public function setPaginationLinks( * * adds included resources if found inside the resource's relationships, unless $options['includeContainedResources'] is set to false * - * @param PHPStanTypeAlias_InternalOptions $options {@see CollectionDocument::$defaults} + * @param PHPStanTypeAlias_CollectionDocumentOptions $options {@see CollectionDocument::$collectionDocumentDefaults} * * @throws InputException if the resource is empty */ @@ -98,7 +98,7 @@ public function addResource(ResourceInterface $resource, array $options=[]): voi throw new InputException('does not make sense to add empty resources to a collection'); } - $options = [...self::$defaults, ...$options]; + $options = [...self::$collectionDocumentDefaults, ...$options]; $this->validator->claimUsedResourceIdentifier($resource); diff --git a/src/Document.php b/src/Document.php index f1d599bb..06bd2b51 100644 --- a/src/Document.php +++ b/src/Document.php @@ -47,8 +47,8 @@ abstract class Document implements DocumentInterface, \JsonSerializable, HasLink protected array $extensions = []; /** @var ProfileInterface[] */ protected array $profiles = []; - /** @var PHPStanTypeAlias_InternalOptions */ - protected static array $defaults = [ + /** @var PHPStanTypeAlias_DocumentOptions */ + protected static array $documentDefaults = [ /** * encode to json with these default options */ @@ -260,7 +260,7 @@ public function toArray(): array { * @throws \JsonException */ public function toJson(array $options=[]): string { - $options = [...self::$defaults, ...$options]; + $options = [...self::$documentDefaults, ...$options]; $array = $options['array'] ?? $this->toArray(); @@ -278,7 +278,7 @@ public function toJson(array $options=[]): string { } public function sendResponse(array $options=[]): void { - $options = [...self::$defaults, ...$options]; + $options = [...self::$documentDefaults, ...$options]; if ($this->httpStatusCode === 204) { http_response_code($this->httpStatusCode); diff --git a/src/ErrorsDocument.php b/src/ErrorsDocument.php index 546fbab1..8e6c7bc9 100644 --- a/src/ErrorsDocument.php +++ b/src/ErrorsDocument.php @@ -15,8 +15,8 @@ class ErrorsDocument extends Document { protected array $errors = []; /** @var array> */ protected array $httpStatusCodes; - /** @var PHPStanTypeAlias_InternalOptions */ - protected static array $defaults = [ + /** @var PHPStanTypeAlias_ErrorsDocumentOptions */ + protected static array $errorsDocumentDefaults = [ /** * add the trace of exceptions when adding exceptions * in some cases it might be handy to disable if traces are too big @@ -42,10 +42,10 @@ public function __construct(?ErrorObject $errorObject=null) { */ /** - * @param PHPStanTypeAlias_InternalOptions $options {@see ErrorsDocument::$defaults} + * @param PHPStanTypeAlias_ErrorsDocumentOptions $options {@see ErrorsDocument::$errorsDocumentDefaults} */ public static function fromException(\Throwable $exception, array $options=[]): static { - $options = [...self::$defaults, ...$options]; + $options = [...self::$errorsDocumentDefaults, ...$options]; $errorsDocument = new static(); $errorsDocument->addException($exception, $options); @@ -58,10 +58,10 @@ public static function fromException(\Throwable $exception, array $options=[]): * * recursively adds multiple ErrorObjects if $exception carries a ->getPrevious() * - * @param PHPStanTypeAlias_InternalOptions $options {@see ErrorsDocument::$defaults} + * @param PHPStanTypeAlias_ErrorsDocumentAndErrorObjectOptions $options {@see ErrorsDocument::$errorsDocumentDefaults} */ public function addException(\Throwable $exception, array $options=[]): void { - $options = [...self::$defaults, ...$options]; + $options = [...self::$errorsDocumentDefaults, ...$options]; $this->addErrorObject(ErrorObject::fromException($exception, $options)); diff --git a/src/ResourceDocument.php b/src/ResourceDocument.php index 17bfda7d..6d9580cd 100644 --- a/src/ResourceDocument.php +++ b/src/ResourceDocument.php @@ -27,8 +27,8 @@ */ class ResourceDocument extends DataDocument implements HasAttributesInterface, ResourceInterface { protected ResourceIdentifierObject|ResourceObject $resource; - /** @var PHPStanTypeAlias_InternalOptions */ - protected static array $defaults = [ + /** @var PHPStanTypeAlias_ResourceDocumentOptions */ + protected static array $resourceDocumentDefaults = [ /** * add resources inside relationships to /included when adding resources to the collection */ @@ -51,7 +51,7 @@ public function __construct(?string $type=null, string|int|null $id=null) { */ /** - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceDocument::$defaults} {@see ResourceObject::$defaults} + * @param PHPStanTypeAlias_ResourceDocumentAndValidatorOptions $options {@see ResourceDocument::$resourceDocumentDefaults} {@see ResourceObject::$resourceObjectDefaults} */ public static function fromArray( array $attributes, @@ -66,7 +66,7 @@ public static function fromArray( } /** - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceDocument::$defaults} + * @param PHPStanTypeAlias_ResourceDocumentAndValidatorOptions $options {@see ResourceDocument::$resourceDocumentDefaults} */ public static function fromObject( object $attributes, @@ -82,8 +82,8 @@ public static function fromObject( /** * add key-value pairs to the resource's attributes * - * @param mixed $value objects will be converted using `get_object_vars()` - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceDocument::$defaults} + * @param mixed $value objects will be converted using `get_object_vars()` + * @param PHPStanTypeAlias_ValidatorOptions $options {@see ResourceDocument::$resourceDocumentDefaults} */ public function add(string $key, mixed $value, array $options=[]): void { if ($this->resource instanceof ResourceObject === false) { @@ -101,7 +101,7 @@ public function add(string $key, mixed $value, array $options=[]): void { * @param CollectionDocument|ResourceInterface|ResourceInterface[]|null $relation * @param array $links * @param array $meta - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceDocument::$defaults} + * @param PHPStanTypeAlias_ResourceDocumentOptions $options {@see ResourceDocument::$resourceDocumentDefaults} */ public function addRelationship( string $key, @@ -114,7 +114,7 @@ public function addRelationship( throw new Exception('the resource is an identifier-only object'); } - $options = [...self::$defaults, ...$options]; + $options = [...self::$resourceDocumentDefaults, ...$options]; $relationshipObject = $this->resource->addRelationship($key, $relation, $links, $meta); @@ -188,7 +188,7 @@ public function setLocalId(string|int $localId): void { } /** - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceObject::$defaults} + * @param PHPStanTypeAlias_ValidatorOptions $options {@see ResourceObject::$resourceObjectDefaults} */ public function setAttributesObject(AttributesObject $attributesObject, array $options=[]): void { if ($this->resource instanceof ResourceObject === false) { @@ -203,14 +203,14 @@ public function setAttributesObject(AttributesObject $attributesObject, array $o * * adds included resources if found inside the RelationshipObject, unless $options['includeContainedResources'] is set to false * - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceDocument::$defaults} + * @param PHPStanTypeAlias_ResourceDocumentOptions $options {@see ResourceDocument::$resourceDocumentDefaults} */ public function addRelationshipObject(string $key, RelationshipObject $relationshipObject, array $options=[]): void { if ($this->resource instanceof ResourceObject === false) { throw new Exception('the resource is an identifier-only object'); } - $options = [...self::$defaults, ...$options]; + $options = [...self::$resourceDocumentDefaults, ...$options]; $this->resource->addRelationshipObject($key, $relationshipObject); @@ -224,14 +224,14 @@ public function addRelationshipObject(string $key, RelationshipObject $relations * * adds included resources if found inside the RelationshipObjects inside the RelationshipsObject, unless $options['includeContainedResources'] is set to false * - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceDocument::$defaults} + * @param PHPStanTypeAlias_ResourceDocumentOptions $options {@see ResourceDocument::$resourceDocumentDefaults} */ public function setRelationshipsObject(RelationshipsObject $relationshipsObject, array $options=[]): void { if ($this->resource instanceof ResourceObject === false) { throw new Exception('the resource is an identifier-only object'); } - $options = [...self::$defaults, ...$options]; + $options = [...self::$resourceDocumentDefaults, ...$options]; $this->resource->setRelationshipsObject($relationshipsObject); @@ -249,7 +249,7 @@ public function setRelationshipsObject(RelationshipsObject $relationshipsObject, * * adds included resources if found inside the resource's relationships, unless $options['includeContainedResources'] is set to false * - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceDocument::$defaults} + * @param PHPStanTypeAlias_ResourceDocumentOptions $options {@see ResourceDocument::$resourceDocumentDefaults} * * @throws InputException if the $resource is a ResourceDocument itself */ @@ -260,7 +260,7 @@ public function setPrimaryResource(ResourceInterface $resource, array $options=[ /** @var ResourceIdentifierObject|ResourceObject $resource */ - $options = [...self::$defaults, ...$options]; + $options = [...self::$resourceDocumentDefaults, ...$options]; $this->resource = $resource; diff --git a/src/helpers/RequestParser.php b/src/helpers/RequestParser.php index 24918ca8..e2a951c3 100644 --- a/src/helpers/RequestParser.php +++ b/src/helpers/RequestParser.php @@ -15,8 +15,8 @@ * that might break the class since we use `new static()` */ class RequestParser { - /** @var PHPStanTypeAlias_InternalOptions */ - protected static array $defaults = [ + /** @var PHPStanTypeAlias_RequestParserOptions */ + protected static array $requestParserDefaults = [ /** * reformat the include query parameter paths to nested arrays * this allows easier processing on each step of the chain @@ -111,7 +111,7 @@ public function hasIncludePaths(): bool { * the nested format allows easier processing on each step of the chain * the raw format allows for custom processing * - * @param PHPStanTypeAlias_InternalOptions $options {@see RequestParser::$defaults} + * @param PHPStanTypeAlias_RequestParserOptions $options {@see RequestParser::$requestParserDefaults} * @return array|array */ public function getIncludePaths(array $options=[]): array { @@ -121,7 +121,7 @@ public function getIncludePaths(array $options=[]): array { $includePaths = explode(',', (string) $this->queryParameters['include']); - $options = [...self::$defaults, ...$options]; + $options = [...self::$requestParserDefaults, ...$options]; if ($options['useNestedIncludePaths'] === false) { return $includePaths; } @@ -169,7 +169,7 @@ public function hasSortFields(): bool { * * @todo return some kind of SortFieldObject * - * @param PHPStanTypeAlias_InternalOptions $options {@see RequestParser::$defaults} + * @param PHPStanTypeAlias_RequestParserOptions $options {@see RequestParser::$requestParserDefaults} * @return string[]|arrayqueryParameters['sort']); - $options = [...self::$defaults, ...$options]; + $options = [...self::$requestParserDefaults, ...$options]; if ($options['useAnnotatedSortFields'] === false) { return $fields; } diff --git a/src/helpers/Validator.php b/src/helpers/Validator.php index d1010b61..40f81fac 100644 --- a/src/helpers/Validator.php +++ b/src/helpers/Validator.php @@ -17,8 +17,8 @@ class Validator { protected array $usedFields = []; /** @var array */ protected array $usedResourceIdentifiers = []; - /** @var PHPStanTypeAlias_InternalOptions */ - protected static array $defaults = [ + /** @var PHPStanTypeAlias_ValidatorOptions */ + protected static array $validatorDefaults = [ /** * blocks 'type' as a keyword inside attributes or relationships * the specification doesn't allow this as 'type' is already set at the root of a resource @@ -33,12 +33,12 @@ class Validator { * @see https://jsonapi.org/format/1.1/#document-resource-object-fields * * @param string[] $fieldNames - * @param PHPStanTypeAlias_InternalOptions $options {@see Validator::$defaults} + * @param PHPStanTypeAlias_ValidatorOptions $options {@see Validator::$validatorDefaults} * * @throws DuplicateException */ public function claimUsedFields(array $fieldNames, ObjectContainerEnum $objectContainer, array $options=[]): void { - $options = [...self::$defaults, ...$options]; + $options = [...self::$validatorDefaults, ...$options]; foreach ($fieldNames as $fieldName) { if (isset($this->usedFields[$fieldName]) === false) { diff --git a/src/interfaces/DocumentInterface.php b/src/interfaces/DocumentInterface.php index fc80a4a0..fc400dd9 100644 --- a/src/interfaces/DocumentInterface.php +++ b/src/interfaces/DocumentInterface.php @@ -17,7 +17,7 @@ public function toArray(): array; /** * generate json with the contents of the document, used by {@see ->sendResponse()} * - * @param PHPStanTypeAlias_InternalOptions $options + * @param PHPStanTypeAlias_DocumentOptions $options * * @throws Exception if generating json fails */ @@ -28,7 +28,7 @@ public function toJson(array $options=[]): string; * * @note will set http status code and content type, and echo json * - * @param PHPStanTypeAlias_InternalOptions $options + * @param PHPStanTypeAlias_DocumentOptions $options */ public function sendResponse(array $options=[]): void; } diff --git a/src/interfaces/HasAttributesInterface.php b/src/interfaces/HasAttributesInterface.php index f42b426b..15f37e54 100644 --- a/src/interfaces/HasAttributesInterface.php +++ b/src/interfaces/HasAttributesInterface.php @@ -8,9 +8,9 @@ interface HasAttributesInterface { /** * add key-value pairs to attributes * - * @see ResourceObject::$defaults + * @see Validator::$validatorDefaults * - * @param PHPStanTypeAlias_InternalOptions $options + * @param PHPStanTypeAlias_ValidatorOptions $options */ public function addAttribute(string $key, mixed $value, array $options=[]): void; } diff --git a/src/objects/ErrorObject.php b/src/objects/ErrorObject.php index cd47138c..6ab16982 100644 --- a/src/objects/ErrorObject.php +++ b/src/objects/ErrorObject.php @@ -29,8 +29,8 @@ class ErrorObject extends AbstractObject implements HasLinksInterface, HasMetaIn /** @var array{pointer?: string, parameter?: string, header?: string} */ protected array $source = []; protected MetaObject $meta; - /** @var PHPStanTypeAlias_InternalOptions */ - protected static array $defaults = [ + /** @var PHPStanTypeAlias_ErrorObjectOptions */ + protected static array $errorObjectDefaults = [ /** * add the trace of exceptions when adding exceptions * in some cases it might be handy to disable if traces are too big @@ -71,10 +71,10 @@ public function __construct( */ /** - * @param PHPStanTypeAlias_InternalOptions $options {@see ErrorObject::$defaults} + * @param PHPStanTypeAlias_ErrorObjectOptions $options {@see ErrorObject::$errorObjectDefaults} */ public static function fromException(\Throwable $exception, array $options=[]): static { - $options = [...self::$defaults, ...$options]; + $options = [...self::$errorObjectDefaults, ...$options]; $errorObject = new static(); diff --git a/src/objects/ResourceObject.php b/src/objects/ResourceObject.php index 8f115f59..ac62ff73 100644 --- a/src/objects/ResourceObject.php +++ b/src/objects/ResourceObject.php @@ -23,8 +23,8 @@ class ResourceObject extends ResourceIdentifierObject implements HasAttributesIn protected AttributesObject $attributes; protected RelationshipsObject $relationships; - /** @var PHPStanTypeAlias_InternalOptions */ - protected static array $defaults = [ + /** @var PHPStanTypeAlias_ValidatorOptions */ + protected static array $resourceObjectDefaults = [ /** * blocks 'type' as a keyword inside attributes or relationships * the specification doesn't allow this as 'type' is already set at the root of a resource @@ -42,8 +42,8 @@ class ResourceObject extends ResourceIdentifierObject implements HasAttributesIn * and if $id is null, it is filled with that value * it is common to find it inside, and not doing so will cause an exception * - * @param array $attributes - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceObject::$defaults} + * @param array $attributes + * @param PHPStanTypeAlias_ValidatorOptions $options {@see ResourceObject::$resourceObjectDefaults} */ public static function fromArray(array $attributes, ?string $type=null, string|int|null $id=null, array $options=[]): static { if (isset($attributes['id'])) { @@ -61,7 +61,7 @@ public static function fromArray(array $attributes, ?string $type=null, string|i } /** - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceObject::$defaults} + * @param PHPStanTypeAlias_ValidatorOptions $options {@see ResourceObject::$resourceObjectDefaults} */ public static function fromObject(object $attributes, ?string $type=null, string|int|null $id=null, array $options=[]): static { $array = Converter::objectToArray($attributes); @@ -72,10 +72,10 @@ public static function fromObject(object $attributes, ?string $type=null, string /** * add key-value pairs to attributes * - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceObject::$defaults} + * @param PHPStanTypeAlias_ValidatorOptions $options {@see ResourceObject::$resourceObjectDefaults} */ public function add(string $key, mixed $value, array $options=[]): void { - $options = [...self::$defaults, ...$options]; + $options = [...self::$resourceObjectDefaults, ...$options]; if (isset($this->attributes) === false) { $this->attributes = new AttributesObject(); @@ -90,7 +90,7 @@ public function add(string $key, mixed $value, array $options=[]): void { * @param CollectionDocument|ResourceInterface|ResourceInterface[]|null $relation * @param array $links * @param array $meta - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceObject::$defaults} + * @param PHPStanTypeAlias_ValidatorOptions $options {@see ResourceObject::$resourceObjectDefaults} */ public function addRelationship( string $key, @@ -118,7 +118,7 @@ public function setSelfLink(string $href, array $meta=[]): void { */ /** - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceObject::$defaults} + * @param PHPStanTypeAlias_ValidatorOptions $options {@see ResourceObject::$resourceObjectDefaults} */ public function setAttributesObject(AttributesObject $attributesObject, array $options=[]): void { $newKeys = $attributesObject->getKeys(); @@ -129,7 +129,7 @@ public function setAttributesObject(AttributesObject $attributesObject, array $o } /** - * @param PHPStanTypeAlias_InternalOptions $options {@see ResourceObject::$defaults} + * @param PHPStanTypeAlias_ValidatorOptions $options {@see ResourceObject::$resourceObjectDefaults} * * @throws DuplicateException if the resource is contained as a resource in the relationship */ From 30bd4a955042f60893042064dbe4906b5e342987 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Fri, 2 Jan 2026 23:50:43 +0100 Subject: [PATCH 7/8] pass missed options --- src/ResourceDocument.php | 2 +- src/objects/ResourceObject.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ResourceDocument.php b/src/ResourceDocument.php index 6d9580cd..e60a2b47 100644 --- a/src/ResourceDocument.php +++ b/src/ResourceDocument.php @@ -293,7 +293,7 @@ public function toArray(): array { */ public function addAttribute(string $key, mixed $value, array $options=[]): void { - $this->add($key, $value); + $this->add($key, $value, $options); } /** diff --git a/src/objects/ResourceObject.php b/src/objects/ResourceObject.php index ac62ff73..a92edc7e 100644 --- a/src/objects/ResourceObject.php +++ b/src/objects/ResourceObject.php @@ -185,7 +185,7 @@ public function hasIdentifierPropertiesOnly(): bool { */ public function addAttribute(string $key, mixed $value, array $options=[]): void { - $this->add($key, $value); + $this->add($key, $value, $options); } /** From a61efa31f12566a4e9f2ce77124491932f3c9f61 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 3 Jan 2026 00:35:50 +0100 Subject: [PATCH 8/8] improving linting for phpstan level 6, mostly hinting array --- examples/bootstrap_examples.php | 37 +++++++++++++------ phpstan.neon | 2 +- rector.php | 2 + src/Document.php | 3 ++ src/ResourceDocument.php | 1 + src/exceptions/DuplicateException.php | 2 +- src/exceptions/InputException.php | 2 +- src/helpers/Converter.php | 3 ++ src/objects/AttributesObject.php | 2 + src/objects/RelationshipObject.php | 4 +- src/profiles/CursorPaginationProfile.php | 4 +- tests/CollectionDocumentTest.php | 13 ++++++- tests/ConverterTest.php | 18 ++++++--- tests/ErrorsDocumentTest.php | 5 ++- tests/ExampleOutputTest.php | 26 +++++++------ tests/ValidatorTest.php | 19 +++++++--- .../ExampleTimestampsProfile.php | 2 +- tests/example_output/ExampleUser.php | 10 ++--- .../ExampleVersionExtension.php | 2 +- .../at_members_everywhere.php | 2 +- .../at_members_in_errors.php | 2 +- .../example_output/collection/collection.php | 2 +- .../collection_canonical.php | 2 +- .../cursor_pagination_profile.php | 2 +- .../errors_all_options/errors_all_options.php | 2 +- .../errors_exception_native.php | 2 +- tests/example_output/extension/extension.php | 2 +- .../extension_members_everywhere.php | 2 +- tests/example_output/meta_only/meta_only.php | 2 +- .../null_values/null_values.php | 2 +- tests/example_output/profile/profile.php | 2 +- .../relationship_to_many_document.php | 2 +- .../relationship_to_one_document.php | 2 +- .../relationships/relationships.php | 2 +- .../resource_document_identifier_only.php | 2 +- .../resource_human_api/resource_human_api.php | 2 +- .../resource_links/resource_links.php | 2 +- .../resource_nested_relations.php | 2 +- .../resource_spec_api/resource_spec_api.php | 2 +- .../status_only/status_only.php | 2 +- tests/objects/RelationshipObjectTest.php | 10 ++++- 41 files changed, 135 insertions(+), 74 deletions(-) diff --git a/examples/bootstrap_examples.php b/examples/bootstrap_examples.php index c18cf55c..d461a932 100644 --- a/examples/bootstrap_examples.php +++ b/examples/bootstrap_examples.php @@ -18,7 +18,8 @@ require_once __DIR__.'/../vendor/autoload.php'; class ExampleDataset { - private static $records = [ + /** @var array>> */ + private static array $records = [ 'articles' => [ 1 => [ 'title' => 'JSON:API paints my bikeshed!', @@ -58,7 +59,10 @@ class ExampleDataset { ], ]; - public static function getRecord($type, $id) { + /** + * @return array + */ + public static function getRecord(string $type, int $id): array { if (isset(self::$records[$type][$id]) === false) { throw new \Exception('sorry, we have a limited dataset'); } @@ -66,7 +70,10 @@ public static function getRecord($type, $id) { return self::$records[$type][$id]; } - public static function getEntity($type, $id) { + /** + * @return ExampleUser + */ + public static function getEntity(string $type, int $id): ExampleUser { $record = self::getRecord($type, $id); $user = new ExampleUser($id); @@ -77,11 +84,17 @@ public static function getEntity($type, $id) { return $user; } - public static function findRecords($type) { + /** + * @return array> + */ + public static function findRecords(string $type): array { return self::$records[$type]; } - public static function findEntities($type) { + /** + * @return ExampleUser[] + */ + public static function findEntities(string $type): array { $records = self::findRecords($type); $entities = []; @@ -94,15 +107,15 @@ public static function findEntities($type) { } class ExampleUser { - public $name; - public $heads; - public $unknown; + public string $name; + public int|string $heads; + public mixed $unknown; public function __construct( - public $id, + public int $id, ) {} - function getCurrentLocation() { + function getCurrentLocation(): string { return 'Earth'; } } @@ -124,7 +137,7 @@ public function getNamespace(): string { * optionally helpers for the specific extension */ - public function setVersion(ResourceInterface $resource, $version) { + public function setVersion(ResourceInterface $resource, string $version): void { if ($resource instanceof HasExtensionMembersInterface === false) { throw new \Exception('resource doesn\'t have extension members'); } @@ -155,7 +168,7 @@ public function setTimestamps( ResourceInterface & HasAttributesInterface $resource, ?\DateTimeInterface $created=null, ?\DateTimeInterface $updated=null, - ) { + ): void { $timestamps = []; if ($created !== null) { $timestamps['created'] = $created->format(\DateTime::ISO8601); diff --git a/phpstan.neon b/phpstan.neon index bbd71f3d..3cf6d585 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,6 @@ parameters: # slowly increase - level: 5 + level: 6 paths: - src/ - tests/ diff --git a/rector.php b/rector.php index bba93d87..53d77893 100644 --- a/rector.php +++ b/rector.php @@ -4,6 +4,7 @@ use Rector\Config\RectorConfig; use Rector\Php70\Rector\StmtsAwareInterface\IfIssetToCoalescingRector; +use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector; use Rector\TypeDeclaration\Rector\Class_\AddTestsVoidReturnTypeWhereNoReturnRector; use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector; @@ -18,6 +19,7 @@ ->withRules([ DeclareStrictTypesRector::class, AddTestsVoidReturnTypeWhereNoReturnRector::class, + AddVoidReturnTypeWhereNoReturnRector::class, ]) ->withSkip([ // better explicit readability diff --git a/src/Document.php b/src/Document.php index 06bd2b51..77d8f01b 100644 --- a/src/Document.php +++ b/src/Document.php @@ -299,6 +299,9 @@ public function sendResponse(array $options=[]): void { * JsonSerializable */ + /** + * @return array + */ #[\ReturnTypeWillChange] public function jsonSerialize(): array { return $this->toArray(); diff --git a/src/ResourceDocument.php b/src/ResourceDocument.php index e60a2b47..3a74d14b 100644 --- a/src/ResourceDocument.php +++ b/src/ResourceDocument.php @@ -51,6 +51,7 @@ public function __construct(?string $type=null, string|int|null $id=null) { */ /** + * @param array $attributes * @param PHPStanTypeAlias_ResourceDocumentAndValidatorOptions $options {@see ResourceDocument::$resourceDocumentDefaults} {@see ResourceObject::$resourceObjectDefaults} */ public static function fromArray( diff --git a/src/exceptions/DuplicateException.php b/src/exceptions/DuplicateException.php index a1c5c9a1..a1a7c176 100644 --- a/src/exceptions/DuplicateException.php +++ b/src/exceptions/DuplicateException.php @@ -7,7 +7,7 @@ use alsvanzelf\jsonapi\exceptions\Exception; class DuplicateException extends Exception { - public function __construct($message='', $code=409, $previous=null) { + public function __construct(string $message='', int $code=409, ?\Throwable $previous=null) { parent::__construct($message, $code, $previous); } } diff --git a/src/exceptions/InputException.php b/src/exceptions/InputException.php index ce121c14..4f049cb3 100644 --- a/src/exceptions/InputException.php +++ b/src/exceptions/InputException.php @@ -7,7 +7,7 @@ use alsvanzelf\jsonapi\exceptions\Exception; class InputException extends Exception { - public function __construct($message='', $code=400, $previous=null) { + public function __construct(string $message='', int $code=400, ?\Throwable $previous=null) { parent::__construct($message, $code, $previous); } } diff --git a/src/helpers/Converter.php b/src/helpers/Converter.php index b0c0edd3..0052e086 100644 --- a/src/helpers/Converter.php +++ b/src/helpers/Converter.php @@ -13,6 +13,9 @@ * @internal */ class Converter { + /** + * @return array + */ public static function objectToArray(object $object): array { if ($object instanceof ObjectInterface) { return $object->toArray(); diff --git a/src/objects/AttributesObject.php b/src/objects/AttributesObject.php index 86600ffe..6463e07e 100644 --- a/src/objects/AttributesObject.php +++ b/src/objects/AttributesObject.php @@ -24,6 +24,8 @@ class AttributesObject extends AbstractObject { /** * @note if an `id` is set inside $attributes, it is removed from there * it is common to find it inside, and not doing so will cause an exception + * + * @param array $attributes */ public static function fromArray(array $attributes): static { unset($attributes['id']); diff --git a/src/objects/RelationshipObject.php b/src/objects/RelationshipObject.php index 6d72df4b..4c74e41c 100644 --- a/src/objects/RelationshipObject.php +++ b/src/objects/RelationshipObject.php @@ -117,14 +117,14 @@ public static function fromCollectionDocument(CollectionDocument $collectionDocu } /** - * @param array $meta if given a LinkObject is added, otherwise a link string is added + * @param array $meta if given a LinkObject is added, otherwise a link string is added */ public function setSelfLink(string $href, array $meta=[]): void { $this->addLink('self', $href, $meta); } /** - * @param array $meta if given a LinkObject is added, otherwise a link string is added + * @param array $meta if given a LinkObject is added, otherwise a link string is added */ public function setRelatedLink(string $href, array $meta=[]): void { $this->addLink('related', $href, $meta); diff --git a/src/profiles/CursorPaginationProfile.php b/src/profiles/CursorPaginationProfile.php index 399b5f86..784300b1 100644 --- a/src/profiles/CursorPaginationProfile.php +++ b/src/profiles/CursorPaginationProfile.php @@ -96,7 +96,7 @@ public function setCursor(ResourceInterface & HasMetaInterface $resource, string * * @param PaginableInterface & HasMetaInterface $paginable a CollectionDocument or RelationshipObject */ - public function setCount(PaginableInterface & HasMetaInterface $paginable, ?int $exactTotal=null, ?int $bestGuessTotal=null) { + public function setCount(PaginableInterface & HasMetaInterface $paginable, ?int $exactTotal=null, ?int $bestGuessTotal=null): void { $this->setPaginationMeta($paginable, $exactTotal, $bestGuessTotal); } @@ -114,7 +114,7 @@ public function generatePreviousLink(string $baseOrCurrentUrl, string $beforeCur /** * helper to get generate a correct page[after] link, use to apply manually */ - public function generateNextLink($baseOrCurrentUrl, $afterCursor) { + public function generateNextLink(string $baseOrCurrentUrl, string $afterCursor): string { return $this->setQueryParameter($baseOrCurrentUrl, 'page[after]', $afterCursor); } diff --git a/tests/CollectionDocumentTest.php b/tests/CollectionDocumentTest.php index b05004ab..fe0ce50f 100644 --- a/tests/CollectionDocumentTest.php +++ b/tests/CollectionDocumentTest.php @@ -101,7 +101,7 @@ public function testSetPaginationLinks_HappyPath(): void { } #[DataProvider('dataProviderSetPaginationLinks_IndividualLinks')] - public function testSetPaginationLinks_IndividualLinks($key, $previous, $next, $first, $last): void { + public function testSetPaginationLinks_IndividualLinks(?string $key, ?string $previous, ?string $next, ?string $first, ?string $last): void { $document = new CollectionDocument(); $document->setPaginationLinks($previous, $next, $first, $last); @@ -119,7 +119,16 @@ public function testSetPaginationLinks_IndividualLinks($key, $previous, $next, $ } } - public static function dataProviderSetPaginationLinks_IndividualLinks() { + /** + * @return array + */ + public static function dataProviderSetPaginationLinks_IndividualLinks(): array { return [ ['prev', 'https://jsonapi.org', null, null, null], ['next', null, 'https://jsonapi.org', null, null], diff --git a/tests/ConverterTest.php b/tests/ConverterTest.php index d2af386a..7bf54758 100644 --- a/tests/ConverterTest.php +++ b/tests/ConverterTest.php @@ -29,10 +29,10 @@ public function testObjectToArray_HappyPath(): void { public function testObjectToArray_MethodsAndPrivateProperties(): void { $object = new class { - public $foo = 'bar'; - public $baz = 42; - private $secret = 'value'; // @phpstan-ignore property.onlyWritten - public function method() {} + public string $foo = 'bar'; + public int $baz = 42; + private string $secret = 'value'; // @phpstan-ignore property.onlyWritten + public function method(): void {} }; $array = Converter::objectToArray($object); @@ -58,11 +58,17 @@ public function testObjectToArray_FromInternalObject(): void { } #[DataProvider('dataProviderCamelCaseToWords_HappyPath')] - public function testCamelCaseToWords_HappyPath($camelCase, $expectedOutput): void { + public function testCamelCaseToWords_HappyPath(string $camelCase, string $expectedOutput): void { parent::assertSame($expectedOutput, Converter::camelCaseToWords($camelCase)); } - public static function dataProviderCamelCaseToWords_HappyPath() { + /** + * @return array + */ + public static function dataProviderCamelCaseToWords_HappyPath(): array { return [ ['value', 'value'], ['camelValue', 'camel Value'], diff --git a/tests/ErrorsDocumentTest.php b/tests/ErrorsDocumentTest.php index c7952517..88b9b830 100644 --- a/tests/ErrorsDocumentTest.php +++ b/tests/ErrorsDocumentTest.php @@ -120,7 +120,10 @@ public function testDetermineHttpStatusCode_HappyPath(int $expectedAdvisedErrorC parent::assertSame($expectedAdvisedErrorCode, $advisedErrorCode); } - public static function dataProviderDetermineHttpStatusCode_HappyPath() { + /** + * @return array}> + */ + public static function dataProviderDetermineHttpStatusCode_HappyPath(): array { return [ [422, [422]], [422, [422, 422]], diff --git a/tests/ExampleOutputTest.php b/tests/ExampleOutputTest.php index 75a699f0..db6e9c70 100644 --- a/tests/ExampleOutputTest.php +++ b/tests/ExampleOutputTest.php @@ -6,21 +6,22 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +use alsvanzelf\jsonapi\interfaces\DocumentInterface; /** * @group OutputOnly */ class ExampleOutputTest extends TestCase { - private static $defaults = [ + /** @var PHPStanTypeAlias_DocumentOptions */ + private static array $defaults = [ 'prettyPrint' => true, ]; #[DataProvider('dataProviderTestOutput')] - public function testOutput($generator, $expectedJson, array $options=[], $testName=null): void { - $options = [...self::$defaults, ...$options]; - + public function testOutput(object $generator, ?string $expectedJson, ?string $testName): void { + /** @var DocumentInterface $document */ $document = $generator::createJsonapiDocument(); - $actualJson = $document->toJson($options); + $actualJson = $document->toJson(self::$defaults); // adhere to editorconfig $actualJson = str_replace(' ', "\t", $actualJson).PHP_EOL; @@ -35,7 +36,14 @@ public function testOutput($generator, $expectedJson, array $options=[], $testNa parent::assertSame($expectedJson, $actualJson); } - public static function dataProviderTestOutput() { + /** + * @return array + */ + public static function dataProviderTestOutput(): array { $directories = glob(__DIR__.'/example_output/*', GLOB_ONLYDIR); $testCases = []; @@ -47,16 +55,12 @@ public static function dataProviderTestOutput() { $generator = new $className; $expectedJson = null; - $options = []; if (file_exists($directory.'/'.$testName.'.json')) { $expectedJson = file_get_contents($directory.'/'.$testName.'.json'); } - if (file_exists($directory.'/options.txt')) { - $options = json_decode(file_get_contents($directory.'/options.txt'), associative: true, flags: JSON_THROW_ON_ERROR); - } - $testCases[$testName] = [$generator, $expectedJson, $options, $testName]; + $testCases[$testName] = [$generator, $expectedJson, $testName]; } return $testCases; diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 6fa9235d..f93ce1f3 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -147,11 +147,14 @@ public function testClaimUsedResourceIdentifier_BlocksDuplicates(): void { #[DoesNotPerformAssertions] #[DataProvider('dataProviderCheckMemberName_HappyPath')] - public function testCheckMemberName_HappyPath($memberName): void { + public function testCheckMemberName_HappyPath(string $memberName): void { Validator::checkMemberName($memberName); } - public static function dataProviderCheckMemberName_HappyPath() { + /** + * @return array + */ + public static function dataProviderCheckMemberName_HappyPath(): array { return [ ['foo'], ['f_o'], @@ -162,13 +165,16 @@ public static function dataProviderCheckMemberName_HappyPath() { } #[DataProvider('dataProviderCheckMemberName_InvalidNames')] - public function testCheckMemberName_InvalidNames($memberName): void { + public function testCheckMemberName_InvalidNames(string $memberName): void { $this->expectException(InputException::class); Validator::checkMemberName($memberName); } - public static function dataProviderCheckMemberName_InvalidNames() { + /** + * @return array + */ + public static function dataProviderCheckMemberName_InvalidNames(): array { return [ ['_'], ['-'], @@ -178,10 +184,13 @@ public static function dataProviderCheckMemberName_InvalidNames() { } #[DataProvider('dataProviderCheckHttpStatusCode_HappyPath')] - public function testCheckHttpStatusCode_HappyPath($expectedOutput, $httpStatusCode): void { + public function testCheckHttpStatusCode_HappyPath(bool $expectedOutput, int|string $httpStatusCode): void { parent::assertSame($expectedOutput, Validator::checkHttpStatusCode($httpStatusCode)); } + /** + * @return array + */ public static function dataProviderCheckHttpStatusCode_HappyPath() { return [ [false, 42], diff --git a/tests/example_output/ExampleTimestampsProfile.php b/tests/example_output/ExampleTimestampsProfile.php index 4129a35f..24fc00e5 100644 --- a/tests/example_output/ExampleTimestampsProfile.php +++ b/tests/example_output/ExampleTimestampsProfile.php @@ -18,7 +18,7 @@ public function setTimestamps( ResourceInterface & HasAttributesInterface $resource, ?\DateTimeInterface $created=null, ?\DateTimeInterface $updated=null, - ) { + ): void { $timestamps = []; if ($created !== null) { $timestamps['created'] = $created->format(\DateTime::ISO8601); diff --git a/tests/example_output/ExampleUser.php b/tests/example_output/ExampleUser.php index b01edab3..c01cf47d 100644 --- a/tests/example_output/ExampleUser.php +++ b/tests/example_output/ExampleUser.php @@ -5,15 +5,15 @@ namespace alsvanzelf\jsonapiTests\example_output; class ExampleUser { - public $name; - public $heads; - public $unknown; + public ?string $name = null; + public null|int|string $heads = null; + public mixed $unknown = null; public function __construct( - public $id, + public int $id, ) {} - function getCurrentLocation() { + function getCurrentLocation(): string { return 'Earth'; } } diff --git a/tests/example_output/ExampleVersionExtension.php b/tests/example_output/ExampleVersionExtension.php index dea5cde6..e8061cd4 100644 --- a/tests/example_output/ExampleVersionExtension.php +++ b/tests/example_output/ExampleVersionExtension.php @@ -19,7 +19,7 @@ public function getNamespace(): string { return 'version'; } - public function setVersion(ResourceInterface $resource, $version) { + public function setVersion(ResourceInterface $resource, string $version): void { if ($resource instanceof HasExtensionMembersInterface === false) { throw new InputException('resource doesn\'t have extension members'); } diff --git a/tests/example_output/at_members_everywhere/at_members_everywhere.php b/tests/example_output/at_members_everywhere/at_members_everywhere.php index a6774a0d..49c1c29c 100644 --- a/tests/example_output/at_members_everywhere/at_members_everywhere.php +++ b/tests/example_output/at_members_everywhere/at_members_everywhere.php @@ -17,7 +17,7 @@ use alsvanzelf\jsonapi\objects\ResourceObject; class at_members_everywhere { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { /** * root */ diff --git a/tests/example_output/at_members_in_errors/at_members_in_errors.php b/tests/example_output/at_members_in_errors/at_members_in_errors.php index e937eb88..55783709 100644 --- a/tests/example_output/at_members_in_errors/at_members_in_errors.php +++ b/tests/example_output/at_members_in_errors/at_members_in_errors.php @@ -12,7 +12,7 @@ use alsvanzelf\jsonapi\objects\MetaObject; class at_members_in_errors { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ErrorsDocument { /** * root */ diff --git a/tests/example_output/collection/collection.php b/tests/example_output/collection/collection.php index a3a02659..a7a3e4f4 100644 --- a/tests/example_output/collection/collection.php +++ b/tests/example_output/collection/collection.php @@ -9,7 +9,7 @@ use alsvanzelf\jsonapiTests\example_output\ExampleUser; class collection { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): CollectionDocument { $user1 = new ExampleUser(1); $user1->name = 'Ford Prefect'; $user1->heads = 1; diff --git a/tests/example_output/collection_canonical/collection_canonical.php b/tests/example_output/collection_canonical/collection_canonical.php index a58f9451..3ea39447 100644 --- a/tests/example_output/collection_canonical/collection_canonical.php +++ b/tests/example_output/collection_canonical/collection_canonical.php @@ -8,7 +8,7 @@ use alsvanzelf\jsonapi\objects\ResourceObject; class collection_canonical { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): CollectionDocument { $articleRecords = [ 1 => [ 'title' => 'JSON:API paints my bikeshed!', diff --git a/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.php b/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.php index 77363cdf..ccf26f69 100644 --- a/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.php +++ b/tests/example_output/cursor_pagination_profile/cursor_pagination_profile.php @@ -9,7 +9,7 @@ use alsvanzelf\jsonapi\profiles\CursorPaginationProfile; class cursor_pagination_profile { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): CollectionDocument { $profile = new CursorPaginationProfile(); $user1 = new ResourceObject('user', 1); diff --git a/tests/example_output/errors_all_options/errors_all_options.php b/tests/example_output/errors_all_options/errors_all_options.php index 6d44d3a8..fd9b3dc5 100644 --- a/tests/example_output/errors_all_options/errors_all_options.php +++ b/tests/example_output/errors_all_options/errors_all_options.php @@ -8,7 +8,7 @@ use alsvanzelf\jsonapi\objects\ErrorObject; class errors_all_options { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ErrorsDocument { $errorHumanApi = new ErrorObject('Invalid input', 'Too much options', 'Please, choose a bit less. Consult your ...', 'https://www.example.com/explanation.html', 'https://www.example.com/documentation.html'); $errorSpecApi = new ErrorObject(); diff --git a/tests/example_output/errors_exception_native/errors_exception_native.php b/tests/example_output/errors_exception_native/errors_exception_native.php index 9cc0e54d..933a4d15 100644 --- a/tests/example_output/errors_exception_native/errors_exception_native.php +++ b/tests/example_output/errors_exception_native/errors_exception_native.php @@ -7,7 +7,7 @@ use alsvanzelf\jsonapi\ErrorsDocument; class errors_exception_native { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ErrorsDocument { $exception = new \Exception('unknown user', 404); $options = [ 'includeExceptionTrace' => false, diff --git a/tests/example_output/extension/extension.php b/tests/example_output/extension/extension.php index 8870a337..2648ebb3 100644 --- a/tests/example_output/extension/extension.php +++ b/tests/example_output/extension/extension.php @@ -8,7 +8,7 @@ use alsvanzelf\jsonapiTests\example_output\ExampleVersionExtension; class extension { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { $extension = new ExampleVersionExtension(); $document = new ResourceDocument('user', 42); diff --git a/tests/example_output/extension_members_everywhere/extension_members_everywhere.php b/tests/example_output/extension_members_everywhere/extension_members_everywhere.php index 6790a1f3..9b4779fb 100644 --- a/tests/example_output/extension_members_everywhere/extension_members_everywhere.php +++ b/tests/example_output/extension_members_everywhere/extension_members_everywhere.php @@ -18,7 +18,7 @@ use alsvanzelf\jsonapiTests\example_output\ExampleEverywhereExtension; class extension_members_everywhere { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { $extension = new ExampleEverywhereExtension(); $document = new ResourceDocument('user', 42); diff --git a/tests/example_output/meta_only/meta_only.php b/tests/example_output/meta_only/meta_only.php index 2bf360d4..2aee4e7d 100644 --- a/tests/example_output/meta_only/meta_only.php +++ b/tests/example_output/meta_only/meta_only.php @@ -7,7 +7,7 @@ use alsvanzelf\jsonapi\MetaDocument; class meta_only { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): MetaDocument { $document = new MetaDocument(); $document->addMeta('foo', 'bar'); diff --git a/tests/example_output/null_values/null_values.php b/tests/example_output/null_values/null_values.php index 488910b9..f8fd8e1e 100644 --- a/tests/example_output/null_values/null_values.php +++ b/tests/example_output/null_values/null_values.php @@ -10,7 +10,7 @@ use alsvanzelf\jsonapi\objects\RelationshipObject; class null_values { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { $document = new ResourceDocument('user', 42); $document->add('foo', null); diff --git a/tests/example_output/profile/profile.php b/tests/example_output/profile/profile.php index 56bdde48..0796a3ac 100644 --- a/tests/example_output/profile/profile.php +++ b/tests/example_output/profile/profile.php @@ -8,7 +8,7 @@ use alsvanzelf\jsonapiTests\example_output\ExampleTimestampsProfile; class profile { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { $profile = new ExampleTimestampsProfile(); $document = new ResourceDocument('user', 42); diff --git a/tests/example_output/relationship_to_many_document/relationship_to_many_document.php b/tests/example_output/relationship_to_many_document/relationship_to_many_document.php index d5ea3561..de34adf6 100644 --- a/tests/example_output/relationship_to_many_document/relationship_to_many_document.php +++ b/tests/example_output/relationship_to_many_document/relationship_to_many_document.php @@ -7,7 +7,7 @@ use alsvanzelf\jsonapi\CollectionDocument; class relationship_to_many_document { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): CollectionDocument { $document = new CollectionDocument(); $document->add('tags', 2); $document->add('tags', 3); diff --git a/tests/example_output/relationship_to_one_document/relationship_to_one_document.php b/tests/example_output/relationship_to_one_document/relationship_to_one_document.php index 25979b76..1d5200a8 100644 --- a/tests/example_output/relationship_to_one_document/relationship_to_one_document.php +++ b/tests/example_output/relationship_to_one_document/relationship_to_one_document.php @@ -8,7 +8,7 @@ use alsvanzelf\jsonapi\enums\DocumentLevelEnum; class relationship_to_one_document { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { $document = new ResourceDocument('author', 12); $document->setSelfLink('/articles/1/relationship/author', level: DocumentLevelEnum::Root); diff --git a/tests/example_output/relationships/relationships.php b/tests/example_output/relationships/relationships.php index 4fc9d1ed..8b01eef3 100644 --- a/tests/example_output/relationships/relationships.php +++ b/tests/example_output/relationships/relationships.php @@ -11,7 +11,7 @@ use alsvanzelf\jsonapi\objects\ResourceObject; class relationships { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { $document = new ResourceDocument('user', 1); $ship1Resource = new ResourceObject('ship', 24); diff --git a/tests/example_output/resource_document_identifier_only/resource_document_identifier_only.php b/tests/example_output/resource_document_identifier_only/resource_document_identifier_only.php index 3c960584..72511a9a 100644 --- a/tests/example_output/resource_document_identifier_only/resource_document_identifier_only.php +++ b/tests/example_output/resource_document_identifier_only/resource_document_identifier_only.php @@ -7,7 +7,7 @@ use alsvanzelf\jsonapi\ResourceDocument; class resource_document_identifier_only { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { return new ResourceDocument('user', 42); } } diff --git a/tests/example_output/resource_human_api/resource_human_api.php b/tests/example_output/resource_human_api/resource_human_api.php index 7f7d3284..60a3e83d 100644 --- a/tests/example_output/resource_human_api/resource_human_api.php +++ b/tests/example_output/resource_human_api/resource_human_api.php @@ -8,7 +8,7 @@ use alsvanzelf\jsonapiTests\example_output\ExampleUser; class resource_human_api { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { $user1 = new ExampleUser(1); $user1->name = 'Ford Prefect'; $user1->heads = 1; diff --git a/tests/example_output/resource_links/resource_links.php b/tests/example_output/resource_links/resource_links.php index 8ce95ac7..48caf52e 100644 --- a/tests/example_output/resource_links/resource_links.php +++ b/tests/example_output/resource_links/resource_links.php @@ -9,7 +9,7 @@ use alsvanzelf\jsonapiTests\example_output\ExampleUser; class resource_links { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { $user42 = new ExampleUser(42); $user42->name = 'Zaphod Beeblebrox'; $user42->heads = 2; diff --git a/tests/example_output/resource_nested_relations/resource_nested_relations.php b/tests/example_output/resource_nested_relations/resource_nested_relations.php index 244c5574..a81c99f1 100644 --- a/tests/example_output/resource_nested_relations/resource_nested_relations.php +++ b/tests/example_output/resource_nested_relations/resource_nested_relations.php @@ -9,7 +9,7 @@ use alsvanzelf\jsonapiTests\example_output\ExampleUser; class resource_nested_relations { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { $user42 = new ExampleUser(42); $user42->name = 'Zaphod Beeblebrox'; $user42->heads = 2; diff --git a/tests/example_output/resource_spec_api/resource_spec_api.php b/tests/example_output/resource_spec_api/resource_spec_api.php index 62bcc5e6..fd542c66 100644 --- a/tests/example_output/resource_spec_api/resource_spec_api.php +++ b/tests/example_output/resource_spec_api/resource_spec_api.php @@ -15,7 +15,7 @@ use alsvanzelf\jsonapiTests\example_output\ExampleUser; class resource_spec_api { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): ResourceDocument { $user1 = new ExampleUser(1); $user1->name = 'Ford Prefect'; $user1->heads = 1; diff --git a/tests/example_output/status_only/status_only.php b/tests/example_output/status_only/status_only.php index 209e9cb7..e141fdbf 100644 --- a/tests/example_output/status_only/status_only.php +++ b/tests/example_output/status_only/status_only.php @@ -7,7 +7,7 @@ use alsvanzelf\jsonapi\MetaDocument; class status_only { - public static function createJsonapiDocument() { + public static function createJsonapiDocument(): MetaDocument { $document = new MetaDocument(); $document->setHttpStatusCode(201); diff --git a/tests/objects/RelationshipObjectTest.php b/tests/objects/RelationshipObjectTest.php index e35a0a51..d784bf9f 100644 --- a/tests/objects/RelationshipObjectTest.php +++ b/tests/objects/RelationshipObjectTest.php @@ -311,7 +311,10 @@ public function testIsEmpty_WithExtensionMembers(): void { parent::assertFalse($relationshipObject->isEmpty()); } - private function validateToOneRelationshipArray(array $array) { + /** + * @param array $array + */ + private function validateToOneRelationshipArray(array $array): void { parent::assertNotEmpty($array); parent::assertArrayHasKey('data', $array); parent::assertArrayHasKey('type', $array['data']); @@ -320,7 +323,10 @@ private function validateToOneRelationshipArray(array $array) { parent::assertSame('42', $array['data']['id']); } - private function validateToManyRelationshipArray(array $array) { + /** + * @param array $array + */ + private function validateToManyRelationshipArray(array $array): void { parent::assertNotEmpty($array); parent::assertArrayHasKey('data', $array); parent::assertCount(1, $array['data']);