Test Failed
Pull Request — master (#85)
by Alex
05:36
created

calculateRoundTripRelationsSecondPass()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 45
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 45
ccs 0
cts 0
cp 0
rs 8.439
cc 6
eloc 31
nc 6
nop 2
crap 42
1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Providers;
4
5
use Illuminate\Support\Facades\App;
6
use Illuminate\Support\ServiceProvider;
7
use Illuminate\Support\Facades\Cache;
8
use POData\Providers\Metadata\IMetadataProvider;
9
use POData\Providers\Metadata\ResourceEntityType;
10
use POData\Providers\Metadata\ResourceType;
11
use POData\Providers\Metadata\SimpleMetadataProvider;
12
use Illuminate\Support\Facades\Route;
13
use Illuminate\Support\Facades\Schema as Schema;
14
15
class MetadataProvider extends MetadataBaseProvider
16
{
17
    protected $multConstraints = [ '0..1' => ['1'], '1' => ['0..1', '*'], '*' => ['1', '*']];
18
    protected static $metaNAMESPACE = 'Data';
19
20 1
    /**
21
     * Bootstrap the application services.  Post-boot.
22 1
     *
23
     * @return void
24
     */
25 1
    public function boot()
26
    {
27
        self::$metaNAMESPACE = env('ODataMetaNamespace', 'Data');
28 1
        // If we aren't migrated, there's no DB tables to pull metadata _from_, so bail out early
29 1
        try {
30
            if (!Schema::hasTable(config('database.migrations'))) {
31
                return;
32
            }
33
        } catch (\Exception $e) {
34
            return;
35
        }
36
37
        $isCaching = true === $this->getIsCaching();
38
        $meta = Cache::get('metadata');
39
        $hasCache = null != $meta;
40
41
        if ($isCaching && $hasCache) {
42
            App::instance('metadata', $meta);
43
            return;
44
        }
45
        $meta = App::make('metadata');
46
47
        $stdRef = new \ReflectionClass(new \stdClass());
48
        $meta->addEntityType($stdRef, 'polyMorphicPlaceholder', true, null);
49
50
        $modelNames = $this->getCandidateModels();
51
52
        list($entityTypes) = $this->getEntityTypesAndResourceSets($meta, $modelNames);
53
54
        // need to lift EntityTypes
55
        $biDirect = $this->calculateRoundTripRelations();
56
57
        $groups = $this->getPolymorphicRelationGroups();
0 ignored issues
show
Unused Code introduced by
$groups 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...
58
59
        // now that endpoints are hooked up, tackle the relationships
60
        // if we'd tried earlier, we'd be guaranteed to try to hook a relation up to null, which would be bad
61
        foreach ($biDirect as $line) {
62
            $this->processRelationLine($line, $entityTypes, $meta);
63
        }
64
65
        $key = 'metadata';
66
        $this->handlePostBoot($isCaching, $hasCache, $key, $meta);
67
    }
68
69
    /**
70
     * Register the application services.  Boot-time only.
71
     *
72
     * @return void
73
     */
74
    public function register()
75
    {
76
        $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...
77
            return new SimpleMetadataProvider('Data', self::$metaNAMESPACE);
78
        });
79
    }
80
81
    /**
82
     * @return array
83
     */
84
    protected function getCandidateModels()
85
    {
86
        $classes = $this->getClassMap();
87
        $ends = [];
88
        $startName = defined('PODATA_LARAVEL_APP_ROOT_NAMESPACE') ? PODATA_LARAVEL_APP_ROOT_NAMESPACE : 'App';
89
        foreach ($classes as $name) {
90
            if (\Illuminate\Support\Str::startsWith($name, $startName)) {
91
                if (in_array('AlgoWeb\\PODataLaravel\\Models\\MetadataTrait', class_uses($name))) {
92
                    $ends[] = $name;
93
                }
94
            }
95
        }
96
        return $ends;
97
    }
98
99
    /**
100
     * @param $meta
101
     * @param $ends
102
     * @return array[]
103
     */
104
    protected function getEntityTypesAndResourceSets($meta, $ends)
105
    {
106
        assert($meta instanceof IMetadataProvider, get_class($meta));
107
        $entityTypes = [];
108
        $resourceSets = [];
109
        $begins = [];
110
        $numEnds = count($ends);
111
112
        for ($i = 0; $i < $numEnds; $i++) {
113
            $bitter = $ends[$i];
114
            $fqModelName = $bitter;
115
116
            $instance = App::make($fqModelName);
117
            $name = strtolower($instance->getEndpointName());
118
            $metaSchema = $instance->getXmlSchema();
119
120
            // if for whatever reason we don't get an XML schema, move on to next entry and drop current one from
121
            // further processing
122
            if (null == $metaSchema) {
123
                continue;
124
            }
125
            $entityTypes[$fqModelName] = $metaSchema;
126
            $resourceSets[$fqModelName] = $meta->addResourceSet($name, $metaSchema);
127
            $begins[] = $bitter;
128
        }
129
130
        return [$entityTypes, $resourceSets, $begins];
131
    }
132
133
    public function calculateRoundTripRelations()
134
    {
135
        $modelNames = $this->getCandidateModels();
136
137
        $hooks = [];
138
        foreach ($modelNames as $name) {
139
            $model = new $name();
140
            $rels = $model->getRelationships();
141
            // it doesn't matter if a model has no relationships here, that lack will simply be skipped over
142
            // during hookup processing
143
            $hooks[$name] = $rels;
144
        }
145
146
        // model relation gubbins are assembled, now the hard bit starts
147
        // storing assembled bidirectional relationship schema
148
        $rawLines = [];
149
        // storing unprocessed relation gubbins for second-pass processing
150
        $remix = [];
151
        $this->calculateRoundTripRelationsFirstPass($hooks, $rawLines, $remix);
152
153
        // now for second processing pass, to pick up stuff that first didn't handle
154
        $rawLines = $this->calculateRoundTripRelationsSecondPass($remix, $rawLines);
155
156
        // deduplicate rawLines - can't use array_unique as array value elements are themselves arrays
157
        $lines = [];
158
        foreach ($rawLines as $line) {
159
            if (!in_array($line, $lines)) {
160
                $lines[] = $line;
161
            }
162
        }
163
164
        return $lines;
165
    }
166
167
    public function getPolymorphicRelationGroups()
168
    {
169
        $modelNames = $this->getCandidateModels();
170
171
        $knownSide = [];
172
        $unknownSide = [];
173
174
        $hooks = [];
175
        // fish out list of polymorphic-affected models for further processing
176
        foreach ($modelNames as $name) {
177
            $model = new $name();
178
            $isPoly = false;
179
            if ($model->isKnownPolymorphSide()) {
180
                $knownSide[$name] = [];
181
                $isPoly = true;
182
            }
183
            if ($model->isUnknownPolymorphSide()) {
184
                $unknownSide[$name] = [];
185
                $isPoly = true;
186
            }
187
            if (false === $isPoly) {
188
                continue;
189
            }
190
191
            $rels = $model->getRelationships();
192
            // it doesn't matter if a model has no relationships here, that lack will simply be skipped over
193
            // during hookup processing
194
            $hooks[$name] = $rels;
195
        }
196
        // ensure we've only loaded up polymorphic-affected models
197
        $knownKeys = array_keys($knownSide);
198
        $unknownKeys = array_keys($unknownSide);
199
        $dualKeys = array_intersect($knownKeys, $unknownKeys);
200
        assert(count($hooks) == (count($unknownKeys) + count($knownKeys) - count($dualKeys)));
201
        // if either list is empty, bail out - there's nothing to do
202
        if (0 === count($knownSide) || 0 === count($unknownSide)) {
203
            return [];
204
        }
205
206
        // commence primary ignition
207
208
        foreach ($unknownKeys as $key) {
209
            assert(isset($hooks[$key]));
210
            $hook = $hooks[$key];
211
            foreach ($hook as $barb) {
212
                foreach ($barb as $knownType => $propData) {
213
                    if (in_array($knownType, $knownKeys)) {
214
                        if (!isset($knownSide[$knownType][$key])) {
215
                            $knownSide[$knownType][$key] = [];
216
                        }
217
                        assert(isset($knownSide[$knownType][$key]));
218
                        $knownSide[$knownType][$key][] = $propData['property'];
219
                    }
220
                }
221
            }
222
        }
223
224
        return $knownSide;
225
    }
226
227
    /**
228
     * @param $remix
229
     * @param $lines
230
     * @return array
231
     */
232
    private function calculateRoundTripRelationsSecondPass($remix, $lines)
233
    {
234
        foreach ($remix as $principalType => $value) {
235
            foreach ($value as $fk => $localRels) {
236
                foreach ($localRels as $dependentType => $deets) {
237
                    $principalMult = $deets['multiplicity'];
238
                    $principalProperty = $deets['property'];
239
                    $principalKey = $deets['local'];
240
241
                    if (!isset($remix[$dependentType])) {
242
                        continue;
243
                    }
244
                    $foreign = $remix[$dependentType];
245
                    if (!isset($foreign[$principalKey])) {
246
                        continue;
247
                    }
248
                    $foreign = $foreign[$principalKey];
249
                    $dependentMult = $foreign[$dependentType]['multiplicity'];
250
                    $dependentProperty = $foreign[$dependentType]['property'];
251
                    assert(
252
                        in_array($dependentMult, $this->multConstraints[$principalMult]),
253
                        'Cannot pair multiplicities ' . $dependentMult . ' and ' . $principalMult
254
                    );
255
                    assert(
256
                        in_array($principalMult, $this->multConstraints[$dependentMult]),
257
                        'Cannot pair multiplicities ' . $principalMult . ' and ' . $dependentMult
258
                    );
259
                    // generate forward and reverse relations
260
                    list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
261
                        $principalType,
262
                        $principalMult,
263
                        $principalProperty,
264
                        $dependentType,
265
                        $dependentMult,
266
                        $dependentProperty
267
                    );
268
                    // add forward relation
269
                    $lines[] = $forward;
270
                    // add reverse relation
271
                    $lines[] = $reverse;
272
                }
273
            }
274
        }
275
        return $lines;
276
    }
277
278
    /**
279
     * @param $hooks
280
     * @param $lines
281
     * @param $remix
282
     */
283
    private function calculateRoundTripRelationsFirstPass($hooks, &$lines, &$remix)
284
    {
285
        foreach ($hooks as $principalType => $value) {
286
            foreach ($value as $fk => $localRels) {
287
                foreach ($localRels as $dependentType => $deets) {
288
                    if (!isset($hooks[$dependentType])) {
289
                        continue;
290
                    }
291
                    $principalMult = $deets['multiplicity'];
292
                    $principalProperty = $deets['property'];
293
                    $principalKey = $deets['local'];
294
295
                    $foreign = $hooks[$dependentType];
296
                    $foreign = null != $foreign && isset($foreign[$principalKey]) ? $foreign[$principalKey] : null;
297
298
                    if (null != $foreign && isset($foreign[$principalType])) {
299
                        $foreign = $foreign[$principalType];
300
                        $dependentMult = $foreign['multiplicity'];
301
                        $dependentProperty = $foreign['property'];
302
                        assert(
303
                            in_array($dependentMult, $this->multConstraints[$principalMult]),
304
                            'Cannot pair multiplicities ' . $dependentMult . ' and ' . $principalMult
305
                        );
306
                        assert(
307
                            in_array($principalMult, $this->multConstraints[$dependentMult]),
308
                            'Cannot pair multiplicities ' . $principalMult . ' and ' . $dependentMult
309
                        );
310
                        // generate forward and reverse relations
311
                        list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
312
                            $principalType,
313
                            $principalMult,
314
                            $principalProperty,
315
                            $dependentType,
316
                            $dependentMult,
317
                            $dependentProperty
318
                        );
319
                        // add forward relation
320
                        $lines[] = $forward;
321
                        // add reverse relation
322
                        $lines[] = $reverse;
323
                    } else {
324
                        if (!isset($remix[$principalType])) {
325
                            $remix[$principalType] = [];
326
                        }
327
                        if (!isset($remix[$principalType][$fk])) {
328
                            $remix[$principalType][$fk] = [];
329
                        }
330
                        if (!isset($remix[$principalType][$fk][$dependentType])) {
331
                            $remix[$principalType][$fk][$dependentType] = $deets;
332
                        }
333
                        assert(isset($remix[$principalType][$fk][$dependentType]));
334
                    }
335
                }
336
            }
337
        }
338
    }
339
340
    /**
341
     * @param $principalType
342
     * @param $principalMult
343
     * @param $principalProperty
344
     * @param $dependentType
345
     * @param $dependentMult
346
     * @param $dependentProperty
347
     * @return array[]
348
     */
349
    private function calculateRoundTripRelationsGenForwardReverse(
350
        $principalType,
351
        $principalMult,
352
        $principalProperty,
353
        $dependentType,
354
        $dependentMult,
355
        $dependentProperty
356
    ) {
357
        $forward = [
358
            'principalType' => $principalType,
359
            'principalMult' => $dependentMult,
360
            'principalProp' => $principalProperty,
361
            'dependentType' => $dependentType,
362
            'dependentMult' => $principalMult,
363
            'dependentProp' => $dependentProperty
364
        ];
365
        $reverse = [
366
            'principalType' => $dependentType,
367
            'principalMult' => $principalMult,
368
            'principalProp' => $dependentProperty,
369
            'dependentType' => $principalType,
370
            'dependentMult' => $dependentMult,
371
            'dependentProp' => $principalProperty
372
        ];
373
        return [$forward, $reverse];
374
    }
375
376
    private function processRelationLine($line, $entityTypes, &$meta)
377
    {
378
        $principalType = $line['principalType'];
379
        $principalMult = $line['principalMult'];
380
        $principalProp = $line['principalProp'];
381
        $dependentType = $line['dependentType'];
382
        $dependentMult = $line['dependentMult'];
383
        $dependentProp = $line['dependentProp'];
384
        if (!isset($entityTypes[$principalType]) || !isset($entityTypes[$dependentType])) {
385
            return;
386
        }
387
        $principal = $entityTypes[$principalType];
388
        $dependent = $entityTypes[$dependentType];
389
        //many-to-many
390
        if ('*' == $principalMult && '*' == $dependentMult) {
391
            $meta->addResourceSetReferencePropertyBidirectional(
392
                $principal,
393
                $dependent,
394
                $principalProp,
395
                $dependentProp
396
            );
397
            return;
398
        }
399
        //one-to-one
400
        if ('0..1' == $principalMult || '0..1' == $dependentMult) {
401
            assert($principalMult != $dependentMult, 'Cannot have both ends with 0..1 multiplicity');
402
            $meta->addResourceReferenceSinglePropertyBidirectional(
403
                $principal,
404
                $dependent,
405
                $principalProp,
406
                $dependentProp
407
            );
408
            return;
409
        }
410
        assert($principalMult != $dependentMult, 'Cannot have both ends same multiplicity for 1:N relation');
411
        //principal-one-to-dependent-many
412
        if ('*' == $principalMult) {
413
            $meta->addResourceReferencePropertyBidirectional(
414
                $principal,
415
                $dependent,
416
                $principalProp,
417
                $dependentProp
418
            );
419
            return;
420
        }
421
        //dependent-one-to-principal-many
422
        $meta->addResourceReferencePropertyBidirectional(
423
            $dependent,
424
            $principal,
425
            $dependentProp,
426
            $principalProp
427
        );
428
        return;
429
    }
430
}
431