Issues (9)

src/Setting.php (9 issues)

1
<?php
2
3
namespace Ottosmops\Settings;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Support\Facades\Cache;
7
use Illuminate\Support\Facades\Validator;
8
use Illuminate\Validation\ValidationException;
9
10
use Ottosmops\Settings\Exceptions\NoKeyIsFound;
11
12
class Setting extends Model
13
{
14
    protected $keyType = 'string';
15
16
    protected $guarded = [];
17
18
    public $timestamps = false;
19
20
    public $incrementing = false;
21
22
    protected $primaryKey = 'key';
23
24
    protected $table;
25
26
    static $all_settings;
27
28
    protected $casts = [
29
        'editable' => 'boolean',
30
        'value' => 'array',
31
        'default' => 'array'
32
    ];
33
34 42
    protected function asJson($value)
35
    {
36 42
        if (is_string($value) && $this->type == 'regex') {
0 ignored issues
show
The property type does not seem to exist on Ottosmops\Settings\Setting. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
37 3
            $value = str_replace('\\', 'ç€π', $value);
38 3
            $value = str_replace('/', '@ƻ', $value);
39
        }
40 42
        return json_encode($value, JSON_UNESCAPED_UNICODE);
41
    }
42
43 66
    public function setTypeAttribute($value)
44
    {
45 66
        switch ($value) {
46 66
            case 'arr':
47 63
            case 'array':
48 6
                $this->attributes['type'] = 'array';
49 6
                break;
50 63
            case 'int':
51 60
            case 'integer':
52 12
                $this->attributes['type'] = 'integer';
53 12
                break;
54 54
            case 'bool':
55 51
            case 'boolean':
56 12
                $this->attributes['type'] = 'boolean';
57 12
                break;
58 42
            case 'regex':
59 3
                $this->attributes['type'] = 'regex';
60 3
                break;
61 39
            case 'string':
62 36
                $this->attributes['type'] = 'string';
63 36
                break;
64
            default:
65 3
                throw new \UnexpectedValueException($value);
66
        }
67 63
    }
68
69 66
    public function __construct(array $attributes = [])
70
    {
71 66
        $this->table = config('settings.table', 'settings');
72 66
        parent::__construct($attributes);
73 66
    }
74
75
    /**
76
     * Get the editable status
77
     *
78
     * @param  string  $value
79
     * @return bool
80
     */
81 63
    public function getEditableAttribute($value)
82
    {
83 63
        if (!isset($value)) {
84 60
            return true;
85
        }
86
87 6
        return (bool) $value;
88
    }
89
90
    /**
91
     * Check if setting is editable
92
     *
93
     * @param  string  $value
94
     * @return bool
95
     */
96 3
    public static function isEditable(string $key) : bool
97
    {
98 3
        return Setting::where('key', $key)->first()->editable;
99
    }
100
101
    /**
102
     * Cast a value to the expected type ($this->type)
103
     * Possible types: array, integer, boolean, string
104
     *
105
     * @param  mixed $value
106
     * @return mixed
107
     */
108 57
    public function getValueAttribute($value)
109
    {
110 57
        if ($value === null) {
111 45
            return null;
112
        }
113
114 39
        switch ($this->type) {
0 ignored issues
show
The property type does not seem to exist on Ottosmops\Settings\Setting. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
115 39
            case 'arr':
116 39
            case 'array':
117 6
                return json_decode($value, true);
118 36
            case 'int':
119 36
            case 'integer':
120 6
                return intval($value);
121 33
            case 'bool':
122 33
            case 'boolean':
123 12
                if ($value === "false") {
124 6
                    return false;
125
                }
126 12
                return boolval($value);
127 21
            case 'regex':
128 3
                $value = str_replace('ç€π', '\\', $value);
129 3
                $value = str_replace('@ƻ', '/', $value);
130 3
                return trim($value, '"');
131 18
            case 'string':
132 18
                $value = json_decode($value, true);
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
133
            default:
134 18
                return trim($value, '"');
135
        }
136
    }
137
138 6
    public function getValueAsStringAttribute()
139
    {
140 6
        if ($this->value === null) {
141
            return '';
142
        }
143
144 6
        $type = $this->type ?: gettype($this->value);
0 ignored issues
show
The property type does not seem to exist on Ottosmops\Settings\Setting. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
145 6
        $value = $this->value;
146 6
        switch ($type) {
147 6
            case 'array':
148 6
            case 'object':
149 3
                return json_encode($value, JSON_UNESCAPED_UNICODE);
150 6
            case 'integer':
151 3
                return (string) $value;
152 6
            case 'boolean':
153 3
                if ($value === false) {
154
                    return "false";
155
                }
156 3
                return "true";
157 3
            case 'regex':
158
                $value = str_replace('ç€π', '\\', $value);
159
                $value = str_replace('@ƻ', '/', $value);
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
160 3
            case 'string':
161 3
                $value =  trim($value, '"');
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
It seems like $value can also be of type boolean; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

161
                $value =  trim(/** @scrutinizer ignore-type */ $value, '"');
Loading history...
162
            default:
163 3
                return (string) $value;
164
        }
165
    }
166
167
    /**
168
     * Get a type casted setting value
169
     *
170
     * @param  string $key
171
     * @param  mixed  $default is returned if value is empty (except boolean false)
172
     * @return mixed
173
     */
174 39
    public static function getValue(string $key, $default = null)
175
    {
176 39
        if (!self::has($key)) {
177 9
            throw new NoKeyIsFound();
178
        }
179
180 30
        if (self::hasValue($key)) {
181 30
            return \Ottosmops\Settings\Setting::allSettings()[$key]['value'];
182
        }
183
184 3
        return $default;
185
    }
186
187 6
    public static function getValueAsString(string $key, $default = null)
188
    {
189 6
        if (!self::has($key)) {
190
            throw new NoKeyIsFound();
191
        }
192
193 6
        $setting = static::where('key', $key)->first();
194
195 6
        return $setting->valueAsString ?: $default;
196
    }
197
198
    /**
199
    * Check if setting exists
200
    *
201
    * @param $key
202
    * @return bool
203
    */
204 57
    public static function has(string $key) : bool
205
    {
206 57
        return (boolean) isset(self::allSettings()[$key]);
207
    }
208
209
    /**
210
     * If a setting has a value (also boolean false, integer 0 and an empty string are values)
211
     *
212
     * @param  string  $key
213
     * @return boolean
214
     */
215 33
    public static function hasValue(string $key) : bool
216
    {
217 33
        if (self::has($key) && isset(Setting::allSettings()[$key]['value'])) {
218 33
            $value = Setting::allSettings()[$key]['value'];
219 33
            return !empty($value) || $value === false || $value === 0 || $value === '';
220
        }
221
222 6
        return false;
223
    }
224
225
    /**
226
     * Set a new value
227
     * @param string  $key
228
     * @param mixed  $value    // string, integer, boolean or array
229
     * @param boolean
230
     */
231 33
    public static function setValue(string $key, $value = null, $validate = true)
232
    {
233 33
        if (!self::has($key)) {
234
            throw new NoKeyIsFound();
235
        }
236
237 33
        $setting = self::find($key);
238
239 33
        if ($setting->type === 'boolean' && is_string($value)) {
240
            $value = ($value === 'false') ? false : $value;
241
            $value = ($value === 'true')  ? true  : $value;
242
        }
243 33
        if ($setting->type === 'integer' && is_string($value) && ctype_digit($value)) {
244
            $value = (int) $value;
245
        }
246
247 33
        if ($validate && !$setting->validateNewValue($value)) {
248 6
            throw new ValidationException(null);
249
        }
250
251 27
        $setting->value = $value;
252
253 27
        return $setting->save();
254
    }
255
256
257
    /**
258
     * Remove a setting
259
     *
260
     * @param $key
261
     * @return bool
262
     */
263 6
    public static function remove(string $key)
264
    {
265 6
        if (self::has($key)) {
266 3
            return self::find($key)->delete();
267
        }
268
269 3
        throw new NoKeyIsFound();
270
    }
271
272
    /**
273
     * Get all the settings
274
     *
275
     * @return mixed
276
     */
277 57
    public static function allSettings() : array
278
    {
279 57
        if (! static::$all_settings) {
280
            static::$all_settings = Cache::rememberForever('settings.all', function () {
281 57
                return self::all()->keyBy('key')->toArray();
282 57
            });
283
        }
284
285 57
        return static::$all_settings;
286
    }
287
288
    /**
289
     * Helper function: Validate a value against its type and its rules.
290
     * @param  mixed $value
291
     * @return bool
292
     */
293 33
    public function validateNewValue($value) : bool
294
    {
295 33
        if ($this->type === 'regex') {
0 ignored issues
show
The property type does not seem to exist on Ottosmops\Settings\Setting. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
296 3
            $validator = Validator::make([$this->key => $value], [$this->key => 'string']);
0 ignored issues
show
The property key does not seem to exist on Ottosmops\Settings\Setting. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
297 3
            return !$validator->fails();
298
        }
299 30
        $validator = Validator::make([$this->key => $value], self::getValidationRules());
300
301 30
        return !$validator->fails();
302
    }
303
304
    /**
305
     * Get the validation rules for setting fields
306
     *
307
     * @return array
308
     */
309 33
    public static function getValidationRules() : array
310
    {
311 33
        if ('mysql' === \DB::connection()->getPDO()->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
312
            return Cache::rememberForever('settings.rules', function () {
313
                return Setting::select(\DB::raw('concat_ws("|", rules, type) as rules'))
314
                            ->select('key')
315
                            ->get()
316
                            ->toArray();
317
            });
318
        }
319
320
        return Cache::rememberForever('settings.rules', function () {
321 33
            return Setting::select(\DB::raw("printf('%s|%s', rules, type) as rules, `key`"))
322 33
                            ->pluck('rules', 'key')
323 33
                            ->toArray();
324 33
        });
325
    }
326
327
    /**
328
     * Flush the cache
329
     */
330 63
    public static function flushCache()
331
    {
332 63
        Cache::forget('settings.all');
333 63
        Cache::forget('settings.rules');
334 63
    }
335
336
    /**
337
     * The "booting" method of the model.
338
     *
339
     * @return void
340
     */
341 66
    protected static function boot()
342
    {
343 66
        parent::boot();
344
345
        static::deleted(function () {
346 3
            self::flushCache();
347 3
            static::$all_settings = [];
348 66
        });
349
        static::updated(function () {
350 33
            self::flushCache();
351 33
            static::$all_settings = [];
352 66
        });
353
        static::created(function () {
354 63
            self::flushCache();
355 63
            static::$all_settings = [];
356 66
        });
357 66
    }
358
}
359