Completed
Push — master ( 52b2ea...d5cfd4 )
by Jasper
12s queued 10s
created

DocumentParser::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 6
c 0
b 0
f 0
nc 1
nop 6
dl 0
loc 14
ccs 7
cts 7
cp 1
crap 1
rs 10
1
<?php
2
3
namespace Swis\JsonApi\Client\Parsers;
4
5
use Swis\JsonApi\Client\Collection;
6
use Swis\JsonApi\Client\CollectionDocument;
7
use Swis\JsonApi\Client\Document;
8
use Swis\JsonApi\Client\Exceptions\ValidationException;
9
use Swis\JsonApi\Client\Interfaces\DocumentInterface;
10
use Swis\JsonApi\Client\Interfaces\DocumentParserInterface;
11
use Swis\JsonApi\Client\Interfaces\ItemInterface;
12
use Swis\JsonApi\Client\Interfaces\ManyRelationInterface;
13
use Swis\JsonApi\Client\Interfaces\OneRelationInterface;
14
use Swis\JsonApi\Client\ItemDocument;
15
16
class DocumentParser implements DocumentParserInterface
17
{
18
    /**
19
     * @var \Swis\JsonApi\Client\Parsers\ItemParser
20
     */
21
    private $itemParser;
22
23
    /**
24
     * @var \Swis\JsonApi\Client\Parsers\CollectionParser
25
     */
26
    private $collectionParser;
27
28
    /**
29
     * @var \Swis\JsonApi\Client\Parsers\ErrorCollectionParser
30
     */
31
    private $errorCollectionParser;
32
33
    /**
34
     * @var \Swis\JsonApi\Client\Parsers\LinksParser
35
     */
36
    private $linksParser;
37
38
    /**
39
     * @var \Swis\JsonApi\Client\Parsers\JsonapiParser
40
     */
41
    private $jsonapiParser;
42
43
    /**
44
     * @var \Swis\JsonApi\Client\Parsers\MetaParser
45
     */
46
    private $metaParser;
47
48
    /**
49
     * @param \Swis\JsonApi\Client\Parsers\ItemParser            $itemParser
50
     * @param \Swis\JsonApi\Client\Parsers\CollectionParser      $collectionParser
51
     * @param \Swis\JsonApi\Client\Parsers\ErrorCollectionParser $errorCollectionParser
52
     * @param \Swis\JsonApi\Client\Parsers\LinksParser           $linksParser
53
     * @param \Swis\JsonApi\Client\Parsers\JsonapiParser         $jsonapiParser
54
     * @param \Swis\JsonApi\Client\Parsers\MetaParser            $metaParser
55
     */
56 156
    public function __construct(
57
        ItemParser $itemParser,
58
        CollectionParser $collectionParser,
59
        ErrorCollectionParser $errorCollectionParser,
60
        LinksParser $linksParser,
61
        JsonapiParser $jsonapiParser,
62
        MetaParser $metaParser
63
    ) {
64 156
        $this->itemParser = $itemParser;
65 156
        $this->collectionParser = $collectionParser;
66 156
        $this->errorCollectionParser = $errorCollectionParser;
67 156
        $this->linksParser = $linksParser;
68 156
        $this->jsonapiParser = $jsonapiParser;
69 156
        $this->metaParser = $metaParser;
70 156
    }
71
72
    /**
73
     * @param string $json
74
     *
75
     * @return \Swis\JsonApi\Client\Interfaces\DocumentInterface
76
     */
77 156
    public function parse(string $json): DocumentInterface
78
    {
79 156
        $data = $this->decodeJson($json);
80
81 144
        if (!is_object($data)) {
82 36
            throw new ValidationException(sprintf('Document has to be an object, "%s" given.', gettype($data)));
83
        }
84 108
        if (!property_exists($data, 'data') && !property_exists($data, 'errors') && !property_exists($data, 'meta')) {
85 6
            throw new ValidationException('Document MUST contain at least one of the following properties: `data`, `errors`, `meta`.');
86
        }
87 102
        if (property_exists($data, 'data') && property_exists($data, 'errors')) {
88 6
            throw new ValidationException('The properties `data` and `errors` MUST NOT coexist in Document.');
89
        }
90 96
        if (!property_exists($data, 'data') && property_exists($data, 'included')) {
91 6
            throw new ValidationException('If Document does not contain a `data` property, the `included` property MUST NOT be present either.');
92
        }
93 90
        if (property_exists($data, 'data') && !is_object($data->data) && !is_array($data->data) && $data->data !== null) {
94 24
            throw new ValidationException(sprintf('Document property "data" has to be null, an array or an object, "%s" given.', gettype($data)));
95
        }
96
97 66
        $document = $this->getDocument($data);
98
99 66
        if (property_exists($data, 'links')) {
100 6
            $document->setLinks($this->linksParser->parse($data->links));
101
        }
102
103 66
        if (property_exists($data, 'errors')) {
104 6
            $document->setErrors($this->errorCollectionParser->parse($data->errors));
105
        }
106
107 66
        if (property_exists($data, 'meta')) {
108 12
            $document->setMeta($this->metaParser->parse($data->meta));
109
        }
110
111 66
        if (property_exists($data, 'jsonapi')) {
112 6
            $document->setJsonapi($this->jsonapiParser->parse($data->jsonapi));
113
        }
114
115 66
        return $document;
116
    }
117
118
    /**
119
     * @param string $json
120
     *
121
     * @return mixed
122
     */
123 156
    private function decodeJson(string $json)
124
    {
125 156
        $data = json_decode($json, false);
126
127 156
        if (json_last_error() !== JSON_ERROR_NONE) {
128 12
            throw new ValidationException(sprintf('Unable to parse JSON data: %s', json_last_error_msg()), json_last_error());
129
        }
130
131 144
        return $data;
132
    }
133
134
    /**
135
     * @param mixed $data
136
     *
137
     * @return \Swis\JsonApi\Client\Interfaces\DocumentInterface
138
     */
139 66
    private function getDocument($data): DocumentInterface
140
    {
141 66
        if (!property_exists($data, 'data') || $data->data === null) {
142 12
            return new Document();
143
        }
144
145 54
        if (is_array($data->data)) {
146 36
            $document = (new CollectionDocument())
147 36
                ->setData($this->collectionParser->parse($data->data));
148
        } else {
149 18
            $document = (new ItemDocument())
150 18
                ->setData($this->itemParser->parse($data->data));
151
        }
152
153 54
        if (property_exists($data, 'included')) {
154 18
            $document->setIncluded($this->collectionParser->parse($data->included));
155
        }
156
157 54
        $allItems = Collection::wrap($document->getData())
158 54
            ->concat($document->getIncluded());
159
160 54
        $this->linkRelationships($allItems);
161
162 54
        return $document;
163
    }
164
165
    /**
166
     * @param \Swis\JsonApi\Client\Collection $items
167
     */
168 54
    private function linkRelationships(Collection $items): void
169
    {
170
        // N.B. We reverse the items to make sure the first item in the collection takes precedence
171 54
        $keyedItems = $items->reverse()->keyBy(
172 18
            function (ItemInterface $item) {
173 30
                return $this->getItemKey($item);
174 54
            }
175
        );
176
177 54
        $items->each(
178 18
            function (ItemInterface $item) use ($keyedItems) {
179 30
                foreach ($item->getRelations() as $name => $relation) {
180 12
                    if ($relation instanceof OneRelationInterface) {
181
                        /** @var \Swis\JsonApi\Client\Interfaces\ItemInterface $relatedItem */
182 6
                        $relatedItem = $relation->getIncluded();
183
184 6
                        $includedItem = $this->getItem($keyedItems, $relatedItem);
185 6
                        if ($includedItem !== null) {
186 6
                            $relation->associate($includedItem);
187
                        }
188 6
                    } elseif ($relation instanceof ManyRelationInterface) {
189
                        /** @var \Swis\JsonApi\Client\Collection $relatedCollection */
190 6
                        $relatedCollection = $relation->getIncluded();
191
192
                        /** @var \Swis\JsonApi\Client\Interfaces\ItemInterface $relatedItem */
193 6
                        foreach ($relatedCollection as $key => $relatedItem) {
194 6
                            $includedItem = $this->getItem($keyedItems, $relatedItem);
195 6
                            if ($includedItem !== null) {
196 7
                                $relatedCollection->put($key, $includedItem);
197
                            }
198
                        }
199
                    }
200
                }
201 54
            }
202
        );
203 54
    }
204
205
    /**
206
     * @param \Swis\JsonApi\Client\Collection               $included
207
     * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item
208
     *
209
     * @return \Swis\JsonApi\Client\Interfaces\ItemInterface|null
210
     */
211 12
    private function getItem(Collection $included, ItemInterface $item): ?ItemInterface
212
    {
213 12
        return $included->get($this->getItemKey($item));
214
    }
215
216
    /**
217
     * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item
218
     *
219
     * @return string
220
     */
221 30
    private function getItemKey(ItemInterface $item): string
222
    {
223 30
        return sprintf('%s:%s', $item->getType(), $item->getId());
224
    }
225
}
226