Completed
Push — master ( 29fa90...c30131 )
by Alex
01:00
created

MetadataTrait::getFillable()

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