Test Failed
Pull Request — master (#93)
by Alex
02:58
created

MetadataProvider::attachReferencePolymorphic()   D

Complexity

Conditions 9
Paths 36

Size

Total Lines 41
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 41
ccs 0
cts 0
cp 0
rs 4.909
cc 9
eloc 33
nc 36
nop 9
crap 90

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