Test Failed
Pull Request — master (#85)
by Alex
02:56
created

MetadataProvider::attachReferencePolymorphic()   C

Complexity

Conditions 7
Paths 36

Size

Total Lines 38
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 38
ccs 0
cts 0
cp 0
rs 6.7272
cc 7
eloc 31
nc 36
nop 9
crap 56

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Providers;
4
5
use AlgoWeb\PODataLaravel\Models\TestMorphOneParent;
6
use Illuminate\Support\Facades\App;
7
use Illuminate\Support\ServiceProvider;
8
use Illuminate\Support\Facades\Cache;
9
use Illuminate\Support\Str;
10
use POData\Providers\Metadata\IMetadataProvider;
11
use POData\Providers\Metadata\ResourceEntityType;
12
use POData\Providers\Metadata\ResourceSet;
13
use POData\Providers\Metadata\ResourceType;
14
use POData\Providers\Metadata\SimpleMetadataProvider;
15
use Illuminate\Support\Facades\Route;
16
use Illuminate\Support\Facades\Schema as Schema;
17
18
class MetadataProvider extends MetadataBaseProvider
19
{
20 1
    protected $multConstraints = [ '0..1' => ['1'], '1' => ['0..1', '*'], '*' => ['1', '*']];
21
    protected static $metaNAMESPACE = 'Data';
22 1
    const POLYMORPHIC = 'polyMorphicPlaceholder';
23
    const POLYMORPHIC_PLURAL = 'polyMorphicPlaceholders';
24
25 1
    /**
26
     * Bootstrap the application services.  Post-boot.
27
     *
28 1
     * @return void
29 1
     */
30
    public function boot()
31
    {
32
        self::$metaNAMESPACE = env('ODataMetaNamespace', 'Data');
33
        // If we aren't migrated, there's no DB tables to pull metadata _from_, so bail out early
34
        try {
35
            if (!Schema::hasTable(config('database.migrations'))) {
36
                return;
37
            }
38
        } catch (\Exception $e) {
39
            return;
40
        }
41
42
        $isCaching = true === $this->getIsCaching();
43
        $meta = Cache::get('metadata');
44
        $hasCache = null != $meta;
45
46
        if ($isCaching && $hasCache) {
47
            App::instance('metadata', $meta);
48
            return;
49
        }
50
        $meta = App::make('metadata');
51
52
        $stdRef = new \ReflectionClass(new \stdClass());
53
        $abstract = $meta->addEntityType($stdRef, static::POLYMORPHIC, true, null);
54
        $abstractSet = $meta->addResourceSet(static::POLYMORPHIC, $abstract);
0 ignored issues
show
Unused Code introduced by
$abstractSet is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
55
56
        $modelNames = $this->getCandidateModels();
57
58
        list($entityTypes) = $this->getEntityTypesAndResourceSets($meta, $modelNames);
59
        $entityTypes[static::POLYMORPHIC] = $abstract;
60
61
        // need to lift EntityTypes, adjust for polymorphic-affected relations, etc
62
        $biDirect = $this->getRepairedRoundTripRelations();
63
64
        // now that endpoints are hooked up, tackle the relationships
65
        // if we'd tried earlier, we'd be guaranteed to try to hook a relation up to null, which would be bad
66
        foreach ($biDirect as $line) {
67
            $this->processRelationLine($line, $entityTypes, $meta);
68
        }
69
70
        $key = 'metadata';
71
        $this->handlePostBoot($isCaching, $hasCache, $key, $meta);
72
    }
73
74
    /**
75
     * Register the application services.  Boot-time only.
76
     *
77
     * @return void
78
     */
79
    public function register()
80
    {
81
        $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...
82
            return new SimpleMetadataProvider('Data', self::$metaNAMESPACE);
83
        });
84
    }
85
86
    /**
87
     * @return array
88
     */
89
    protected function getCandidateModels()
90
    {
91
        $classes = $this->getClassMap();
92
        $ends = [];
93
        $startName = defined('PODATA_LARAVEL_APP_ROOT_NAMESPACE') ? PODATA_LARAVEL_APP_ROOT_NAMESPACE : 'App';
94
        foreach ($classes as $name) {
95
            if (\Illuminate\Support\Str::startsWith($name, $startName)) {
96
                if (in_array('AlgoWeb\\PODataLaravel\\Models\\MetadataTrait', class_uses($name))) {
97
                    $ends[] = $name;
98
                }
99
            }
100
        }
101
        return $ends;
102
    }
103
104
    /**
105
     * @param $meta
106
     * @param $ends
107
     * @return array[]
108
     */
109
    protected function getEntityTypesAndResourceSets($meta, $ends)
110
    {
111
        assert($meta instanceof IMetadataProvider, get_class($meta));
112
        $entityTypes = [];
113
        $resourceSets = [];
114
        $begins = [];
115
        $numEnds = count($ends);
116
117
        for ($i = 0; $i < $numEnds; $i++) {
118
            $bitter = $ends[$i];
119
            $fqModelName = $bitter;
120
121
            $instance = App::make($fqModelName);
122
            $name = strtolower($instance->getEndpointName());
123
            $metaSchema = $instance->getXmlSchema();
124
125
            // if for whatever reason we don't get an XML schema, move on to next entry and drop current one from
126
            // further processing
127
            if (null == $metaSchema) {
128
                continue;
129
            }
130
            $entityTypes[$fqModelName] = $metaSchema;
131
            $resourceSets[$fqModelName] = $meta->addResourceSet($name, $metaSchema);
132
            $begins[] = $bitter;
133
        }
134
135
        return [$entityTypes, $resourceSets, $begins];
136
    }
137
138
    public function calculateRoundTripRelations()
139
    {
140
        $modelNames = $this->getCandidateModels();
141
142
        $hooks = [];
143
        foreach ($modelNames as $name) {
144
            $model = new $name();
145
            $rels = $model->getRelationships();
146
            // it doesn't matter if a model has no relationships here, that lack will simply be skipped over
147
            // during hookup processing
148
            $hooks[$name] = $rels;
149
        }
150
151
        // model relation gubbins are assembled, now the hard bit starts
152
        // storing assembled bidirectional relationship schema
153
        $rawLines = [];
154
        // storing unprocessed relation gubbins for second-pass processing
155
        $remix = [];
156
        $this->calculateRoundTripRelationsFirstPass($hooks, $rawLines, $remix);
157
158
        // now for second processing pass, to pick up stuff that first didn't handle
159
        $rawLines = $this->calculateRoundTripRelationsSecondPass($remix, $rawLines);
160
161
        $numLines = count($rawLines);
162
        for ($i = 0; $i < $numLines; $i++) {
163
            $rawLines[$i]['principalRSet'] = $rawLines[$i]['principalType'];
164
            $rawLines[$i]['dependentRSet'] = $rawLines[$i]['dependentType'];
165
        }
166
167
        // deduplicate rawLines - can't use array_unique as array value elements are themselves arrays
168
        $lines = [];
169
        foreach ($rawLines as $line) {
170
            if (!in_array($line, $lines)) {
171
                $lines[] = $line;
172
            }
173
        }
174
175
        return $lines;
176
    }
177
178
    public function getPolymorphicRelationGroups()
179
    {
180
        $modelNames = $this->getCandidateModels();
181
182
        $knownSide = [];
183
        $unknownSide = [];
184
185
        $hooks = [];
186
        // fish out list of polymorphic-affected models for further processing
187
        foreach ($modelNames as $name) {
188
            $model = new $name();
189
            $isPoly = false;
190
            if ($model->isKnownPolymorphSide()) {
191
                $knownSide[$name] = [];
192
                $isPoly = true;
193
            }
194
            if ($model->isUnknownPolymorphSide()) {
195
                $unknownSide[$name] = [];
196
                $isPoly = true;
197
            }
198
            if (false === $isPoly) {
199
                continue;
200
            }
201
202
            $rels = $model->getRelationships();
203
            // it doesn't matter if a model has no relationships here, that lack will simply be skipped over
204
            // during hookup processing
205
            $hooks[$name] = $rels;
206
        }
207
        // ensure we've only loaded up polymorphic-affected models
208
        $knownKeys = array_keys($knownSide);
209
        $unknownKeys = array_keys($unknownSide);
210
        $dualKeys = array_intersect($knownKeys, $unknownKeys);
211
        assert(count($hooks) == (count($unknownKeys) + count($knownKeys) - count($dualKeys)));
212
        // if either list is empty, bail out - there's nothing to do
213
        if (0 === count($knownSide) || 0 === count($unknownSide)) {
214
            return [];
215
        }
216
217
        // commence primary ignition
218
219
        foreach ($unknownKeys as $key) {
220
            assert(isset($hooks[$key]));
221
            $hook = $hooks[$key];
222
            foreach ($hook as $barb) {
223
                foreach ($barb as $knownType => $propData) {
224
                    if (in_array($knownType, $knownKeys)) {
225
                        if (!isset($knownSide[$knownType][$key])) {
226
                            $knownSide[$knownType][$key] = [];
227
                        }
228
                        assert(isset($knownSide[$knownType][$key]));
229
                        $knownSide[$knownType][$key][] = $propData['property'];
230
                    }
231
                }
232
            }
233
        }
234
235
        return $knownSide;
236
    }
237
238
    /**
239
     * Get round-trip relations after inserting polymorphic-powered placeholders
240
     *
241
     * @return array
242
     */
243
    public function getRepairedRoundTripRelations()
244
    {
245
        $rels = $this->calculateRoundTripRelations();
246
        $groups = $this->getPolymorphicRelationGroups();
247
248
        if (0 === count($groups)) {
249
            return $rels;
250
        }
251
252
        $placeholder = static::POLYMORPHIC;
253
254
        $groupKeys = array_keys($groups);
255
256
        // we have at least one polymorphic relation, need to dig it out
257
        $numRels = count($rels);
258
        for ($i = 0; $i < $numRels; $i++) {
259
            $relation = $rels[$i];
260
            $principalType = $relation['principalType'];
261
            $dependentType = $relation['dependentType'];
262
            $principalPoly = in_array($principalType, $groupKeys);
263
            $dependentPoly = in_array($dependentType, $groupKeys);
264
            // if relation is not polymorphic, then move on
265
            if (!($principalPoly || $dependentPoly)) {
266
                continue;
267
            } else {
268
                // if only one end is a known end of a polymorphic relation
269
                // for moment we're punting on both
270
                $oneEnd = $principalPoly !== $dependentPoly;
271
                assert($oneEnd, 'Multi-generational polymorphic relation chains not implemented');
272
                $targRels = $principalPoly ? $groups[$principalType] : $groups[$dependentType];
273
                $targUnknown = $targRels[$principalPoly ? $dependentType : $principalType];
274
                $targProperty = $principalPoly ? $relation['dependentProp'] : $relation['principalProp'];
275
                $msg = 'Specified unknown-side property ' . $targProperty . ' not found in polymorphic relation map';
276
                assert(in_array($targProperty, $targUnknown), $msg);
277
278
                $targType = $principalPoly ? 'dependentRSet' : 'principalRSet';
279
                $otherType = $principalPoly ? 'principalType' : 'dependentType';
0 ignored issues
show
Unused Code introduced by
$otherType is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
280
                $rels[$i][$targType] = $placeholder;
281
                continue;
282
            }
283
        }
284
285
        return $rels;
286
    }
287
288
    /**
289
     * @param $remix
290
     * @param $lines
291
     * @return array
292
     */
293
    private function calculateRoundTripRelationsSecondPass($remix, $lines)
294
    {
295
        foreach ($remix as $principalType => $value) {
296
            foreach ($value as $fk => $localRels) {
297
                foreach ($localRels as $dependentType => $deets) {
298
                    $principalMult = $deets['multiplicity'];
299
                    $principalProperty = $deets['property'];
300
                    $principalKey = $deets['local'];
301
302
                    if (!isset($remix[$dependentType])) {
303
                        continue;
304
                    }
305
                    $foreign = $remix[$dependentType];
306
                    if (!isset($foreign[$principalKey])) {
307
                        continue;
308
                    }
309
                    $foreign = $foreign[$principalKey];
310
                    $dependentMult = $foreign[$dependentType]['multiplicity'];
311
                    $dependentProperty = $foreign[$dependentType]['property'];
312
                    assert(
313
                        in_array($dependentMult, $this->multConstraints[$principalMult]),
314
                        'Cannot pair multiplicities ' . $dependentMult . ' and ' . $principalMult
315
                    );
316
                    assert(
317
                        in_array($principalMult, $this->multConstraints[$dependentMult]),
318
                        'Cannot pair multiplicities ' . $principalMult . ' and ' . $dependentMult
319
                    );
320
                    // generate forward and reverse relations
321
                    list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
322
                        $principalType,
323
                        $principalMult,
324
                        $principalProperty,
325
                        $dependentType,
326
                        $dependentMult,
327
                        $dependentProperty
328
                    );
329
                    // add forward relation
330
                    $lines[] = $forward;
331
                    // add reverse relation
332
                    $lines[] = $reverse;
333
                }
334
            }
335
        }
336
        return $lines;
337
    }
338
339
    /**
340
     * @param $hooks
341
     * @param $lines
342
     * @param $remix
343
     */
344
    private function calculateRoundTripRelationsFirstPass($hooks, &$lines, &$remix)
345
    {
346
        foreach ($hooks as $principalType => $value) {
347
            foreach ($value as $fk => $localRels) {
348
                foreach ($localRels as $dependentType => $deets) {
349
                    if (!isset($hooks[$dependentType])) {
350
                        continue;
351
                    }
352
                    $principalMult = $deets['multiplicity'];
353
                    $principalProperty = $deets['property'];
354
                    $principalKey = $deets['local'];
355
356
                    $foreign = $hooks[$dependentType];
357
                    $foreign = null != $foreign && isset($foreign[$principalKey]) ? $foreign[$principalKey] : null;
358
359
                    if (null != $foreign && isset($foreign[$principalType])) {
360
                        $foreign = $foreign[$principalType];
361
                        $dependentMult = $foreign['multiplicity'];
362
                        $dependentProperty = $foreign['property'];
363
                        assert(
364
                            in_array($dependentMult, $this->multConstraints[$principalMult]),
365
                            'Cannot pair multiplicities ' . $dependentMult . ' and ' . $principalMult
366
                        );
367
                        assert(
368
                            in_array($principalMult, $this->multConstraints[$dependentMult]),
369
                            'Cannot pair multiplicities ' . $principalMult . ' and ' . $dependentMult
370
                        );
371
                        // generate forward and reverse relations
372
                        list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
373
                            $principalType,
374
                            $principalMult,
375
                            $principalProperty,
376
                            $dependentType,
377
                            $dependentMult,
378
                            $dependentProperty
379
                        );
380
                        // add forward relation
381
                        $lines[] = $forward;
382
                        // add reverse relation
383
                        $lines[] = $reverse;
384
                    } else {
385
                        if (!isset($remix[$principalType])) {
386
                            $remix[$principalType] = [];
387
                        }
388
                        if (!isset($remix[$principalType][$fk])) {
389
                            $remix[$principalType][$fk] = [];
390
                        }
391
                        if (!isset($remix[$principalType][$fk][$dependentType])) {
392
                            $remix[$principalType][$fk][$dependentType] = $deets;
393
                        }
394
                        assert(isset($remix[$principalType][$fk][$dependentType]));
395
                    }
396
                }
397
            }
398
        }
399
    }
400
401
    /**
402
     * @param $principalType
403
     * @param $principalMult
404
     * @param $principalProperty
405
     * @param $dependentType
406
     * @param $dependentMult
407
     * @param $dependentProperty
408
     * @return array[]
409
     */
410
    private function calculateRoundTripRelationsGenForwardReverse(
411
        $principalType,
412
        $principalMult,
413
        $principalProperty,
414
        $dependentType,
415
        $dependentMult,
416
        $dependentProperty
417
    ) {
418
        $forward = [
419
            'principalType' => $principalType,
420
            'principalMult' => $dependentMult,
421
            'principalProp' => $principalProperty,
422
            'dependentType' => $dependentType,
423
            'dependentMult' => $principalMult,
424
            'dependentProp' => $dependentProperty
425
        ];
426
        $reverse = [
427
            'principalType' => $dependentType,
428
            'principalMult' => $principalMult,
429
            'principalProp' => $dependentProperty,
430
            'dependentType' => $principalType,
431
            'dependentMult' => $dependentMult,
432
            'dependentProp' => $principalProperty
433
        ];
434
        return [$forward, $reverse];
435
    }
436
437
    private function processRelationLine($line, $entityTypes, &$meta)
438
    {
439
        $principalType = $line['principalType'];
440
        $principalMult = $line['principalMult'];
441
        $principalProp = $line['principalProp'];
442
        $principalRSet = $line['principalRSet'];
443
        $dependentType = $line['dependentType'];
444
        $dependentMult = $line['dependentMult'];
445
        $dependentProp = $line['dependentProp'];
446
        $dependentRSet = $line['dependentRSet'];
447
        if (!isset($entityTypes[$principalType]) || !isset($entityTypes[$dependentType])) {
448
            return;
449
        }
450
        $principal = $entityTypes[$principalType];
451
        $dependent = $entityTypes[$dependentType];
452
        $isPoly = static::POLYMORPHIC == $principalRSet || static::POLYMORPHIC == $dependentRSet;
453
        if ($isPoly) {
454
            $this->attachReferencePolymorphic(
455
                $meta,
456
                $principalMult,
457
                $dependentMult,
458
                $principal,
459
                $dependent,
460
                $principalProp,
461
                $dependentProp,
462
                $principalRSet,
463
                $dependentRSet
464
            );
465
            return null;
466
        }
467
        $this->attachReferenceNonPolymorphic(
468
            $meta,
469
            $principalMult,
470
            $dependentMult,
471
            $principal,
472
            $dependent,
473
            $principalProp,
474
            $dependentProp
475
        );
476
        return null;
477
    }
478
479
    /**
480
     * @param $meta
481
     * @param $principalMult
482
     * @param $dependentMult
483
     * @param $principal
484
     * @param $dependent
485
     * @param $principalProp
486
     * @param $dependentProp
487
     */
488
    private function attachReferenceNonPolymorphic(
489
        &$meta,
490
        $principalMult,
491
        $dependentMult,
492
        $principal,
493
        $dependent,
494
        $principalProp,
495
        $dependentProp
496
    ) {
497
        //many-to-many
498
        if ('*' == $principalMult && '*' == $dependentMult) {
499
            $meta->addResourceSetReferencePropertyBidirectional(
500
                $principal,
501
                $dependent,
502
                $principalProp,
503
                $dependentProp
504
            );
505
            return;
506
        }
507
        //one-to-one
508
        if ('0..1' == $principalMult || '0..1' == $dependentMult) {
509
            assert($principalMult != $dependentMult, 'Cannot have both ends with 0..1 multiplicity');
510
            $meta->addResourceReferenceSinglePropertyBidirectional(
511
                $principal,
512
                $dependent,
513
                $principalProp,
514
                $dependentProp
515
            );
516
            return;
517
        }
518
        assert($principalMult != $dependentMult, 'Cannot have both ends same multiplicity for 1:N relation');
519
        //principal-one-to-dependent-many
520
        if ('*' == $principalMult) {
521
            $meta->addResourceReferencePropertyBidirectional(
522
                $principal,
523
                $dependent,
524
                $principalProp,
525
                $dependentProp
526
            );
527
            return;
528
        }
529
        //dependent-one-to-principal-many
530
        $meta->addResourceReferencePropertyBidirectional(
531
            $dependent,
532
            $principal,
533
            $dependentProp,
534
            $principalProp
535
        );
536
        return;
537
    }
538
539
    /**
540
     * @param $meta
541
     * @param $principalMult
542
     * @param $dependentMult
543
     * @param $principal
544
     * @param $dependent
545
     * @param $principalProp
546
     * @param $dependentProp
547
     */
548
    private function attachReferencePolymorphic(
549
        &$meta,
550
        $principalMult,
551
        $dependentMult,
552
        $principal,
553
        $dependent,
554
        $principalProp,
555
        $dependentProp,
556
        $principalRSet,
557
        $dependentRSet
558
    ) {
559
        $prinPoly = static::POLYMORPHIC == $principalRSet;
560
        $depPoly = static::POLYMORPHIC == $dependentRSet;
561
        $principalSet = (!$prinPoly) ? $principal->getCustomState()
562
            : $meta->resolveResourceSet(static::POLYMORPHIC_PLURAL);
563
        $dependentSet = (!$depPoly) ? $dependent->getCustomState()
564
            : $meta->resolveResourceSet(static::POLYMORPHIC_PLURAL);
565
        assert($principalSet instanceof ResourceSet, $principalRSet);
566
        assert($dependentSet instanceof ResourceSet, $dependentRSet);
567
        $isPrincipalAdded = null !== $principal->resolveProperty($principalProp);
568
        $isDependentAdded = null !== $dependent->resolveProperty($dependentProp);
569
570
        if (!$isPrincipalAdded) {
571
            if ('*' == $principalMult) {
572
                $meta->addResourceSetReferenceProperty($principal, $principalProp, $dependentSet);
573
            } else {
574
                $meta->addResourceReferenceProperty($principal, $principalProp, $dependentSet);
575
            }
576
        }
577
        if (!$isDependentAdded) {
578
            if ('*' == $dependentMult) {
579
                $meta->addResourceSetReferenceProperty($dependent, $dependentProp, $principalSet);
580
            } else {
581
                $meta->addResourceReferenceProperty($dependent, $dependentProp, $principalSet);
582
            }
583
        }
584
        return;
585
    }
586
}
587