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