Completed
Push — 5.1 ( c7eca6...1f8916 )
by Jarek
05:49
created

Attribute::setCustomTable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 2
eloc 3
nc 2
nop 1
1
<?php
2
3
namespace Sofa\Eloquence\Metable;
4
5
use InvalidArgumentException;
6
use Illuminate\Database\Eloquent\Model;
7
use Sofa\Eloquence\Contracts\Attribute as AttributeContract;
8
use Sofa\Eloquence\Mutator\Mutator;
9
10
/**
11
 * @property array $attributes
12
 */
13
class Attribute extends Model implements AttributeContract
14
{
15
    /**
16
     * Attribute mutator instance.
17
     *
18
     * @var \Sofa\Eloquence\Contracts\Mutator
19
     */
20
    protected static $attributeMutator;
21
22
    /**
23
     * Custom table name.
24
     *
25
     * @var string
26
     */
27
    protected static $customTable;
28
29
    /**
30
     * The database table used by the model.
31
     *
32
     * @var string
33
     */
34
    protected $table = 'meta_attributes';
35
36
    /**
37
     * Indicates if the model should be timestamped.
38
     *
39
     * @var boolean
40
     */
41
    public $timestamps = false;
42
43
    /**
44
     * The primary key for the model.
45
     *
46
     * @var string
47
     */
48
    protected $primaryKey = 'meta_id';
49
50
    /**
51
     * @var array
52
     */
53
    protected $getterMutators = [
54
        'array'      => 'json_decode',
55
        'StdClass'   => 'json_decode',
56
        'DateTime'   => 'asDateTime',
57
        Model::class => 'unserialize',
58
    ];
59
60
    /**
61
     * @var array
62
     */
63
    protected $setterMutators = [
64
        'array'      => 'json_encode',
65
        'StdClass'   => 'json_encode',
66
        'DateTime'   => 'fromDateTime',
67
        Model::class => 'serialize',
68
    ];
69
70
    /**
71
     * The attributes included in the model's JSON and array form.
72
     *
73
     * @var array
74
     */
75
    protected $visible = ['meta_key', 'meta_value', 'meta_type'];
76
77
    /**
78
     * Create new attribute instance.
79
     *
80
     * @param string|array  $key
81
     * @param mixed  $value
82
     */
83
    public function __construct($key = null, $value = null)
84
    {
85
        // default behaviour
86
        if (is_array($key)) {
87
            parent::__construct($key);
88
89
        } else {
90
            parent::__construct();
91
92
            if (is_string($key)) {
93
                $this->set($key, $value);
94
            }
95
        }
96
    }
97
98
    /**
99
     * Boot this model.
100
     *
101
     * @codeCoverageIgnore
102
     *
103
     * @return void
104
     */
105
    protected static function boot()
106
    {
107
        parent::boot();
108
109
        if (!isset(static::$attributeMutator)) {
110
            if (function_exists('app') && isset(app()['eloquence.mutator'])) {
111
                static::$attributeMutator = app('eloquence.mutator');
112
            } else {
113
                static::$attributeMutator = new Mutator;
114
            }
115
        }
116
    }
117
118
    /**
119
     * Set the meta attribute.
120
     *
121
     * @param string $key
122
     * @param mixed  $value
123
     */
124
    protected function set($key, $value)
125
    {
126
        $this->setMetaKey($key);
127
        $this->setValue($value);
128
    }
129
130
    /**
131
     * Create new AttributeBag.
132
     *
133
     * @param  array  $models
134
     * @return \Sofa\Eloquence\Metable\AttributeBag
135
     */
136
    public function newBag(array $models = [])
137
    {
138
        return new AttributeBag($models);
139
    }
140
141
    /**
142
     * Get the meta attribute value.
143
     *
144
     * @return mixed
145
     */
146
    public function getValue()
147
    {
148
        if ($this->hasMutator($this->attributes['meta_value'], 'getter', $this->attributes['meta_type'])) {
149
            return $this->mutateValue($this->attributes['meta_value'], 'getter');
150
        }
151
152
        return $this->castValue();
153
    }
154
155
    /**
156
     * Get the meta attribute key.
157
     *
158
     * @return string
159
     */
160
    public function getMetaKey()
161
    {
162
        return $this->attributes['meta_key'];
163
    }
164
165
    /**
166
     * Cast value to proper type.
167
     *
168
     * @return mixed
169
     */
170
    protected function castValue()
171
    {
172
        $value = $this->attributes['meta_value'];
173
174
        $validTypes = ['boolean', 'integer', 'float', 'double', 'array', 'object', 'null'];
175
176
        if (in_array($this->attributes['meta_type'], $validTypes)) {
177
            settype($value, $this->attributes['meta_type']);
178
        }
179
180
        return $value;
181
    }
182
183
    /**
184
     * Set key of the meta attribute.
185
     *
186
     * @param string $key
187
     *
188
     * @throws \InvalidArgumentException
189
     */
190
    protected function setMetaKey($key)
191
    {
192
        if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $key)) {
193
            throw new InvalidArgumentException("Provided key [{$key}] is not valid variable name.");
194
        }
195
196
        $this->attributes['meta_key'] = $key;
197
    }
198
199
    /**
200
     * Set type of the meta attribute.
201
     *
202
     * @param mixed $value
203
     */
204
    protected function setType($value)
205
    {
206
        $this->attributes['meta_type'] = $this->hasMutator($value, 'setter')
207
            ? $this->getMutatedType($value, 'setter')
208
            : $this->getValueType($value);
209
    }
210
211
    /**
212
     * Set value of the meta attribute.
213
     *
214
     * @param mixed $value
215
     *
216
     * @throws \Sofa\Eloquence\Metable\InvalidTypeException
217
     */
218
    public function setValue($value)
219
    {
220
        $this->setType($value);
221
222
        if ($this->hasMutator($value, 'setter')) {
223
            $value = $this->mutateValue($value, 'setter');
224
225
        } elseif (!$this->isStringable($value) && !is_null($value)) {
226
            throw new InvalidTypeException(
227
                "Unsupported meta value type [{$this->getValueType($value)}]."
228
            );
229
        }
230
231
        $this->attributes['meta_value'] = $value;
232
    }
233
234
    /**
235
     * Mutate attribute value.
236
     *
237
     * @param  mixed  $value
238
     * @param  string $dir
239
     * @return mixed
240
     */
241
    protected function mutateValue($value, $dir = 'setter')
242
    {
243
        $mutator = $this->getMutator($value, $dir, $this->attributes['meta_type']);
244
245
        if (method_exists($this, $mutator)) {
246
            return $this->{$mutator}($value);
247
        }
248
249
        return static::$attributeMutator->mutate($value, $mutator);
250
    }
251
252
    /**
253
     * Determine whether the value type can be set to string.
254
     *
255
     * @param  mixed   $value
256
     * @return boolean
257
     */
258
    protected function isStringable($value)
259
    {
260
        return is_scalar($value);
261
    }
262
263
    /**
264
     * Get the value type.
265
     *
266
     * @param  mixed  $value
267
     * @return string
268
     */
269
    protected function getValueType($value)
270
    {
271
        $type = is_object($value) ? get_class($value) : gettype($value);
272
273
        // use float instead of deprecated double
274
        return ($type == 'double') ? 'float' : $type;
275
    }
276
277
    /**
278
     * Get the mutated type.
279
     *
280
     * @param  mixed  $value
281
     * @param  string $dir
282
     * @return string
283
     */
284
    protected function getMutatedType($value, $dir = 'setter')
285
    {
286 View Code Duplication
        foreach ($this->{"{$dir}Mutators"} as $mutated => $mutator) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
287
            if ($this->getValueType($value) == $mutated || $value instanceof $mutated) {
288
                return $mutated;
289
            }
290
        }
291
    }
292
293
    /**
294
     * Determine whether a mutator exists for the value type.
295
     *
296
     * @param  mixed   $value
297
     * @param  string  $dir
298
     * @return boolean
299
     */
300
    protected function hasMutator($value, $dir = 'setter', $type = null)
301
    {
302
        return (bool) $this->getMutator($value, $dir, $type);
303
    }
304
305
    /**
306
     * Get mutator for the type.
307
     *
308
     * @param  mixed  $value
309
     * @param  string $dir
310
     * @return string
311
     */
312
    protected function getMutator($value, $dir = 'setter', $type = null)
313
    {
314
        $type = $type ?: $this->getValueType($value);
315
316 View Code Duplication
        foreach ($this->{"{$dir}Mutators"} as $mutated => $mutator) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
317
            if ($type == $mutated || $value instanceof $mutated) {
318
                return $mutator;
319
            }
320
        }
321
    }
322
323
    /**
324
     * Allow custom table name for meta attributes via config.
325
     *
326
     * @return string
327
     */
328
    public function getTable()
329
    {
330
        return isset(static::$customTable) ? static::$customTable : parent::getTable();
331
    }
332
333
    /**
334
     * Set custom table for the meta attributes. Allows doing it only once
335
     * in order to mimic protected behaviour, most likely in the service
336
     * provider, which in turn gets the table name from configuration.
337
     *
338
     * @param string $table
339
     */
340
    public static function setCustomTable($table)
341
    {
342
        if (!isset(static::$customTable)) {
343
            static::$customTable = $table;
344
        }
345
    }
346
347
    /**
348
     * Handle casting value to string.
349
     *
350
     * @return string
351
     */
352
    public function castToString()
353
    {
354
        if ($this->attributes['meta_type'] == 'array') {
355
            return $this->attributes['meta_value'];
356
        }
357
358
        $value = $this->getValue();
359
360
        if ($this->isStringable($value) || is_object($value) && method_exists($value, '__toString')) {
361
            return (string) $value;
362
        }
363
364
        return '';
365
    }
366
367
    /**
368
     * Handle dynamic casting to string.
369
     *
370
     * @return string
371
     */
372
    public function __toString()
373
    {
374
        return $this->castToString();
375
    }
376
}
377