Completed
Push — master ( 8877af...028b89 )
by Christopher
15s
created

CynicDeserialiser::isEntryProcessed()   C

Complexity

Conditions 11
Paths 9

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 31
rs 5.2653
cc 11
eloc 20
nc 9
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace POData\ObjectModel;
4
5
use POData\Providers\Metadata\IMetadataProvider;
6
use POData\Providers\Metadata\ResourceEntityType;
7
use POData\Providers\Metadata\ResourceSet;
8
use POData\Providers\Metadata\Type\IType;
9
use POData\Providers\ProvidersWrapper;
10
use POData\Providers\Query\IQueryProvider;
11
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor;
12
13
class CynicDeserialiser
14
{
15
    /**
16
     * @var IMetadataProvider
17
     */
18
    private $metaProvider;
19
20
    /**
21
     * @var ProvidersWrapper
22
     */
23
    private $wrapper;
24
25
    /**
26
     * @var ModelDeserialiser
27
     */
28
    private $cereal;
29
30
    public function __construct(IMetadataProvider $meta, ProvidersWrapper $wrapper)
31
    {
32
        $this->metaProvider = $meta;
33
        $this->wrapper = $wrapper;
34
        $this->cereal = new ModelDeserialiser();
35
    }
36
37
    /**
38
     * @param ODataEntry $payload
39
     */
40
    public function processPayload(ODataEntry &$payload)
41
    {
42
        assert($this->isEntryOK($payload));
43
        list($sourceSet, $source) = $this->processEntryContent($payload);
44
        assert($sourceSet instanceof ResourceSet);
45
        $numLinks = count($payload->links);
46
        for ($i = 0; $i < $numLinks; $i++) {
47
            $this->processLink($payload->links[$i], $sourceSet, $source);
48
        }
49
        assert($this->isEntryProcessed($payload));
50
        return $source;
51
    }
52
53
    protected function isEntryOK(ODataEntry $payload)
54
    {
55
        // check links
56
        foreach ($payload->links as $link) {
57
            $hasUrl = isset($link->url);
58
            $hasExpanded = isset($link->expandedResult);
59
            if ($hasUrl) {
60
                if (!is_string($link->url)) {
61
                    $msg = 'Url must be either string or null';
62
                    throw new \InvalidArgumentException($msg);
63
                }
64
            }
65
            if ($hasExpanded) {
66
                $isGood = $link->expandedResult instanceof ODataEntry || $link->expandedResult instanceof ODataFeed;
67
                if (!$isGood) {
68
                    $msg = 'Expanded result must null, or be instance of ODataEntry or ODataFeed';
69
                    throw new \InvalidArgumentException($msg);
70
                }
71
            }
72
            $isEntry = $link->expandedResult instanceof ODataEntry;
73
74
            if ($hasExpanded) {
75
                if ($isEntry) {
76
                    $this->isEntryOK($link->expandedResult);
77
                } else {
78
                    foreach ($link->expandedResult->entries as $expanded) {
79
                        $this->isEntryOK($expanded);
80
                    }
81
                }
82
            }
83
        }
84
85
        $set = $this->getMetaProvider()->resolveResourceSet($payload->resourceSetName);
86
        if (null === $set) {
87
            $msg = 'Specified resource set could not be resolved';
88
            throw new \InvalidArgumentException($msg);
89
        }
90
        return true;
91
    }
92
93
    protected function isEntryProcessed(ODataEntry $payload, $depth = 0)
94
    {
95
        assert(is_int($depth) && 0 <= $depth && 100 >= $depth, 'Maximum recursion depth exceeded');
96
        if (!$payload->id instanceof KeyDescriptor) {
97
            return false;
98
        }
99
        foreach ($payload->links as $link) {
100
            $expand = $link->expandedResult;
101
            if (null === $expand) {
102
                continue;
103
            }
104
            if ($expand instanceof ODataEntry) {
105
                if (!$this->isEntryProcessed($expand, $depth + 1)) {
106
                    return false;
107
                } else {
108
                    continue;
109
                }
110
            }
111
            if ($expand instanceof ODataFeed) {
112
                foreach ($expand->entries as $entry) {
113
                    if (!$this->isEntryProcessed($entry, $depth + 1)) {
114
                        return false;
115
                    }
116
                }
117
                continue;
118
            }
119
            assert(false, 'Expanded result cannot be processed');
120
        }
121
122
        return true;
123
    }
124
125
    protected function processEntryContent(ODataEntry &$content)
126
    {
127
        assert(null === $content->id || is_string($content->id), 'Entry id must be null or string');
128
129
        $isCreate = null === $content->id || empty($content->id);
130
        $set = $this->getMetaProvider()->resolveResourceSet($content->resourceSetName);
131
        assert($set instanceof ResourceSet, get_class($set));
132
        $type = $set->getResourceType();
133
        $properties = $this->getDeserialiser()->bulkDeserialise($type, $content);
134
        $properties = (object) $properties;
135
136
        if ($isCreate) {
137
            $result = $this->getWrapper()->createResourceforResourceSet($set, null, $properties);
138
            assert(isset($result), get_class($result));
139
            $key = $this->generateKeyDescriptor($type, $result);
140
            $keyProp = $key->getODataProperties();
141
            foreach ($keyProp as $keyName => $payload) {
142
                $content->propertyContent->properties[$keyName] = $payload;
143
            }
144
        } else {
145
            $key = $this->generateKeyDescriptor($type, $content->propertyContent, $content->id);
146
            assert($key instanceof KeyDescriptor, get_class($key));
147
            $source = $this->getWrapper()->getResourceFromResourceSet($set, $key);
148
            assert(isset($source), get_class($source));
149
            $result = $this->getWrapper()->updateResource($set, $source, $key, $properties);
150
        }
151
        assert($key instanceof KeyDescriptor, get_class($key));
152
        $content->id = $key;
153
154
        $numLinks = count($content->links);
155
        for ($i = 0; $i < $numLinks; $i++) {
156
            $this->processLink($content->links[$i], $set, $result);
157
        }
158
159
        return [$set, $result];
160
    }
161
162
    /**
163
     * @return IMetadataProvider
164
     */
165
    protected function getMetaProvider()
166
    {
167
        return $this->metaProvider;
168
    }
169
170
    /**
171
     * @return ProvidersWrapper
172
     */
173
    protected function getWrapper()
174
    {
175
        return $this->wrapper;
176
    }
177
178
    /**
179
     * @return ModelDeserialiser
180
     */
181
    protected function getDeserialiser()
182
    {
183
        return $this->cereal;
184
    }
185
186
    /**
187
     * @param ResourceEntityType $type
188
     * @param ODataPropertyContent|object $result
189
     * @param string|null $id
190
     * @return null|KeyDescriptor
191
     */
192
    protected function generateKeyDescriptor(ResourceEntityType $type, $result, $id = null)
193
    {
194
        $isOData = $result instanceof ODataPropertyContent;
195
        $keyProp = $type->getKeyProperties();
196
        if (null === $id) {
197
            $keyPredicate = '';
198
            foreach ($keyProp as $prop) {
199
                $iType = $prop->getInstanceType();
200
                assert($iType instanceof IType, get_class($iType));
201
                $keyName = $prop->getName();
202
                $rawKey = $isOData ? $result->properties[$keyName]->value : $result->$keyName;
203
                $keyVal = $iType->convertToOData($rawKey);
204
                assert(isset($keyVal), 'Key property ' . $keyName . ' must not be null');
205
                $keyPredicate .= $keyName . '=' . $keyVal . ', ';
206
            }
207
            $keyPredicate[strlen($keyPredicate) - 2] = ' ';
208
        } else {
209
            $idBits = explode('/', $id);
210
            $keyRaw = $idBits[count($idBits)-1];
211
            $rawBits = explode('(', $keyRaw, 2);
212
            $rawBits = explode(')', $rawBits[count($rawBits)-1]);
213
            $keyPredicate = $rawBits[0];
214
        }
215
        $keyPredicate = trim($keyPredicate);
216
        $keyDesc = null;
217
        $isParsed = KeyDescriptor::tryParseKeysFromKeyPredicate($keyPredicate, $keyDesc);
218
        assert(true === $isParsed, 'Key descriptor not successfully parsed');
219
        $keyDesc->validate($keyPredicate, $type);
220
        // this is deliberate - ODataEntry/Feed has the structure we need for processing, and we're inserting
221
        // keyDescriptor objects in id fields to indicate the given record has been processed
222
        return $keyDesc;
223
    }
224
225
    protected function processLink(ODataLink &$link, ResourceSet $sourceSet, $source)
226
    {
227
        $hasUrl = isset($link->url);
228
        $hasPayload = isset($link->expandedResult);
229
        assert(
230
            null == $link->expandedResult
231
            || $link->expandedResult instanceof ODataEntry
232
            || $link->expandedResult instanceof ODataFeed,
233
            get_class($link->expandedResult)
234
        );
235
        $isFeed = $link->expandedResult instanceof ODataFeed;
236
237
        // if nothing to hook up, bail out now
238
        if (!$hasUrl && !$hasPayload) {
239
            return;
240
        }
241
242
        if ($isFeed) {
243
            $this->processLinkFeed($link, $sourceSet, $source, $hasUrl, $hasPayload);
244
        } else {
245
            $this->processLinkSingleton($link, $sourceSet, $source, $hasUrl, $hasPayload);
246
        }
247
        return;
248
    }
249
250
    /**
251
     * @param ODataLink $link
252
     * @param ResourceSet $sourceSet
253
     * @param $source
254
     * @param $hasUrl
255
     * @param $hasPayload
256
     */
257
    protected function processLinkSingleton(ODataLink &$link, ResourceSet $sourceSet, $source, $hasUrl, $hasPayload)
258
    {
259
        assert(
260
            null === $link->expandedResult || $link->expandedResult instanceof ODataEntry,
261
            get_class($link->expandedResult)
262
        );
263
        // if link result has already been processed, bail out
264
        if (null !== $link->expandedResult || null !== $link->url) {
265
            $isUrlKey = $link->url instanceof KeyDescriptor;
266
            $isIdKey = $link->expandedResult instanceof ODataEntry &&
267
                       $link->expandedResult->id instanceof KeyDescriptor;
268
            if ($isUrlKey || $isIdKey) {
269
                if ($isIdKey) {
270
                    $link->url = $link->expandedResult->id;
271
                }
272
                return;
273
            }
274
        }
275
        assert(null === $link->expandedResult || !$link->expandedResult->id instanceof KeyDescriptor);
276
        assert(null === $link->url || is_string($link->url));
277
        if ($hasUrl) {
278
            $urlBitz = explode('/', $link->url);
279
            $rawPredicate = $urlBitz[count($urlBitz) - 1];
280
            $rawPredicate = explode('(', $rawPredicate);
281
            $setName = $rawPredicate[0];
282
            $rawPredicate = trim($rawPredicate[count($rawPredicate) - 1], ')');
283
            $targSet = $this->getMetaProvider()->resolveResourceSet($setName);
284
            assert(null !== $targSet, get_class($targSet));
285
            $type = $targSet->getResourceType();
286
        } else {
287
            $type = $this->getMetaProvider()->resolveResourceType($link->expandedResult->type->term);
288
        }
289
        assert($type instanceof ResourceEntityType, get_class($type));
290
        $propName = $link->title;
291
292
        if ($hasUrl) {
293
            $keyDesc = null;
294
            assert(isset($rawPredicate));
295
            KeyDescriptor::tryParseKeysFromKeyPredicate($rawPredicate, $keyDesc);
296
            $keyDesc->validate($rawPredicate, $type);
297
            assert(null !== $keyDesc, 'Key description must not be null');
298
        }
299
300
        // hooking up to existing resource
301
        if ($hasUrl && !$hasPayload) {
302
            assert(isset($targSet));
303
            assert(isset($keyDesc));
304
            $target = $this->getWrapper()->getResourceFromResourceSet($targSet, $keyDesc);
305
            assert(isset($target));
306
            $this->getWrapper()->hookSingleModel($sourceSet, $source, $targSet, $target, $propName);
307
            $link->url = $keyDesc;
308
            return;
309
        }
310
        // creating new resource
311
        if (!$hasUrl && $hasPayload) {
312
            list($targSet, $target) = $this->processEntryContent($link->expandedResult);
313
            assert(isset($target));
314
            $key = $this->generateKeyDescriptor($type, $link->expandedResult->propertyContent);
315
            $link->url = $key;
316
            $link->expandedResult->id = $key;
317
            $this->getWrapper()->hookSingleModel($sourceSet, $source, $targSet, $target, $propName);
318
            return;
319
        }
320
        // updating existing resource and connecting to it
321
        list($targSet, $target) = $this->processEntryContent($link->expandedResult);
322
        assert(isset($target));
323
        $link->url = $keyDesc;
324
        $link->expandedResult->id = $keyDesc;
325
        $this->getWrapper()->hookSingleModel($sourceSet, $source, $targSet, $target, $propName);
326
        return;
327
    }
328
329
    protected function processLinkFeed(ODataLink &$link, ResourceSet $sourceSet, $source, $hasUrl, $hasPayload)
330
    {
331
        assert(
332
            $link->expandedResult instanceof ODataFeed,
333
            get_class($link->expandedResult)
334
        );
335
        $propName = $link->title;
336
337
        // if entries is empty, bail out - nothing to do
338
        $numEntries = count($link->expandedResult->entries);
339
        if (0 === $numEntries) {
340
            return;
341
        }
342
        // check that each entry is of consistent resource set after checking it hasn't been processed
343
        $first = $link->expandedResult->entries[0]->resourceSetName;
344
        if ($link->expandedResult->entries[0]->id instanceof KeyDescriptor) {
345
            return;
346
        }
347
        for ($i = 1; $i < $numEntries; $i++) {
348
            if ($first !== $link->expandedResult->entries[$i]->resourceSetName) {
349
                $msg = 'All entries in given feed must have same resource set';
350
                throw new \InvalidArgumentException($msg);
351
            }
352
        }
353
354
        $targSet = $this->getMetaProvider()->resolveResourceSet($first);
355
        assert($targSet instanceof ResourceSet);
356
        $targType = $targSet->getResourceType();
357
        assert($targType instanceof ResourceEntityType);
358
        $instanceType = $targType->getInstanceType();
359
        assert($instanceType instanceof \ReflectionClass);
360
        $targObj = $instanceType->newInstanceArgs();
361
362
        // assemble payload
363
        $data = [];
364
        $keys = [];
365
        for ($i = 0; $i < $numEntries; $i++) {
366
            $data[] = $this->getDeserialiser()->bulkDeserialise(
367
                $targType,
368
                $link->expandedResult->entries[$i]
369
            );
370
            $keys[] = $hasUrl ? $this->generateKeyDescriptor(
371
                $targType,
372
                $link->expandedResult->entries[$i]->propertyContent
373
            ) : null;
374
        }
375
376
        // creation
377
        if (!$hasUrl && $hasPayload) {
378
            $bulkResult = $this->getWrapper()->createBulkResourceforResourceSet($targSet, $data);
379
            assert(is_array($bulkResult));
380 View Code Duplication
            for ($i = 0; $i < $numEntries; $i++) {
381
                $targEntityInstance = $bulkResult[$i];
382
                $this->getWrapper()->hookSingleModel($sourceSet, $source, $targSet, $targEntityInstance, $propName);
383
                $key = $this->generateKeyDescriptor($targType, $targEntityInstance);
384
                $link->expandedResult->entries[$i]->id = $key;
385
            }
386
        }
387
        // update
388
        if ($hasUrl && $hasPayload) {
389
            $bulkResult = $this->getWrapper()->updateBulkResource($targSet, $targObj, $keys, $data);
390 View Code Duplication
            for ($i = 0; $i < $numEntries; $i++) {
391
                $targEntityInstance = $bulkResult[$i];
392
                $this->getWrapper()->hookSingleModel($sourceSet, $source, $targSet, $targEntityInstance, $propName);
393
                $link->expandedResult->entries[$i]->id = $keys[$i];
394
            }
395
        }
396
        assert(isset($bulkResult) && is_array($bulkResult));
397
398
        for ($i = 0; $i < $numEntries; $i++) {
399
            assert($link->expandedResult->entries[$i]->id instanceof KeyDescriptor);
400
            $numLinks = count($link->expandedResult->entries[$i]->links);
401
            for ($j = 0; $j < $numLinks; $j++) {
402
                $this->processLink($link->expandedResult->entries[$i]->links[$j], $targSet, $bulkResult[$i]);
403
            }
404
        }
405
406
        return;
407
    }
408
}
409