Test Setup Failed
Push — master ( a0faeb...558d89 )
by Alex
14:29
created

MetadataProvider::setupRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
ccs 0
cts 2
cp 0
rs 9.4285
cc 1
eloc 5
nc 1
nop 0
crap 2
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\ResourceType;
10
use POData\Providers\Metadata\SimpleMetadataProvider;
11
use Illuminate\Support\Facades\Route;
12
use Illuminate\Support\Facades\Schema as Schema;
13
14
class MetadataProvider extends MetadataBaseProvider
15
{
16
    protected $multConstraints = [ '0..1' => ['1'], '1' => ['0..1', '*'], '*' => ['1', '*']];
17
    protected static $METANAMESPACE = "Data";
18
19
    /**
20 1
     * Bootstrap the application services.  Post-boot.
21
     *
22 1
     * @return void
23
     */
24
    public function boot()
25 1
    {
26
        self::$METANAMESPACE = env('ODataMetaNamespace', 'Data');
27
        // If we aren't migrated, there's no DB tables to pull metadata _from_, so bail out early
28 1
        try {
29 1
            if (!Schema::hasTable('migrations')) {
30
                return;
31
            }
32
        } catch (\Exception $e) {
33
            return;
34
        }
35
36
        $isCaching = true === $this->getIsCaching();
37
        $hasCache = Cache::has('metadata');
38
39
        if ($isCaching && $hasCache) {
40
            $meta = Cache::get('metadata');
41
            App::instance('metadata', $meta);
1 ignored issue
show
Bug introduced by
The method instance() does not exist on Illuminate\Support\Facades\App. Did you maybe mean clearResolvedInstance()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
42
            return;
43
        }
44
        $meta = App::make('metadata');
45
46
        $modelNames = $this->getCandidateModels();
47
48
        list($EntityTypes) = $this->getEntityTypesAndResourceSets($meta, $modelNames);
49
50
        // need to lift EntityTypes
51
        $biDirect = $this->calculateRoundTripRelations();
52
53
        // now that endpoints are hooked up, tackle the relationships
54
        // if we'd tried earlier, we'd be guaranteed to try to hook a relation up to null, which would be bad
55
        foreach ($biDirect as $line) {
56
            $this->processRelationLine($line, $EntityTypes, $meta);
57
        }
58
59
        $key = 'metadata';
60
        $this->handlePostBoot($isCaching, $hasCache, $key, $meta);
61
    }
62
63
    /**
64
     * Register the application services.  Boot-time only.
65
     *
66
     * @return void
67
     */
68
    public function register()
69
    {
70
        $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...
71
            return new SimpleMetadataProvider('Data', self::$METANAMESPACE);
72
        });
73
    }
74
75
    /**
76
     * @return array
77
     */
78
    protected function getCandidateModels()
79
    {
80
        $Classes = $this->getClassMap();
81
        $ends = [];
82
        $startName = defined('PODATA_LARAVEL_APP_ROOT_NAMESPACE') ? PODATA_LARAVEL_APP_ROOT_NAMESPACE : "App";
83
        foreach ($Classes as $name) {
84
            if (\Illuminate\Support\Str::startsWith($name, $startName)) {
85
                if (in_array("AlgoWeb\\PODataLaravel\\Models\\MetadataTrait", class_uses($name))) {
86
                    $ends[] = $name;
87
                }
88
            }
89
        }
90
        return $ends;
91
    }
92
93
    /**
94
     * @param $meta
95
     * @param $ends
96
     * @return array
97
     */
98
    protected function getEntityTypesAndResourceSets($meta, $ends)
99
    {
100
        assert($meta instanceof IMetadataProvider, get_class($meta));
101
        $EntityTypes = [];
102
        $ResourceSets = [];
103
        $begins = [];
104
        $numEnds = count($ends);
105
106
        for ($i = 0; $i < $numEnds; $i++) {
107
            $bitter = $ends[$i];
108
            $fqModelName = $bitter;
109
110
            $instance = App::make($fqModelName);
111
            $name = strtolower($instance->getEndpointName());
112
            $metaSchema = $instance->getXmlSchema();
113
114
            // if for whatever reason we don't get an XML schema, move on to next entry and drop current one from
115
            // further processing
116
            if (null == $metaSchema) {
117
                continue;
118
            }
119
            $EntityTypes[$fqModelName] = $metaSchema;
120
            $ResourceSets[$fqModelName] = $meta->addResourceSet($name, $metaSchema);
121
            $begins[] = $bitter;
122
        }
123
124
        return array($EntityTypes, $ResourceSets, $begins);
125
    }
126
127
    public function calculateRoundTripRelations()
128
    {
129
        $modelNames = $this->getCandidateModels();
130
131
        $hooks = [];
132
        foreach ($modelNames as $name) {
133
            $model = new $name();
134
            $rels = $model->getRelationships();
135
            // it doesn't matter if a model has no relationships here, that lack will simply be skipped over
136
            // during hookup processing
137
            $hooks[$name] = $rels;
138
        }
139
140
        // model relation gubbins are assembled, now the hard bit starts
141
        // storing assembled bidirectional relationship schema
142
        $rawLines = [];
143
        // storing unprocessed relation gubbins for second-pass processing
144
        $remix = [];
145
        $this->calculateRoundTripRelationsFirstPass($hooks, $rawLines, $remix);
146
147
        // now for second processing pass, to pick up stuff that first didn't handle
148
        $rawLines = $this->calculateRoundTripRelationsSecondPass($remix, $rawLines);
149
150
        // deduplicate rawLines - can't use array_unique as array value elements are themselves arrays
151
        $lines = [];
152
        foreach ($rawLines as $line) {
153
            if (!in_array($line, $lines)) {
154
                $lines[] = $line;
155
            }
156
        }
157
158
        return $lines;
159
    }
160
161
    /**
162
     * @param $remix
163
     * @param $lines
164
     * @return array
165
     */
166
    private function calculateRoundTripRelationsSecondPass($remix, $lines)
167
    {
168
        foreach ($remix as $principalType => $value) {
169
            foreach ($value as $fk => $localRels) {
170
                foreach ($localRels as $dependentType => $deets) {
171
                    $principalMult = $deets['multiplicity'];
172
                    $principalProperty = $deets['property'];
173
                    $principalKey = $deets['local'];
174
175
                    if (!isset($remix[$dependentType])) {
176
                        continue;
177
                    }
178
                    $foreign = $remix[$dependentType];
179
                    if (!isset($foreign[$principalKey])) {
180
                        continue;
181
                    }
182
                    $foreign = $foreign[$principalKey];
183
                    $dependentKey = $foreign[$dependentType]['local'];
184
                    if ($fk != $dependentKey) {
185
                        continue;
186
                    }
187
                    $dependentMult = $foreign[$dependentType]['multiplicity'];
188
                    $dependentProperty = $foreign[$dependentType]['property'];
189
                    assert(
190
                        in_array($dependentMult, $this->multConstraints[$principalMult]),
191
                        "Cannot pair multiplicities " . $dependentMult . " and " . $principalMult
192
                    );
193
                    assert(
194
                        in_array($principalMult, $this->multConstraints[$dependentMult]),
195
                        "Cannot pair multiplicities " . $principalMult . " and " . $dependentMult
196
                    );
197
                    // generate forward and reverse relations
198
                    list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
199
                        $principalType,
200
                        $principalMult,
201
                        $principalProperty,
202
                        $dependentType,
203
                        $dependentMult,
204
                        $dependentProperty
205
                    );
206
                    // add forward relation
207
                    $lines[] = $forward;
208
                    // add reverse relation
209
                    $lines[] = $reverse;
210
                }
211
            }
212
        }
213
        return $lines;
214
    }
215
216
    /**
217
     * @param $hooks
218
     * @param $lines
219
     * @param $remix
220
     */
221
    private function calculateRoundTripRelationsFirstPass($hooks, &$lines, &$remix)
222
    {
223
        foreach ($hooks as $principalType => $value) {
224
            foreach ($value as $fk => $localRels) {
225
                foreach ($localRels as $dependentType => $deets) {
226
                    if (!isset($hooks[$dependentType])) {
227
                        continue;
228
                    }
229
                    $principalMult = $deets['multiplicity'];
230
                    $principalProperty = $deets['property'];
231
                    $principalKey = $deets['local'];
232
233
                    $foreign = $hooks[$dependentType];
234
                    $foreign = null != $foreign && isset($foreign[$principalKey]) ? $foreign[$principalKey] : null;
235
236
                    if (null != $foreign && isset($foreign[$principalType])) {
237
                        $foreign = $foreign[$principalType];
238
                        $dependentMult = $foreign['multiplicity'];
239
                        $dependentProperty = $foreign['property'];
240
                        assert(
241
                            in_array($dependentMult, $this->multConstraints[$principalMult]),
242
                            "Cannot pair multiplicities " . $dependentMult . " and " . $principalMult
243
                        );
244
                        assert(
245
                            in_array($principalMult, $this->multConstraints[$dependentMult]),
246
                            "Cannot pair multiplicities " . $principalMult . " and " . $dependentMult
247
                        );
248
                        // generate forward and reverse relations
249
                        list($forward, $reverse) = $this->calculateRoundTripRelationsGenForwardReverse(
250
                            $principalType,
251
                            $principalMult,
252
                            $principalProperty,
253
                            $dependentType,
254
                            $dependentMult,
255
                            $dependentProperty
256
                        );
257
                        // add forward relation
258
                        $lines[] = $forward;
259
                        // add reverse relation
260
                        $lines[] = $reverse;
261
                    } else {
262
                        if (!isset($remix[$principalType])) {
263
                            $remix[$principalType] = [];
264
                        }
265
                        if (!isset($remix[$principalType][$fk])) {
266
                            $remix[$principalType][$fk] = [];
267
                        }
268
                        if (!isset($remix[$principalType][$fk][$dependentType])) {
269
                            $remix[$principalType][$fk][$dependentType] = $deets;
270
                        }
271
                        assert(isset($remix[$principalType][$fk][$dependentType]));
272
                    }
273
                }
274
            }
275
        }
276
    }
277
278
    /**
279
     * @param $principalType
280
     * @param $principalMult
281
     * @param $principalProperty
282
     * @param $dependentType
283
     * @param $dependentMult
284
     * @param $dependentProperty
285
     * @return array
286
     */
287
    private function calculateRoundTripRelationsGenForwardReverse(
288
        $principalType,
289
        $principalMult,
290
        $principalProperty,
291
        $dependentType,
292
        $dependentMult,
293
        $dependentProperty
294
    ) {
295
        $forward = [
296
            'principalType' => $principalType,
297
            'principalMult' => $principalMult,
298
            'principalProp' => $principalProperty,
299
            'dependentType' => $dependentType,
300
            'dependentMult' => $dependentMult,
301
            'dependentProp' => $dependentProperty
302
        ];
303
        $reverse = [
304
            'principalType' => $dependentType,
305
            'principalMult' => $dependentMult,
306
            'principalProp' => $dependentProperty,
307
            'dependentType' => $principalType,
308
            'dependentMult' => $principalMult,
309
            'dependentProp' => $principalProperty
310
        ];
311
        return array($forward, $reverse);
312
    }
313
314
    private function processRelationLine($line, $EntityTypes, &$meta)
315
    {
316
        $principalType = $line['principalType'];
317
        $principalMult = $line['principalMult'];
318
        $principalProp = $line['principalProp'];
319
        $dependentType = $line['dependentType'];
320
        $dependentMult = $line['dependentMult'];
321
        $dependentProp = $line['dependentProp'];
322
        if (!isset($EntityTypes[$principalType])) {
323
            return;
324
        }
325
        if (!isset($EntityTypes[$dependentType])) {
326
            return;
327
        }
328
        $principal = $EntityTypes[$principalType];
329
        $dependent = $EntityTypes[$dependentType];
330
        //many-to-many
331
        if ('*' == $principalMult && '*' == $dependentMult) {
332
            $meta->addResourceSetReferencePropertyBidirectional(
333
                $principal,
334
                $dependent,
335
                $principalProp,
336
                $dependentProp
337
            );
338
            return;
339
        }
340
        //one-to-one
341
        if ('0..1' == $principalMult || '0..1' == $dependentMult) {
342
            assert($principalMult != $dependentMult, "Cannot have both ends with 0..1 multiplicity");
343
            $meta->addResourceReferenceSinglePropertyBidirectional(
344
                $principal,
345
                $dependent,
346
                $principalProp,
347
                $dependentProp
348
            );
349
            return;
350
        }
351
        //principal-one-to-dependent-many
352
        if ('*' == $principalMult) {
353
            $meta->addResourceReferencePropertyBidirectional(
354
                $principal,
355
                $dependent,
356
                $principalProp,
357
                $dependentProp
358
            );
359
            return;
360
        }
361
        //dependent-one-to-principal-many
362
        $meta->addResourceReferencePropertyBidirectional(
363
            $dependent,
364
            $principal,
365
            $dependentProp,
366
            $principalProp
367
        );
368
        return;
369
    }
370
}
371