Test Setup Failed
Pull Request — master (#46)
by Alex
03:05
created

MetadataTrait::getConnectionName()

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