Test Failed
Pull Request — master (#85)
by Alex
03:30
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
use POData\Providers\Metadata\Type\TypeCode;
18
19
class MetadataProvider extends MetadataBaseProvider
20 1
{
21
    protected $multConstraints = [ '0..1' => ['1'], '1' => ['0..1', '*'], '*' => ['1', '*']];
22 1
    protected static $metaNAMESPACE = 'Data';
23
    const POLYMORPHIC = 'polyMorphicPlaceholder';
24
    const POLYMORPHIC_PLURAL = 'polyMorphicPlaceholders';
25 1
26
    /**
27
     * Bootstrap the application services.  Post-boot.
28 1
     *
29 1
     * @return void
30
     */
31
    public function boot()
32
    {
33
        self::$metaNAMESPACE = env('ODataMetaNamespace', 'Data');
34
        // If we aren't migrated, there's no DB tables to pull metadata _from_, so bail out early
35
        try {
36
            if (!Schema::hasTable(config('database.migrations'))) {
37
                return;
38
            }
39
        } catch (\Exception $e) {
40
            return;
41
        }
42
43
        $isCaching = true === $this->getIsCaching();
44
        $meta = Cache::get('metadata');
45
        $hasCache = null != $meta;
46
47
        if ($isCaching && $hasCache) {
48
            App::instance('metadata', $meta);
49
            return;
50
        }
51
        $meta = App::make('metadata');
52
53
        $stdRef = new \ReflectionClass(new \stdClass());
54
        $abstract = $meta->addEntityType($stdRef, static::POLYMORPHIC, true, null);
55
        $meta->addKeyProperty($abstract, 'PrimaryKey', TypeCode::STRING);
56
57
        $meta->addResourceSet(static::POLYMORPHIC, $abstract);
58
59
        $modelNames = $this->getCandidateModels();
60
61
        list($entityTypes) = $this->getEntityTypesAndResourceSets($meta, $modelNames);
62
        $entityTypes[static::POLYMORPHIC] = $abstract;
63
64
        // need to lift EntityTypes, adjust for polymorphic-affected relations, etc
65
        $biDirect = $this->getRepairedRoundTripRelations();
66
67
        // now that endpoints are hooked up, tackle the relationships
68
        // if we'd tried earlier, we'd be guaranteed to try to hook a relation up to null, which would be bad
69
        foreach ($biDirect as $line) {
70
            $this->processRelationLine($line, $entityTypes, $meta);
71
        }
72
73
        $key = 'metadata';
74
        $this->handlePostBoot($isCaching, $hasCache, $key, $meta);
75
    }
76
77
    /**
78
     * Register the application services.  Boot-time only.
79
     *
80
     * @return void
81
     */
82
    public function register()
83
    {
84
        $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...
85
            return new SimpleMetadataProvider('Data', self::$metaNAMESPACE);
86
        });
87
    }
88
89
    /**
90
     * @return array
91
     */
92
    protected function getCandidateModels()
93
    {
94
        $classes = $this->getClassMap();
95
        $ends = [];
96
        $startName = defined('PODATA_LARAVEL_APP_ROOT_NAMESPACE') ? PODATA_LARAVEL_APP_ROOT_NAMESPACE : 'App';
97
        foreach ($classes as $name) {
98
            if (\Illuminate\Support\Str::startsWith($name, $startName)) {
99
                if (in_array('AlgoWeb\\PODataLaravel\\Models\\MetadataTrait', class_uses($name))) {
100
                    $ends[] = $name;
101
                }
102
            }
103
        }
104
        return $ends;
105
    }
106
107
    /**
108
     * @param $meta
109
     * @param $ends
110
     * @return array[]
111
     */
112
    protected function getEntityTypesAndResourceSets($meta, $ends)
113
    {
114
        assert($meta instanceof IMetadataProvider, get_class($meta));
115
        $entityTypes = [];
116
        $resourceSets = [];
117
        $begins = [];
118
        $numEnds = count($ends);
119
120
        for ($i = 0; $i < $numEnds; $i++) {
121
            $bitter = $ends[$i];
122
            $fqModelName = $bitter;
123
124
            $instance = App::make($fqModelName);
125
            $name = strtolower($instance->getEndpointName());
126
            $metaSchema = $instance->getXmlSchema();
127
128
            // if for whatever reason we don't get an XML schema, move on to next entry and drop current one from
129
            // further processing
130
            if (null == $metaSchema) {
131
                continue;
132
            }
133
            $entityTypes[$fqModelName] = $metaSchema;
134
            $resourceSets[$fqModelName] = $meta->addResourceSet($name, $metaSchema);
135
            $begins[] = $bitter;
136
        }
137
138
        return [$entityTypes, $resourceSets, $begins];
139
    }
140
141
    public function calculateRoundTripRelations()
142
    {
143
        $modelNames = $this->getCandidateModels();
144
145
        $hooks = [];
146
        foreach ($modelNames as $name) {
147
            $model = new $name();
148
            $rels = $model->getRelationships();
149
            // it doesn't matter if a model has no relationships here, that lack will simply be skipped over
150
            // during hookup processing
151
            $hooks[$name] = $rels;
152
        }
153
154
        // model relation gubbins are assembled, now the hard bit starts
155
        // storing assembled bidirectional relationship schema
156
        $rawLines = [];
157
        // storing unprocessed relation gubbins for second-pass processing
158
        $remix = [];
159
        $this->calculateRoundTripRelationsFirstPass($hooks, $rawLines, $remix);
160
161
        // now for second processing pass, to pick up stuff that first didn't handle
162
        $rawLines = $this->calculateRoundTripRelationsSecondPass($remix, $rawLines);
163
164
        $numLines = count($rawLines);
165
        for ($i = 0; $i < $numLines; $i++) {
166
            $rawLines[$i]['principalRSet'] = $rawLines[$i]['principalType'];
167
            $rawLines[$i]['dependentRSet'] = $rawLines[$i]['dependentType'];
168
        }
169
170
        // deduplicate rawLines - can't use array_unique as array value elements are themselves arrays
171
        $lines = [];
172
        foreach ($rawLines as $line) {
173
            if (!in_array($line, $lines)) {
174
                $lines[] = $line;
175
            }
176
        }
177
178
        return $lines;
179
    }
180
181
    public function getPolymorphicRelationGroups()
182
    {
183
        $modelNames = $this->getCandidateModels();
184
185
        $knownSide = [];
186
        $unknownSide = [];
187
188
        $hooks = [];
189
        // fish out list of polymorphic-affected models for further processing
190
        foreach ($modelNames as $name) {
191
            $model = new $name();
192
            $isPoly = false;
193
            if ($model->isKnownPolymorphSide()) {
194
                $knownSide[$name] = [];
195
                $isPoly = true;
196
            }
197
            if ($model->isUnknownPolymorphSide()) {
198
                $unknownSide[$name] = [];
199
                $isPoly = true;
200
            }
201
            if (false === $isPoly) {
202
                continue;
203
            }
204
205
            $rels = $model->getRelationships();
206
            // it doesn't matter if a model has no relationships here, that lack will simply be skipped over
207
            // during hookup processing
208
            $hooks[$name] = $rels;
209
        }
210
        // ensure we've only loaded up polymorphic-affected models
211
        $knownKeys = array_keys($knownSide);
212
        $unknownKeys = array_keys($unknownSide);
213
        $dualKeys = array_intersect($knownKeys, $unknownKeys);
214
        assert(count($hooks) == (count($unknownKeys) + count($knownKeys) - count($dualKeys)));
215
        // if either list is empty, bail out - there's nothing to do
216
        if (0 === count($knownSide) || 0 === count($unknownSide)) {
217
            return [];
218
        }
219
220
        // commence primary ignition
221
222
        foreach ($unknownKeys as $key) {
223
            assert(isset($hooks[$key]));
224
            $hook = $hooks[$key];
225
            foreach ($hook as $barb) {
226
                foreach ($barb as $knownType => $propData) {
227
                    if (in_array($knownType, $knownKeys)) {
228
                        if (!isset($knownSide[$knownType][$key])) {
229
                            $knownSide[$knownType][$key] = [];
230
                        }
231
                        assert(isset($knownSide[$knownType][$key]));
232
                        $knownSide[$knownType][$key][] = $propData['property'];
233
                    }
234
                }
235
            }
236
        }
237
238
        return $knownSide;
239
    }
240
241
    /**
242
     * Get round-trip relations after inserting polymorphic-powered placeholders
243
     *
244
     * @return array
245
     */
246
    public function getRepairedRoundTripRelations()
247
    {
248
        $rels = $this->calculateRoundTripRelations();
249
        $groups = $this->getPolymorphicRelationGroups();
250
251
        if (0 === count($groups)) {
252
            return $rels;
253
        }
254
255
        $placeholder = static::POLYMORPHIC;
256
257
        $groupKeys = array_keys($groups);
258
259
        // we have at least one polymorphic relation, need to dig it out
260
        $numRels = count($rels);
261
        for ($i = 0; $i < $numRels; $i++) {
262
            $relation = $rels[$i];
263
            $principalType = $relation['principalType'];
264
            $dependentType = $relation['dependentType'];
265
            $principalPoly = in_array($principalType, $groupKeys);
266
            $dependentPoly = in_array($dependentType, $groupKeys);
267
            // if relation is not polymorphic, then move on
268
            if (!($principalPoly || $dependentPoly)) {
269
                continue;
270
            } else {
271
                // if only one end is a known end of a polymorphic relation
272
                // for moment we're punting on both
273
                $oneEnd = $principalPoly !== $dependentPoly;
274
                assert($oneEnd, 'Multi-generational polymorphic relation chains not implemented');
275
                $targRels = $principalPoly ? $groups[$principalType] : $groups[$dependentType];
276
                $targUnknown = $targRels[$principalPoly ? $dependentType : $principalType];
277
                $targProperty = $principalPoly ? $relation['dependentProp'] : $relation['principalProp'];
278
                $msg = 'Specified unknown-side property ' . $targProperty . ' not found in polymorphic relation map';
279
                assert(in_array($targProperty, $targUnknown), $msg);
280
281
                $targType = $principalPoly ? 'dependentRSet' : 'principalRSet';
282
                $rels[$i][$targType] = $placeholder;
283
                continue;
284
            }
285
        }
286
287
        return $rels;
288
    }
289
290
    /**
291
     * @param $remix
292
     * @param $lines
293
     * @return array
294
     */
295
    private function calculateRoundTripRelationsSecondPass($remix, $lines)
296
    {
297
        foreach ($remix as $principalType => $value) {
298
            foreach ($value as $fk => $localRels) {
299
                foreach ($localRels as $dependentType => $deets) {
300
                    $principalMult = $deets['multiplicity'];
301
                    $principalProperty = $deets['property'];
302
                    $principalKey = $deets['local'];
303
304
                    if (!isset($remix[$dependentType])) {
305
                        continue;
306
                    }
307
                    $foreign = $remix[$dependentType];
308
                    if (!isset($foreign[$principalKey])) {
309
                        continue;
310
                    }
311
                    $foreign = $foreign[$principalKey];
312
                    $dependentMult = $foreign[$dependentType]['multiplicity'];
313
                    $dependentProperty = $foreign[$dependentType]['property'];
314
                    assert(
315
                        in_array($dependentMult, $this->multConstraints[$principalMult]),
316
                        'Cannot pair multiplicities ' . $dependentMult . ' and ' . $principalMult
317
                    );
318
                    assert(
319
                        in_array($principalMult, $this->multConstraints[$dependentMult]),
320
                        'Cannot pair multiplicities ' . $principalMult . ' and ' . $dependentMult
321
                    );
322
                    // generate forward and reverse relations
323
                    list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
324
                        $principalType,
325
                        $principalMult,
326
                        $principalProperty,
327
                        $dependentType,
328
                        $dependentMult,
329
                        $dependentProperty
330
                    );
331
                    // add forward relation
332
                    $lines[] = $forward;
333
                    // add reverse relation
334
                    $lines[] = $reverse;
335
                }
336
            }
337
        }
338
        return $lines;
339
    }
340
341
    /**
342
     * @param $hooks
343
     * @param $lines
344
     * @param $remix
345
     */
346
    private function calculateRoundTripRelationsFirstPass($hooks, &$lines, &$remix)
347
    {
348
        foreach ($hooks as $principalType => $value) {
349
            foreach ($value as $fk => $localRels) {
350
                foreach ($localRels as $dependentType => $deets) {
351
                    if (!isset($hooks[$dependentType])) {
352
                        continue;
353
                    }
354
                    $principalMult = $deets['multiplicity'];
355
                    $principalProperty = $deets['property'];
356
                    $principalKey = $deets['local'];
357
358
                    $foreign = $hooks[$dependentType];
359
                    $foreign = null != $foreign && isset($foreign[$principalKey]) ? $foreign[$principalKey] : null;
360
361
                    if (null != $foreign && isset($foreign[$principalType])) {
362
                        $foreign = $foreign[$principalType];
363
                        $dependentMult = $foreign['multiplicity'];
364
                        $dependentProperty = $foreign['property'];
365
                        assert(
366
                            in_array($dependentMult, $this->multConstraints[$principalMult]),
367
                            'Cannot pair multiplicities ' . $dependentMult . ' and ' . $principalMult
368
                        );
369
                        assert(
370
                            in_array($principalMult, $this->multConstraints[$dependentMult]),
371
                            'Cannot pair multiplicities ' . $principalMult . ' and ' . $dependentMult
372
                        );
373
                        // generate forward and reverse relations
374
                        list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
375
                            $principalType,
376
                            $principalMult,
377
                            $principalProperty,
378
                            $dependentType,
379
                            $dependentMult,
380
                            $dependentProperty
381
                        );
382
                        // add forward relation
383
                        $lines[] = $forward;
384
                        // add reverse relation
385
                        $lines[] = $reverse;
386
                    } else {
387
                        if (!isset($remix[$principalType])) {
388
                            $remix[$principalType] = [];
389
                        }
390
                        if (!isset($remix[$principalType][$fk])) {
391
                            $remix[$principalType][$fk] = [];
392
                        }
393
                        if (!isset($remix[$principalType][$fk][$dependentType])) {
394
                            $remix[$principalType][$fk][$dependentType] = $deets;
395
                        }
396
                        assert(isset($remix[$principalType][$fk][$dependentType]));
397
                    }
398
                }
399
            }
400
        }
401
    }
402
403
    /**
404
     * @param $principalType
405
     * @param $principalMult
406
     * @param $principalProperty
407
     * @param $dependentType
408
     * @param $dependentMult
409
     * @param $dependentProperty
410
     * @return array[]
411
     */
412
    private function calculateRoundTripRelationsGenForwardReverse(
413
        $principalType,
414
        $principalMult,
415
        $principalProperty,
416
        $dependentType,
417
        $dependentMult,
418
        $dependentProperty
419
    ) {
420
        $forward = [
421
            'principalType' => $principalType,
422
            'principalMult' => $dependentMult,
423
            'principalProp' => $principalProperty,
424
            'dependentType' => $dependentType,
425
            'dependentMult' => $principalMult,
426
            'dependentProp' => $dependentProperty
427
        ];
428
        $reverse = [
429
            'principalType' => $dependentType,
430
            'principalMult' => $principalMult,
431
            'principalProp' => $dependentProperty,
432
            'dependentType' => $principalType,
433
            'dependentMult' => $dependentMult,
434
            'dependentProp' => $principalProperty
435
        ];
436
        return [$forward, $reverse];
437
    }
438
439
    private function processRelationLine($line, $entityTypes, &$meta)
440
    {
441
        $principalType = $line['principalType'];
442
        $principalMult = $line['principalMult'];
443
        $principalProp = $line['principalProp'];
444
        $principalRSet = $line['principalRSet'];
445
        $dependentType = $line['dependentType'];
446
        $dependentMult = $line['dependentMult'];
447
        $dependentProp = $line['dependentProp'];
448
        $dependentRSet = $line['dependentRSet'];
449
        if (!isset($entityTypes[$principalType]) || !isset($entityTypes[$dependentType])) {
450
            return;
451
        }
452
        $principal = $entityTypes[$principalType];
453
        $dependent = $entityTypes[$dependentType];
454
        $isPoly = static::POLYMORPHIC == $principalRSet || static::POLYMORPHIC == $dependentRSet;
455
        if ($isPoly) {
456
            $this->attachReferencePolymorphic(
457
                $meta,
458
                $principalMult,
459
                $dependentMult,
460
                $principal,
461
                $dependent,
462
                $principalProp,
463
                $dependentProp,
464
                $principalRSet,
465
                $dependentRSet
466
            );
467
            return null;
468
        }
469
        $this->attachReferenceNonPolymorphic(
470
            $meta,
471
            $principalMult,
472
            $dependentMult,
473
            $principal,
474
            $dependent,
475
            $principalProp,
476
            $dependentProp
477
        );
478
        return null;
479
    }
480
481
    /**
482
     * @param $meta
483
     * @param $principalMult
484
     * @param $dependentMult
485
     * @param $principal
486
     * @param $dependent
487
     * @param $principalProp
488
     * @param $dependentProp
489
     */
490
    private function attachReferenceNonPolymorphic(
491
        &$meta,
492
        $principalMult,
493
        $dependentMult,
494
        $principal,
495
        $dependent,
496
        $principalProp,
497
        $dependentProp
498
    ) {
499
        //many-to-many
500
        if ('*' == $principalMult && '*' == $dependentMult) {
501
            $meta->addResourceSetReferencePropertyBidirectional(
502
                $principal,
503
                $dependent,
504
                $principalProp,
505
                $dependentProp
506
            );
507
            return;
508
        }
509
        //one-to-one
510
        if ('0..1' == $principalMult || '0..1' == $dependentMult) {
511
            assert($principalMult != $dependentMult, 'Cannot have both ends with 0..1 multiplicity');
512
            $meta->addResourceReferenceSinglePropertyBidirectional(
513
                $principal,
514
                $dependent,
515
                $principalProp,
516
                $dependentProp
517
            );
518
            return;
519
        }
520
        assert($principalMult != $dependentMult, 'Cannot have both ends same multiplicity for 1:N relation');
521
        //principal-one-to-dependent-many
522
        if ('*' == $principalMult) {
523
            $meta->addResourceReferencePropertyBidirectional(
524
                $principal,
525
                $dependent,
526
                $principalProp,
527
                $dependentProp
528
            );
529
            return;
530
        }
531
        //dependent-one-to-principal-many
532
        $meta->addResourceReferencePropertyBidirectional(
533
            $dependent,
534
            $principal,
535
            $dependentProp,
536
            $principalProp
537
        );
538
        return;
539
    }
540
541
    /**
542
     * @param $meta
543
     * @param $principalMult
544
     * @param $dependentMult
545
     * @param $principal
546
     * @param $dependent
547
     * @param $principalProp
548
     * @param $dependentProp
549
     */
550
    private function attachReferencePolymorphic(
551
        &$meta,
552
        $principalMult,
553
        $dependentMult,
554
        $principal,
555
        $dependent,
556
        $principalProp,
557
        $dependentProp,
558
        $principalRSet,
559
        $dependentRSet
560
    ) {
561
        $prinPoly = static::POLYMORPHIC == $principalRSet;
562
        $depPoly = static::POLYMORPHIC == $dependentRSet;
563
        $principalSet = (!$prinPoly) ? $principal->getCustomState()
564
            : $meta->resolveResourceSet(static::POLYMORPHIC_PLURAL);
565
        $dependentSet = (!$depPoly) ? $dependent->getCustomState()
566
            : $meta->resolveResourceSet(static::POLYMORPHIC_PLURAL);
567
        assert($principalSet instanceof ResourceSet, $principalRSet);
568
        assert($dependentSet instanceof ResourceSet, $dependentRSet);
569
        $isPrincipalAdded = null !== $principal->resolveProperty($principalProp);
570
        $isDependentAdded = null !== $dependent->resolveProperty($dependentProp);
571
572
        if (!$isPrincipalAdded) {
573
            if ('*' == $principalMult) {
574
                $meta->addResourceSetReferenceProperty($principal, $principalProp, $dependentSet);
575
            } else {
576
                $meta->addResourceReferenceProperty($principal, $principalProp, $dependentSet);
577
            }
578
        }
579
        if (!$isDependentAdded) {
580
            if ('*' == $dependentMult) {
581
                $meta->addResourceSetReferenceProperty($dependent, $dependentProp, $principalSet);
582
            } else {
583
                $meta->addResourceReferenceProperty($dependent, $dependentProp, $principalSet);
584
            }
585
        }
586
        return;
587
    }
588
}
589