Completed
Pull Request — master (#17)
by Alex
03:07
created

MetadataTrait::getRelationshipsFromMethods()   C

Complexity

Conditions 11
Paths 2

Size

Total Lines 67
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 54
CRAP Score 11

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 67
ccs 54
cts 54
cp 1
rs 5.8904
cc 11
eloc 48
nc 2
nop 0
crap 11

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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