Test Setup Failed
Push — master ( 27cf32...a0faeb )
by Alex
03:22
created

MetadataProvider   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 357
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 7.14%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
wmc 47
c 9
b 0
f 0
lcom 1
cbo 6
dl 0
loc 357
ccs 5
cts 70
cp 0.0714
rs 8.439

9 Methods

Rating   Name   Duplication   Size   Complexity  
A setupRoute() 0 8 1
A register() 0 6 1
B getCandidateModels() 0 14 5
B getEntityTypesAndResourceSets() 0 28 3
B calculateRoundTripRelations() 0 33 4
C calculateRoundTripRelationsSecondPass() 0 49 7
C calculateRoundTripRelationsFirstPass() 0 56 12
B calculateRoundTripRelationsGenForwardReverse() 0 26 1
D boot() 0 86 13

How to fix   Complexity   

Complex Class

Complex classes like MetadataProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MetadataProvider, and based on these observations, apply Extract Interface, too.

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