Test Setup Failed
Pull Request — master (#46)
by Alex
02:58
created

MetadataTrait::getKeyName()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 1
ccs 0
cts 0
cp 0
nc 1
1
<?php
2
namespace AlgoWeb\PODataLaravel\Models;
3
4
use Illuminate\Database\Eloquent\Relations\BelongsTo;
5
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
6
use Illuminate\Database\Eloquent\Relations\MorphMany;
7
use Illuminate\Database\Eloquent\Relations\MorphOne;
8
use Illuminate\Database\Eloquent\Relations\MorphToMany;
9
use Illuminate\Support\Facades\App as App;
10
use Illuminate\Database\Eloquent\Relations\Relation;
11
use POData\Providers\Metadata\ResourceStreamInfo;
12
use POData\Providers\Metadata\Type\EdmPrimitiveType;
13
use Illuminate\Database\Eloquent\Model;
14
15
trait MetadataTrait
16
{
17
    /*
18
     * Array to record mapping between doctrine types and OData types
19
     */
20
    protected $mapping = [
21
        'integer' => EdmPrimitiveType::INT32,
22
        'string' => EdmPrimitiveType::STRING,
23
        'datetime' => EdmPrimitiveType::DATETIME,
24
        'float' => EdmPrimitiveType::SINGLE,
25
        'decimal' => EdmPrimitiveType::DECIMAL,
26
        'text' => EdmPrimitiveType::STRING,
27
        'boolean' => EdmPrimitiveType::BOOLEAN,
28 3
        'blob' => "stream"
29
    ];
30 3
31
    /*
32
     * Retrieve and assemble this model's metadata for OData packaging
33 2
     */
34 2
    public function metadata()
35
    {
36 2
        assert($this instanceof Model, get_class($this));
37
38 2
        // Break these out separately to enable separate reuse
39 1
        $connect = $this->getConnection();
40
        $builder = $connect->getSchemaBuilder();
41
42 1
        $table = $this->getTable();
43 1
44 1
        if (!$builder->hasTable($table)) {
45
            return [];
46 1
        }
47
48 1
        $columns = $builder->getColumnListing($table);
49 1
        $mask = $this->metadataMask();
50 1
        $columns = array_intersect($columns, $mask);
51
52 1
        $tableData = [];
53 1
54 1
        $rawFoo = $connect->getDoctrineSchemaManager()->listTableColumns($table);
55
        $foo = [];
56 1
        $getters = $this->collectGetters();
57
58 1
        foreach ($rawFoo as $key => $val) {
59 1
            // Work around glitch in Doctrine when reading from MariaDB which added ` characters to root key value
60 1
            $key = trim($key, '`');
61 1
            $foo[$key] = $val;
62 1
        }
63 1
64 1
        foreach ($columns as $column) {
65
            // Doctrine schema manager returns columns with lowercased names
66 1
            $rawColumn = $foo[strtolower($column)];
67
            $nullable = !($rawColumn->getNotNull());
68
            $fillable = in_array($column, $this->getFillable());
69
            $rawType = $rawColumn->getType();
70
            $type = $rawType->getName();
71
            $tableData[$column] = ['type' => $type, 'nullable' => $nullable, 'fillable' => $fillable];
72
        }
73 4
74
        foreach ($getters as $get) {
75 4
            $tableData[$get] = ['type' => 'text', 'nullable' => false, 'fillable' => false];
76
        }
77 4
78 4
        return $tableData;
79 4
    }
80 2
81 4
    /*
82 1
     * Return the set of fields that are permitted to be in metadata
83 1
     * - following same visible-trumps-hidden guideline as Laravel
84
     */
85 4
    public function metadataMask()
86
    {
87
        $attribs = array_keys($this->getAllAttributes());
88
89
        $visible = $this->getVisible();
90
        $hidden = $this->getHidden();
91 5
        if (0 < count($visible)) {
92
            assert(!empty($visible));
93 5
            $attribs = array_intersect($visible, $attribs);
94 5
        } elseif (0 < count($hidden)) {
95 1
            assert(!empty($hidden));
96
            $attribs = array_diff($attribs, $hidden);
97
        }
98 4
99
        return $attribs;
100 4
    }
101
102 4
    /*
103 4
     * Get the endpoint name being exposed
104 4
     *
105 4
     */
106 4
    public function getEndpointName()
107 4
    {
108
        $endpoint = isset($this->endpoint) ? $this->endpoint : null;
109 4
110 1
        if (!isset($endpoint)) {
111 1
            $bitter = get_class();
112 1
            $name = substr($bitter, strrpos($bitter, '\\')+1);
113 1
            return strtolower($name);
114
        }
115 4
        return strtolower($endpoint);
116 4
    }
117
118 4
    /*
119
     * Assemble this model's OData metadata as xml schema
120
     */
121
    public function getXmlSchema($MetaNamespace = "Data")
122
    {
123
        $raw = $this->metadata();
124
        if ([] == $raw) {
125
            return null;
126
        }
127
128
        $metadata = App::make('metadata');
129
130
        $rf = new \ReflectionClass(get_class($this));
131
        $complex = $metadata->addEntityType($rf, $rf->getShortName(), $MetaNamespace);
132
        $keyName = $this->getKeyName();
133
        if (null != $keyName) {
134
            $metadata->addKeyProperty($complex, $keyName, $this->mapping[$raw[$keyName]['type']]);
135
        }
136
137
        foreach ($raw as $key => $secret) {
138
            if ($key == $keyName) {
139
                continue;
140
            }
141
            if ($secret['type'] == "blob") {
142
                $complex->setMediaLinkEntry(true);
143
                $streamInfo = new ResourceStreamInfo($key);
144
                assert($complex->isMediaLinkEntry());
145
                $complex->addNamedStream($streamInfo);
146
                continue;
147
            }
148
            $metadata->addPrimitiveProperty($complex, $key, $this->mapping[$secret['type']]); // tag as isBag?
149
        }
150
151
        return $complex;
152
    }
153
154
    public function hookUpRelationships($entityTypes, $resourceSets)
155
    {
156
        assert(is_array($entityTypes) && is_array($resourceSets), "Both entityTypes and resourceSets must be arrays");
157
        $metadata = App::make('metadata');
158
        $rel = $this->getRelationshipsFromMethods();
159
        $thisClass = get_class($this);
160
        $thisInTypes = array_key_exists($thisClass, $entityTypes);
161
        $thisInSets = array_key_exists($thisClass, $resourceSets);
162
163
        if (!($thisInSets && $thisInTypes)) {
164
            return $rel;
165
        }
166
167
        $resourceType = $entityTypes[$thisClass];
168
        // if $r is in $combined keys, then its in keyspaces of both $entityTypes and $resourceSets
169
        $combinedKeys = array_intersect(array_keys($entityTypes), array_keys($resourceSets));
170 View Code Duplication
        foreach ($rel["HasOne"] as $n => $r) {
171
            $r = trim($r, '\\');
172
            if (in_array($r, $combinedKeys)) {
173
                $targResourceSet = $resourceSets[$r];
174
                $metadata->addResourceReferenceProperty($resourceType, $n, $targResourceSet);
175 3
            }
176
        }
177 3 View Code Duplication
        foreach ($rel["HasMany"] as $n => $r) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
178
            $r = trim($r, '\\');
179 3
            if (in_array($r, $combinedKeys)) {
180 3
                $targResourceSet = $resourceSets[$r];
181 3
                $metadata->addResourceSetReferenceProperty($resourceType, $n, $targResourceSet);
182 3
            }
183 3
        }
184 3
        return $rel;
185 3
    }
186 3
187 3
    public function getRelationships()
188 3
    {
189
        $hooks = [];
190 3
191
        $rels = $this->getRelationshipsFromMethods(true);
192 3
193 3
        foreach ($rels['UnknownPolyMorphSide'] as $property => $foo) {
194 3
            $isMany = $foo instanceof MorphToMany;
195 3
            $targ = get_class($foo->getRelated());
196 3
            $mult = $isMany ? '*' : '1';
197 3
198 3
            if ($isMany) {
199 3
                list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo);
200 3
            }
201 3
202
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->getForeignKey();
203 3
            $keySegments = explode('.', $keyRaw);
204 3
            $keyName = $keySegments[count($keySegments) - 1];
205 3
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->getQualifiedParentKeyName();
206 3
            $localSegments = explode('.', $localRaw);
207 3
            $localName = $localSegments[count($localSegments) - 1];
208 3
209 3
            $first = $keyName;
210 3
            $last = $localName;
211
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
212 3
        }
213 3
214 3
        foreach ($rels['KnownPolyMorphSide'] as $property => $foo) {
215
            $isMany = $foo instanceof MorphToMany;
216 3
            $targ = get_class($foo->getRelated());
217 3
            $mult = $isMany ? '*' : $foo instanceof MorphMany ? '*' : '1';
218 3
            $mult = $foo instanceof MorphOne ? '0..1' : $mult;
219 3
220 3
            if ($isMany) {
221
                list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo);
222 1
            }
223 3
224
            $keyRaw = $isMany ? $foo->$fkMethodName() : $foo->getForeignKeyName();
225 1
            $keySegments = explode('.', $keyRaw);
226
            $keyName = $keySegments[count($keySegments) - 1];
227 1
            $localRaw = $isMany ? $foo->$rkMethodName() : $foo->getQualifiedParentKeyName();
228
            $localSegments = explode('.', $localRaw);
229 1
            $localName = $localSegments[count($localSegments) - 1];
230
            $first = $isMany ? $keyName : $localName;
231 3
            $last = $isMany ? $localName : $keyName;
232 2
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
233 2
        }
234 3
235 3
        foreach ($rels['HasOne'] as $property => $foo) {
236 3
            if ($foo instanceof MorphOne) {
237 3
                continue;
238 3
            }
239 3
            $isBelong = $foo instanceof BelongsTo;
240 3
            $mult = $isBelong ? '1' : '0..1';
241
            $targ = get_class($foo->getRelated());
242
            $keyName = $isBelong ? $foo->getForeignKey() : $foo->getForeignKeyName();
243
            $localRaw = $isBelong ? $foo->getOwnerKey() : $foo->getQualifiedParentKeyName();
244
            $localSegments = explode('.', $localRaw);
245
            $localName = $localSegments[count($localSegments) - 1];
246
            $first = $isBelong ? $localName : $keyName;
247
            $last = $isBelong ? $keyName : $localName;
248
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
249
        }
250
        foreach ($rels['HasMany'] as $property => $foo) {
251
            if ($foo instanceof MorphMany || $foo instanceof MorphToMany) {
252
                continue;
253
            }
254
            $isBelong = $foo instanceof BelongsToMany;
255
            $mult = '*';
256
            $targ = get_class($foo->getRelated());
257
258
            if ($isBelong) {
259
                list($fkMethodName, $rkMethodName) = $this->polyglotKeyMethodNames($foo);
260
            }
261
262
            $keyRaw = $isBelong ? $foo->$fkMethodName() : $foo->getForeignKeyName();
263
            $keySegments = explode('.', $keyRaw);
264
            $keyName = $keySegments[count($keySegments) - 1];
265
            $localRaw = $isBelong ? $foo->$rkMethodName() : $foo->getQualifiedParentKeyName();
266
            $localSegments = explode('.', $localRaw);
267
            $localName = $localSegments[count($localSegments) - 1];
268
            $first = $keyName;
269
            $last = $localName;
270
            $this->addRelationsHook($hooks, $first, $property, $last, $mult, $targ);
271
        }
272
273
        return $hooks;
274
    }
275
276
    protected function getAllAttributes()
277
    {
278
        // Adapted from http://stackoverflow.com/a/33514981
279
        // $columns = $this->getFillable();
280
        // Another option is to get all columns for the table like so:
281
        $builder = $this->getConnection()->getSchemaBuilder();
282
        $columns = $builder->getColumnListing($this->getTable());
283
        // but it's safer to just get the fillable fields
284
285
        $attributes = $this->getAttributes();
286
287
        foreach ($columns as $column) {
288
            if (!array_key_exists($column, $attributes)) {
289
                $attributes[$column] = null;
290
            }
291
        }
292
293
        $methods = $this->collectGetters();
294
295
        foreach ($methods as $method) {
296
            $attributes[$method] = null;
297
        }
298
299
        return $attributes;
300
    }
301
302
    protected function getRelationshipsFromMethods($biDir = false)
303
    {
304
        $model = $this;
305
        $relationships = array(
306
            "HasOne" => array(),
307
            "UnknownPolyMorphSide"=>array(),
308
            "HasMany"=>array(),
309
            "KnownPolyMorphSide"=>array()
310
        );
311
        $methods = get_class_methods($model);
312
        if (!empty($methods)) {
313
            foreach ($methods as $method) {
314
                if (!method_exists('Illuminate\Database\Eloquent\Model', $method)
315
                ) {
316
                    //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
317
                    $reflection = new \ReflectionMethod($model, $method);
318
319
                    $file = new \SplFileObject($reflection->getFileName());
320
                    $file->seek($reflection->getStartLine()-1);
321
                    $code = '';
322
                    while ($file->key() < $reflection->getEndLine()) {
323
                        $code .= $file->current();
324
                        $file->next();
325
                    }
326
327
                    $code = trim(preg_replace('/\s\s+/', '', $code));
328
                    assert(false !== stripos($code, 'function'), 'Function definition must have keyword \'function\'');
329
                    $begin = strpos($code, 'function(');
330
                    $code = substr($code, $begin, strrpos($code, '}')-$begin+1);
331
                    $lastCode = $code[strlen($code)-1];
332
                    assert("}" == $lastCode, "Final character of function definition must be closing brace");
333
                    foreach (array(
334
                                'hasMany',
335
                                'hasManyThrough',
336
                                'belongsToMany',
337
                                'hasOne',
338
                                'belongsTo',
339
                                'morphOne',
340
                                'morphTo',
341
                                'morphMany',
342
                                'morphToMany',
343
                                'morphedByMany'
344
                                ) as $relation) {
345
                        $search = '$this->'.$relation.'(';
346
                        if ($pos = stripos($code, $search)) {
347
                            //Resolve the relation's model to a Relation object.
348
                            $relationObj = $model->$method();
349
                            if ($relationObj instanceof Relation) {
350
                                $relatedModel = '\\'.get_class($relationObj->getRelated());
351
                                $relations = [
352
                                    'hasManyThrough',
353
                                    'belongsToMany',
354
                                    'hasMany',
355
                                    'morphMany',
356
                                    'morphToMany',
357
                                    'morphedByMany'
358
                                ];
359
                                if (in_array($relation, $relations)) {
360
                                    //Collection or array of models (because Collection is Arrayable)
361
                                    $relationships["HasMany"][$method] = $biDir ? $relationObj : $relatedModel;
362
                                } elseif ($relation === "morphTo") {
363
                                    // Model isn't specified because relation is polymorphic
364
                                    $relationships["UnknownPolyMorphSide"][$method] =
365
                                        $biDir ? $relationObj : '\Illuminate\Database\Eloquent\Model|\Eloquent';
366
                                } else {
367
                                    //Single model is returned
368
                                    $relationships["HasOne"][$method] = $biDir ? $relationObj : $relatedModel;
369
                                }
370
                                if (in_array($relation, ["morphMany", "morphOne", "morphedByMany"])) {
371
                                    $relationships["KnownPolyMorphSide"][$method] =
372
                                        $biDir ? $relationObj : $relatedModel;
373
                                }
374
                                if (in_array($relation, ["morphToMany"])) {
375
                                    $relationships["UnknownPolyMorphSide"][$method] =
376
                                        $biDir ? $relationObj : $relatedModel;
377
                                }
378
                            }
379
                        }
380
                    }
381
                }
382
            }
383
        }
384
        return $relationships;
385
    }
386
387
    /**
388
     * Get the visible attributes for the model.
389
     *
390
     * @return array
391
     */
392
    public abstract function getVisible();
393
394
    /**
395
     * Get the hidden attributes for the model.
396
     *
397
     * @return array
398
     */
399
    public abstract function getHidden();
400
401
    /**
402
     * Get the primary key for the model.
403
     *
404
     * @return string
405
     */
406
    public abstract function getKeyName();
407
408
    /**
409
     * Get the current connection name for the model.
410
     *
411
     * @return string
412
     */
413
    public abstract function getConnectionName();
414
415
    /**
416
     * Get the database connection for the model.
417
     *
418
     * @return \Illuminate\Database\Connection
419
     */
420
    public abstract function getConnection();
421
422
    /**
423
     * Get all of the current attributes on the model.
424
     *
425
     * @return array
426
     */
427
    public abstract function getAttributes();
428
429
    /**
430
     * Get the table associated with the model.
431
     *
432
     * @return string
433
     */
434
    public abstract function getTable();
435
436
    /**
437
     * Get the fillable attributes for the model.
438
     *
439
     * @return array
440
     */
441
    public abstract function getFillable();
442
443
    /**
444
     * Dig up all defined getters on the model
445
     *
446
     * @return array
447
     */
448
    protected function collectGetters()
449
    {
450
        $getterz = [];
451
        $methods = get_class_methods($this);
452
        foreach ($methods as $method) {
453
            if (starts_with($method, 'get') && ends_with($method, 'Attribute') && 'getAttribute' != $method) {
454
                $getterz[] = $method;
455
            }
456
        }
457
        $methods = [];
458
459
        foreach ($getterz as $getter) {
460
            $residual = substr($getter, 3);
461
            $residual = substr($residual, 0, -9);
462
            $methods[] = $residual;
463
        }
464
        return $methods;
465
    }
466
467
    /**
468
     * @param $foo
469
     * @return array
470
     */
471
    private function polyglotKeyMethodNames($foo)
472
    {
473
        $fkMethodName = method_exists($foo, 'getQualifiedForeignKeyName')
474
            ? 'getQualifiedForeignKeyName' : 'getQualifiedForeignPivotKeyName';
475
        $rkMethodName = method_exists($foo, 'getQualifiedRelatedKeyName')
476
            ? 'getQualifiedRelatedKeyName' : 'getQualifiedRelatedPivotKeyName';
477
        return array($fkMethodName, $rkMethodName);
478
    }
479
480
    /**
481
     * @param $hooks
482
     * @param $first
483
     * @param $property
484
     * @param $last
485
     * @param $mult
486
     * @param $targ
487
     */
488
    private function addRelationsHook(&$hooks, $first, $property, $last, $mult, $targ)
489
    {
490
        if (!isset($hooks[$first])) {
491
            $hooks[$first] = [];
492
        }
493
        $hooks[$first][$targ] = [
494
            'property' => $property,
495
            'local' => $last,
496
            'multiplicity' => $mult
497
        ];
498
    }
499
}
500