Completed
Pull Request — master (#23)
by Evan
04:00
created

Model::aliasSet()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 3
eloc 6
c 2
b 0
f 1
nc 2
nop 2
dl 0
loc 11
rs 9.4285
1
<?php
2
3
namespace Silk\Type;
4
5
use Silk\Meta\ObjectMeta;
6
use Illuminate\Support\Collection;
7
8
/**
9
 * @property-read int    $id
10
 * @property-read object $object
11
 */
12
abstract class Model
13
{
14
    /**
15
     * The core model object
16
     * @var object
17
     */
18
    protected $object;
19
20
    /**
21
     * Type object property aliases
22
     * @var array
23
     */
24
    protected $objectAliases = [
25
        // 'aliasName' => 'propertyNameOnObject'
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
26
    ];
27
28
    /**
29
     * The object type in WordPress
30
     */
31
    const OBJECT_TYPE = '';
32
33
    /**
34
     * The name of the primary ID property on the object
35
     */
36
    const ID_PROPERTY = '';
37
38
    /**
39
    * Get a new query builder for the model.
40
    *
41
    * @return \Silk\Contracts\BuildsQueries
42
    */
43
    abstract public function newQuery();
44
45
    /**
46
     * Save the changes to the database.
47
     *
48
     * @return $this
49
     */
50
    abstract public function save();
51
52
    /**
53
     * Delete the modeled record from the database.
54
     *
55
     * @return $this
56
     */
57
    abstract public function delete();
58
59
    /**
60
     * Reload the object from the database.
61
     *
62
     * @return $this
63
     */
64
    abstract public function refresh();
65
66
    /**
67
     * Make new instance.
68
     *
69
     * All provided arguments are forwarded to the constructor of the called class.
70
     *
71
     * @return static
72
     */
73
    public static function make()
74
    {
75
        if ($arguments = func_get_args()) {
76
            return (new \ReflectionClass(static::class))->newInstanceArgs($arguments);
77
        }
78
79
        return new static;
80
    }
81
82
    /**
83
     * Fill the model with an array of attributes.
84
     *
85
     * @param  array  $attributes
86
     *
87
     * @return $this
88
     */
89
    public function fill(array $attributes)
90
    {
91
        foreach ($attributes as $key => $value) {
92
            if ($this->expandAlias($key)) {
93
                $this->aliasSet($key, $value);
94
                continue;
95
            }
96
97
            $this->object->$key = $value;
98
        }
99
100
        return $this;
101
    }
102
103
    /**
104
     * Create a new model of the model's type, and save it to the database.
105
     *
106
     * @param  array $attributes
107
     *
108
     * @return static
109
     */
110
    public static function create($attributes = [])
111
    {
112
        $model = new static($attributes);
0 ignored issues
show
Unused Code introduced by
The call to Model::__construct() has too many arguments starting with $attributes.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
113
114
        return $model->save();
115
    }
116
117
    /**
118
     * Create a new query builder instance for this model type.
119
     *
120
     * @return \Silk\Contracts\BuildsQueries
121
     */
122
    public static function query()
123
    {
124
        return (new static)->newQuery();
125
    }
126
127
    /**
128
     * Meta API for this type
129
     *
130
     * @param  string $key  Meta key to retrieve or empty to retrieve all.
131
     *
132
     * @return ObjectMeta|\Silk\Meta\Meta
133
     */
134
    public function meta($key = '')
135
    {
136
        $meta = new ObjectMeta(static::OBJECT_TYPE, $this->id);
137
138
        if ($key) {
139
            return $meta->get($key);
140
        }
141
142
        return $meta;
143
    }
144
145
    /**
146
     * Set the primary ID on the model.
147
     *
148
     * @param string|int $id  The model's ID
149
     *
150
     * @return $this
151
     */
152
    protected function setId($id)
153
    {
154
        $this->object->{static::ID_PROPERTY} = (int) $id;
155
156
        return $this;
157
    }
158
159
    /**
160
     * Set the object for the model.
161
     *
162
     * @param $object
163
     *
164
     * @return $this
165
     */
166
    protected function setObject($object)
167
    {
168
        $this->object = $object;
169
170
        return $this;
171
    }
172
173
    /**
174
     * Set a property on the aliased object.
175
     *
176
     * @param string $key   The alias name on the model
177
     * @param mixed  $value The value to set on the aliased object
178
     *
179
     * @return bool          True if the alias was resolved and set; otherwise false
180
     */
181
    protected function aliasSet($key, $value)
182
    {
183
        $expanded = $this->expandAlias($key);
184
185
        if ($expanded && is_object($aliased = $this->getAliasedObject())) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $expanded of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
186
            $aliased->$expanded = $value;
187
            return true;
188
        }
189
190
        return false;
191
    }
192
193
    /**
194
     * Get a property from the aliased object by the model's key.
195
     *
196
     * @param $key
197
     *
198
     * @return mixed|null
199
     */
200
    protected function aliasGet($key)
201
    {
202
        if (! $expanded = $this->expandAlias($key)) {
203
            return null;
204
        }
205
206
        return data_get($this->getAliasedObject(), $expanded);
207
    }
208
209
    /**
210
     * Get the aliased object instance.
211
     *
212
     * @return object
213
     */
214
    protected function getAliasedObject()
215
    {
216
        return $this->object;
217
    }
218
219
    /**
220
     * Expands an alias into its respective object property name.
221
     *
222
     * @param string $key  Alias key
223
     *
224
     * @return string|false  The expanded alias, or false no alias exists for the key.
225
     */
226
    protected function expandAlias($key)
227
    {
228
        return data_get($this->objectAliases, $key, false);
229
    }
230
231
    /**
232
     * Magic getter.
233
     *
234
     * @param  string $property
235
     *
236
     * @return mixed
237
     */
238
    public function __get($property)
239
    {
240
        if ($property == 'id') {
241
            return $this->object->{static::ID_PROPERTY};
242
        }
243
244
        if (in_array($property, ['object', static::OBJECT_TYPE])) {
245
            return $this->object;
246
        }
247
248
        if (! is_null($aliased = $this->aliasGet($property))) {
249
            return $aliased;
250
        }
251
252
        /**
253
         * Finally, hand-off the request to the wrapped object.
254
         * We don't check for existence as we leverage the magic __get
255
         * on the wrapped object as well.
256
         */
257
        return $this->object->$property;
258
    }
259
260
    /**
261
     * Magic Isset Checker.
262
     *
263
     * @return bool
264
     */
265
    public function __isset($property)
266
    {
267
        return ! is_null($this->__get($property));
268
    }
269
270
    /**
271
     * Magic setter.
272
     *
273
     * @param string $property  The property name
274
     * @param mixed  $value     The new property value
275
     */
276
    public function __set($property, $value)
277
    {
278
        if ($this->aliasSet($property, $value)) {
279
            return;
280
        }
281
282
        $this->object->$property = $value;
283
    }
284
285
    /**
286
     * Handle dynamic method calls into the model.
287
     *
288
     * @param  string $method
289
     * @param  array $arguments
290
     *
291
     * @return mixed
292
     */
293
    public function __call($method, $arguments)
294
    {
295
        $query = $this->newQuery();
296
297
        return call_user_func_array([$query, $method], $arguments);
298
    }
299
300
    /**
301
     * Handle dynamic static method calls on the model class.
302
     *
303
     * Proxies calls to direct method calls on a new instance
304
     *
305
     * @param string $method
306
     * @param array $arguments
307
     *
308
     * @return mixed
309
     */
310
    public static function __callStatic($method, array $arguments)
311
    {
312
        return call_user_func_array([new static, $method], $arguments);
313
    }
314
}
315