1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Swis\JsonApi\Client\Parsers; |
||
6 | |||
7 | use Swis\JsonApi\Client\Collection; |
||
8 | use Swis\JsonApi\Client\Exceptions\ValidationException; |
||
9 | use Swis\JsonApi\Client\Interfaces\DataInterface; |
||
10 | use Swis\JsonApi\Client\Interfaces\ItemInterface; |
||
11 | use Swis\JsonApi\Client\Interfaces\TypeMapperInterface; |
||
12 | use Swis\JsonApi\Client\Item; |
||
13 | |||
14 | /** |
||
15 | * @internal |
||
16 | */ |
||
17 | class ItemParser |
||
18 | { |
||
19 | private TypeMapperInterface $typeMapper; |
||
20 | |||
21 | private LinksParser $linksParser; |
||
22 | |||
23 | private MetaParser $metaParser; |
||
24 | |||
25 | 444 | public function __construct(TypeMapperInterface $typeMapper, LinksParser $linksParser, MetaParser $metaParser) |
|
26 | { |
||
27 | 444 | $this->typeMapper = $typeMapper; |
|
28 | 444 | $this->linksParser = $linksParser; |
|
29 | 444 | $this->metaParser = $metaParser; |
|
30 | 222 | } |
|
31 | |||
32 | /** |
||
33 | * @param mixed $data |
||
34 | */ |
||
35 | 328 | public function parse($data): ItemInterface |
|
36 | { |
||
37 | 328 | if (! is_object($data)) { |
|
38 | 24 | throw new ValidationException(sprintf('Resource MUST be an object, "%s" given.', gettype($data))); |
|
39 | } |
||
40 | 304 | if (! property_exists($data, 'type')) { |
|
41 | 4 | throw new ValidationException('Resource object MUST contain a type.'); |
|
42 | } |
||
43 | 300 | if (! property_exists($data, 'id')) { |
|
44 | 4 | throw new ValidationException('Resource object MUST contain an id.'); |
|
45 | } |
||
46 | 296 | if (! is_string($data->type)) { |
|
47 | 24 | throw new ValidationException(sprintf('Resource property "type" MUST be a string, "%s" given.', gettype($data->type))); |
|
48 | } |
||
49 | 272 | if (! is_string($data->id) && ! is_numeric($data->id)) { |
|
50 | 16 | throw new ValidationException(sprintf('Resource property "id" MUST be a string, "%s" given.', gettype($data->id))); |
|
51 | } |
||
52 | 256 | if (property_exists($data, 'attributes')) { |
|
53 | 132 | if (! is_object($data->attributes)) { |
|
54 | 24 | throw new ValidationException(sprintf('Resource property "attributes" MUST be an object, "%s" given.', gettype($data->attributes))); |
|
55 | } |
||
56 | 108 | if (property_exists($data->attributes, 'type') || property_exists($data->attributes, 'id') || property_exists($data->attributes, 'relationships') || property_exists($data->attributes, 'links')) { |
|
57 | 16 | throw new ValidationException('These properties are not allowed in attributes: `type`, `id`, `relationships`, `links`.'); |
|
58 | } |
||
59 | } |
||
60 | |||
61 | 216 | $item = $this->getItemInstance($data->type); |
|
62 | 216 | $item->setId((string) $data->id); |
|
63 | |||
64 | 216 | if (property_exists($data, 'attributes')) { |
|
65 | 92 | $item->fill((array) $data->attributes); |
|
66 | } |
||
67 | |||
68 | 216 | if (property_exists($data, 'relationships')) { |
|
69 | 200 | $this->setRelations($item, $data->relationships); |
|
70 | } |
||
71 | |||
72 | 88 | if (property_exists($data, 'links')) { |
|
73 | 52 | $item->setLinks($this->linksParser->parse($data->links, LinksParser::SOURCE_ITEM)); |
|
74 | } |
||
75 | |||
76 | 88 | if (property_exists($data, 'meta')) { |
|
77 | 56 | $item->setMeta($this->metaParser->parse($data->meta)); |
|
78 | } |
||
79 | |||
80 | 88 | return $item; |
|
81 | } |
||
82 | |||
83 | 216 | private function getItemInstance(string $type): ItemInterface |
|
84 | { |
||
85 | 216 | if ($this->typeMapper->hasMapping($type)) { |
|
86 | 192 | return $this->typeMapper->getMapping($type); |
|
87 | } |
||
88 | |||
89 | 24 | return (new Item)->setType($type); |
|
90 | } |
||
91 | |||
92 | /** |
||
93 | * @param mixed $data |
||
94 | */ |
||
95 | 200 | private function setRelations(ItemInterface $item, $data): void |
|
96 | { |
||
97 | 200 | if (! is_object($data)) { |
|
98 | 24 | throw new ValidationException(sprintf('Resource property "relationships" MUST be an object, "%s" given.', gettype($data))); |
|
99 | } |
||
100 | 176 | if (property_exists($data, 'type') || property_exists($data, 'id')) { |
|
101 | 8 | throw new ValidationException('These properties are not allowed in relationships: `type`, `id`.'); |
|
102 | } |
||
103 | |||
104 | 168 | foreach ($data as $name => $relationship) { |
|
105 | 168 | if ($item->hasAttribute($name)) { |
|
106 | 4 | throw new ValidationException(sprintf('Relationship "%s" cannot be set because it already exists in Resource object.', $name)); |
|
107 | } |
||
108 | 164 | if (! is_object($relationship)) { |
|
109 | 24 | throw new ValidationException(sprintf('Relationship MUST be an object, "%s" given.', gettype($relationship))); |
|
110 | } |
||
111 | 140 | if (! property_exists($relationship, 'links') && ! property_exists($relationship, 'data') && ! property_exists($relationship, 'meta')) { |
|
112 | 4 | throw new ValidationException('Relationship object MUST contain at least one of the following properties: `links`, `data`, `meta`.'); |
|
113 | } |
||
114 | |||
115 | 136 | $value = false; |
|
116 | 136 | if (property_exists($relationship, 'data')) { |
|
117 | 136 | $value = null; |
|
118 | 136 | if ($relationship->data !== null) { |
|
119 | 132 | $value = $this->parseRelationshipData($relationship->data); |
|
120 | } |
||
121 | } |
||
122 | |||
123 | 72 | $links = null; |
|
124 | 72 | if (property_exists($relationship, 'links')) { |
|
125 | 52 | $links = $this->linksParser->parse($relationship->links, LinksParser::SOURCE_RELATIONSHIP); |
|
126 | } |
||
127 | |||
128 | 72 | $meta = null; |
|
129 | 72 | if (property_exists($relationship, 'meta')) { |
|
130 | 52 | $meta = $this->metaParser->parse($relationship->meta); |
|
131 | } |
||
132 | |||
133 | 72 | $item->setRelation($name, $value, $links, $meta); |
|
134 | } |
||
135 | 36 | } |
|
136 | |||
137 | /** |
||
138 | * @param mixed $data |
||
139 | * |
||
140 | * @throws \InvalidArgumentException |
||
141 | */ |
||
142 | 132 | private function parseRelationshipData($data): DataInterface |
|
143 | { |
||
144 | 132 | if (is_array($data)) { |
|
145 | 60 | return Collection::make($data) |
|
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
146 | 60 | ->map(fn ($identifier) => $this->parseRelationshipData($identifier)); |
|
147 | } |
||
148 | |||
149 | 128 | if (! is_object($data)) { |
|
150 | 16 | throw new ValidationException(sprintf('ResourceIdentifier MUST be an object, "%s" given.', gettype($data))); |
|
151 | } |
||
152 | 112 | if (! property_exists($data, 'type')) { |
|
153 | 4 | throw new ValidationException('ResourceIdentifier object MUST contain a type.'); |
|
154 | } |
||
155 | 108 | if (! property_exists($data, 'id')) { |
|
156 | 4 | throw new ValidationException('ResourceIdentifier object MUST contain an id.'); |
|
157 | } |
||
158 | 104 | if (! is_string($data->type)) { |
|
159 | 24 | throw new ValidationException(sprintf('ResourceIdentifier property "type" MUST be a string, "%s" given.', gettype($data->type))); |
|
160 | } |
||
161 | 80 | if (! is_string($data->id) && ! is_numeric($data->id)) { |
|
162 | 16 | throw new ValidationException(sprintf('ResourceIdentifier property "id" MUST be a string, "%s" given.', gettype($data->id))); |
|
163 | } |
||
164 | |||
165 | 64 | $item = $this->getItemInstance($data->type)->setId((string) $data->id); |
|
166 | 64 | if (property_exists($data, 'meta')) { |
|
167 | 56 | $item->setMeta($this->metaParser->parse($data->meta)); |
|
168 | } |
||
169 | |||
170 | 64 | return $item; |
|
171 | } |
||
172 | } |
||
173 |