From faef4f59a165e02931ca3ecb95eb3159f0f6ccc9 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 16:45:32 +0100 Subject: [PATCH 01/16] increase phpstan level --- phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index d1b333d..73eba5c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,6 @@ parameters: # slowly increase - level: 1 + level: 2 paths: - src/ - tests/ From a97f087b75f1e75ae5abdcf3f31e4c07b6a8d9e4 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 16:47:04 +0100 Subject: [PATCH 02/16] fixes in examples and docblocks --- examples/relationships.php | 2 +- src/helpers/Validator.php | 4 ++-- src/interfaces/RecursiveResourceContainerInterface.php | 2 +- src/objects/LinkObject.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/relationships.php b/examples/relationships.php index 483ab1b..bc25dfe 100644 --- a/examples/relationships.php +++ b/examples/relationships.php @@ -78,7 +78,7 @@ $custom_relation = [ 'data' => ['cus' => 'tom'], ]; -$jsonapi->add_relation('custom', $custom_relation); +$jsonapi->addRelationship('custom', $custom_relation); /** * sending the response diff --git a/src/helpers/Validator.php b/src/helpers/Validator.php index 692d473..2c9e573 100644 --- a/src/helpers/Validator.php +++ b/src/helpers/Validator.php @@ -35,7 +35,7 @@ class Validator { * * @see https://jsonapi.org/format/1.1/#document-resource-object-fields * - * @param string[] $fieldName + * @param string[] $fieldNames * @param string $objectContainer one of the Validator::OBJECT_CONTAINER_* constants * @param array $options optional {@see Validator::$defaults} * @@ -65,7 +65,7 @@ public function claimUsedFields(array $fieldNames, $objectContainer, array $opti } /** - * @param string $objectContainer one of the Validator::OBJECT_CONTAINER_* constants + * @param string $objectContainerToClear one of the Validator::OBJECT_CONTAINER_* constants */ public function clearUsedFields($objectContainerToClear) { foreach ($this->usedFields as $fieldName => $containerFound) { diff --git a/src/interfaces/RecursiveResourceContainerInterface.php b/src/interfaces/RecursiveResourceContainerInterface.php index b2d30fd..93c81f1 100644 --- a/src/interfaces/RecursiveResourceContainerInterface.php +++ b/src/interfaces/RecursiveResourceContainerInterface.php @@ -16,7 +16,7 @@ interface RecursiveResourceContainerInterface { * * @internal * - * @return ResourceObjects[] + * @return ResourceObject[] */ public function getNestedContainedResourceObjects(); } diff --git a/src/objects/LinkObject.php b/src/objects/LinkObject.php index 244d103..3c5e11b 100644 --- a/src/objects/LinkObject.php +++ b/src/objects/LinkObject.php @@ -101,7 +101,7 @@ public function setDescribedByLinkObject(LinkObject $describedBy) { } /** - * @param string $friendlyTitle + * @param string $humanTitle */ public function setHumanTitle($humanTitle) { $this->title = $humanTitle; From 9833658fb1a7f066293689d63434610b541218e6 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 16:48:00 +0100 Subject: [PATCH 03/16] less noise, improved clarity on typing --- examples/bootstrap_examples.php | 2 +- examples/errors_exception_native.php | 2 +- src/CollectionDocument.php | 2 +- src/helpers/RequestParser.php | 8 ++++---- .../TestableNonInterfaceServerRequestInterface.php | 5 ++++- tests/helpers/TestableNonInterfaceStreamInterface.php | 8 ++++++-- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/examples/bootstrap_examples.php b/examples/bootstrap_examples.php index 36a7c36..d4de402 100644 --- a/examples/bootstrap_examples.php +++ b/examples/bootstrap_examples.php @@ -55,7 +55,7 @@ class ExampleDataset { public static function getRecord($type, $id) { if (!isset(self::$records[$type][$id])) { - throw new Exception('sorry, we have a limited dataset'); + throw new \Exception('sorry, we have a limited dataset'); } return self::$records[$type][$id]; diff --git a/examples/errors_exception_native.php b/examples/errors_exception_native.php index b187b59..59fde02 100644 --- a/examples/errors_exception_native.php +++ b/examples/errors_exception_native.php @@ -13,7 +13,7 @@ */ try { - throw new Exception('unknown user', 404); + throw new \Exception('unknown user', 404); } catch (Exception $e) { $options = [ diff --git a/src/CollectionDocument.php b/src/CollectionDocument.php index ed139c2..8dab3c1 100644 --- a/src/CollectionDocument.php +++ b/src/CollectionDocument.php @@ -9,8 +9,8 @@ use alsvanzelf\jsonapi\interfaces\RecursiveResourceContainerInterface; use alsvanzelf\jsonapi\interfaces\ResourceContainerInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; -use alsvanzelf\jsonapi\objects\ResourceObject; use alsvanzelf\jsonapi\objects\ResourceIdentifierObject; +use alsvanzelf\jsonapi\objects\ResourceObject; /** * this document is a set of Resources diff --git a/src/helpers/RequestParser.php b/src/helpers/RequestParser.php index 14a8373..d65b180 100644 --- a/src/helpers/RequestParser.php +++ b/src/helpers/RequestParser.php @@ -190,10 +190,10 @@ public function hasSortFields() { * @todo return some kind of SortFieldObject * * @param array $options optional {@see RequestParser::$defaults} - * @return string[]|array[] { - * @var string $field the sort field, without any minus sign for descending sort order - * @var string $order one of the RequestParser::SORT_* constants - * } + * @return string[]|array */ public function getSortFields(array $options=[]) { if ($this->queryParameters['sort'] === '') { diff --git a/tests/helpers/TestableNonInterfaceServerRequestInterface.php b/tests/helpers/TestableNonInterfaceServerRequestInterface.php index 944861b..5794c86 100644 --- a/tests/helpers/TestableNonInterfaceServerRequestInterface.php +++ b/tests/helpers/TestableNonInterfaceServerRequestInterface.php @@ -39,7 +39,10 @@ public function withUploadedFiles(array $uploadedFiles) { return $this; } - public function getParsedBody() {} + public function getParsedBody() { + return null; + } + public function withParsedBody($data) { return $this; } diff --git a/tests/helpers/TestableNonInterfaceStreamInterface.php b/tests/helpers/TestableNonInterfaceStreamInterface.php index 1590964..fb36096 100644 --- a/tests/helpers/TestableNonInterfaceStreamInterface.php +++ b/tests/helpers/TestableNonInterfaceStreamInterface.php @@ -30,9 +30,13 @@ public function __toString() { public function close() {} - public function detach() {} + public function detach() { + return null; + } - public function getSize() {} + public function getSize() { + return null; + } public function tell() { return 0; From cdff53f34ed2d51fbf39f98f19743bd402331c2a Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 17:11:46 +0100 Subject: [PATCH 04/16] make sure we call methods we can assume --- examples/bootstrap_examples.php | 20 +++++-- src/Document.php | 4 +- src/ResourceDocument.php | 53 ++++++++++++------- src/interfaces/HasAttributesInterface.php | 16 ++++++ .../HasExtensionMembersInterface.php | 13 +++++ src/interfaces/HasLinksInterface.php | 18 +++++++ src/interfaces/HasMetaInterface.php | 13 +++++ src/objects/AbstractObject.php | 14 +++++ src/objects/AttributesObject.php | 8 +-- src/objects/ErrorObject.php | 8 ++- src/objects/JsonapiObject.php | 8 +-- src/objects/LinkObject.php | 8 +-- src/objects/LinksObject.php | 8 +-- src/objects/MetaObject.php | 8 +-- src/objects/RelationshipObject.php | 10 ++-- src/objects/RelationshipsObject.php | 8 +-- src/objects/ResourceIdentifierObject.php | 10 ++-- src/objects/ResourceObject.php | 14 ++++- src/profiles/CursorPaginationProfile.php | 33 ++++++++---- .../ExampleTimestampsProfile.php | 9 ++-- .../ExampleVersionExtension.php | 9 +++- 21 files changed, 199 insertions(+), 93 deletions(-) create mode 100644 src/interfaces/HasAttributesInterface.php create mode 100644 src/interfaces/HasExtensionMembersInterface.php create mode 100644 src/interfaces/HasLinksInterface.php create mode 100644 src/interfaces/HasMetaInterface.php create mode 100644 src/objects/AbstractObject.php diff --git a/examples/bootstrap_examples.php b/examples/bootstrap_examples.php index d4de402..3e359dd 100644 --- a/examples/bootstrap_examples.php +++ b/examples/bootstrap_examples.php @@ -3,9 +3,12 @@ use alsvanzelf\jsonapi\Document; use alsvanzelf\jsonapi\ResourceDocument; use alsvanzelf\jsonapi\interfaces\ExtensionInterface; +use alsvanzelf\jsonapi\interfaces\HasAttributesInterface; +use alsvanzelf\jsonapi\interfaces\HasExtensionMembersInterface; use alsvanzelf\jsonapi\interfaces\ProfileInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; use alsvanzelf\jsonapi\objects\ResourceIdentifierObject; +use alsvanzelf\jsonapi\objects\ResourceObject; ini_set('display_errors', 1); error_reporting(-1); @@ -123,10 +126,14 @@ public function getNamespace() { public function setVersion(ResourceInterface $resource, $version) { if ($resource instanceof ResourceDocument) { $resource->getResource()->addExtensionMember($this, 'id', $version); + return; } - else { - $resource->addExtensionMember($this, 'id', $version); + + if ($resource instanceof HasExtensionMembersInterface === false) { + throw new \Exception('resource doesn\'t have extension members'); } + + $resource->addExtensionMember($this, 'id', $version); } } @@ -143,9 +150,12 @@ public function getOfficialLink() { * optionally helpers for the specific profile */ + /** + * @param ResourceInterface&HasAttributesInterface $resource + */ public function setTimestamps(ResourceInterface $resource, ?\DateTimeInterface $created=null, ?\DateTimeInterface $updated=null) { - if ($resource instanceof ResourceIdentifierObject) { - throw new Exception('cannot add attributes to identifier objects'); + if ($resource instanceof HasAttributesInterface === false) { + throw new \Exception('cannot add attributes to identifier objects'); } $timestamps = []; @@ -156,6 +166,6 @@ public function setTimestamps(ResourceInterface $resource, ?\DateTimeInterface $ $timestamps['updated'] = $updated->format(\DateTime::ISO8601); } - $resource->add('timestamps', $timestamps); + $resource->addAttribute('timestamps', $timestamps); } } diff --git a/src/Document.php b/src/Document.php index 7fafe17..0c54821 100644 --- a/src/Document.php +++ b/src/Document.php @@ -13,6 +13,8 @@ use alsvanzelf\jsonapi\helpers\Validator; use alsvanzelf\jsonapi\interfaces\DocumentInterface; use alsvanzelf\jsonapi\interfaces\ExtensionInterface; +use alsvanzelf\jsonapi\interfaces\HasLinksInterface; +use alsvanzelf\jsonapi\interfaces\HasMetaInterface; use alsvanzelf\jsonapi\interfaces\ProfileInterface; use alsvanzelf\jsonapi\objects\JsonapiObject; use alsvanzelf\jsonapi\objects\LinkObject; @@ -22,7 +24,7 @@ /** * @see ResourceDocument, CollectionDocument, ErrorsDocument or MetaDocument */ -abstract class Document implements DocumentInterface, \JsonSerializable { +abstract class Document implements DocumentInterface, \JsonSerializable, HasLinksInterface, HasMetaInterface { use AtMemberManager, ExtensionMemberManager, HttpStatusCodeManager, LinksManager { LinksManager::addLink as linkManagerAddLink; } diff --git a/src/ResourceDocument.php b/src/ResourceDocument.php index 8db9d09..5ee60ae 100644 --- a/src/ResourceDocument.php +++ b/src/ResourceDocument.php @@ -8,6 +8,7 @@ use alsvanzelf\jsonapi\exceptions\Exception; use alsvanzelf\jsonapi\exceptions\InputException; use alsvanzelf\jsonapi\helpers\Converter; +use alsvanzelf\jsonapi\interfaces\HasAttributesInterface; use alsvanzelf\jsonapi\interfaces\RecursiveResourceContainerInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; use alsvanzelf\jsonapi\objects\AttributesObject; @@ -21,7 +22,7 @@ * it can contain other Resources as relationships * a CollectionDocument should be used if the primary Resource is (or can be) a set */ -class ResourceDocument extends DataDocument implements ResourceInterface { +class ResourceDocument extends DataDocument implements HasAttributesInterface, ResourceInterface { /** @var ResourceIdentifierObject|ResourceObject */ protected $resource; /** @var array */ @@ -85,7 +86,9 @@ public static function fromObject($attributes, $type=null, $id=null, array $opti * @param array $options optional {@see ResourceDocument::$defaults} */ public function add($key, $value, array $options=[]) { - $this->ensureResourceObject(); + if ($this->resource instanceof ResourceObject === false) { + throw new Exception('the resource is an identifier-only object'); + } $this->resource->add($key, $value, $options); } @@ -102,7 +105,9 @@ public function add($key, $value, array $options=[]) { * @param array $options optional {@see ResourceDocument::$defaults} */ public function addRelationship($key, $relation, array $links=[], array $meta=[], array $options=[]) { - $this->ensureResourceObject(); + if ($this->resource instanceof ResourceObject === false) { + throw new Exception('the resource is an identifier-only object'); + } $options = array_merge(self::$defaults, $options); @@ -120,7 +125,9 @@ public function addRelationship($key, $relation, array $links=[], array $meta=[] * @param string $level one of the Document::LEVEL_* constants, optional, defaults to Document::LEVEL_ROOT */ public function addLink($key, $href, array $meta=[], $level=Document::LEVEL_ROOT) { - $this->ensureResourceObject(); + if ($this->resource instanceof ResourceObject === false) { + throw new Exception('the resource is an identifier-only object'); + } if ($level === Document::LEVEL_RESOURCE) { $this->resource->addLink($key, $href, $meta); @@ -137,7 +144,9 @@ public function addLink($key, $href, array $meta=[], $level=Document::LEVEL_ROOT * @param array $meta optional */ public function setSelfLink($href, array $meta=[], $level=Document::LEVEL_RESOURCE) { - $this->ensureResourceObject(); + if ($this->resource instanceof ResourceObject === false) { + throw new Exception('the resource is an identifier-only object'); + } if ($level === Document::LEVEL_RESOURCE) { $this->resource->setSelfLink($href, $meta); @@ -191,7 +200,9 @@ public function setLocalId($localId) { * @param array $options optional {@see ResourceObject::$defaults} */ public function setAttributesObject(AttributesObject $attributesObject, array $options=[]) { - $this->ensureResourceObject(); + if ($this->resource instanceof ResourceObject === false) { + throw new Exception('the resource is an identifier-only object'); + } $this->resource->setAttributesObject($attributesObject, $options); } @@ -206,7 +217,9 @@ public function setAttributesObject(AttributesObject $attributesObject, array $o * @param array $options optional {@see ResourceDocument::$defaults} */ public function addRelationshipObject($key, RelationshipObject $relationshipObject, array $options=[]) { - $this->ensureResourceObject(); + if ($this->resource instanceof ResourceObject === false) { + throw new Exception('the resource is an identifier-only object'); + } $options = array_merge(self::$defaults, $options); @@ -226,7 +239,9 @@ public function addRelationshipObject($key, RelationshipObject $relationshipObje * @param array $options optional {@see ResourceDocument::$defaults} */ public function setRelationshipsObject(RelationshipsObject $relationshipsObject, array $options=[]) { - $this->ensureResourceObject(); + if ($this->resource instanceof ResourceObject === false) { + throw new Exception('the resource is an identifier-only object'); + } $options = array_merge(self::$defaults, $options); @@ -269,17 +284,6 @@ public function setPrimaryResource(ResourceInterface $resource, array $options=[ * internal api */ - /** - * @internal - * - * @throws Exception - */ - private function ensureResourceObject() { - if ($this->resource instanceof ResourceObject === false) { - throw new Exception('the resource is an identifier-only object'); - } - } - /** * DocumentInterface */ @@ -298,6 +302,17 @@ public function toArray() { return $array; } + /** + * HasAttributesInterface + */ + + /** + * @inheritDoc + */ + public function addAttribute(string $key, $value, array $options=[]) { + return $this->add($key, $value); + } + /** * ResourceInterface */ diff --git a/src/interfaces/HasAttributesInterface.php b/src/interfaces/HasAttributesInterface.php new file mode 100644 index 0000000..900a76d --- /dev/null +++ b/src/interfaces/HasAttributesInterface.php @@ -0,0 +1,16 @@ +add($key, $value); + } + /** * ResourceInterface */ diff --git a/src/profiles/CursorPaginationProfile.php b/src/profiles/CursorPaginationProfile.php index e95f523..cdfff36 100644 --- a/src/profiles/CursorPaginationProfile.php +++ b/src/profiles/CursorPaginationProfile.php @@ -4,6 +4,9 @@ use alsvanzelf\jsonapi\Document; use alsvanzelf\jsonapi\ResourceDocument; +use alsvanzelf\jsonapi\exceptions\Exception; +use alsvanzelf\jsonapi\interfaces\HasLinksInterface; +use alsvanzelf\jsonapi\interfaces\HasMetaInterface; use alsvanzelf\jsonapi\interfaces\PaginableInterface; use alsvanzelf\jsonapi\interfaces\ProfileInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; @@ -138,11 +141,15 @@ public function generateNextLink($baseOrCurrentUrl, $afterCursor) { * * @see https://jsonapi.org/profiles/ethanresnick/cursor-pagination/#terms-pagination-links * - * @param PaginableInterface $paginable - * @param LinkObject $previousLinkObject - * @param LinkObject $nextLinkObject + * @param PaginableInterface&HasLinksInterface $paginable + * @param LinkObject $previousLinkObject + * @param LinkObject $nextLinkObject */ public function setPaginationLinkObjects(PaginableInterface $paginable, LinkObject $previousLinkObject, LinkObject $nextLinkObject) { + if ($paginable instanceof HasLinksInterface === false) { + throw new Exception('unsupported paginable to set pagination links on'); + } + $paginable->addLinkObject('prev', $previousLinkObject); $paginable->addLinkObject('next', $nextLinkObject); } @@ -182,10 +189,14 @@ public function setPaginationLinkObjectsExplicitlyEmpty(PaginableInterface $pagi * * @see https://jsonapi.org/profiles/ethanresnick/cursor-pagination/#terms-pagination-item-metadata * - * @param ResourceInterface $resource - * @param string $cursor + * @param ResourceInterface&HasMetaInterface $resource + * @param string $cursor */ public function setItemMeta(ResourceInterface $resource, $cursor) { + if ($resource instanceof HasMetaInterface === false) { + throw new Exception('resource doesn\'t support meta'); + } + $metadata = [ 'cursor' => $cursor, ]; @@ -208,12 +219,16 @@ public function setItemMeta(ResourceInterface $resource, $cursor) { * * @see https://jsonapi.org/profiles/ethanresnick/cursor-pagination/#terms-pagination-metadata * - * @param PaginableInterface $paginable - * @param int $exactTotal optional - * @param int $bestGuessTotal optional - * @param boolean $rangeIsTruncated optional, if both after and before are supplied but the items exceed requested or max size + * @param PaginableInterface&HasMetaInterface $paginable + * @param int $exactTotal optional + * @param int $bestGuessTotal optional + * @param boolean $rangeIsTruncated optional, if both after and before are supplied but the items exceed requested or max size */ public function setPaginationMeta(PaginableInterface $paginable, $exactTotal=null, $bestGuessTotal=null, $rangeIsTruncated=null) { + if ($paginable instanceof HasMetaInterface === false) { + throw new \Exception('paginable doesn\'t support meta'); + } + $metadata = []; if ($exactTotal !== null) { diff --git a/tests/example_output/ExampleTimestampsProfile.php b/tests/example_output/ExampleTimestampsProfile.php index ad485bc..326a150 100644 --- a/tests/example_output/ExampleTimestampsProfile.php +++ b/tests/example_output/ExampleTimestampsProfile.php @@ -2,17 +2,20 @@ namespace alsvanzelf\jsonapiTests\example_output; +use alsvanzelf\jsonapi\interfaces\HasAttributesInterface; use alsvanzelf\jsonapi\interfaces\ProfileInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; -use alsvanzelf\jsonapi\objects\ResourceIdentifierObject; class ExampleTimestampsProfile implements ProfileInterface { public function getOfficialLink() { 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 ResourceIdentifierObject) { + if ($resource instanceof HasAttributesInterface === false) { throw new \Exception('cannot add attributes to identifier objects'); } @@ -24,6 +27,6 @@ public function setTimestamps(ResourceInterface $resource, ?\DateTimeInterface $ $timestamps['updated'] = $updated->format(\DateTime::ISO8601); } - $resource->add('timestamps', $timestamps); + $resource->addAttribute('timestamps', $timestamps); } } diff --git a/tests/example_output/ExampleVersionExtension.php b/tests/example_output/ExampleVersionExtension.php index bde454a..71a5097 100644 --- a/tests/example_output/ExampleVersionExtension.php +++ b/tests/example_output/ExampleVersionExtension.php @@ -4,6 +4,7 @@ use alsvanzelf\jsonapi\ResourceDocument; use alsvanzelf\jsonapi\interfaces\ExtensionInterface; +use alsvanzelf\jsonapi\interfaces\HasExtensionMembersInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; class ExampleVersionExtension implements ExtensionInterface { @@ -18,9 +19,13 @@ public function getNamespace() { public function setVersion(ResourceInterface $resource, $version) { if ($resource instanceof ResourceDocument) { $resource->getResource()->addExtensionMember($this, 'id', $version); + return; } - else { - $resource->addExtensionMember($this, 'id', $version); + + if ($resource instanceof HasExtensionMembersInterface === false) { + throw new \Exception('resource doesn\'t have extension members'); } + + $resource->addExtensionMember($this, 'id', $version); } } From 3afb04d74cfb717bd64bdb6e4a7a8c40264ca322 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 17:13:00 +0100 Subject: [PATCH 05/16] increase phpstan level --- phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index 73eba5c..9798c31 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,6 @@ parameters: # slowly increase - level: 2 + level: 3 paths: - src/ - tests/ From b4af33e0bcf63b9a55a38be9ea49229e83e86b47 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 17:58:06 +0100 Subject: [PATCH 06/16] less noise, IDE's don't need this anyway --- src/CollectionDocument.php | 9 --------- src/DataDocument.php | 3 --- src/Document.php | 9 --------- src/ErrorsDocument.php | 3 --- src/MetaDocument.php | 3 --- src/ResourceDocument.php | 9 --------- src/extensions/AtomicOperationsDocument.php | 3 --- src/extensions/AtomicOperationsExtension.php | 6 ------ src/objects/AttributesObject.php | 6 ------ src/objects/ErrorObject.php | 6 ------ src/objects/JsonapiObject.php | 6 ------ src/objects/LinkObject.php | 6 ------ src/objects/LinksArray.php | 6 ------ src/objects/LinksObject.php | 6 ------ src/objects/MetaObject.php | 6 ------ src/objects/RelationshipObject.php | 11 ----------- src/objects/RelationshipsObject.php | 9 --------- src/objects/ResourceIdentifierObject.php | 9 --------- src/objects/ResourceObject.php | 15 --------------- src/profiles/CursorPaginationProfile.php | 3 --- 20 files changed, 134 deletions(-) diff --git a/src/CollectionDocument.php b/src/CollectionDocument.php index 8dab3c1..ba7c458 100644 --- a/src/CollectionDocument.php +++ b/src/CollectionDocument.php @@ -63,9 +63,6 @@ public function add($type, $id, array $attributes=[]) { } } - /** - * @inheritDoc - */ public function setPaginationLinks($previousHref=null, $nextHref=null, $firstHref=null, $lastHref=null) { if ($previousHref !== null) { $this->addLink('prev', $previousHref); @@ -115,9 +112,6 @@ public function addResource(ResourceInterface $resource, array $options=[]) { * DocumentInterface */ - /** - * @inheritDoc - */ public function toArray() { $array = parent::toArray(); @@ -133,9 +127,6 @@ public function toArray() { * ResourceContainerInterface */ - /** - * @inheritDoc - */ public function getContainedResources() { return $this->resources; } diff --git a/src/DataDocument.php b/src/DataDocument.php index ed97d7f..a1667c1 100644 --- a/src/DataDocument.php +++ b/src/DataDocument.php @@ -58,9 +58,6 @@ public function addIncludedResourceObject(ResourceObject ...$resourceObjects) { * DocumentInterface */ - /** - * @inheritDoc - */ public function toArray() { $array = parent::toArray(); diff --git a/src/Document.php b/src/Document.php index 0c54821..31f5120 100644 --- a/src/Document.php +++ b/src/Document.php @@ -261,9 +261,6 @@ public function applyProfile(ProfileInterface $profile) { * DocumentInterface */ - /** - * @inheritDoc - */ public function toArray() { $array = []; @@ -287,9 +284,6 @@ public function toArray() { return $array; } - /** - * @inheritDoc - */ public function toJson(array $options=[]) { $options = array_merge(self::$defaults, $options); @@ -311,9 +305,6 @@ public function toJson(array $options=[]) { return $json; } - /** - * @inheritDoc - */ public function sendResponse(array $options=[]) { $options = array_merge(self::$defaults, $options); diff --git a/src/ErrorsDocument.php b/src/ErrorsDocument.php index 12f2ade..4a8b4d1 100644 --- a/src/ErrorsDocument.php +++ b/src/ErrorsDocument.php @@ -125,9 +125,6 @@ public function addErrorObject(ErrorObject $errorObject) { * DocumentInterface */ - /** - * @inheritDoc - */ public function toArray() { $array = parent::toArray(); diff --git a/src/MetaDocument.php b/src/MetaDocument.php index 3f6e7dc..299ad20 100644 --- a/src/MetaDocument.php +++ b/src/MetaDocument.php @@ -55,9 +55,6 @@ public function add($key, $value, $level=Document::LEVEL_ROOT) { * DocumentInterface */ - /** - * @inheritDoc - */ public function toArray() { $array = parent::toArray(); diff --git a/src/ResourceDocument.php b/src/ResourceDocument.php index 5ee60ae..f36869d 100644 --- a/src/ResourceDocument.php +++ b/src/ResourceDocument.php @@ -288,9 +288,6 @@ public function setPrimaryResource(ResourceInterface $resource, array $options=[ * DocumentInterface */ - /** - * @inheritDoc - */ public function toArray() { $array = parent::toArray(); @@ -306,9 +303,6 @@ public function toArray() { * HasAttributesInterface */ - /** - * @inheritDoc - */ public function addAttribute(string $key, $value, array $options=[]) { return $this->add($key, $value); } @@ -317,9 +311,6 @@ public function addAttribute(string $key, $value, array $options=[]) { * ResourceInterface */ - /** - * @inheritDoc - */ public function getResource($identifierOnly=false) { return $this->resource->getResource($identifierOnly); } diff --git a/src/extensions/AtomicOperationsDocument.php b/src/extensions/AtomicOperationsDocument.php index 1599fd8..fc16caf 100644 --- a/src/extensions/AtomicOperationsDocument.php +++ b/src/extensions/AtomicOperationsDocument.php @@ -39,9 +39,6 @@ public function addResults(ResourceInterface ...$resources) { * DocumentInterface */ - /** - * @inheritDoc - */ public function toArray() { $results = []; foreach ($this->results as $result) { diff --git a/src/extensions/AtomicOperationsExtension.php b/src/extensions/AtomicOperationsExtension.php index b94cfe6..6b6cffd 100644 --- a/src/extensions/AtomicOperationsExtension.php +++ b/src/extensions/AtomicOperationsExtension.php @@ -16,16 +16,10 @@ class AtomicOperationsExtension implements ExtensionInterface { * ExtensionInterface */ - /** - * @inheritDoc - */ public function getOfficialLink() { return 'https://jsonapi.org/ext/atomic/'; } - /** - * @inheritDoc - */ public function getNamespace() { return 'atomic'; } diff --git a/src/objects/AttributesObject.php b/src/objects/AttributesObject.php index b418627..8947110 100644 --- a/src/objects/AttributesObject.php +++ b/src/objects/AttributesObject.php @@ -78,9 +78,6 @@ public function getKeys() { * ObjectInterface */ - /** - * @inheritDoc - */ public function isEmpty() { if ($this->attributes !== []) { return false; @@ -95,9 +92,6 @@ public function isEmpty() { return true; } - /** - * @inheritDoc - */ public function toArray() { $array = []; diff --git a/src/objects/ErrorObject.php b/src/objects/ErrorObject.php index 6453809..b2a3ac6 100644 --- a/src/objects/ErrorObject.php +++ b/src/objects/ErrorObject.php @@ -277,9 +277,6 @@ public function setMetaObject(MetaObject $metaObject) { * ObjectInterface */ - /** - * @inheritDoc - */ public function isEmpty() { if ($this->id !== null) { return false; @@ -315,9 +312,6 @@ public function isEmpty() { return true; } - /** - * @inheritDoc - */ public function toArray() { $array = []; diff --git a/src/objects/JsonapiObject.php b/src/objects/JsonapiObject.php index 7f449e6..f805a00 100644 --- a/src/objects/JsonapiObject.php +++ b/src/objects/JsonapiObject.php @@ -79,9 +79,6 @@ public function setMetaObject(MetaObject $metaObject) { * ObjectInterface */ - /** - * @inheritDoc - */ public function isEmpty() { if ($this->version !== null) { return false; @@ -105,9 +102,6 @@ public function isEmpty() { return true; } - /** - * @inheritDoc - */ public function toArray() { $array = []; diff --git a/src/objects/LinkObject.php b/src/objects/LinkObject.php index a78b643..75f5f1f 100644 --- a/src/objects/LinkObject.php +++ b/src/objects/LinkObject.php @@ -130,9 +130,6 @@ public function setMetaObject(MetaObject $metaObject) { * ObjectInterface */ - /** - * @inheritDoc - */ public function isEmpty() { if ($this->href !== null) { return false; @@ -165,9 +162,6 @@ public function isEmpty() { return true; } - /** - * @inheritDoc - */ public function toArray() { $array = []; diff --git a/src/objects/LinksArray.php b/src/objects/LinksArray.php index d4ae042..182c85a 100644 --- a/src/objects/LinksArray.php +++ b/src/objects/LinksArray.php @@ -77,16 +77,10 @@ public function addLinkObject(LinkObject $linkObject) { * ObjectInterface */ - /** - * @inheritDoc - */ public function isEmpty() { return ($this->links === []); } - /** - * @inheritDoc - */ public function toArray() { $array = []; diff --git a/src/objects/LinksObject.php b/src/objects/LinksObject.php index 7d1558e..6a03567 100644 --- a/src/objects/LinksObject.php +++ b/src/objects/LinksObject.php @@ -160,9 +160,6 @@ public function appendLinkObject($key, LinkObject $linkObject) { * ObjectInterface */ - /** - * @inheritDoc - */ public function isEmpty() { if ($this->links !== []) { return false; @@ -177,9 +174,6 @@ public function isEmpty() { return true; } - /** - * @inheritDoc - */ public function toArray() { $array = []; diff --git a/src/objects/MetaObject.php b/src/objects/MetaObject.php index 112e7fd..e98a2c9 100644 --- a/src/objects/MetaObject.php +++ b/src/objects/MetaObject.php @@ -60,9 +60,6 @@ public function add($key, $value) { * ObjectInterface */ - /** - * @inheritDoc - */ public function isEmpty() { if ($this->meta !== []) { return false; @@ -77,9 +74,6 @@ public function isEmpty() { return true; } - /** - * @inheritDoc - */ public function toArray() { $array = []; diff --git a/src/objects/RelationshipObject.php b/src/objects/RelationshipObject.php index 8428062..6cb0efd 100644 --- a/src/objects/RelationshipObject.php +++ b/src/objects/RelationshipObject.php @@ -145,8 +145,6 @@ public function setRelatedLink($href, array $meta=[]) { } /** - * @inheritDoc - * * @throws InputException if used on a to-one relationship */ public function setPaginationLinks($previousHref=null, $nextHref=null, $firstHref=null, $lastHref=null) { @@ -255,9 +253,6 @@ public function hasResource(ResourceInterface $otherResource) { * ObjectInterface */ - /** - * @inheritDoc - */ public function isEmpty() { if ($this->type === RelationshipObject::TO_ONE && $this->resource !== null) { return false; @@ -281,9 +276,6 @@ public function isEmpty() { return true; } - /** - * @inheritDoc - */ public function toArray() { $array = []; @@ -320,9 +312,6 @@ public function toArray() { * RecursiveResourceContainerInterface */ - /** - * @inheritDoc - */ public function getNestedContainedResourceObjects() { if ($this->isEmpty()) { return []; diff --git a/src/objects/RelationshipsObject.php b/src/objects/RelationshipsObject.php index 43d8964..8c8d0be 100644 --- a/src/objects/RelationshipsObject.php +++ b/src/objects/RelationshipsObject.php @@ -70,9 +70,6 @@ public function getKeys() { * ObjectInterface */ - /** - * @inheritDoc - */ public function isEmpty() { if ($this->relationships !== []) { return false; @@ -87,9 +84,6 @@ public function isEmpty() { return true; } - /** - * @inheritDoc - */ public function toArray() { $array = []; @@ -111,9 +105,6 @@ public function toArray() { * RecursiveResourceContainerInterface */ - /** - * @inheritDoc - */ public function getNestedContainedResourceObjects() { $resourceObjects = []; diff --git a/src/objects/ResourceIdentifierObject.php b/src/objects/ResourceIdentifierObject.php index 67af9eb..b937f8f 100644 --- a/src/objects/ResourceIdentifierObject.php +++ b/src/objects/ResourceIdentifierObject.php @@ -175,9 +175,6 @@ public function getIdentificationKey() { * ObjectInterface */ - /** - * @inheritDoc - */ public function isEmpty() { if ($this->type !== null || $this->primaryId() !== null) { return false; @@ -195,9 +192,6 @@ public function isEmpty() { return true; } - /** - * @inheritDoc - */ public function toArray() { $array = []; @@ -228,9 +222,6 @@ public function toArray() { * ResourceInterface */ - /** - * @inheritDoc - */ public function getResource($identifierOnly=false) { return $this; } diff --git a/src/objects/ResourceObject.php b/src/objects/ResourceObject.php index 4133980..91d2264 100644 --- a/src/objects/ResourceObject.php +++ b/src/objects/ResourceObject.php @@ -197,9 +197,6 @@ public function hasIdentifierPropertiesOnly() { * HasAttributesInterface */ - /** - * @inheritDoc - */ public function addAttribute(string $key, $value, array $options=[]) { return $this->add($key, $value); } @@ -208,9 +205,6 @@ public function addAttribute(string $key, $value, array $options=[]) { * ResourceInterface */ - /** - * @inheritDoc - */ public function getResource($identifierOnly=false) { if ($identifierOnly) { return ResourceIdentifierObject::fromResourceObject($this); @@ -223,9 +217,6 @@ public function getResource($identifierOnly=false) { * ObjectInterface */ - /** - * @inheritDoc - */ public function isEmpty() { if (parent::isEmpty() === false) { return false; @@ -243,9 +234,6 @@ public function isEmpty() { return true; } - /** - * @inheritDoc - */ public function toArray() { $array = parent::toArray(); @@ -266,9 +254,6 @@ public function toArray() { * RecursiveResourceContainerInterface */ - /** - * @inheritDoc - */ public function getNestedContainedResourceObjects() { if ($this->relationships === null) { return []; diff --git a/src/profiles/CursorPaginationProfile.php b/src/profiles/CursorPaginationProfile.php index cdfff36..d4e1072 100644 --- a/src/profiles/CursorPaginationProfile.php +++ b/src/profiles/CursorPaginationProfile.php @@ -400,9 +400,6 @@ private function setQueryParameter($url, $key, $value) { * ProfileInterface */ - /** - * @inheritDoc - */ public function getOfficialLink() { return 'https://jsonapi.org/profiles/ethanresnick/cursor-pagination/'; } From 58adda24d36422fb29bf8d35f467978130bcca0c Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 17:58:39 +0100 Subject: [PATCH 07/16] don't be so strict without a major deprecation --- src/ResourceDocument.php | 2 +- src/interfaces/HasAttributesInterface.php | 3 ++- src/objects/ResourceObject.php | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ResourceDocument.php b/src/ResourceDocument.php index f36869d..7ea0ac8 100644 --- a/src/ResourceDocument.php +++ b/src/ResourceDocument.php @@ -303,7 +303,7 @@ public function toArray() { * HasAttributesInterface */ - public function addAttribute(string $key, $value, array $options=[]) { + public function addAttribute($key, $value, array $options=[]) { return $this->add($key, $value); } diff --git a/src/interfaces/HasAttributesInterface.php b/src/interfaces/HasAttributesInterface.php index 900a76d..889190d 100644 --- a/src/interfaces/HasAttributesInterface.php +++ b/src/interfaces/HasAttributesInterface.php @@ -10,7 +10,8 @@ interface HasAttributesInterface { * * @see ResourceObject::$defaults * + * @param string $key * @param mixed $value */ - public function addAttribute(string $key, $value, array $options=[]); + public function addAttribute($key, $value, array $options=[]); } diff --git a/src/objects/ResourceObject.php b/src/objects/ResourceObject.php index 91d2264..443621b 100644 --- a/src/objects/ResourceObject.php +++ b/src/objects/ResourceObject.php @@ -197,7 +197,7 @@ public function hasIdentifierPropertiesOnly() { * HasAttributesInterface */ - public function addAttribute(string $key, $value, array $options=[]) { + public function addAttribute($key, $value, array $options=[]) { return $this->add($key, $value); } From 74127b83dcde12b8d6913fbd6796028ab69c383e Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 17:59:02 +0100 Subject: [PATCH 08/16] use own specialized exceptions --- src/profiles/CursorPaginationProfile.php | 7 ++++--- tests/example_output/ExampleTimestampsProfile.php | 3 ++- tests/example_output/ExampleVersionExtension.php | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/profiles/CursorPaginationProfile.php b/src/profiles/CursorPaginationProfile.php index d4e1072..349f075 100644 --- a/src/profiles/CursorPaginationProfile.php +++ b/src/profiles/CursorPaginationProfile.php @@ -5,6 +5,7 @@ use alsvanzelf\jsonapi\Document; use alsvanzelf\jsonapi\ResourceDocument; use alsvanzelf\jsonapi\exceptions\Exception; +use alsvanzelf\jsonapi\exceptions\InputException; use alsvanzelf\jsonapi\interfaces\HasLinksInterface; use alsvanzelf\jsonapi\interfaces\HasMetaInterface; use alsvanzelf\jsonapi\interfaces\PaginableInterface; @@ -147,7 +148,7 @@ public function generateNextLink($baseOrCurrentUrl, $afterCursor) { */ public function setPaginationLinkObjects(PaginableInterface $paginable, LinkObject $previousLinkObject, LinkObject $nextLinkObject) { if ($paginable instanceof HasLinksInterface === false) { - throw new Exception('unsupported paginable to set pagination links on'); + throw new InputException('unsupported paginable to set pagination links on'); } $paginable->addLinkObject('prev', $previousLinkObject); @@ -194,7 +195,7 @@ public function setPaginationLinkObjectsExplicitlyEmpty(PaginableInterface $pagi */ public function setItemMeta(ResourceInterface $resource, $cursor) { if ($resource instanceof HasMetaInterface === false) { - throw new Exception('resource doesn\'t support meta'); + throw new InputException('resource doesn\'t support meta'); } $metadata = [ @@ -226,7 +227,7 @@ public function setItemMeta(ResourceInterface $resource, $cursor) { */ public function setPaginationMeta(PaginableInterface $paginable, $exactTotal=null, $bestGuessTotal=null, $rangeIsTruncated=null) { if ($paginable instanceof HasMetaInterface === false) { - throw new \Exception('paginable doesn\'t support meta'); + throw new InputException('paginable doesn\'t support meta'); } $metadata = []; diff --git a/tests/example_output/ExampleTimestampsProfile.php b/tests/example_output/ExampleTimestampsProfile.php index 326a150..db6aaa8 100644 --- a/tests/example_output/ExampleTimestampsProfile.php +++ b/tests/example_output/ExampleTimestampsProfile.php @@ -2,6 +2,7 @@ namespace alsvanzelf\jsonapiTests\example_output; +use alsvanzelf\jsonapi\exceptions\InputException; use alsvanzelf\jsonapi\interfaces\HasAttributesInterface; use alsvanzelf\jsonapi\interfaces\ProfileInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; @@ -16,7 +17,7 @@ public function getOfficialLink() { */ 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'); + throw new InputException('cannot add attributes to identifier objects'); } $timestamps = []; diff --git a/tests/example_output/ExampleVersionExtension.php b/tests/example_output/ExampleVersionExtension.php index 71a5097..66b3982 100644 --- a/tests/example_output/ExampleVersionExtension.php +++ b/tests/example_output/ExampleVersionExtension.php @@ -3,6 +3,7 @@ namespace alsvanzelf\jsonapiTests\example_output; use alsvanzelf\jsonapi\ResourceDocument; +use alsvanzelf\jsonapi\exceptions\InputException; use alsvanzelf\jsonapi\interfaces\ExtensionInterface; use alsvanzelf\jsonapi\interfaces\HasExtensionMembersInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; @@ -23,7 +24,7 @@ public function setVersion(ResourceInterface $resource, $version) { } if ($resource instanceof HasExtensionMembersInterface === false) { - throw new \Exception('resource doesn\'t have extension members'); + throw new InputException('resource doesn\'t have extension members'); } $resource->addExtensionMember($this, 'id', $version); From f5bf3c7840a618cb49fb0a2e25f60ecf5e0ccc2b Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 17:59:19 +0100 Subject: [PATCH 09/16] improve typing --- src/Document.php | 2 +- src/ResourceDocument.php | 2 ++ src/objects/JsonapiObject.php | 2 +- tests/helpers/TestableNonInterfaceUriInterface.php | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Document.php b/src/Document.php index 31f5120..398b8b7 100644 --- a/src/Document.php +++ b/src/Document.php @@ -43,7 +43,7 @@ abstract class Document implements DocumentInterface, \JsonSerializable, HasLink /** @var MetaObject */ protected $meta; - /** @var JsonapiObject */ + /** @var ?JsonapiObject */ protected $jsonapi; /** @var ExtensionInterface[] */ protected $extensions = []; diff --git a/src/ResourceDocument.php b/src/ResourceDocument.php index 7ea0ac8..ff7ebca 100644 --- a/src/ResourceDocument.php +++ b/src/ResourceDocument.php @@ -271,6 +271,8 @@ public function setPrimaryResource(ResourceInterface $resource, array $options=[ throw new InputException('does not make sense to set a document inside a document, use ResourceObject or ResourceIdentifierObject instead'); } + /** @var ResourceIdentifierObject|ResourceObject $resource */ + $options = array_merge(self::$defaults, $options); $this->resource = $resource; diff --git a/src/objects/JsonapiObject.php b/src/objects/JsonapiObject.php index f805a00..d1181b9 100644 --- a/src/objects/JsonapiObject.php +++ b/src/objects/JsonapiObject.php @@ -13,7 +13,7 @@ class JsonapiObject extends AbstractObject { protected $version; /** @var ExtensionInterface[] */ protected $extensions = []; - /** @var ProfileInterface */ + /** @var ProfileInterface[] */ protected $profiles = []; /** @var MetaObject */ protected $meta; diff --git a/tests/helpers/TestableNonInterfaceUriInterface.php b/tests/helpers/TestableNonInterfaceUriInterface.php index e6f6ad0..bdb9af8 100644 --- a/tests/helpers/TestableNonInterfaceUriInterface.php +++ b/tests/helpers/TestableNonInterfaceUriInterface.php @@ -43,7 +43,7 @@ public function getHost() { } public function getPort() { - return ''; + return null; } public function getPath() { From 1e8434ede9661fc1b8e1f020f56d44ee6da7db24 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 17:59:29 +0100 Subject: [PATCH 10/16] better docs --- .../HasExtensionMembersInterface.php | 17 +++++++ src/interfaces/HasLinksInterface.php | 45 +++++++++++++++++++ src/objects/LinksArray.php | 2 + src/objects/LinksObject.php | 4 +- 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/interfaces/HasExtensionMembersInterface.php b/src/interfaces/HasExtensionMembersInterface.php index 8f2407c..81f71a5 100644 --- a/src/interfaces/HasExtensionMembersInterface.php +++ b/src/interfaces/HasExtensionMembersInterface.php @@ -7,7 +7,24 @@ use alsvanzelf\jsonapi\interfaces\ExtensionInterface; interface HasExtensionMembersInterface { + /** + * @param ExtensionInterface $extension + * @param string $key + * @param mixed $value + */ public function addExtensionMember(ExtensionInterface $extension, $key, $value); + + /** + * @internal + * + * @return boolean + */ public function hasExtensionMembers(); + + /** + * @internal + * + * @return array + */ public function getExtensionMembers(); } diff --git a/src/interfaces/HasLinksInterface.php b/src/interfaces/HasLinksInterface.php index ce6d774..eb071c4 100644 --- a/src/interfaces/HasLinksInterface.php +++ b/src/interfaces/HasLinksInterface.php @@ -9,10 +9,55 @@ use alsvanzelf\jsonapi\objects\LinksObject; interface HasLinksInterface { + /** + * set a key containing a link + * + * if $meta is given, a LinkObject is added, otherwise a link string is added + * + * @param string $key + * @param string $href + */ public function addLink($key, $href, array $meta=[]); + + /** + * append a link to a key with an array of links + * + * if $meta is given, a LinkObject is added, otherwise a link string is added + * + * @deprecated array links are not supported anymore {@see ->addLink()} + * + * @param string $key + * @param string $href + */ public function appendLink($key, $href, array $meta=[]); + + /** + * set a key containing a LinkObject + * + * @param string $key + */ public function addLinkObject($key, LinkObject $linkObject); + + /** + * set a key containing a LinksArray + * + * @deprecated array links are not supported anymore {@see ->addLinkObject()} + * + * @param string $key + */ public function addLinksArray($key, LinksArray $linksArray); + + /** + * append a LinkObject to a key with a LinksArray + * + * @deprecated array links are not supported anymore {@see ->addLinkObject()} + * + * @param string $key + */ public function appendLinkObject($key, LinkObject $linkObject); + + /** + * set a LinksObject containing all links + */ public function setLinksObject(LinksObject $linksObject); } diff --git a/src/objects/LinksArray.php b/src/objects/LinksArray.php index 182c85a..a0f27b1 100644 --- a/src/objects/LinksArray.php +++ b/src/objects/LinksArray.php @@ -9,6 +9,8 @@ /** * an array of links (strings and LinkObjects), used for: * - type links in an ErrorObject + * + * @deprecated array links are not supported anymore */ class LinksArray implements ObjectInterface { /** @var array with string|LinkObject */ diff --git a/src/objects/LinksObject.php b/src/objects/LinksObject.php index 6a03567..2422d54 100644 --- a/src/objects/LinksObject.php +++ b/src/objects/LinksObject.php @@ -188,8 +188,8 @@ public function toArray() { if ($link instanceof LinkObject && $link->isEmpty() === false) { $array[$key] = $link->toArray(); } - elseif ($link instanceof LinksArray && $link->isEmpty() === false) { - $array[$key] = $link->toArray(); + elseif ($link instanceof LinksArray && $link->isEmpty() === false) { // @phpstan-ignore method.deprecatedClass + $array[$key] = $link->toArray(); // @phpstan-ignore method.deprecatedClass } elseif ($link instanceof LinkObject && $link->isEmpty()) { $array[$key] = null; From 94bcdc96ee05820820b2184b644bb618160a94cf Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 18:13:57 +0100 Subject: [PATCH 11/16] increase phpstan level --- phpstan.neon | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 9798c31..496ebc7 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,6 @@ parameters: # slowly increase - level: 3 + level: 4 paths: - src/ - tests/ @@ -14,9 +14,11 @@ parameters: - src/resource.php - src/response.php + treatPhpDocTypesAsCertain: false + strictRules: allRules: false - + reportUnmatchedIgnoredErrors: true ignoreErrors: # add cases to ignore because they are too much work for now From 7eb3c3758b7f09aeb2e500df32f41835a0a14974 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 18:14:29 +0100 Subject: [PATCH 12/16] minor cleanups --- src/objects/JsonapiObject.php | 2 +- tests/ConverterTest.php | 2 +- tests/DocumentTest.php | 35 ++++-------------------- tests/ErrorsDocumentTest.php | 5 ---- tests/ExampleOutputTest.php | 1 - tests/objects/ErrorObjectTest.php | 5 ---- tests/objects/RelationshipObjectTest.php | 7 +---- 7 files changed, 8 insertions(+), 49 deletions(-) diff --git a/src/objects/JsonapiObject.php b/src/objects/JsonapiObject.php index d1181b9..d643f39 100644 --- a/src/objects/JsonapiObject.php +++ b/src/objects/JsonapiObject.php @@ -19,7 +19,7 @@ class JsonapiObject extends AbstractObject { protected $meta; /** - * @param string $version one of the Document::JSONAPI_VERSION_* constants, optional, defaults to Document::JSONAPI_VERSION_LATEST + * @param ?string $version one of the Document::JSONAPI_VERSION_* constants, optional, defaults to Document::JSONAPI_VERSION_LATEST */ public function __construct($version=Document::JSONAPI_VERSION_LATEST) { if ($version !== null) { diff --git a/tests/ConverterTest.php b/tests/ConverterTest.php index 0f4f35e..d2d5112 100644 --- a/tests/ConverterTest.php +++ b/tests/ConverterTest.php @@ -127,6 +127,6 @@ public function testMergeProfilesInContentType_HappyPath() { class TestObject { public $foo = 'bar'; public $baz = 42; - private $secret = 'value'; + private $secret = 'value'; // @phpstan-ignore property.onlyWritten public function method() {} } diff --git a/tests/DocumentTest.php b/tests/DocumentTest.php index 8a26204..6e8e51c 100644 --- a/tests/DocumentTest.php +++ b/tests/DocumentTest.php @@ -41,12 +41,7 @@ public function testAddLink_HappyPath() { $this->assertArrayHasKey('links', $array); $this->assertCount(1, $array['links']); $this->assertArrayHasKey('foo', $array['links']); - if (method_exists($this, 'assertIsString')) { - $this->assertIsString($array['links']['foo']); - } - else { - $this->assertIsString($array['links']['foo']); - } + $this->assertIsString($array['links']['foo']); $this->assertSame('https://jsonapi.org', $array['links']['foo']); } @@ -57,12 +52,7 @@ public function testAddLink_WithMeta() { $array = $document->toArray(); $this->assertCount(1, $array['links']); - if (method_exists($this, 'assertIsArray')) { - $this->assertIsArray($array['links']['foo']); - } - else { - $this->assertIsArray($array['links']['foo']); - } + $this->assertIsArray($array['links']['foo']); $this->assertCount(2, $array['links']['foo']); $this->assertArrayHasKey('href', $array['links']['foo']); $this->assertArrayHasKey('meta', $array['links']['foo']); @@ -121,12 +111,7 @@ public function testSetDescribedByLink_HappyPath() { $array = $document->toArray(); $this->assertCount(1, $array['links']); - if (method_exists($this, 'assertIsArray')) { - $this->assertIsArray($array['links']['describedby']); - } - else { - $this->assertIsArray($array['links']['describedby']); - } + $this->assertIsArray($array['links']['describedby']); $this->assertCount(2, $array['links']['describedby']); $this->assertArrayHasKey('href', $array['links']['describedby']); $this->assertArrayHasKey('meta', $array['links']['describedby']); @@ -164,12 +149,7 @@ public function testAddMeta_HappyPath() { $this->assertArrayHasKey('meta', $array); $this->assertCount(1, $array['meta']); $this->assertArrayHasKey('foo', $array['meta']); - if (method_exists($this, 'assertIsString')) { - $this->assertIsString($array['meta']['foo']); - } - else { - $this->assertIsString($array['meta']['foo']); - } + $this->assertIsString($array['meta']['foo']); $this->assertSame('bar', $array['meta']['foo']); } @@ -188,12 +168,7 @@ public function testAddMeta_AtJsonapiLevel() { $this->assertArrayHasKey('meta', $array['jsonapi']); $this->assertCount(1, $array['jsonapi']['meta']); $this->assertArrayHasKey('foo', $array['jsonapi']['meta']); - if (method_exists($this, 'assertIsString')) { - $this->assertIsString($array['jsonapi']['meta']['foo']); - } - else { - $this->assertIsString($array['jsonapi']['meta']['foo']); - } + $this->assertIsString($array['jsonapi']['meta']['foo']); $this->assertSame('bar', $array['jsonapi']['meta']['foo']); } diff --git a/tests/ErrorsDocumentTest.php b/tests/ErrorsDocumentTest.php index d3add16..d7be29b 100644 --- a/tests/ErrorsDocumentTest.php +++ b/tests/ErrorsDocumentTest.php @@ -36,11 +36,6 @@ public function testFromException_HappyPath() { * @group non-php5 */ public function testFromException_AllowsThrowable() { - if (PHP_MAJOR_VERSION < 7) { - $this->markTestSkipped('can not run in php5'); - return; - } - $document = ErrorsDocument::fromException(new \Error('foo', 42)); $array = $document->toArray(); diff --git a/tests/ExampleOutputTest.php b/tests/ExampleOutputTest.php index 6c648a3..4ccd0c9 100644 --- a/tests/ExampleOutputTest.php +++ b/tests/ExampleOutputTest.php @@ -28,7 +28,6 @@ public function testOutput($generator, $expectedJson, array $options=[], $testNa if ($expectedJson === null && file_exists($actualJsonPath) === false) { file_put_contents($actualJsonPath, $actualJson); $this->markTestSkipped('no stored json to test against, try again'); - return; } $this->assertSame($expectedJson, $actualJson); diff --git a/tests/objects/ErrorObjectTest.php b/tests/objects/ErrorObjectTest.php index bf43f57..9463a1c 100644 --- a/tests/objects/ErrorObjectTest.php +++ b/tests/objects/ErrorObjectTest.php @@ -91,11 +91,6 @@ public function testFromException_NamespacedException() { * @group non-php5 */ public function testFromException_NamespacedThrowable() { - if (PHP_MAJOR_VERSION < 7) { - $this->markTestSkipped('can not run in php5'); - return; - } - $exception = new TestError(); $errorObject = ErrorObject::fromException($exception); diff --git a/tests/objects/RelationshipObjectTest.php b/tests/objects/RelationshipObjectTest.php index 9861ac6..a09dec1 100644 --- a/tests/objects/RelationshipObjectTest.php +++ b/tests/objects/RelationshipObjectTest.php @@ -298,12 +298,7 @@ public function testToArray_EmptyResources() { $array = $relationshipObject->toArray(); $this->assertArrayHasKey('data', $array); - if (method_exists($this, 'assertIsArray')) { - $this->assertIsArray($array['data']); - } - else { - $this->assertIsArray($array['data']); - } + $this->assertIsArray($array['data']); } public function testIsEmpty_WithAtMembers() { From 2effeb225f233473af74f99c2609382295aa6065 Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sat, 29 Nov 2025 18:15:15 +0100 Subject: [PATCH 13/16] running phpstan 4! --- phpstan.bonus.neon | 2 +- script/fix | 2 -- script/lint | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/phpstan.bonus.neon b/phpstan.bonus.neon index 7a702e5..ae9db4e 100644 --- a/phpstan.bonus.neon +++ b/phpstan.bonus.neon @@ -3,7 +3,7 @@ includes: - vendor/phpstan/phpstan/conf/bleedingEdge.neon parameters: - level: 9 + level: 10 treatPhpDocTypesAsCertain: true diff --git a/script/fix b/script/fix index 3d07227..c98b17e 100755 --- a/script/fix +++ b/script/fix @@ -81,7 +81,6 @@ if test "$HAS_TYPE" = 1; then $CONSOLE_PREFIX ./vendor/bin/phpcbf $EXTRA_ARGUMENTS $FILE_FILTER elif test "$TYPE" = "rector"; then - echo "${T_WARNING}Rector might not produce correct results until phpstan is on level 4 on the main branch${T_RESET}" $CONSOLE_PREFIX ./vendor/bin/rector process $EXTRA_ARGUMENTS $FILE_FILTER else @@ -93,7 +92,6 @@ else echo -e "${T_BOLD}Running fixes on the changed files (not only git-diff) between the base and the head branch${T_RESET}\n" echo "${T_INFO}Fixing refactors (rector)${T_RESET}" - echo "${T_WARNING}Rector might not produce correct results until phpstan is on level 4 on the main branch${T_RESET}" $CONSOLE_PREFIX ./vendor/bin/rector process $FILE_FILTER || true echo "${T_INFO}Fixing coding standards (phpcs)${T_RESET}" diff --git a/script/lint b/script/lint index d6fe577..41580fc 100755 --- a/script/lint +++ b/script/lint @@ -120,7 +120,6 @@ if test "$HAS_TYPE" = 1; then fi elif test "$TYPE" = "rector"; then - echo "${T_WARNING}Rector might not produce correct results until phpstan is on level 4 on the main branch${T_RESET}" $CONSOLE_PREFIX ./vendor/bin/rector process --dry-run $EXTRA_ARGUMENTS $FILE_FILTER else @@ -161,7 +160,6 @@ else echo -e '\n' echo "${T_INFO}Checking refactors (rector)${T_RESET}" - echo "${T_WARNING}Rector might not produce correct results until phpstan is on level 4 on the main branch${T_RESET}" $CONSOLE_PREFIX ./vendor/bin/rector process --memory-limit=4g --dry-run $FILE_FILTER 2> /dev/null \ && echo "${T_SUCCESS}Success${T_RESET}" From 0772a24e943afc49b4c1ed7ba7b93b520a3f03cc Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sun, 30 Nov 2025 14:57:14 +0100 Subject: [PATCH 14/16] run tests on github actions as well --- .github/workflows/test.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..cd83e0a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: test +on: + pull_request: + paths: + - "**.php" + +jobs: + test: + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + + - name: Setup Composer + uses: ramsey/composer-install@v3 + with: + composer-options: "--no-scripts" + + - name: Run phpunit + run: ./vendor/bin/phpunit From 89f98401255f2e88cc671c821273c61086a034dd Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sun, 30 Nov 2025 14:57:49 +0100 Subject: [PATCH 15/16] add interface to a few missing places --- src/objects/ErrorObject.php | 4 +++- src/objects/JsonapiObject.php | 3 ++- src/objects/LinkObject.php | 3 ++- src/objects/ResourceIdentifierObject.php | 3 ++- src/objects/ResourceObject.php | 3 ++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/objects/ErrorObject.php b/src/objects/ErrorObject.php index b2a3ac6..29e9ae7 100644 --- a/src/objects/ErrorObject.php +++ b/src/objects/ErrorObject.php @@ -8,9 +8,11 @@ use alsvanzelf\jsonapi\helpers\HttpStatusCodeManager; use alsvanzelf\jsonapi\helpers\LinksManager; use alsvanzelf\jsonapi\helpers\Validator; +use alsvanzelf\jsonapi\interfaces\HasLinksInterface; +use alsvanzelf\jsonapi\interfaces\HasMetaInterface; use alsvanzelf\jsonapi\objects\AbstractObject; -class ErrorObject extends AbstractObject { +class ErrorObject extends AbstractObject implements HasLinksInterface, HasMetaInterface { use HttpStatusCodeManager, LinksManager; /** @var string */ diff --git a/src/objects/JsonapiObject.php b/src/objects/JsonapiObject.php index d643f39..3d581d9 100644 --- a/src/objects/JsonapiObject.php +++ b/src/objects/JsonapiObject.php @@ -4,11 +4,12 @@ use alsvanzelf\jsonapi\Document; use alsvanzelf\jsonapi\interfaces\ExtensionInterface; +use alsvanzelf\jsonapi\interfaces\HasMetaInterface; use alsvanzelf\jsonapi\interfaces\ProfileInterface; use alsvanzelf\jsonapi\objects\AbstractObject; use alsvanzelf\jsonapi\objects\MetaObject; -class JsonapiObject extends AbstractObject { +class JsonapiObject extends AbstractObject implements HasMetaInterface { /** @var string */ protected $version; /** @var ExtensionInterface[] */ diff --git a/src/objects/LinkObject.php b/src/objects/LinkObject.php index 75f5f1f..e7a06e0 100644 --- a/src/objects/LinkObject.php +++ b/src/objects/LinkObject.php @@ -2,10 +2,11 @@ namespace alsvanzelf\jsonapi\objects; +use alsvanzelf\jsonapi\interfaces\HasMetaInterface; use alsvanzelf\jsonapi\objects\AbstractObject; use alsvanzelf\jsonapi\objects\MetaObject; -class LinkObject extends AbstractObject { +class LinkObject extends AbstractObject implements HasMetaInterface { /** @var string */ protected $href; /** @var string */ diff --git a/src/objects/ResourceIdentifierObject.php b/src/objects/ResourceIdentifierObject.php index b937f8f..715456d 100644 --- a/src/objects/ResourceIdentifierObject.php +++ b/src/objects/ResourceIdentifierObject.php @@ -5,11 +5,12 @@ use alsvanzelf\jsonapi\exceptions\DuplicateException; use alsvanzelf\jsonapi\exceptions\Exception; use alsvanzelf\jsonapi\helpers\Validator; +use alsvanzelf\jsonapi\interfaces\HasMetaInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; use alsvanzelf\jsonapi\objects\AbstractObject; use alsvanzelf\jsonapi\objects\MetaObject; -class ResourceIdentifierObject extends AbstractObject implements ResourceInterface { +class ResourceIdentifierObject extends AbstractObject implements HasMetaInterface, ResourceInterface { /** @var string */ protected $type; /** @var string */ diff --git a/src/objects/ResourceObject.php b/src/objects/ResourceObject.php index 443621b..e0d59d3 100644 --- a/src/objects/ResourceObject.php +++ b/src/objects/ResourceObject.php @@ -8,6 +8,7 @@ use alsvanzelf\jsonapi\helpers\LinksManager; use alsvanzelf\jsonapi\helpers\Validator; use alsvanzelf\jsonapi\interfaces\HasAttributesInterface; +use alsvanzelf\jsonapi\interfaces\HasLinksInterface; use alsvanzelf\jsonapi\interfaces\RecursiveResourceContainerInterface; use alsvanzelf\jsonapi\interfaces\ResourceInterface; use alsvanzelf\jsonapi\objects\AttributesObject; @@ -15,7 +16,7 @@ use alsvanzelf\jsonapi\objects\RelationshipsObject; use alsvanzelf\jsonapi\objects\ResourceIdentifierObject; -class ResourceObject extends ResourceIdentifierObject implements HasAttributesInterface, RecursiveResourceContainerInterface { +class ResourceObject extends ResourceIdentifierObject implements HasAttributesInterface, HasLinksInterface, RecursiveResourceContainerInterface { use LinksManager; /** @var AttributesObject */ From 5fa5254ca3463f2be69cd5cfe6803795e99a72ca Mon Sep 17 00:00:00 2001 From: Lode Claassen Date: Sun, 30 Nov 2025 15:10:11 +0100 Subject: [PATCH 16/16] cleanup --- examples/bootstrap_examples.php | 12 ++++++------ src/Document.php | 3 ++- src/profiles/CursorPaginationProfile.php | 1 - tests/example_output/ExampleVersionExtension.php | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/bootstrap_examples.php b/examples/bootstrap_examples.php index 3e359dd..33abef7 100644 --- a/examples/bootstrap_examples.php +++ b/examples/bootstrap_examples.php @@ -124,16 +124,16 @@ public function getNamespace() { */ public function setVersion(ResourceInterface $resource, $version) { - if ($resource instanceof ResourceDocument) { - $resource->getResource()->addExtensionMember($this, 'id', $version); - return; - } - if ($resource instanceof HasExtensionMembersInterface === false) { throw new \Exception('resource doesn\'t have extension members'); } - $resource->addExtensionMember($this, 'id', $version); + if ($resource instanceof ResourceDocument) { + $resource->getResource()->addExtensionMember($this, 'id', $version); + } + else { + $resource->addExtensionMember($this, 'id', $version); + } } } diff --git a/src/Document.php b/src/Document.php index 398b8b7..be79032 100644 --- a/src/Document.php +++ b/src/Document.php @@ -13,6 +13,7 @@ use alsvanzelf\jsonapi\helpers\Validator; use alsvanzelf\jsonapi\interfaces\DocumentInterface; use alsvanzelf\jsonapi\interfaces\ExtensionInterface; +use alsvanzelf\jsonapi\interfaces\HasExtensionMembersInterface; use alsvanzelf\jsonapi\interfaces\HasLinksInterface; use alsvanzelf\jsonapi\interfaces\HasMetaInterface; use alsvanzelf\jsonapi\interfaces\ProfileInterface; @@ -24,7 +25,7 @@ /** * @see ResourceDocument, CollectionDocument, ErrorsDocument or MetaDocument */ -abstract class Document implements DocumentInterface, \JsonSerializable, HasLinksInterface, HasMetaInterface { +abstract class Document implements DocumentInterface, \JsonSerializable, HasLinksInterface, HasMetaInterface, HasExtensionMembersInterface { use AtMemberManager, ExtensionMemberManager, HttpStatusCodeManager, LinksManager { LinksManager::addLink as linkManagerAddLink; } diff --git a/src/profiles/CursorPaginationProfile.php b/src/profiles/CursorPaginationProfile.php index 349f075..bed7ad4 100644 --- a/src/profiles/CursorPaginationProfile.php +++ b/src/profiles/CursorPaginationProfile.php @@ -4,7 +4,6 @@ use alsvanzelf\jsonapi\Document; use alsvanzelf\jsonapi\ResourceDocument; -use alsvanzelf\jsonapi\exceptions\Exception; use alsvanzelf\jsonapi\exceptions\InputException; use alsvanzelf\jsonapi\interfaces\HasLinksInterface; use alsvanzelf\jsonapi\interfaces\HasMetaInterface; diff --git a/tests/example_output/ExampleVersionExtension.php b/tests/example_output/ExampleVersionExtension.php index 66b3982..fc80ffb 100644 --- a/tests/example_output/ExampleVersionExtension.php +++ b/tests/example_output/ExampleVersionExtension.php @@ -18,15 +18,15 @@ public function getNamespace() { } public function setVersion(ResourceInterface $resource, $version) { - if ($resource instanceof ResourceDocument) { - $resource->getResource()->addExtensionMember($this, 'id', $version); - return; - } - if ($resource instanceof HasExtensionMembersInterface === false) { throw new InputException('resource doesn\'t have extension members'); } - $resource->addExtensionMember($this, 'id', $version); + if ($resource instanceof ResourceDocument) { + $resource->getResource()->addExtensionMember($this, 'id', $version); + } + else { + $resource->addExtensionMember($this, 'id', $version); + } } }