Completed
Push — master ( b3189d...dd79fb )
by Alex
02:28
created

MetadataTrait::getConnection()

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\Support\Facades\App as App;
5
use Illuminate\Database\Eloquent\Relations\Relation;
6
use POData\Providers\Metadata\ResourceStreamInfo;
7
use POData\Providers\Metadata\Type\EdmPrimitiveType;
8
use Illuminate\Database\Eloquent\Model;
9
10
trait MetadataTrait
11
{
12
    /*
13
     * Array to record mapping between doctrine types and OData types
14
     */
15
    protected $mapping = [
16
        'integer' => EdmPrimitiveType::INT32,
17
        'string' => EdmPrimitiveType::STRING,
18
        'datetime' => EdmPrimitiveType::DATETIME,
19
        'decimal' => EdmPrimitiveType::DECIMAL,
20
        'text' => EdmPrimitiveType::STRING,
21
        'boolean' => EdmPrimitiveType::BOOLEAN,
22
        'blob' => "stream"
23
    ];
24
25
    /*
26
     * Retrieve and assemble this model's metadata for OData packaging
27
     */
28 3
    public function metadata()
29
    {
30 3
        assert($this instanceof Model, get_class($this));
31
32
        // Break these out separately to enable separate reuse
33 2
        $connect = $this->getConnection();
34 2
        $builder = $connect->getSchemaBuilder();
35
36 2
        $table = $this->getTable();
37
38 2
        if (!$builder->hasTable($table)) {
39 1
            return [];
40
        }
41
42 1
        $columns = $builder->getColumnListing($table);
43 1
        $mask = $this->metadataMask();
44 1
        $columns = array_intersect($columns, $mask);
45
46 1
        $tableData = [];
47
48 1
        $rawFoo = $connect->getDoctrineSchemaManager()->listTableColumns($table);
49 1
        $foo = [];
50 1
        $getters = $this->collectGetters();
51
52 1
        foreach ($rawFoo as $key => $val) {
53 1
            // Work around glitch in Doctrine when reading from MariaDB which added ` characters to root key value
54 1
            $key = trim($key, '`');
55
            $foo[$key] = $val;
56 1
        }
57
58 1
        foreach ($columns as $column) {
59 1
            // Doctrine schema manager returns columns with lowercased names
60 1
            $rawColumn = $foo[strtolower($column)];
61 1
            $nullable = !($rawColumn->getNotNull());
62 1
            $fillable = in_array($column, $this->getFillable());
63 1
            $rawType = $rawColumn->getType();
64 1
            $type = $rawType->getName();
65
            $tableData[$column] = ['type' => $type, 'nullable' => $nullable, 'fillable' => $fillable];
66 1
        }
67
68
        foreach ($getters as $get) {
69
            $tableData[$get] = ['type' => 'text', 'nullable' => false, 'fillable' => false];
70
        }
71
72
        return $tableData;
73 4
    }
74
75 4
    /*
76
     * Return the set of fields that are permitted to be in metadata
77 4
     * - following same visible-trumps-hidden guideline as Laravel
78 4
     */
79 4
    public function metadataMask()
80 2
    {
81 4
        $attribs = array_keys($this->getAllAttributes());
82 1
83 1
        $visible = $this->getVisible();
84
        $hidden = $this->getHidden();
85 4
        if (0 != count($visible)) {
86
            $attribs = array_intersect($visible, $attribs);
87
        } elseif (0 != count($hidden)) {
88
            $attribs = array_diff($attribs, $hidden);
89
        }
90
91 5
        return $attribs;
92
    }
93 5
94 5
    /*
95 1
     * Get the endpoint name being exposed
96
     *
97
     */
98 4
    public function getEndpointName()
99
    {
100 4
        $endpoint = isset($this->endpoint) ? $this->endpoint : null;
101
102 4
        if (!isset($endpoint)) {
103 4
            $bitter = get_class();
104 4
            $name = substr($bitter, strrpos($bitter, '\\')+1);
105 4
            return strtolower($name);
106 4
        }
107 4
        return strtolower($endpoint);
108
    }
109 4
110 1
    /*
111 1
     * Assemble this model's OData metadata as xml schema
112 1
     */
113 1
    public function getXmlSchema($MetaNamespace = "Data")
114
    {
115 4
        $raw = $this->metadata();
116 4
        if ([] == $raw) {
117
            return null;
118 4
        }
119
120
        $metadata = App::make('metadata');
121
122
        $table = $this->getTable();
123
124
        $complex = $metadata->addEntityType(new \ReflectionClass(get_class($this)), $table, $MetaNamespace);
125
        $keyName = $this->getKeyName();
126
        $metadata->addKeyProperty($complex, $keyName, $this->mapping[$raw[$keyName]['type']]);
127
        foreach ($raw as $key => $secret) {
128
            if ($key == $keyName) {
129
                continue;
130
            }
131
            if ($secret['type'] == "blob") {
132
                $complex->setMediaLinkEntry(true);
133
                $streamInfo = new ResourceStreamInfo($key);
134
                $complex->addNamedStream($streamInfo);
135
                continue;
136
            }
137
            $metadata->addPrimitiveProperty($complex, $key, $this->mapping[$secret['type']]); // tag as isBag?
138
        }
139
140
        return $complex;
141
    }
142
143
    public function hookUpRelationships($entityTypes, $resourceSets)
144
    {
145
        $metadata = \App::make('metadata');
146
        $rel = $this->getRelationshipsFromMethods();
147
        $thisClass = get_class($this);
148 View Code Duplication
        foreach ($rel["HasOne"] as $n => $r) {
149
            if ($r[0] == "\\") {
150
                $r = substr($r, 1);
151
            }
152
            if (array_key_exists($r, $entityTypes)
153
                && array_key_exists($r, $resourceSets)
154
                && array_key_exists($thisClass, $entityTypes)
155
                && array_key_exists($thisClass, $resourceSets)) {
156
                $resourceType = $entityTypes[$thisClass];
157
                $targResourceSet = $resourceSets[$r];
158
                $metadata->addResourceReferenceProperty($resourceType, $n, $targResourceSet);
159
            }
160
        }
161 View Code Duplication
        foreach ($rel["HasMany"] as $n => $r) {
162
            if ($r[0] == "\\") {
163
                $r = substr($r, 1);
164
            }
165
            if (array_key_exists($r, $entityTypes)
166
                 && array_key_exists($r, $resourceSets)
167
                 && array_key_exists($thisClass, $entityTypes)
168
                 && array_key_exists($thisClass, $resourceSets)) {
169
                $resourceType = $entityTypes[$thisClass];
170
                $targResourceSet = $resourceSets[$r];
171
                $metadata->addResourceSetReferenceProperty($resourceType, $n, $targResourceSet);
172
            }
173
        }
174
175 3
        return $rel;
176
    }
177 3
178
    protected function getAllAttributes()
179 3
    {
180 3
        // Adapted from http://stackoverflow.com/a/33514981
181 3
        // $columns = $this->getFillable();
182 3
        // Another option is to get all columns for the table like so:
183 3
        $builder = $this->getConnection()->getSchemaBuilder();
184 3
        $columns = $builder->getColumnListing($this->getTable());
185 3
        // but it's safer to just get the fillable fields
186 3
187 3
        $attributes = $this->getAttributes();
188 3
189
        foreach ($columns as $column) {
190 3
            if (!array_key_exists($column, $attributes)) {
191
                $attributes[$column] = null;
192 3
            }
193 3
        }
194 3
195 3
        $methods = $this->collectGetters();
196 3
197 3
        foreach ($methods as $method) {
198 3
            $attributes[$method] = null;
199 3
        }
200 3
201 3
        return $attributes;
202
    }
203 3
204 3
    protected function getRelationshipsFromMethods()
205 3
    {
206 3
        $model = $this;
207 3
        $relationships = array(
208 3
            "HasOne" => array(),
209 3
            "UnknownPolyMorphSide"=>array(),
210 3
            "HasMany"=>array(),
211
            "KnownPolyMorphSide"=>array()
212 3
        );
213 3
        $methods = get_class_methods($model);
214 3
        if (!empty($methods)) {
215
            foreach ($methods as $method) {
216 3
                if (!method_exists('Illuminate\Database\Eloquent\Model', $method)
217 3
                ) {
218 3
                    //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
219 3
                    $reflection = new \ReflectionMethod($model, $method);
220 3
221
                    $file = new \SplFileObject($reflection->getFileName());
222 1
                    $file->seek($reflection->getStartLine()-1);
223 3
                    $code = '';
224
                    while ($file->key() < $reflection->getEndLine()) {
225 1
                        $code .= $file->current();
226
                        $file->next();
227 1
                    }
228
                    $code = trim(preg_replace('/\s\s+/', '', $code));
229 1
                    $begin = strpos($code, 'function(');
230
                    $code = substr($code, $begin, strrpos($code, '}')-$begin+1);
231 3
                    foreach (array(
232 2
                                'hasMany',
233 2
                                'hasManyThrough',
234 3
                                'belongsToMany',
235 3
                                'hasOne',
236 3
                                'belongsTo',
237 3
                                'morphOne',
238 3
                                'morphTo',
239 3
                                'morphMany',
240 3
                                'morphToMany'
241
                                ) as $relation) {
242
                        $search = '$this->'.$relation.'(';
243
                        if ($pos = stripos($code, $search)) {
244
                            //Resolve the relation's model to a Relation object.
245
                            $relationObj = $model->$method();
246
                            if ($relationObj instanceof Relation) {
247
                                $relatedModel = '\\'.get_class($relationObj->getRelated());
248
                                $relations = ['hasManyThrough', 'belongsToMany', 'hasMany', 'morphMany', 'morphToMany'];
249
                                if (in_array($relation, $relations)) {
250
                                    //Collection or array of models (because Collection is Arrayable)
251
                                    $relationships["HasMany"][$method] = $relatedModel;
252
                                } elseif ($relation === "morphTo") {
253
                                    // Model isn't specified because relation is polymorphic
254
                                    $relationships["UnknownPolyMorphSide"][$method] =
255
                                        '\Illuminate\Database\Eloquent\Model|\Eloquent';
256
                                } else {
257
                                    //Single model is returned
258
                                    $relationships["HasOne"][$method] = $relatedModel;
259
                                }
260
                                if (in_array($relation, ["morphMany", "morphOne"])) {
261
                                    $relationships["KnownPolyMorphSide"][$method] = $relatedModel;
262
                                }
263
                            }
264
                        }
265
                    }
266
                }
267
            }
268
        }
269
        return $relationships;
270
    }
271
272
    /**
273
     * Get the visible attributes for the model.
274
     *
275
     * @return array
276
     */
277
    public abstract function getVisible();
278
279
    /**
280
     * Get the hidden attributes for the model.
281
     *
282
     * @return array
283
     */
284
    public abstract function getHidden();
285
286
    /**
287
     * Get the primary key for the model.
288
     *
289
     * @return string
290
     */
291
    public abstract function getKeyName();
292
293
    /**
294
     * Get the current connection name for the model.
295
     *
296
     * @return string
297
     */
298
    public abstract function getConnectionName();
299
300
    /**
301
     * Get the database connection for the model.
302
     *
303
     * @return \Illuminate\Database\Connection
304
     */
305
    public abstract function getConnection();
306
307
    /**
308
     * Get all of the current attributes on the model.
309
     *
310
     * @return array
311
     */
312
    public abstract function getAttributes();
313
314
    /**
315
     * Get the table associated with the model.
316
     *
317
     * @return string
318
     */
319
    public abstract function getTable();
320
321
    /**
322
     * Get the fillable attributes for the model.
323
     *
324
     * @return array
325
     */
326
    public abstract function getFillable();
327
328
    /**
329
     * Dig up all defined getters on the model
330
     *
331
     * @return array
332
     */
333
    protected function collectGetters()
334
    {
335
        $getterz = [];
336
        $methods = get_class_methods($this);
337
        foreach ($methods as $method) {
338
            if (starts_with($method, 'get') && ends_with($method, 'Attribute') && 'getAttribute' != $method) {
339
                $getterz[] = $method;
340
            }
341
        }
342
        $methods = [];
343
344
        foreach ($getterz as $getter) {
345
            $residual = substr($getter, 3);
346
            $residual = substr($residual, 0, -9);
347
            $methods[] = $residual;
348
        }
349
        return $methods;
350
    }
351
}
352