Passed
Pull Request — master (#201)
by Alex
10:25 queued 04:19
created

MetadataTrait::addRelationsHook()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 14
ccs 0
cts 0
cp 0
rs 9.9332
cc 3
nc 4
nop 8
crap 12

How to fix   Many Parameters   

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
namespace AlgoWeb\PODataLaravel\Models;
3
4
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\Associations\AssociationStubMonomorphic;
5
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\Associations\AssociationStubPolymorphic;
6
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\Associations\AssociationStubRelationType;
7
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\EntityField;
8
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\EntityFieldPrimitiveType;
9
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\EntityFieldType;
10
use AlgoWeb\PODataLaravel\Models\ObjectMap\Entities\EntityGubbins;
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Database\Eloquent\Relations\BelongsTo;
13
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
14
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
15
use Illuminate\Database\Eloquent\Relations\MorphMany;
16
use Illuminate\Database\Eloquent\Relations\MorphOne;
17
use Illuminate\Database\Eloquent\Relations\MorphToMany;
18
use Illuminate\Database\Eloquent\Relations\Relation;
19
use Illuminate\Support\Facades\App;
20
use Mockery\Mock;
21
use POData\Common\InvalidOperationException;
22
use POData\Providers\Metadata\Type\IType;
23
24
trait MetadataTrait
25
{
26
    use MetadataRelationsTrait;
27
28 3
    protected $loadEagerRelations = [];
29
    protected static $tableColumns = [];
30 3
    protected static $tableColumnsDoctrine = [];
31
    protected static $tableData = [];
32
    protected static $dontCastTypes = ['object', 'array', 'collection', 'int'];
33 2
34 2
    protected static $relTypes = [
35
        'hasMany',
36 2
        'hasManyThrough',
37
        'belongsToMany',
38 2
        'hasOne',
39 1
        'belongsTo',
40
        'morphOne',
41
        'morphTo',
42 1
        'morphMany',
43 1
        'morphToMany',
44 1
        'morphedByMany'
45
    ];
46 1
47
    protected static $manyRelTypes = [
48 1
        'hasManyThrough',
49 1
        'belongsToMany',
50 1
        'hasMany',
51
        'morphMany',
52 1
        'morphToMany',
53 1
        'morphedByMany'
54 1
    ];
55
56 1
    /**
57
     * Retrieve and assemble this model's metadata for OData packaging
58 1
     * @return array
59 1
     * @throws InvalidOperationException
60 1
     * @throws \Doctrine\DBAL\DBALException
61 1
     */
62 1
    public function metadata()
63 1
    {
64 1
        if (!$this instanceof Model) {
65
            throw new InvalidOperationException(get_class($this));
66 1
        }
67
68
        if (0 !== count(self::$tableData)) {
69
            return self::$tableData;
70
        } elseif (isset($this->odata)) {
71
            return self::$tableData = $this->odata;
72
        }
73 4
74
        // Break these out separately to enable separate reuse
75 4
        $connect = $this->getConnection();
76
        $builder = $connect->getSchemaBuilder();
77 4
78 4
        $table = $this->getTable();
79 4
80 2
        if (!$builder->hasTable($table)) {
81 4
            return self::$tableData = [];
82 1
        }
83 1
84
        /** @var array $columns */
85 4
        $columns = $this->getTableColumns();
86
        /** @var array $mask */
87
        $mask = $this->metadataMask();
88
        $columns = array_intersect($columns, $mask);
89
90
        $tableData = [];
91 5
92
        $rawFoo = $this->getTableDoctrineColumns();
93 5
        $foo = [];
94 5
        /** @var array $getters */
95 1
        $getters = $this->collectGetters();
96
        $getters = array_intersect($getters, $mask);
97
        $casts = $this->retrieveCasts();
98 4
99
        foreach ($rawFoo as $key => $val) {
100 4
            // Work around glitch in Doctrine when reading from MariaDB which added ` characters to root key value
101
            $key = trim($key, '`"');
102 4
            $foo[$key] = $val;
103 4
        }
104 4
105 4
        foreach ($columns as $column) {
106 4
            // Doctrine schema manager returns columns with lowercased names
107 4
            $rawColumn = $foo[strtolower($column)];
108
            /** @var IType $rawType */
109 4
            $rawType = $rawColumn->getType();
110 1
            $type = $rawType->getName();
111 1
            $default = $this->$column;
112 1
            $tableData[$column] = ['type' => $type,
113 1
                'nullable' => !($rawColumn->getNotNull()),
114
                'fillable' => in_array($column, $this->getFillable()),
115 4
                'default' => $default
116 4
            ];
117
        }
118 4
119
        foreach ($getters as $get) {
120
            if (isset($tableData[$get])) {
121
                continue;
122
            }
123
            $default = $this->$get;
124
            $tableData[$get] = ['type' => 'text', 'nullable' => true, 'fillable' => false, 'default' => $default];
125
        }
126
127
        // now, after everything's gathered up, apply Eloquent model's $cast array
128
        foreach ($casts as $key => $type) {
129
            $type = strtolower($type);
130
            if (array_key_exists($key, $tableData) && !in_array($type, self::$dontCastTypes)) {
131
                $tableData[$key]['type'] = $type;
132
            }
133
        }
134
135
        self::$tableData = $tableData;
136
        return $tableData;
137
    }
138
139
    /**
140
     * Return the set of fields that are permitted to be in metadata
141
     * - following same visible-trumps-hidden guideline as Laravel
142
     *
143
     * @return array
144
     */
145
    public function metadataMask()
146
    {
147
        $attribs = array_keys($this->getAllAttributes());
148
149
        $visible = $this->getVisible();
150
        $hidden = $this->getHidden();
151
        if (0 < count($visible)) {
152
            $attribs = array_intersect($visible, $attribs);
153
        } elseif (0 < count($hidden)) {
154
            $attribs = array_diff($attribs, $hidden);
155
        }
156
157
        return $attribs;
158
    }
159
160
    /*
161
     * Get the endpoint name being exposed
162
     *
163
     * @return null|string;
164
     */
165
    public function getEndpointName()
166
    {
167
        $endpoint = isset($this->endpoint) ? $this->endpoint : null;
168
169
        if (!isset($endpoint)) {
170
            $bitter = get_class($this);
171
            $name = substr($bitter, strrpos($bitter, '\\')+1);
172
            return ($name);
173
        }
174
        return ($endpoint);
175 3
    }
176
177 3
    protected function getAllAttributes()
178
    {
179 3
        // Adapted from http://stackoverflow.com/a/33514981
180 3
        // $columns = $this->getFillable();
181 3
        // Another option is to get all columns for the table like so:
182 3
        $columns = $this->getTableColumns();
183 3
        // but it's safer to just get the fillable fields
184 3
185 3
        $attributes = $this->getAttributes();
186 3
187 3
        foreach ($columns as $column) {
188 3
            if (!array_key_exists($column, $attributes)) {
189
                $attributes[$column] = null;
190 3
            }
191
        }
192 3
193 3
        $methods = $this->collectGetters();
194 3
195 3
        foreach ($methods as $method) {
196 3
            $attributes[$method] = null;
197 3
        }
198 3
199 3
        return $attributes;
200 3
    }
201 3
202
    /**
203 3
     * Get the visible attributes for the model.
204 3
     *
205 3
     * @return array
206 3
     */
207 3
    abstract public function getVisible();
208 3
209 3
    /**
210 3
     * Get the hidden attributes for the model.
211
     *
212 3
     * @return array
213 3
     */
214 3
    abstract public function getHidden();
215
216 3
    /**
217 3
     * Get the primary key for the model.
218 3
     *
219 3
     * @return string
220 3
     */
221
    abstract public function getKeyName();
222 1
223 3
    /**
224
     * Get the current connection name for the model.
225 1
     *
226
     * @return string
227 1
     */
228
    abstract public function getConnectionName();
229 1
230
    /**
231 3
     * Get the database connection for the model.
232 2
     *
233 2
     * @return \Illuminate\Database\Connection
234 3
     */
235 3
    abstract public function getConnection();
236 3
237 3
    /**
238 3
     * Get all of the current attributes on the model.
239 3
     *
240 3
     * @return array
241
     */
242
    abstract public function getAttributes();
243
244
    /**
245
     * Get the table associated with the model.
246
     *
247
     * @return string
248
     */
249
    abstract public function getTable();
250
251
    /**
252
     * Get the fillable attributes for the model.
253
     *
254
     * @return array
255
     */
256
    abstract public function getFillable();
257
258
    /**
259
     * Dig up all defined getters on the model.
260
     *
261
     * @return array
262
     */
263
    protected function collectGetters()
264
    {
265
        $getterz = [];
266
        $methods = get_class_methods($this);
267
        foreach ($methods as $method) {
268
            if (12 < strlen($method) && 'get' == substr($method, 0, 3)) {
269
                if ('Attribute' == substr($method, -9)) {
270
                    $getterz[] = $method;
271
                }
272
            }
273
        }
274
        $methods = [];
275
276
        foreach ($getterz as $getter) {
277
            $residual = substr($getter, 3);
278
            $residual = substr(/** @scrutinizer ignore-type */$residual, 0, -9);
279
            $methods[] = $residual;
280
        }
281
        return $methods;
282
    }
283
284
    /**
285
     * Supplemental function to retrieve cast array for Laravel versions that do not supply hasCasts.
286
     *
287
     * @return array
288
     */
289
    public function retrieveCasts()
290
    {
291
        $exists = method_exists($this, 'getCasts');
292
        return $exists ? (array)$this->getCasts() : (array)$this->casts;
293
    }
294
295
    /**
296
     * Return list of relations to be eager-loaded by Laravel query provider.
297
     *
298
     * @return array
299
     */
300
    public function getEagerLoad()
301
    {
302
        return $this->loadEagerRelations;
303
    }
304
305
    /**
306
     * Set list of relations to be eager-loaded.
307
     *
308
     * @param array $relations
309
     */
310
    public function setEagerLoad(array $relations)
311
    {
312
        $this->loadEagerRelations = array_map('strval', $relations);
313
    }
314
315
    /**
316
     * Extract entity gubbins detail for later downstream use.
317
     *
318
     * @return EntityGubbins
319
     * @throws InvalidOperationException
320
     * @throws \ReflectionException
321
     * @throws \Doctrine\DBAL\DBALException
322
     * @throws \Exception
323
     */
324
    public function extractGubbins()
325
    {
326
        $multArray = [
327
            '*' => AssociationStubRelationType::MANY(),
328
            '1' => AssociationStubRelationType::ONE(),
329
            '0..1' => AssociationStubRelationType::NULL_ONE()
330
        ];
331
332
        $gubbins = new EntityGubbins();
333
        $gubbins->setName($this->getEndpointName());
334
        $gubbins->setClassName(get_class($this));
335
336
        $lowerNames = [];
337
338
        $fields = $this->metadata();
339
        $entityFields = [];
340
        foreach ($fields as $name => $field) {
341
            if (in_array(strtolower($name), $lowerNames)) {
342
                $msg = 'Property names must be unique, without regard to case';
343
                throw new \Exception($msg);
344
            }
345
            $lowerNames[] = strtolower($name);
346
            $nuField = new EntityField();
347
            $nuField->setName($name);
348
            $nuField->setIsNullable($field['nullable']);
349
            $nuField->setReadOnly(false);
350
            $nuField->setCreateOnly(false);
351
            $nuField->setDefaultValue($field['default']);
352
            $nuField->setIsKeyField($this->getKeyName() == $name);
353
            $nuField->setFieldType(EntityFieldType::PRIMITIVE());
354
            $nuField->setPrimitiveType(new EntityFieldPrimitiveType($field['type']));
355
            $entityFields[$name] = $nuField;
356
        }
357
        $isEmpty = (0 === count($entityFields));
358
        if (!($isEmpty && $this->isRunningInArtisan())) {
359
            $gubbins->setFields($entityFields);
360
        }
361
362
        $rawRels = $this->getRelationships();
363
        $stubs = [];
364
        foreach ($rawRels as $key => $rel) {
365
            foreach ($rel as $rawName => $deets) {
366
                foreach ($deets as $relName => $relGubbins) {
367
                    if (in_array(strtolower($relName), $lowerNames)) {
368
                        $msg = 'Property names must be unique, without regard to case';
369
                        throw new \Exception($msg);
370
                    }
371
                    $lowerNames[] = strtolower($relName);
372
                    $gubbinsType = $relGubbins['type'];
373
                    $property = $relGubbins['property'];
374
                    $isPoly = isset($gubbinsType);
375
                    $targType = 'known' != $gubbinsType ? $rawName : null;
376
                    $stub = $isPoly ? new AssociationStubPolymorphic() : new AssociationStubMonomorphic();
377
                    $stub->setBaseType(get_class($this));
378
                    $stub->setRelationName($property);
379
                    $stub->setKeyField($relGubbins['local']);
380
                    $stub->setForeignField($targType ? $key : null);
381
                    $stub->setMultiplicity($multArray[$relGubbins['multiplicity']]);
382
                    $stub->setTargType($targType);
383
                    if (null !== $relGubbins['through']) {
384
                        $stub->setThroughField($relGubbins['through']);
385
                    }
386
                    if (!$stub->isOk()) {
387
                        throw new InvalidOperationException('Generated stub not consistent');
388
                    }
389
                    $stubs[$property] = $stub;
390
                }
391
            }
392
        }
393
        $gubbins->setStubs($stubs);
394
395
        return $gubbins;
396
    }
397
398
    public function isRunningInArtisan()
399
    {
400
        return App::runningInConsole() && !App::runningUnitTests();
401
    }
402
403
    /**
404
     * Get columns for selected table
405
     *
406
     * @return array
407
     */
408
    protected function getTableColumns()
409
    {
410
        if (0 === count(self::$tableColumns)) {
411
            $table = $this->getTable();
412
            $connect = $this->getConnection();
413
            $builder = $connect->getSchemaBuilder();
414
            $columns = $builder->getColumnListing($table);
415
416
            self::$tableColumns = (array)$columns;
417
        }
418
        return self::$tableColumns;
419
    }
420
421
    /**
422
     * Get Doctrine columns for selected table
423
     *
424
     * @return array
425
     * @throws \Doctrine\DBAL\DBALException
426
     */
427
    protected function getTableDoctrineColumns()
428
    {
429
        if (0 === count(self::$tableColumnsDoctrine)) {
430
            $table = $this->getTable();
431
            $connect = $this->getConnection();
432
            $columns = $connect->getDoctrineSchemaManager()->listTableColumns($table);
433
434
            self::$tableColumnsDoctrine = $columns;
435
        }
436
        return self::$tableColumnsDoctrine;
437
    }
438
439
    public function reset()
440
    {
441
        self::$tableData = [];
442
        self::$tableColumnsDoctrine = [];
443
        self::$tableColumns = [];
444
    }
445
}
446