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

MetadataProvider::boot()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 43
ccs 0
cts 33
cp 0
rs 8.439
cc 6
eloc 25
nc 5
nop 0
crap 42
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