Test Failed
Pull Request — master (#85)
by Alex
06:31
created

calculateRoundTripRelationsFirstPass()   C

Complexity

Conditions 12
Paths 40

Size

Total Lines 56
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 56
ccs 0
cts 0
cp 0
rs 6.7092
cc 12
eloc 38
nc 40
nop 3
crap 156

How to fix   Long Method    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 AlgoWeb\PODataLaravel\Providers;
4
5
use Illuminate\Support\Facades\App;
6
use Illuminate\Support\ServiceProvider;
7
use Illuminate\Support\Facades\Cache;
8
use POData\Providers\Metadata\IMetadataProvider;
9
use POData\Providers\Metadata\ResourceType;
10
use POData\Providers\Metadata\SimpleMetadataProvider;
11
use Illuminate\Support\Facades\Route;
12
use Illuminate\Support\Facades\Schema as Schema;
13
14
class MetadataProvider extends MetadataBaseProvider
15
{
16
    protected $multConstraints = [ '0..1' => ['1'], '1' => ['0..1', '*'], '*' => ['1', '*']];
17
    protected static $metaNAMESPACE = 'Data';
18
19
    /**
20 1
     * Bootstrap the application services.  Post-boot.
21
     *
22 1
     * @return void
23
     */
24
    public function boot()
25 1
    {
26
        self::$metaNAMESPACE = env('ODataMetaNamespace', 'Data');
27
        // If we aren't migrated, there's no DB tables to pull metadata _from_, so bail out early
28 1
        try {
29 1
            if (!Schema::hasTable(config('database.migrations'))) {
30
                return;
31
            }
32
        } catch (\Exception $e) {
33
            return;
34
        }
35
36
        $isCaching = true === $this->getIsCaching();
37
        $meta = Cache::get('metadata');
38
        $hasCache = null != $meta;
39
40
        if ($isCaching && $hasCache) {
41
            App::instance('metadata', $meta);
42
            return;
43
        }
44
        $meta = App::make('metadata');
45
46
        $modelNames = $this->getCandidateModels();
47
48
        list($entityTypes) = $this->getEntityTypesAndResourceSets($meta, $modelNames);
49
50
        // need to lift EntityTypes
51
        $biDirect = $this->calculateRoundTripRelations();
52
53
        // now that endpoints are hooked up, tackle the relationships
54
        // if we'd tried earlier, we'd be guaranteed to try to hook a relation up to null, which would be bad
55
        foreach ($biDirect as $line) {
56
            $this->processRelationLine($line, $entityTypes, $meta);
57
        }
58
59
        $key = 'metadata';
60
        $this->handlePostBoot($isCaching, $hasCache, $key, $meta);
61
    }
62
63
    /**
64
     * Register the application services.  Boot-time only.
65
     *
66
     * @return void
67
     */
68
    public function register()
69
    {
70
        $this->app->singleton('metadata', function ($app) {
0 ignored issues
show
Unused Code introduced by
The parameter $app is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
71
            return new SimpleMetadataProvider('Data', self::$metaNAMESPACE);
72
        });
73
    }
74
75
    /**
76
     * @return array
77
     */
78
    protected function getCandidateModels()
79
    {
80
        $classes = $this->getClassMap();
81
        $ends = [];
82
        $startName = defined('PODATA_LARAVEL_APP_ROOT_NAMESPACE') ? PODATA_LARAVEL_APP_ROOT_NAMESPACE : 'App';
83
        foreach ($classes as $name) {
84
            if (\Illuminate\Support\Str::startsWith($name, $startName)) {
85
                if (in_array('AlgoWeb\\PODataLaravel\\Models\\MetadataTrait', class_uses($name))) {
86
                    $ends[] = $name;
87
                }
88
            }
89
        }
90
        return $ends;
91
    }
92
93
    /**
94
     * @param $meta
95
     * @param $ends
96
     * @return array[]
97
     */
98
    protected function getEntityTypesAndResourceSets($meta, $ends)
99
    {
100
        assert($meta instanceof IMetadataProvider, get_class($meta));
101
        $entityTypes = [];
102
        $resourceSets = [];
103
        $begins = [];
104
        $numEnds = count($ends);
105
106
        for ($i = 0; $i < $numEnds; $i++) {
107
            $bitter = $ends[$i];
108
            $fqModelName = $bitter;
109
110
            $instance = App::make($fqModelName);
111
            $name = strtolower($instance->getEndpointName());
112
            $metaSchema = $instance->getXmlSchema();
113
114
            // if for whatever reason we don't get an XML schema, move on to next entry and drop current one from
115
            // further processing
116
            if (null == $metaSchema) {
117
                continue;
118
            }
119
            $entityTypes[$fqModelName] = $metaSchema;
120
            $resourceSets[$fqModelName] = $meta->addResourceSet($name, $metaSchema);
121
            $begins[] = $bitter;
122
        }
123
124
        return [$entityTypes, $resourceSets, $begins];
125
    }
126
127
    public function calculateRoundTripRelations()
128
    {
129
        $modelNames = $this->getCandidateModels();
130
131
        $hooks = [];
132
        foreach ($modelNames as $name) {
133
            $model = new $name();
134
            $rels = $model->getRelationships();
135
            // it doesn't matter if a model has no relationships here, that lack will simply be skipped over
136
            // during hookup processing
137
            $hooks[$name] = $rels;
138
        }
139
140
        // model relation gubbins are assembled, now the hard bit starts
141
        // storing assembled bidirectional relationship schema
142
        $rawLines = [];
143
        // storing unprocessed relation gubbins for second-pass processing
144
        $remix = [];
145
        $this->calculateRoundTripRelationsFirstPass($hooks, $rawLines, $remix);
146
147
        // now for second processing pass, to pick up stuff that first didn't handle
148
        $rawLines = $this->calculateRoundTripRelationsSecondPass($remix, $rawLines);
149
150
        // deduplicate rawLines - can't use array_unique as array value elements are themselves arrays
151
        $lines = [];
152
        foreach ($rawLines as $line) {
153
            if (!in_array($line, $lines)) {
154
                $lines[] = $line;
155
            }
156
        }
157
158
        return $lines;
159
    }
160
161
    public function getPolymorphicRelationGroups()
162
    {
163
        $modelNames = $this->getCandidateModels();
164
165
        $knownSide = [];
166
        $unknownSide = [];
167
168
        $hooks = [];
169
        // fish out list of polymorphic-affected models for further processing
170
        foreach ($modelNames as $name) {
171
            $model = new $name();
172
            $isPoly = false;
173
            if ($model->isKnownPolymorphSide()) {
174
                $knownSide[$name] = [];
175
                $isPoly = true;
176
            }
177
            if ($model->isUnknownPolymorphSide()) {
178
                $unknownSide[$name] = [];
179
                $isPoly = true;
180
            }
181
            if (false === $isPoly) {
182
                continue;
183
            }
184
185
            $rels = $model->getRelationships();
186
            // it doesn't matter if a model has no relationships here, that lack will simply be skipped over
187
            // during hookup processing
188
            $hooks[$name] = $rels;
189
        }
190
        // ensure we've only loaded up polymorphic-affected models
191
        $knownKeys = array_keys($knownSide);
192
        $unknownKeys = array_keys($unknownSide);
193
        $dualKeys = array_intersect($knownKeys, $unknownKeys);
194
        assert(count($hooks) == (count($unknownKeys) + count($knownKeys) - count($dualKeys)));
195
        // if either list is empty, bail out - there's nothing to do
196
        if (0 === count($knownSide) || 0 === count($unknownSide)) {
197
            return [];
198
        }
199
200
        // commence primary ignition
201
202
        foreach ($unknownKeys as $key) {
203
            assert(isset($hooks[$key]));
204
            $hook = $hooks[$key];
205
            foreach ($hook as $barb) {
206
                foreach ($barb as $knownType => $propData) {
207
                    if (in_array($knownType, $knownKeys)) {
208
                        if (!isset($knownSide[$knownType][$key])) {
209
                            $knownSide[$knownType][$key] = [];
210
                        }
211
                        assert(isset($knownSide[$knownType][$key]));
212
                        $knownSide[$knownType][$key][] = $propData['property'];
213
                    }
214
                }
215
            }
216
        }
217
218
        return $knownSide;
219
    }
220
221
    /**
222
     * @param $remix
223
     * @param $lines
224
     * @return array
225
     */
226
    private function calculateRoundTripRelationsSecondPass($remix, $lines)
227
    {
228
        foreach ($remix as $principalType => $value) {
229
            foreach ($value as $fk => $localRels) {
230
                foreach ($localRels as $dependentType => $deets) {
231
                    $principalMult = $deets['multiplicity'];
232
                    $principalProperty = $deets['property'];
233
                    $principalKey = $deets['local'];
234
235
                    if (!isset($remix[$dependentType])) {
236
                        continue;
237
                    }
238
                    $foreign = $remix[$dependentType];
239
                    if (!isset($foreign[$principalKey])) {
240
                        continue;
241
                    }
242
                    $foreign = $foreign[$principalKey];
243
                    $dependentMult = $foreign[$dependentType]['multiplicity'];
244
                    $dependentProperty = $foreign[$dependentType]['property'];
245
                    assert(
246
                        in_array($dependentMult, $this->multConstraints[$principalMult]),
247
                        'Cannot pair multiplicities ' . $dependentMult . ' and ' . $principalMult
248
                    );
249
                    assert(
250
                        in_array($principalMult, $this->multConstraints[$dependentMult]),
251
                        'Cannot pair multiplicities ' . $principalMult . ' and ' . $dependentMult
252
                    );
253
                    // generate forward and reverse relations
254
                    list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
255
                        $principalType,
256
                        $principalMult,
257
                        $principalProperty,
258
                        $dependentType,
259
                        $dependentMult,
260
                        $dependentProperty
261
                    );
262
                    // add forward relation
263
                    $lines[] = $forward;
264
                    // add reverse relation
265
                    $lines[] = $reverse;
266
                }
267
            }
268
        }
269
        return $lines;
270
    }
271
272
    /**
273
     * @param $hooks
274
     * @param $lines
275
     * @param $remix
276
     */
277
    private function calculateRoundTripRelationsFirstPass($hooks, &$lines, &$remix)
278
    {
279
        foreach ($hooks as $principalType => $value) {
280
            foreach ($value as $fk => $localRels) {
281
                foreach ($localRels as $dependentType => $deets) {
282
                    if (!isset($hooks[$dependentType])) {
283
                        continue;
284
                    }
285
                    $principalMult = $deets['multiplicity'];
286
                    $principalProperty = $deets['property'];
287
                    $principalKey = $deets['local'];
288
289
                    $foreign = $hooks[$dependentType];
290
                    $foreign = null != $foreign && isset($foreign[$principalKey]) ? $foreign[$principalKey] : null;
291
292
                    if (null != $foreign && isset($foreign[$principalType])) {
293
                        $foreign = $foreign[$principalType];
294
                        $dependentMult = $foreign['multiplicity'];
295
                        $dependentProperty = $foreign['property'];
296
                        assert(
297
                            in_array($dependentMult, $this->multConstraints[$principalMult]),
298
                            'Cannot pair multiplicities ' . $dependentMult . ' and ' . $principalMult
299
                        );
300
                        assert(
301
                            in_array($principalMult, $this->multConstraints[$dependentMult]),
302
                            'Cannot pair multiplicities ' . $principalMult . ' and ' . $dependentMult
303
                        );
304
                        // generate forward and reverse relations
305
                        list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
306
                            $principalType,
307
                            $principalMult,
308
                            $principalProperty,
309
                            $dependentType,
310
                            $dependentMult,
311
                            $dependentProperty
312
                        );
313
                        // add forward relation
314
                        $lines[] = $forward;
315
                        // add reverse relation
316
                        $lines[] = $reverse;
317
                    } else {
318
                        if (!isset($remix[$principalType])) {
319
                            $remix[$principalType] = [];
320
                        }
321
                        if (!isset($remix[$principalType][$fk])) {
322
                            $remix[$principalType][$fk] = [];
323
                        }
324
                        if (!isset($remix[$principalType][$fk][$dependentType])) {
325
                            $remix[$principalType][$fk][$dependentType] = $deets;
326
                        }
327
                        assert(isset($remix[$principalType][$fk][$dependentType]));
328
                    }
329
                }
330
            }
331
        }
332
    }
333
334
    /**
335
     * @param $principalType
336
     * @param $principalMult
337
     * @param $principalProperty
338
     * @param $dependentType
339
     * @param $dependentMult
340
     * @param $dependentProperty
341
     * @return array[]
342
     */
343
    private function calculateRoundTripRelationsGenForwardReverse(
344
        $principalType,
345
        $principalMult,
346
        $principalProperty,
347
        $dependentType,
348
        $dependentMult,
349
        $dependentProperty
350
    ) {
351
        $forward = [
352
            'principalType' => $principalType,
353
            'principalMult' => $dependentMult,
354
            'principalProp' => $principalProperty,
355
            'dependentType' => $dependentType,
356
            'dependentMult' => $principalMult,
357
            'dependentProp' => $dependentProperty
358
        ];
359
        $reverse = [
360
            'principalType' => $dependentType,
361
            'principalMult' => $principalMult,
362
            'principalProp' => $dependentProperty,
363
            'dependentType' => $principalType,
364
            'dependentMult' => $dependentMult,
365
            'dependentProp' => $principalProperty
366
        ];
367
        return [$forward, $reverse];
368
    }
369
370
    private function processRelationLine($line, $entityTypes, &$meta)
371
    {
372
        $principalType = $line['principalType'];
373
        $principalMult = $line['principalMult'];
374
        $principalProp = $line['principalProp'];
375
        $dependentType = $line['dependentType'];
376
        $dependentMult = $line['dependentMult'];
377
        $dependentProp = $line['dependentProp'];
378
        if (!isset($entityTypes[$principalType]) || !isset($entityTypes[$dependentType])) {
379
            return;
380
        }
381
        $principal = $entityTypes[$principalType];
382
        $dependent = $entityTypes[$dependentType];
383
        //many-to-many
384
        if ('*' == $principalMult && '*' == $dependentMult) {
385
            $meta->addResourceSetReferencePropertyBidirectional(
386
                $principal,
387
                $dependent,
388
                $principalProp,
389
                $dependentProp
390
            );
391
            return;
392
        }
393
        //one-to-one
394
        if ('0..1' == $principalMult || '0..1' == $dependentMult) {
395
            assert($principalMult != $dependentMult, 'Cannot have both ends with 0..1 multiplicity');
396
            $meta->addResourceReferenceSinglePropertyBidirectional(
397
                $principal,
398
                $dependent,
399
                $principalProp,
400
                $dependentProp
401
            );
402
            return;
403
        }
404
        assert($principalMult != $dependentMult, 'Cannot have both ends same multiplicity for 1:N relation');
405
        //principal-one-to-dependent-many
406
        if ('*' == $principalMult) {
407
            $meta->addResourceReferencePropertyBidirectional(
408
                $principal,
409
                $dependent,
410
                $principalProp,
411
                $dependentProp
412
            );
413
            return;
414
        }
415
        //dependent-one-to-principal-many
416
        $meta->addResourceReferencePropertyBidirectional(
417
            $dependent,
418
            $principal,
419
            $dependentProp,
420
            $principalProp
421
        );
422
        return;
423
    }
424
}
425