Skip to content

Commit 69deae9

Browse files
author
developer
committed
Close #214
1 parent 5229bb5 commit 69deae9

File tree

8 files changed

+287
-6
lines changed

8 files changed

+287
-6
lines changed

phpunit.xml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
convertNoticesToExceptions="true"
99
convertWarningsToExceptions="true"
1010
processIsolation="false"
11-
stopOnFailure="false"
12-
syntaxCheck="true">
11+
stopOnFailure="false">
1312
<testsuites>
1413
<testsuite name="All">
1514
<directory>./tests/</directory>
@@ -27,4 +26,4 @@
2726
<log type="coverage-text" target="build/coverage.txt"/>
2827
<log type="coverage-clover" target="build/clover.xml"/>
2928
</logging-->
30-
</phpunit>
29+
</phpunit>

src/Contracts/Schema/ContainerInterface.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ interface ContainerInterface
3030
*/
3131
public function getSchema($resourceObject): ?SchemaInterface;
3232

33+
/**
34+
* If container has a Schema for a given input.
35+
*
36+
* @param mixed $resourceObject
37+
*
38+
* @return bool
39+
*/
40+
public function hasSchema($resourceObject): bool;
41+
3342
/**
3443
* Get schema provider by resource type.
3544
*

src/Encoder/Parser/Parser.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ protected function getCurrentData()
275275
*
276276
* @SuppressWarnings(PHPMD.StaticAccess)
277277
* @SuppressWarnings(PHPMD.ElseExpression)
278+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
278279
*/
279280
protected function analyzeData($data): array
280281
{
@@ -285,11 +286,15 @@ protected function analyzeData($data): array
285286
$isOk = (is_array($data) === true || is_object($data) === true || $data === null);
286287
$isOk ?: Exceptions::throwInvalidArgument('data', $data);
287288

288-
if (is_array($data) === true) {
289+
if ($this->container->hasSchema($data) === true) {
290+
$isCollection = false;
291+
$traversableData = [$data];
292+
} elseif (is_array($data) === true) {
289293
$traversableData = $data;
290294
} elseif ($data instanceof Traversable) {
291295
$traversableData = $data instanceof IteratorAggregate ? $data->getIterator() : $data;
292296
} elseif (is_object($data) === true) {
297+
// normally resources should be handled above but if Schema was not registered for $data we get here
293298
$isCollection = false;
294299
$traversableData = [$data];
295300
} elseif ($data === null) {

src/Schema/Container.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,7 @@ public function __construct(SchemaFactoryInterface $factory, iterable $schemas =
120120
*/
121121
public function register(string $type, $schema): void
122122
{
123-
// Type must be non-empty string
124-
if (empty($type) === true) {
123+
if (empty($type) === true || class_exists($type) === false) {
125124
throw new InvalidArgumentException(_($this->messages[self::MSG_INVALID_TYPE]));
126125
}
127126

@@ -175,6 +174,15 @@ public function getSchema($resource): ?SchemaInterface
175174
return $this->getSchemaByType($resourceType);
176175
}
177176

177+
/**
178+
* @inheritdoc
179+
*/
180+
public function hasSchema($resourceObject): bool
181+
{
182+
return is_object($resourceObject) === true &&
183+
$this->hasProviderMapping($this->getResourceType($resourceObject)) === true;
184+
}
185+
178186
/**
179187
* @inheritdoc
180188
*
@@ -358,6 +366,11 @@ protected function setResourceToJsonTypeMapping(string $resourceType, string $js
358366
*/
359367
protected function getResourceType($resource): string
360368
{
369+
assert(
370+
is_object($resource) === true,
371+
'Unable to get a type of the resource as it is not an object.'
372+
);
373+
361374
return get_class($resource);
362375
}
363376

src/Schema/ResourceIdentifierContainerAdapter.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ public function getSchema($resourceObject): SchemaInterface
5555
return $this->getSchemaAdapter($this->container->getSchema($resourceObject));
5656
}
5757

58+
/**
59+
* @inheritdoc
60+
*/
61+
public function hasSchema($resourceObject): bool
62+
{
63+
return $this->container->hasSchema($resourceObject);
64+
}
65+
5866
/**
5967
* @inheritdoc
6068
*/

tests/Data/AuthorCModel.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php namespace Neomerx\Tests\JsonApi\Data;
2+
3+
/**
4+
* Copyright 2015-2018 [email protected]
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
use ArrayAccess;
20+
use ArrayIterator;
21+
use IteratorAggregate;
22+
23+
/**
24+
* This model emulates a resource which looks like an array (Traversable) with its properties.
25+
* The library should successfully distinguish arrays from resources that may act as arrays.
26+
*
27+
* A real word example of such a resource is Yii CModel https://www.yiiframework.com/doc/api/1.1/CModel
28+
*
29+
* @package Neomerx\Tests\JsonApi
30+
*/
31+
class AuthorCModel implements ArrayAccess, IteratorAggregate
32+
{
33+
const ATTRIBUTE_ID = 'author_id';
34+
const ATTRIBUTE_FIRST_NAME = 'first_name';
35+
const ATTRIBUTE_LAST_NAME = 'last_name';
36+
const LINK_COMMENTS = 'comments';
37+
38+
/**
39+
* Resource properties.
40+
*
41+
* @var array
42+
*/
43+
private $properties = [];
44+
45+
/**
46+
* @param string $identity
47+
* @param string $firstName
48+
* @param string $lastName
49+
* @param array|null $comments
50+
*/
51+
public function __construct(string $identity, string $firstName, string $lastName, array $comments = null)
52+
{
53+
$this[self::ATTRIBUTE_ID] = $identity;
54+
$this[self::ATTRIBUTE_FIRST_NAME] = $firstName;
55+
$this[self::ATTRIBUTE_LAST_NAME] = $lastName;
56+
$this[self::LINK_COMMENTS] = $comments;
57+
}
58+
59+
/**
60+
* @inheritdoc
61+
*/
62+
public function getIterator()
63+
{
64+
return new ArrayIterator($this->properties);
65+
}
66+
67+
/**
68+
* @inheritdoc
69+
*/
70+
public function offsetExists($offset)
71+
{
72+
return array_key_exists($offset, $this->properties);
73+
}
74+
75+
/**
76+
* @inheritdoc
77+
*/
78+
public function offsetGet($offset)
79+
{
80+
return $this->properties[$offset];
81+
}
82+
83+
/**
84+
* @inheritdoc
85+
*/
86+
public function offsetSet($offset, $value)
87+
{
88+
$this->properties[$offset] = $value;
89+
}
90+
91+
/**
92+
* @inheritdoc
93+
*/
94+
public function offsetUnset($offset)
95+
{
96+
unset($this->properties[$offset]);
97+
}
98+
}

tests/Data/AuthorCModelSchema.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php namespace Neomerx\Tests\JsonApi\Data;
2+
3+
/**
4+
* Copyright 2015-2018 [email protected]
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
use Neomerx\JsonApi\Contracts\Document\LinkInterface;
20+
21+
/**
22+
* @package Neomerx\Tests\JsonApi
23+
*/
24+
class AuthorCModelSchema extends DevSchema
25+
{
26+
/**
27+
* @inheritdoc
28+
*/
29+
protected $resourceType = 'people';
30+
31+
/**
32+
* @inheritdoc
33+
*/
34+
public function getId($author): ?string
35+
{
36+
return $author[AuthorCModel::ATTRIBUTE_ID];
37+
}
38+
39+
/**
40+
* @inheritdoc
41+
*/
42+
public function getAttributes($author, array $fieldKeysFilter = null): ?array
43+
{
44+
return [
45+
AuthorCModel::ATTRIBUTE_FIRST_NAME => $author[AuthorCModel::ATTRIBUTE_FIRST_NAME],
46+
AuthorCModel::ATTRIBUTE_LAST_NAME => $author[AuthorCModel::ATTRIBUTE_LAST_NAME],
47+
];
48+
}
49+
50+
/**
51+
* @inheritdoc
52+
*/
53+
public function getRelationships($author, bool $isPrimary, array $includeRelationships): ?array
54+
{
55+
assert($author instanceof AuthorCModel);
56+
57+
if (($isPrimary && $this->isIsLinksInPrimary()) || (!$isPrimary && $this->isIsLinksInIncluded())) {
58+
$selfLink = $this->getRelationshipSelfLink($author, AuthorCModel::LINK_COMMENTS);
59+
$links = [
60+
AuthorCModel::LINK_COMMENTS => [
61+
self::LINKS => [LinkInterface::SELF => $selfLink],
62+
self::SHOW_DATA => false,
63+
],
64+
];
65+
} else {
66+
$links = [
67+
AuthorCModel::LINK_COMMENTS => [
68+
// closures for data are supported as well
69+
self::DATA => function () use ($author) {
70+
return isset($author[AuthorCModel::LINK_COMMENTS]) ?
71+
$author[AuthorCModel::LINK_COMMENTS] : null;
72+
},
73+
],
74+
];
75+
}
76+
77+
// NOTE: The line(s) below for testing purposes only. Not for production.
78+
$this->fixLinks($author, $links);
79+
80+
return $links;
81+
}
82+
}

tests/Encoder/EncodeSimpleObjectsTest.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
use Neomerx\JsonApi\Factories\Factory;
2424
use Neomerx\Tests\JsonApi\BaseTestCase;
2525
use Neomerx\Tests\JsonApi\Data\Author;
26+
use Neomerx\Tests\JsonApi\Data\AuthorCModel;
27+
use Neomerx\Tests\JsonApi\Data\AuthorCModelSchema;
2628
use Neomerx\Tests\JsonApi\Data\AuthorSchema;
2729
use Neomerx\Tests\JsonApi\Data\Collection;
2830
use Neomerx\Tests\JsonApi\Data\Comment;
@@ -825,4 +827,69 @@ public function testEncodingSimilarRelationships(): void
825827

826828
$this->assertEquals($expected, $actual);
827829
}
830+
831+
/**
832+
* Test encode array-based objects.
833+
*
834+
* @see https://github.com/neomerx/json-api/pull/214
835+
*/
836+
public function testEncodeArrayBasedObject(): void
837+
{
838+
$author = new AuthorCModel(9, 'Dan', 'Gebhardt');
839+
$encoder = Encoder::instance([
840+
AuthorCModel::class => AuthorCModelSchema::class,
841+
], $this->encoderOptions);
842+
843+
$actual = $encoder->encodeData($author);
844+
845+
$expected = <<<EOL
846+
{
847+
"data" : {
848+
"type" : "people",
849+
"id" : "9",
850+
"attributes" : {
851+
"first_name" : "Dan",
852+
"last_name" : "Gebhardt"
853+
},
854+
"relationships" : {
855+
"comments" : { "data" : null }
856+
},
857+
"links" : {
858+
"self" : "http://example.com/people/9"
859+
}
860+
}
861+
}
862+
EOL;
863+
// remove formatting from 'expected'
864+
$expected = json_encode(json_decode($expected));
865+
866+
$this->assertEquals($expected, $actual);
867+
868+
// same but as array
869+
870+
$actual = $encoder->encodeData([$author]);
871+
872+
$expected = <<<EOL
873+
{
874+
"data" : [{
875+
"type" : "people",
876+
"id" : "9",
877+
"attributes" : {
878+
"first_name" : "Dan",
879+
"last_name" : "Gebhardt"
880+
},
881+
"relationships" : {
882+
"comments" : { "data" : null }
883+
},
884+
"links" : {
885+
"self" : "http://example.com/people/9"
886+
}
887+
}]
888+
}
889+
EOL;
890+
// remove formatting from 'expected'
891+
$expected = json_encode(json_decode($expected));
892+
893+
$this->assertEquals($expected, $actual);
894+
}
828895
}

0 commit comments

Comments
 (0)