Completed
Pull Request — master (#5)
by Alex
06:08
created

MetadataTrait::getXmlSchema()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 4

Importance

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