Completed
Push — master ( 3901c9...1218b0 )
by Alex
11:19 queued 06:22
created

MetadataProvider::attachReferencePolymorphic()   C

Complexity

Conditions 11
Paths 144

Size

Total Lines 70
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 70
ccs 0
cts 0
cp 0
rs 5.3599
cc 11
eloc 57
nc 144
nop 11
crap 132

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\MetadataRelationHolder;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Support\Facades\App;
8
use Illuminate\Support\ServiceProvider;
9
use Illuminate\Support\Facades\Cache;
10
use Illuminate\Support\Str;
11
use POData\Providers\Metadata\IMetadataProvider;
12
use POData\Providers\Metadata\ResourceEntityType;
13
use POData\Providers\Metadata\ResourceSet;
14
use POData\Providers\Metadata\ResourceType;
15
use POData\Providers\Metadata\SimpleMetadataProvider;
16
use Illuminate\Support\Facades\Route;
17
use Illuminate\Support\Facades\Schema as Schema;
18
use POData\Providers\Metadata\Type\TypeCode;
19
use POData\Providers\ProvidersWrapper;
20 1
21
class MetadataProvider extends MetadataBaseProvider
22 1
{
23
    protected $multConstraints = [ '0..1' => ['1'], '1' => ['0..1', '*'], '*' => ['1', '*']];
24
    protected static $metaNAMESPACE = 'Data';
25 1
    protected static $relationCache;
26
    protected static $isBooted = false;
27
    const POLYMORPHIC = 'polyMorphicPlaceholder';
28 1
    const POLYMORPHIC_PLURAL = 'polyMorphicPlaceholders';
29 1
30
    protected $relationHolder;
31
32
    public function __construct($app)
33
    {
34
        parent::__construct($app);
35
        $this->relationHolder = new MetadataRelationHolder();
36
        self::$isBooted = false;
37
    }
38
39
    /**
40
     * Bootstrap the application services.  Post-boot.
41
     *
42
     * @return void
43
     */
44
    public function boot()
45
    {
46
        self::$metaNAMESPACE = env('ODataMetaNamespace', 'Data');
47
        // If we aren't migrated, there's no DB tables to pull metadata _from_, so bail out early
48
        try {
49
            if (!Schema::hasTable(config('database.migrations'))) {
50
                return;
51
            }
52
        } catch (\Exception $e) {
53
            return;
54
        }
55
56
        assert(false === self::$isBooted, 'Provider booted twice');
57
        $isCaching = true === $this->getIsCaching();
58
        $meta = Cache::get('metadata');
59
        $hasCache = null != $meta;
60
61
        if ($isCaching && $hasCache) {
62
            App::instance('metadata', $meta);
63
            return;
64
        }
65
        $meta = App::make('metadata');
66
        $this->reset();
67
68
        $stdRef = new \ReflectionClass(Model::class);
69
        $abstract = $meta->addEntityType($stdRef, static::POLYMORPHIC, true, null);
70
        $meta->addKeyProperty($abstract, 'PrimaryKey', TypeCode::STRING);
71
72
        $meta->addResourceSet(static::POLYMORPHIC, $abstract);
73
74
        $modelNames = $this->getCandidateModels();
75
76
        list($entityTypes) = $this->getEntityTypesAndResourceSets($meta, $modelNames);
77
        $entityTypes[static::POLYMORPHIC] = $abstract;
78
79
        // need to lift EntityTypes, adjust for polymorphic-affected relations, etc
80
        $biDirect = $this->getRepairedRoundTripRelations();
81
82
        // now that endpoints are hooked up, tackle the relationships
83
        // if we'd tried earlier, we'd be guaranteed to try to hook a relation up to null, which would be bad
84
        foreach ($biDirect as $line) {
85
            $this->processRelationLine($line, $entityTypes, $meta);
86
        }
87
88
        $key = 'metadata';
89
        $this->handlePostBoot($isCaching, $hasCache, $key, $meta);
90
        self::$isBooted = true;
91
    }
92
93
    /**
94
     * Register the application services.  Boot-time only.
95
     *
96
     * @return void
97
     */
98
    public function register()
99
    {
100
        $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...
101
            return new SimpleMetadataProvider('Data', self::$metaNAMESPACE);
102
        });
103
    }
104
105
    /**
106
     * @return array
107
     */
108
    protected function getCandidateModels()
109
    {
110
        $classes = $this->getClassMap();
111
        $ends = [];
112
        $startName = defined('PODATA_LARAVEL_APP_ROOT_NAMESPACE') ? PODATA_LARAVEL_APP_ROOT_NAMESPACE : 'App';
113
        foreach ($classes as $name) {
114
            if (\Illuminate\Support\Str::startsWith($name, $startName)) {
115
                if (in_array('AlgoWeb\\PODataLaravel\\Models\\MetadataTrait', class_uses($name))) {
116
                    $ends[] = $name;
117
                }
118
            }
119
        }
120
        return $ends;
121
    }
122
123
    /**
124
     * @param $meta
125
     * @param $ends
126
     * @return array[]
127
     */
128
    protected function getEntityTypesAndResourceSets($meta, $ends)
129
    {
130
        assert($meta instanceof IMetadataProvider, get_class($meta));
131
        $entityTypes = [];
132
        $resourceSets = [];
133
        $begins = [];
134
        $numEnds = count($ends);
135
136
        for ($i = 0; $i < $numEnds; $i++) {
137
            $bitter = $ends[$i];
138
            $fqModelName = $bitter;
139
140
            $instance = App::make($fqModelName);
141
            $name = strtolower($instance->getEndpointName());
142
            $metaSchema = $instance->getXmlSchema();
143
144
            // if for whatever reason we don't get an XML schema, move on to next entry and drop current one from
145
            // further processing
146
            if (null == $metaSchema) {
147
                continue;
148
            }
149
            $entityTypes[$fqModelName] = $metaSchema;
150
            $resourceSets[$fqModelName] = $meta->addResourceSet($name, $metaSchema);
151
            $begins[] = $bitter;
152
        }
153
154
        return [$entityTypes, $resourceSets, $begins];
155
    }
156
157
    /**
158
     * @return MetadataRelationHolder
159
     */
160
    public function getRelationHolder()
161
    {
162
        return $this->relationHolder;
163
    }
164
165
    public function calculateRoundTripRelations()
166
    {
167
        $modelNames = $this->getCandidateModels();
168
169
        foreach ($modelNames as $name) {
170
            if (!$this->getRelationHolder()->hasClass($name)) {
171
                $model = new $name();
172
                $this->getRelationHolder()->addModel($model);
173
            }
174
        }
175
176
        return $this->getRelationHolder()->getRelations();
177
    }
178
179
    public function getPolymorphicRelationGroups()
180
    {
181
        $modelNames = $this->getCandidateModels();
182
183
        $knownSide = [];
184
        $unknownSide = [];
185
186
        $hooks = [];
187
        // fish out list of polymorphic-affected models for further processing
188
        foreach ($modelNames as $name) {
189
            $model = new $name();
190
            $isPoly = false;
191
            if ($model->isKnownPolymorphSide()) {
192
                $knownSide[$name] = [];
193
                $isPoly = true;
194
            }
195
            if ($model->isUnknownPolymorphSide()) {
196
                $unknownSide[$name] = [];
197
                $isPoly = true;
198
            }
199
            if (false === $isPoly) {
200
                continue;
201
            }
202
203
            $rels = $model->getRelationships();
204
            // it doesn't matter if a model has no relationships here, that lack will simply be skipped over
205
            // during hookup processing
206
            $hooks[$name] = $rels;
207
        }
208
        // ensure we've only loaded up polymorphic-affected models
209
        $knownKeys = array_keys($knownSide);
210
        $unknownKeys = array_keys($unknownSide);
211
        $dualKeys = array_intersect($knownKeys, $unknownKeys);
212
        assert(count($hooks) == (count($unknownKeys) + count($knownKeys) - count($dualKeys)));
213
        // if either list is empty, bail out - there's nothing to do
214
        if (0 === count($knownSide) || 0 === count($unknownSide)) {
215
            return [];
216
        }
217
218
        // commence primary ignition
219
220
        foreach ($unknownKeys as $key) {
221
            assert(isset($hooks[$key]));
222
            $hook = $hooks[$key];
223
            foreach ($hook as $barb) {
224
                foreach ($barb as $knownType => $propData) {
225
                    $propName = array_keys($propData)[0];
226
                    if (in_array($knownType, $knownKeys)) {
227
                        if (!isset($knownSide[$knownType][$key])) {
228
                            $knownSide[$knownType][$key] = [];
229
                        }
230
                        assert(isset($knownSide[$knownType][$key]));
231
                        $knownSide[$knownType][$key][] = $propData[$propName]['property'];
232
                    }
233
                }
234
            }
235
        }
236
237
        return $knownSide;
238
    }
239
240
    /**
241
     * Get round-trip relations after inserting polymorphic-powered placeholders
242
     *
243
     * @return array
244
     */
245
    public function getRepairedRoundTripRelations()
246
    {
247
        if (!isset(self::$relationCache)) {
248
            $rels = $this->calculateRoundTripRelations();
249
            $groups = $this->getPolymorphicRelationGroups();
250
251
            if (0 === count($groups)) {
252
                self::$relationCache = $rels;
253
                return $rels;
254
            }
255
256
            $placeholder = static::POLYMORPHIC;
257
258
            $groupKeys = array_keys($groups);
259
260
            // we have at least one polymorphic relation, need to dig it out
261
            $numRels = count($rels);
262
            for ($i = 0; $i < $numRels; $i++) {
263
                $relation = $rels[$i];
264
                $principalType = $relation['principalType'];
265
                $dependentType = $relation['dependentType'];
266
                $principalPoly = in_array($principalType, $groupKeys);
267
                $dependentPoly = in_array($dependentType, $groupKeys);
268
                $rels[$i]['principalRSet'] = $principalPoly ? $placeholder : $principalType;
269
                $rels[$i]['dependentRSet'] = $dependentPoly ? $placeholder : $dependentType;
270
            }
271
            self::$relationCache = $rels;
272
        }
273
        return self::$relationCache;
274
    }
275
276
    private function processRelationLine($line, $entityTypes, &$meta)
277
    {
278
        $principalType = $line['principalType'];
279
        $principalMult = $line['principalMult'];
280
        $principalProp = $line['principalProp'];
281
        $principalRSet = $line['principalRSet'];
282
        $dependentType = $line['dependentType'];
283
        $dependentMult = $line['dependentMult'];
284
        $dependentProp = $line['dependentProp'];
285
        $dependentRSet = $line['dependentRSet'];
286
287
        if (!isset($entityTypes[$principalType]) || !isset($entityTypes[$dependentType])) {
288
            return;
289
        }
290
        $principal = $entityTypes[$principalType];
291
        $dependent = $entityTypes[$dependentType];
292
        $isPoly = static::POLYMORPHIC == $principalRSet || static::POLYMORPHIC == $dependentRSet;
293
294
        if ($isPoly) {
295
            $this->attachReferencePolymorphic(
296
                $meta,
297
                $principalMult,
298
                $dependentMult,
299
                $principal,
300
                $dependent,
301
                $principalProp,
302
                $dependentProp,
303
                $principalRSet,
304
                $dependentRSet,
305
                $principalType,
306
                $dependentType
307
            );
308
            return null;
309
        }
310
        $this->attachReferenceNonPolymorphic(
311
            $meta,
312
            $principalMult,
313
            $dependentMult,
314
            $principal,
315
            $dependent,
316
            $principalProp,
317
            $dependentProp
318
        );
319
        return null;
320
    }
321
322
    /**
323
     * @param $meta
324
     * @param $principalMult
325
     * @param $dependentMult
326
     * @param $principal
327
     * @param $dependent
328
     * @param $principalProp
329
     * @param $dependentProp
330
     */
331
    private function attachReferenceNonPolymorphic(
332
        &$meta,
333
        $principalMult,
334
        $dependentMult,
335
        $principal,
336
        $dependent,
337
        $principalProp,
338
        $dependentProp
339
    ) {
340
        //many-to-many
341
        if ('*' == $principalMult && '*' == $dependentMult) {
342
            $meta->addResourceSetReferencePropertyBidirectional(
343
                $principal,
344
                $dependent,
345
                $principalProp,
346
                $dependentProp
347
            );
348
            return;
349
        }
350
        //one-to-one
351
        if ('0..1' == $principalMult || '0..1' == $dependentMult) {
352
            assert($principalMult != $dependentMult, 'Cannot have both ends with 0..1 multiplicity');
353
            $meta->addResourceReferenceSinglePropertyBidirectional(
354
                $principal,
355
                $dependent,
356
                $principalProp,
357
                $dependentProp
358
            );
359
            return;
360
        }
361
        assert($principalMult != $dependentMult, 'Cannot have both ends same multiplicity for 1:N relation');
362
        //principal-one-to-dependent-many
363
        if ('*' == $principalMult) {
364
            $meta->addResourceReferencePropertyBidirectional(
365
                $principal,
366
                $dependent,
367
                $principalProp,
368
                $dependentProp
369
            );
370
            return;
371
        }
372
        //dependent-one-to-principal-many
373
        $meta->addResourceReferencePropertyBidirectional(
374
            $dependent,
375
            $principal,
376
            $dependentProp,
377
            $principalProp
378
        );
379
        return;
380
    }
381
382
    /**
383
     * @param $meta
384
     * @param $principalMult
385
     * @param $dependentMult
386
     * @param $principal
387
     * @param $dependent
388
     * @param $principalProp
389
     * @param $dependentProp
390
     */
391
    private function attachReferencePolymorphic(
392
        &$meta,
393
        $principalMult,
394
        $dependentMult,
395
        $principal,
396
        $dependent,
397
        $principalProp,
398
        $dependentProp,
399
        $principalRSet,
400
        $dependentRSet,
401
        $principalType,
402
        $dependentType
403
    ) {
404
        $prinPoly = static::POLYMORPHIC == $principalRSet;
405
        $depPoly = static::POLYMORPHIC == $dependentRSet;
406
        $principalSet = (!$prinPoly) ? $principal->getCustomState()
407
            : $meta->resolveResourceSet(static::POLYMORPHIC_PLURAL);
408
        $dependentSet = (!$depPoly) ? $dependent->getCustomState()
409
            : $meta->resolveResourceSet(static::POLYMORPHIC_PLURAL);
410
        assert($principalSet instanceof ResourceSet, $principalRSet);
411
        assert($dependentSet instanceof ResourceSet, $dependentRSet);
412
413
        $isPrincipalAdded = null !== $principal->resolveProperty($principalProp);
414
        $isDependentAdded = null !== $dependent->resolveProperty($dependentProp);
415
        $prinMany = '*' == $principalMult;
416
        $depMany = '*' == $dependentMult;
417
418
        $prinConcrete = null;
419
        $depConcrete = null;
420
        if ($prinPoly) {
421
            $prinBitz = explode('\\', $principalType);
422
            $prinConcrete = $meta->resolveResourceType($prinBitz[count($prinBitz)-1]);
423
            assert(static::POLYMORPHIC !== $prinConcrete->getName());
424
        }
425
        if ($depPoly) {
426
            $depBitz = explode('\\', $dependentType);
427
            $depConcrete = $meta->resolveResourceType($depBitz[count($depBitz)-1]);
428
            assert(static::POLYMORPHIC !== $depConcrete->getName());
429
        }
430
431
        if (!$isPrincipalAdded) {
432
            if ('*' == $principalMult || $depMany) {
433
                $meta->addResourceSetReferenceProperty($principal, $principalProp, $dependentSet, $depConcrete);
434
            } else {
435
                $meta->addResourceReferenceProperty(
436
                    $principal,
437
                    $principalProp,
438
                    $dependentSet,
439
                    $prinPoly,
440
                    $depMany,
441
                    $depConcrete
442
                );
443
            }
444
        }
445
        if (!$isDependentAdded) {
446
            if ('*' == $dependentMult || $prinMany) {
447
                $meta->addResourceSetReferenceProperty($dependent, $dependentProp, $principalSet, $prinConcrete);
448
            } else {
449
                $meta->addResourceReferenceProperty(
450
                    $dependent,
451
                    $dependentProp,
452
                    $principalSet,
453
                    $depPoly,
454
                    $prinMany,
455
                    $prinConcrete
456
                );
457
            }
458
        }
459
        return;
460
    }
461
462
    public function reset()
463
    {
464
        self::$relationCache = null;
465
        self::$isBooted = false;
466
    }
467
468
    /**
469
     * Resolve possible reverse relation property names
470
     *
471
     * @param Model $source
472
     * @param Model $target
473
     * @param $propName
474
     * @return string|null
475
     */
476
    public function resolveReverseProperty(Model $source, Model $target, $propName)
477
    {
478
        assert(is_string($propName), 'Property name must be string');
479
        $relations = $this->getRepairedRoundTripRelations();
480
481
        $sourceName = get_class($source);
482
        $targName = get_class($target);
483
484
        $filter = function ($segment) use ($sourceName, $targName, $propName) {
485
            $match = $sourceName == $segment['principalType'];
486
            $match &= $targName == $segment['dependentType'];
487
            $match &= $propName == $segment['principalProp'];
488
489
            return $match;
490
        };
491
492
        // array_filter does not reset keys - we have to do it ourselves
493
        $trim = array_values(array_filter($relations, $filter));
494
        $result = 0 === count($trim) ? null : $trim[0]['dependentProp'];
495
496
        return $result;
497
    }
498
}
499