diff --git a/phpunit.xml b/phpunit.xml index 91db872c..9ba52f2a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,8 +8,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" - syntaxCheck="true"> + stopOnFailure="false"> ./tests/ @@ -27,4 +26,4 @@ - \ No newline at end of file + diff --git a/src/Contracts/Schema/ContainerInterface.php b/src/Contracts/Schema/ContainerInterface.php index 9e0ffbb2..ba799d7f 100644 --- a/src/Contracts/Schema/ContainerInterface.php +++ b/src/Contracts/Schema/ContainerInterface.php @@ -30,6 +30,15 @@ interface ContainerInterface */ public function getSchema($resourceObject): ?SchemaInterface; + /** + * If container has a Schema for a given input. + * + * @param mixed $resourceObject + * + * @return bool + */ + public function hasSchema($resourceObject): bool; + /** * Get schema provider by resource type. * diff --git a/src/Encoder/Parser/Parser.php b/src/Encoder/Parser/Parser.php index 8accf9f1..4977d6d6 100644 --- a/src/Encoder/Parser/Parser.php +++ b/src/Encoder/Parser/Parser.php @@ -275,6 +275,7 @@ protected function getCurrentData() * * @SuppressWarnings(PHPMD.StaticAccess) * @SuppressWarnings(PHPMD.ElseExpression) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function analyzeData($data): array { @@ -285,11 +286,15 @@ protected function analyzeData($data): array $isOk = (is_array($data) === true || is_object($data) === true || $data === null); $isOk ?: Exceptions::throwInvalidArgument('data', $data); - if (is_array($data) === true) { + if ($this->container->hasSchema($data) === true) { + $isCollection = false; + $traversableData = [$data]; + } elseif (is_array($data) === true) { $traversableData = $data; } elseif ($data instanceof Traversable) { $traversableData = $data instanceof IteratorAggregate ? $data->getIterator() : $data; } elseif (is_object($data) === true) { + // normally resources should be handled above but if Schema was not registered for $data we get here $isCollection = false; $traversableData = [$data]; } elseif ($data === null) { diff --git a/src/Schema/Container.php b/src/Schema/Container.php index 40ebced5..29773088 100644 --- a/src/Schema/Container.php +++ b/src/Schema/Container.php @@ -120,8 +120,7 @@ public function __construct(SchemaFactoryInterface $factory, iterable $schemas = */ public function register(string $type, $schema): void { - // Type must be non-empty string - if (empty($type) === true) { + if (empty($type) === true || class_exists($type) === false) { throw new InvalidArgumentException(_($this->messages[self::MSG_INVALID_TYPE])); } @@ -175,6 +174,15 @@ public function getSchema($resource): ?SchemaInterface return $this->getSchemaByType($resourceType); } + /** + * @inheritdoc + */ + public function hasSchema($resourceObject): bool + { + return is_object($resourceObject) === true && + $this->hasProviderMapping($this->getResourceType($resourceObject)) === true; + } + /** * @inheritdoc * @@ -358,6 +366,11 @@ protected function setResourceToJsonTypeMapping(string $resourceType, string $js */ protected function getResourceType($resource): string { + assert( + is_object($resource) === true, + 'Unable to get a type of the resource as it is not an object.' + ); + return get_class($resource); } diff --git a/src/Schema/ResourceIdentifierContainerAdapter.php b/src/Schema/ResourceIdentifierContainerAdapter.php index 699e7b4d..843a2a65 100644 --- a/src/Schema/ResourceIdentifierContainerAdapter.php +++ b/src/Schema/ResourceIdentifierContainerAdapter.php @@ -55,6 +55,14 @@ public function getSchema($resourceObject): SchemaInterface return $this->getSchemaAdapter($this->container->getSchema($resourceObject)); } + /** + * @inheritdoc + */ + public function hasSchema($resourceObject): bool + { + return $this->container->hasSchema($resourceObject); + } + /** * @inheritdoc */ diff --git a/tests/Data/AuthorCModel.php b/tests/Data/AuthorCModel.php new file mode 100644 index 00000000..07c4bf13 --- /dev/null +++ b/tests/Data/AuthorCModel.php @@ -0,0 +1,98 @@ +properties); + } + + /** + * @inheritdoc + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->properties); + } + + /** + * @inheritdoc + */ + public function offsetGet($offset) + { + return $this->properties[$offset]; + } + + /** + * @inheritdoc + */ + public function offsetSet($offset, $value) + { + $this->properties[$offset] = $value; + } + + /** + * @inheritdoc + */ + public function offsetUnset($offset) + { + unset($this->properties[$offset]); + } +} diff --git a/tests/Data/AuthorCModelSchema.php b/tests/Data/AuthorCModelSchema.php new file mode 100644 index 00000000..d83d7b92 --- /dev/null +++ b/tests/Data/AuthorCModelSchema.php @@ -0,0 +1,82 @@ + $author[AuthorCModel::ATTRIBUTE_FIRST_NAME], + AuthorCModel::ATTRIBUTE_LAST_NAME => $author[AuthorCModel::ATTRIBUTE_LAST_NAME], + ]; + } + + /** + * @inheritdoc + */ + public function getRelationships($author, bool $isPrimary, array $includeRelationships): ?array + { + assert($author instanceof AuthorCModel); + + if (($isPrimary && $this->isIsLinksInPrimary()) || (!$isPrimary && $this->isIsLinksInIncluded())) { + $selfLink = $this->getRelationshipSelfLink($author, AuthorCModel::LINK_COMMENTS); + $links = [ + AuthorCModel::LINK_COMMENTS => [ + self::LINKS => [LinkInterface::SELF => $selfLink], + self::SHOW_DATA => false, + ], + ]; + } else { + $links = [ + AuthorCModel::LINK_COMMENTS => [ + // closures for data are supported as well + self::DATA => function () use ($author) { + return isset($author[AuthorCModel::LINK_COMMENTS]) ? + $author[AuthorCModel::LINK_COMMENTS] : null; + }, + ], + ]; + } + + // NOTE: The line(s) below for testing purposes only. Not for production. + $this->fixLinks($author, $links); + + return $links; + } +} diff --git a/tests/Encoder/EncodeSimpleObjectsTest.php b/tests/Encoder/EncodeSimpleObjectsTest.php index 4cb0e58e..4be91f42 100644 --- a/tests/Encoder/EncodeSimpleObjectsTest.php +++ b/tests/Encoder/EncodeSimpleObjectsTest.php @@ -23,6 +23,8 @@ use Neomerx\JsonApi\Factories\Factory; use Neomerx\Tests\JsonApi\BaseTestCase; use Neomerx\Tests\JsonApi\Data\Author; +use Neomerx\Tests\JsonApi\Data\AuthorCModel; +use Neomerx\Tests\JsonApi\Data\AuthorCModelSchema; use Neomerx\Tests\JsonApi\Data\AuthorSchema; use Neomerx\Tests\JsonApi\Data\Collection; use Neomerx\Tests\JsonApi\Data\Comment; @@ -825,4 +827,69 @@ public function testEncodingSimilarRelationships(): void $this->assertEquals($expected, $actual); } + + /** + * Test encode array-based objects. + * + * @see https://github.com/neomerx/json-api/pull/214 + */ + public function testEncodeArrayBasedObject(): void + { + $author = new AuthorCModel(9, 'Dan', 'Gebhardt'); + $encoder = Encoder::instance([ + AuthorCModel::class => AuthorCModelSchema::class, + ], $this->encoderOptions); + + $actual = $encoder->encodeData($author); + + $expected = <<assertEquals($expected, $actual); + + // same but as array + + $actual = $encoder->encodeData([$author]); + + $expected = <<assertEquals($expected, $actual); + } }