Completed
Push — master ( 07d9eb...8877af )
by Alex
03:43
created

CynicDeserialiser   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 340
Duplicated Lines 3.24 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 55
c 4
b 0
f 0
lcom 1
cbo 14
dl 11
loc 340
rs 6.8

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A processPayload() 0 11 2
C isEntryOK() 0 39 11
A getMetaProvider() 0 4 1
A getWrapper() 0 4 1
A getDeserialiser() 0 4 1
B generateKeyDescriptor() 0 32 4
B processLink() 0 24 6
B processLinkSingleton() 0 57 8
C processLinkFeed() 11 75 15
B processEntryContent() 0 31 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CynicDeserialiser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CynicDeserialiser, and based on these observations, apply Extract Interface, too.

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
        return $source;
50
    }
51
52
    protected function isEntryOK(ODataEntry $payload)
53
    {
54
        // check links
55
        foreach ($payload->links as $link) {
56
            $hasUrl = isset($link->url);
57
            $hasExpanded = isset($link->expandedResult);
58
            if ($hasUrl) {
59
                if (!is_string($link->url)) {
60
                    $msg = 'Url must be either string or null';
61
                    throw new \InvalidArgumentException($msg);
62
                }
63
            }
64
            if ($hasExpanded) {
65
                $isGood = $link->expandedResult instanceof ODataEntry || $link->expandedResult instanceof ODataFeed;
66
                if (!$isGood) {
67
                    $msg = 'Expanded result must null, or be instance of ODataEntry or ODataFeed';
68
                    throw new \InvalidArgumentException($msg);
69
                }
70
            }
71
            $isEntry = $link->expandedResult instanceof ODataEntry;
72
73
            if ($hasExpanded) {
74
                if ($isEntry) {
75
                    $this->isEntryOK($link->expandedResult);
76
                } else {
77
                    foreach ($link->expandedResult->entries as $expanded) {
78
                        $this->isEntryOK($expanded);
79
                    }
80
                }
81
            }
82
        }
83
84
        $set = $this->getMetaProvider()->resolveResourceSet($payload->resourceSetName);
85
        if (null === $set) {
86
            $msg = 'Specified resource set could not be resolved';
87
            throw new \InvalidArgumentException($msg);
88
        }
89
        return true;
90
    }
91
92
    protected function processEntryContent(ODataEntry &$content)
93
    {
94
        assert(null === $content->id || is_string($content->id), 'Entry id must be null or string');
95
96
        $isCreate = null === $content->id || empty($content->id);
97
        $set = $this->getMetaProvider()->resolveResourceSet($content->resourceSetName);
98
        assert($set instanceof ResourceSet, get_class($set));
99
        $type = $set->getResourceType();
100
        $properties = $this->getDeserialiser()->bulkDeserialise($type, $content);
101
        $properties = (object) $properties;
102
103
        if ($isCreate) {
104
            $result = $this->getWrapper()->createResourceforResourceSet($set, null, $properties);
105
            assert(isset($result), get_class($result));
106
            $key = $this->generateKeyDescriptor($type, $result);
107
            $keyProp = $key->getODataProperties();
108
            foreach ($keyProp as $keyName => $payload) {
109
                $content->propertyContent->properties[$keyName] = $payload;
110
            }
111
        } else {
112
            $key = $this->generateKeyDescriptor($type, $content->propertyContent, $content->id);
113
            assert($key instanceof KeyDescriptor, get_class($key));
114
            $source = $this->getWrapper()->getResourceFromResourceSet($set, $key);
115
            assert(isset($source), get_class($source));
116
            $result = $this->getWrapper()->updateResource($set, $source, $key, $properties);
117
        }
118
119
        assert($key instanceof KeyDescriptor, get_class($key));
120
        $content->id = $key;
121
        return [$set, $result];
122
    }
123
124
    /**
125
     * @return IMetadataProvider
126
     */
127
    protected function getMetaProvider()
128
    {
129
        return $this->metaProvider;
130
    }
131
132
    /**
133
     * @return ProvidersWrapper
134
     */
135
    protected function getWrapper()
136
    {
137
        return $this->wrapper;
138
    }
139
140
    /**
141
     * @return ModelDeserialiser
142
     */
143
    protected function getDeserialiser()
144
    {
145
        return $this->cereal;
146
    }
147
148
    /**
149
     * @param ResourceEntityType $type
150
     * @param ODataPropertyContent|object $result
151
     * @param string|null $id
152
     * @return null|KeyDescriptor
153
     */
154
    protected function generateKeyDescriptor(ResourceEntityType $type, $result, $id = null)
155
    {
156
        $isOData = $result instanceof ODataPropertyContent;
157
        $keyProp = $type->getKeyProperties();
158
        if (null === $id) {
159
            $keyPredicate = '';
160
            foreach ($keyProp as $prop) {
161
                $iType = $prop->getInstanceType();
162
                assert($iType instanceof IType, get_class($iType));
163
                $keyName = $prop->getName();
164
                $rawKey = $isOData ? $result->properties[$keyName]->value : $result->$keyName;
165
                $keyVal = $iType->convertToOData($rawKey);
166
                assert(isset($keyVal), 'Key property ' . $keyName . ' must not be null');
167
                $keyPredicate .= $keyName . '=' . $keyVal . ', ';
168
            }
169
            $keyPredicate[strlen($keyPredicate) - 2] = ' ';
170
        } else {
171
            $idBits = explode('/', $id);
172
            $keyRaw = $idBits[count($idBits)-1];
173
            $rawBits = explode('(', $keyRaw, 2);
174
            $rawBits = explode(')', $rawBits[count($rawBits)-1]);
175
            $keyPredicate = $rawBits[0];
176
        }
177
        $keyPredicate = trim($keyPredicate);
178
        $keyDesc = null;
179
        $isParsed = KeyDescriptor::tryParseKeysFromKeyPredicate($keyPredicate, $keyDesc);
180
        assert(true === $isParsed, 'Key descriptor not successfully parsed');
181
        $keyDesc->validate($keyPredicate, $type);
182
        // this is deliberate - ODataEntry/Feed has the structure we need for processing, and we're inserting
183
        // keyDescriptor objects in id fields to indicate the given record has been processed
184
        return $keyDesc;
185
    }
186
187
    protected function processLink(ODataLink &$link, ResourceSet $sourceSet, $source)
188
    {
189
        $hasUrl = isset($link->url);
190
        $hasPayload = isset($link->expandedResult);
191
        assert(
192
            null == $link->expandedResult
193
            || $link->expandedResult instanceof ODataEntry
194
            || $link->expandedResult instanceof ODataFeed,
195
            get_class($link->expandedResult)
196
        );
197
        $isFeed = $link->expandedResult instanceof ODataFeed;
198
199
        // if nothing to hook up, bail out now
200
        if (!$hasUrl && !$hasPayload) {
201
            return;
202
        }
203
204
        if ($isFeed) {
205
            $this->processLinkFeed($link, $sourceSet, $source, $hasUrl, $hasPayload);
206
        } else {
207
            $this->processLinkSingleton($link, $sourceSet, $source, $hasUrl, $hasPayload);
208
        }
209
        return;
210
    }
211
212
    /**
213
     * @param ODataLink $link
214
     * @param ResourceSet $sourceSet
215
     * @param $source
216
     * @param $hasUrl
217
     * @param $hasPayload
218
     */
219
    protected function processLinkSingleton(ODataLink &$link, ResourceSet $sourceSet, $source, $hasUrl, $hasPayload)
220
    {
221
        assert(
222
            null === $link->expandedResult || $link->expandedResult instanceof ODataEntry,
223
            get_class($link->expandedResult)
224
        );
225
        if ($hasUrl) {
226
            $urlBitz = explode('/', $link->url);
227
            $rawPredicate = $urlBitz[count($urlBitz) - 1];
228
            $rawPredicate = explode('(', $rawPredicate);
229
            $setName = $rawPredicate[0];
230
            $rawPredicate = trim($rawPredicate[count($rawPredicate) - 1], ')');
231
            $targSet = $this->getMetaProvider()->resolveResourceSet($setName);
232
            assert(null !== $targSet, get_class($targSet));
233
            $type = $targSet->getResourceType();
234
        } else {
235
            $type = $this->getMetaProvider()->resolveResourceType($link->expandedResult->type->term);
236
        }
237
        assert($type instanceof ResourceEntityType, get_class($type));
238
        $propName = $link->title;
239
240
        if ($hasUrl) {
241
            $keyDesc = null;
242
            assert(isset($rawPredicate));
243
            KeyDescriptor::tryParseKeysFromKeyPredicate($rawPredicate, $keyDesc);
244
            $keyDesc->validate($rawPredicate, $type);
245
            assert(null !== $keyDesc, 'Key description must not be null');
246
        }
247
248
        // hooking up to existing resource
249
        if ($hasUrl && !$hasPayload) {
250
            assert(isset($targSet));
251
            assert(isset($keyDesc));
252
            $target = $this->getWrapper()->getResourceFromResourceSet($targSet, $keyDesc);
253
            assert(isset($target));
254
            $this->getWrapper()->hookSingleModel($sourceSet, $source, $targSet, $target, $propName);
255
            $link->url = $keyDesc;
256
            return;
257
        }
258
        // creating new resource
259
        if (!$hasUrl && $hasPayload) {
260
            list($targSet, $target) = $this->processEntryContent($link->expandedResult);
261
            assert(isset($target));
262
            $key = $this->generateKeyDescriptor($type, $link->expandedResult->propertyContent);
263
            $link->url = $key;
264
            $link->expandedResult->id = $key;
265
            $this->getWrapper()->hookSingleModel($sourceSet, $source, $targSet, $target, $propName);
266
            return;
267
        }
268
        // updating existing resource and connecting to it
269
        list($targSet, $target) = $this->processEntryContent($link->expandedResult);
270
        assert(isset($target));
271
        $link->url = $keyDesc;
272
        $link->expandedResult->id = $keyDesc;
273
        $this->getWrapper()->hookSingleModel($sourceSet, $source, $targSet, $target, $propName);
274
        return;
275
    }
276
277
    protected function processLinkFeed(ODataLink &$link, ResourceSet $sourceSet, $source, $hasUrl, $hasPayload)
278
    {
279
        assert(
280
            $link->expandedResult instanceof ODataFeed,
281
            get_class($link->expandedResult)
282
        );
283
        $propName = $link->title;
284
285
        // if entries is empty, bail out - nothing to do
286
        $numEntries = count($link->expandedResult->entries);
287
        if (0 === $numEntries) {
288
            return;
289
        }
290
        // check that each entry is of consistent resource set
291
        $first = $link->expandedResult->entries[0]->resourceSetName;
292
        for ($i = 1; $i < $numEntries; $i++) {
293
            if ($first !== $link->expandedResult->entries[$i]->resourceSetName) {
294
                $msg = 'All entries in given feed must have same resource set';
295
                throw new \InvalidArgumentException($msg);
296
            }
297
        }
298
299
        $targSet = $this->getMetaProvider()->resolveResourceSet($first);
300
        assert($targSet instanceof ResourceSet);
301
        $targType = $targSet->getResourceType();
302
        assert($targType instanceof ResourceEntityType);
303
        $instanceType = $targType->getInstanceType();
304
        assert($instanceType instanceof \ReflectionClass);
305
        $targObj = $instanceType->newInstanceArgs();
306
307
        // assemble payload
308
        $data = [];
309
        $keys = [];
310
        for ($i = 0; $i < $numEntries; $i++) {
311
            $data[] = $this->getDeserialiser()->bulkDeserialise(
312
                $targType,
313
                $link->expandedResult->entries[$i]
314
            );
315
            $keys[] = $hasUrl ? $this->generateKeyDescriptor(
316
                $targType,
317
                $link->expandedResult->entries[$i]->propertyContent
318
            ) : null;
319
        }
320
321
        // creation
322
        if (!$hasUrl && $hasPayload) {
323
            $bulkResult = $this->getWrapper()->createBulkResourceforResourceSet($targSet, $data);
324
            assert(is_array($bulkResult));
325 View Code Duplication
            for ($i = 0; $i < $numEntries; $i++) {
326
                $targEntityInstance = $bulkResult[$i];
327
                $this->getWrapper()->hookSingleModel($sourceSet, $source, $targSet, $targEntityInstance, $propName);
328
                $key = $this->generateKeyDescriptor($targType, $targEntityInstance);
329
                $link->expandedResult->entries[$i]->id = $key;
330
            }
331
        }
332
        // update
333
        if ($hasUrl && $hasPayload) {
334
            $bulkResult = $this->getWrapper()->updateBulkResource($targSet, $targObj, $keys, $data);
335 View Code Duplication
            for ($i = 0; $i < $numEntries; $i++) {
336
                $targEntityInstance = $bulkResult[$i];
337
                $this->getWrapper()->hookSingleModel($sourceSet, $source, $targSet, $targEntityInstance, $propName);
338
                $link->expandedResult->entries[$i]->id = $keys[$i];
339
            }
340
        }
341
        assert(isset($bulkResult) && is_array($bulkResult));
342
343
        for ($i = 0; $i < $numEntries; $i++) {
344
            $numLinks = count($link->expandedResult->entries[$i]->links);
345
            for ($j = 0; $j < $numLinks; $j++) {
346
                $this->processLink($link->expandedResult->entries[$i]->links[$j], $targSet, $bulkResult[$i]);
347
            }
348
        }
349
350
        return;
351
    }
352
}
353