Completed
Pull Request — master (#19)
by Alex
02:53 queued 10s
created

MetadataTrait::getEndpointName()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.4285
cc 3
eloc 7
nc 4
nop 0
crap 3
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
        foreach ($rawFoo as $key => $val) {
51
            // Work around glitch in Doctrine when reading from MariaDB which added ` characters to root key value
52 1
            $key = trim($key, '`');
53 1
            $foo[$key] = $val;
54 1
        }
55
56 1
        foreach ($columns as $column) {
57
            // Doctrine schema manager returns columns with lowercased names
58 1
            $rawColumn = $foo[strtolower($column)];
59 1
            $nullable = !($rawColumn->getNotNull());
60 1
            $fillable = in_array($column, $this->getFillable());
61 1
            $rawType = $rawColumn->getType();
62 1
            $type = $rawType->getName();
63 1
            $tableData[$column] = ['type' => $type, 'nullable' => $nullable, 'fillable' => $fillable];
64 1
        }
65
66 1
        return $tableData;
67
    }
68
69
    /*
70
     * Return the set of fields that are permitted to be in metadata
71
     * - following same visible-trumps-hidden guideline as Laravel
72
     */
73 4
    public function metadataMask()
74
    {
75 4
        $attribs = array_keys($this->getAllAttributes());
76
77 4
        $visible = $this->getVisible();
78 4
        $hidden = $this->getHidden();
79 4
        if (0 != count($visible)) {
80 2
            $attribs = array_intersect($visible, $attribs);
81 4
        } elseif (0 != count($hidden)) {
82 1
            $attribs = array_diff($attribs, $hidden);
83 1
        }
84
85 4
        return $attribs;
86
    }
87
88
    /*
89
     * Get the endpoint name being exposed
90
     *
91 5
     */
92
    public function getEndpointName()
93 5
    {
94 5
        $endpoint = isset($this->endpoint) ? $this->endpoint : null;
1 ignored issue
show
Bug introduced by
The property endpoint does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
95 1
96
        if (!isset($endpoint)) {
97
            $bitter = get_class();
98 4
            $name = substr($bitter, strrpos($bitter, '\\')+1);
99
            return strtolower($name);
100 4
        }
101
        return strtolower($endpoint);
102 4
    }
103 4
104 4
    /*
105 4
     * Assemble this model's OData metadata as xml schema
106 4
     */
107 4
    public function getXmlSchema($MetaNamespace = "Data")
108
    {
109 4
        $raw = $this->metadata();
110 1
        if ([] == $raw) {
111 1
            return null;
112 1
        }
113 1
114
        $metadata = App::make('metadata');
115 4
116 4
        $table = $this->getTable();
117
118 4
        $complex = $metadata->addEntityType(new \ReflectionClass(get_class($this)), $table, $MetaNamespace);
119
        $keyName = $this->getKeyName();
120
        $metadata->addKeyProperty($complex, $keyName, $this->mapping[$raw[$keyName]['type']]);
121
        foreach ($raw as $key => $secret) {
122
            if ($key == $keyName) {
123
                continue;
124
            }
125
            if ($secret['type'] == "blob") {
126
                $complex->setMediaLinkEntry(true);
127
                $streamInfo = new ResourceStreamInfo($key);
128
                $complex->addNamedStream($streamInfo);
129
                continue;
130
            }
131
            $metadata->addPrimitiveProperty($complex, $key, $this->mapping[$secret['type']]); // tag as isBag?
132
        }
133
134
        return $complex;
135
    }
136
137
    public function hookUpRelationships($entityTypes, $resourceSets)
138
    {
139
        $metadata = \App::make('metadata');
140
        $rel = $this->getRelationshipsFromMethods();
141
        $thisClass = get_class($this);
142 View Code Duplication
        foreach ($rel["HasOne"] as $n => $r) {
143
            if ($r[0] == "\\") {
144
                $r = substr($r, 1);
145
            }
146
            if (array_key_exists($r, $entityTypes)
147
                && array_key_exists($r, $resourceSets)
148
                && array_key_exists($thisClass, $entityTypes)
149
                && array_key_exists($thisClass, $resourceSets)) {
150
                $resourceType = $entityTypes[$thisClass];
151
                $targResourceSet = $resourceSets[$r];
152
                $metadata->addResourceReferenceProperty($resourceType, $n, $targResourceSet);
153
            }
154
        }
155 View Code Duplication
        foreach ($rel["HasMany"] as $n => $r) {
156
            if ($r[0] == "\\") {
157
                $r = substr($r, 1);
158
            }
159
            if (array_key_exists($r, $entityTypes)
160
                 && array_key_exists($r, $resourceSets)
161
                 && array_key_exists($thisClass, $entityTypes)
162
                 && array_key_exists($thisClass, $resourceSets)) {
163
                $resourceType = $entityTypes[$thisClass];
164
                $targResourceSet = $resourceSets[$r];
165
                $metadata->addResourceSetReferenceProperty($resourceType, $n, $targResourceSet);
166
            }
167
        }
168
169
        return $rel;
170
    }
171
172
    protected function getAllAttributes()
173
    {
174
        // Adapted from http://stackoverflow.com/a/33514981
175 3
        // $columns = $this->getFillable();
176
        // Another option is to get all columns for the table like so:
177 3
        $builder = $this->getConnection()->getSchemaBuilder();
178
        $columns = $builder->getColumnListing($this->getTable());
179 3
        // but it's safer to just get the fillable fields
180 3
181 3
        $attributes = $this->getAttributes();
182 3
183 3
        foreach ($columns as $column) {
184 3
            if (!array_key_exists($column, $attributes)) {
185 3
                $attributes[$column] = null;
186 3
            }
187 3
        }
188 3
        return $attributes;
189
    }
190 3
191
    protected function getRelationshipsFromMethods()
192 3
    {
193 3
        $model = $this;
194 3
        $relationships = array(
195 3
            "HasOne" => array(),
196 3
            "UnknownPolyMorphSide"=>array(),
197 3
            "HasMany"=>array(),
198 3
            "KnownPolyMorphSide"=>array()
199 3
        );
200 3
        $methods = get_class_methods($model);
201 3
        if (!empty($methods)) {
202
            foreach ($methods as $method) {
203 3
                if (!method_exists('Illuminate\Database\Eloquent\Model', $method)
204 3
                ) {
205 3
                    //Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
206 3
                    $reflection = new \ReflectionMethod($model, $method);
207 3
208 3
                    $file = new \SplFileObject($reflection->getFileName());
209 3
                    $file->seek($reflection->getStartLine()-1);
210 3
                    $code = '';
211
                    while ($file->key() < $reflection->getEndLine()) {
212 3
                        $code .= $file->current();
213 3
                        $file->next();
214 3
                    }
215
                    $code = trim(preg_replace('/\s\s+/', '', $code));
216 3
                    $begin = strpos($code, 'function(');
217 3
                    $code = substr($code, $begin, strrpos($code, '}')-$begin+1);
218 3
                    foreach (array(
219 3
                                'hasMany',
220 3
                                'hasManyThrough',
221
                                'belongsToMany',
222 1
                                'hasOne',
223 3
                                'belongsTo',
224
                                'morphOne',
225 1
                                'morphTo',
226
                                'morphMany',
227 1
                                'morphToMany'
228
                                ) as $relation) {
229 1
                        $search = '$this->'.$relation.'(';
230
                        if ($pos = stripos($code, $search)) {
231 3
                            //Resolve the relation's model to a Relation object.
232 2
                            $relationObj = $model->$method();
233 2
                            if ($relationObj instanceof Relation) {
234 3
                                $relatedModel = '\\'.get_class($relationObj->getRelated());
235 3
                                $relations = ['hasManyThrough', 'belongsToMany', 'hasMany', 'morphMany', 'morphToMany'];
236 3
                                if (in_array($relation, $relations)) {
237 3
                                    //Collection or array of models (because Collection is Arrayable)
238 3
                                    $relationships["HasMany"][$method] = $relatedModel;
239 3
                                } elseif ($relation === "morphTo") {
240 3
                                    // Model isn't specified because relation is polymorphic
241
                                    $relationships["UnknownPolyMorphSide"][$method] =
242
                                        '\Illuminate\Database\Eloquent\Model|\Eloquent';
243
                                } else {
244
                                    //Single model is returned
245
                                    $relationships["HasOne"][$method] = $relatedModel;
246
                                }
247
                                if (in_array($relation, ["morphMany", "morphOne"])) {
248
                                    $relationships["KnownPolyMorphSide"][$method] = $relatedModel;
249
                                }
250
                            }
251
                        }
252
                    }
253
                }
254
            }
255
        }
256
        return $relationships;
257
    }
258
259
    /**
260
     * Get the visible attributes for the model.
261
     *
262
     * @return array
263
     */
264
    public abstract function getVisible();
265
266
    /**
267
     * Get the hidden attributes for the model.
268
     *
269
     * @return array
270
     */
271
    public abstract function getHidden();
272
273
    /**
274
     * Get the primary key for the model.
275
     *
276
     * @return string
277
     */
278
    public abstract function getKeyName();
279
280
    /**
281
     * Get the current connection name for the model.
282
     *
283
     * @return string
284
     */
285
    public abstract function getConnectionName();
286
287
    /**
288
     * Get the database connection for the model.
289
     *
290
     * @return \Illuminate\Database\Connection
291
     */
292
    public abstract function getConnection();
293
294
    /**
295
     * Get all of the current attributes on the model.
296
     *
297
     * @return array
298
     */
299
    public abstract function getAttributes();
300
301
    /**
302
     * Get the table associated with the model.
303
     *
304
     * @return string
305
     */
306
    public abstract function getTable();
307
308
    /**
309
     * Get the fillable attributes for the model.
310
     *
311
     * @return array
312
     */
313
    public abstract function getFillable();
314
}
315