Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.41% covered (success)
98.41%
62 / 63
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
SettingsTable
98.41% covered (success)
98.41%
62 / 63
75.00% covered (warning)
75.00%
3 / 4
16
0.00% covered (danger)
0.00%
0 / 1
 initialize
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 validationDefault
97.44% covered (success)
97.44%
38 / 39
0.00% covered (danger)
0.00%
0 / 1
7
 getSettingValue
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
 castValue
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2declare(strict_types=1);
3
4namespace App\Model\Table;
5
6use Cake\ORM\Table;
7use Cake\Validation\Validator;
8
9/**
10 * Settings Model
11 *
12 * @method \App\Model\Entity\Setting newEmptyEntity()
13 * @method \App\Model\Entity\Setting newEntity(array $data, array $options = [])
14 * @method array<\App\Model\Entity\Setting> newEntities(array $data, array $options = [])
15 * @method \App\Model\Entity\Setting get(mixed $primaryKey, array|string $finder = 'all', \Psr\SimpleCache\CacheInterface|string|null $cache = null, \Closure|string|null $cacheKey = null, mixed ...$args)
16 * @method \App\Model\Entity\Setting findOrCreate($search, ?callable $callback = null, array $options = [])
17 * @method \App\Model\Entity\Setting patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
18 * @method array<\App\Model\Entity\Setting> patchEntities(iterable $entities, array $data, array $options = [])
19 * @method \App\Model\Entity\Setting|false save(\Cake\Datasource\EntityInterface $entity, array $options = [])
20 * @method \App\Model\Entity\Setting saveOrFail(\Cake\Datasource\EntityInterface $entity, array $options = [])
21 * @method iterable<\App\Model\Entity\Setting>|\Cake\Datasource\ResultSetInterface<\App\Model\Entity\Setting>|false saveMany(iterable $entities, array $options = [])
22 * @method iterable<\App\Model\Entity\Setting>|\Cake\Datasource\ResultSetInterface<\App\Model\Entity\Setting> saveManyOrFail(iterable $entities, array $options = [])
23 * @method iterable<\App\Model\Entity\Setting>|\Cake\Datasource\ResultSetInterface<\App\Model\Entity\Setting>|false deleteMany(iterable $entities, array $options = [])
24 * @method iterable<\App\Model\Entity\Setting>|\Cake\Datasource\ResultSetInterface<\App\Model\Entity\Setting> deleteManyOrFail(iterable $entities, array $options = [])
25 * @mixin \Cake\ORM\Behavior\TimestampBehavior
26 */
27class SettingsTable extends Table
28{
29    /**
30     * Initialize method
31     *
32     * This method initializes the table configuration, including setting the table name,
33     * display field, primary key, and adding behaviors.
34     *
35     * @param array<string, mixed> $config The configuration for the Table.
36     * @return void
37     */
38    public function initialize(array $config): void
39    {
40        parent::initialize($config);
41
42        $this->setTable('settings');
43        $this->setDisplayField('category');
44        $this->setPrimaryKey('id');
45
46        $this->addBehavior('Timestamp');
47    }
48
49    /**
50     * Default validation rules.
51     *
52     * This method sets up the validation rules for the Settings table fields.
53     * It includes rules for category, key_name, value_type, and value fields.
54     *
55     * @param \Cake\Validation\Validator $validator Validator instance.
56     * @return \Cake\Validation\Validator
57     */
58    public function validationDefault(Validator $validator): Validator
59    {
60        $validator
61            ->scalar('category')
62            ->maxLength('category', 255)
63            ->requirePresence('category', 'create')
64            ->notEmptyString('category');
65
66        $validator
67            ->scalar('key_name')
68            ->maxLength('key_name', 255)
69            ->requirePresence('key_name', 'create')
70            ->notEmptyString('key_name');
71
72        $validator
73            ->scalar('value_type')
74            ->requirePresence('value_type', 'create')
75            ->notEmptyString('value_type')
76            ->inList('value_type', [
77                'text',
78                'numeric',
79                'bool',
80                'textarea',
81                'select',
82                'select-page',
83            ], __('Invalid type'));
84
85        $validator
86            ->requirePresence('value', 'create')
87            ->notEmptyString('value', __('A value is required'))
88            ->add('value', 'custom', [
89                'rule' => function ($value, $context) {
90                    $value_type = $context['data']['value_type'] ?? null;
91                    if ($value_type === 'numeric' && !is_numeric($value)) {
92                        return __('The value must be a number.');
93                    }
94                    if ($value_type === 'bool' && !in_array($value, [0, 1], true)) {
95                        return __('The value must be 0 or 1.');
96                    }
97                    if ($value_type === 'text' && empty($value)) {
98                        return __('The value must not be empty.');
99                    }
100
101                    return true;
102                },
103                'message' => __('Invalid value for the specified type.'),
104            ]);
105
106        return $validator;
107    }
108
109    /**
110     * Retrieves setting values based on the specified category and optional key name.
111     *
112     * This method can fetch either all settings for a given category or a specific setting
113     * within a category, depending on whether a key name is provided.
114     *
115     * @param string $category The category of the setting(s) to retrieve.
116     * @param string|null $keyName The specific key name of the setting. If null, all settings for the category are returned.
117     * @return mixed Returns one of the following:
118     *               - An associative array of all settings for the category if $keyName is null.
119     *               - The value of the specific setting if $keyName is provided and the setting exists.
120     *               - null if a specific setting is requested but not found.
121     * @throws \Cake\Database\Exception\DatabaseException If there's an issue with the database query.
122     */
123    public function getSettingValue(string $category, ?string $keyName = null): mixed
124    {
125        if (empty($keyName)) {
126            // Fetch all settings for the category
127            $settings = $this->find()
128                ->where(['category' => $category])
129                ->all()
130                ->combine('key_name', function ($setting) {
131                    return $this->castValue($setting->value, $setting->value_type);
132                })
133                ->toArray();
134
135            return $settings;
136        }
137
138        // Fetch a single setting
139        $setting = $this->find()
140            ->where(['category' => $category, 'key_name' => $keyName])
141            ->first();
142
143        return $setting ? $this->castValue($setting->value, $setting->value_type) : null;
144    }
145
146    /**
147     * Casts the value to the appropriate type based on the value_type.
148     *
149     * This private method is used internally to ensure that the returned
150     * setting values are of the correct data type.
151     *
152     * @param mixed $value The value to be cast.
153     * @param string $valueType The type to cast the value to ('bool', 'numeric', or 'string').
154     * @return mixed The cast value.
155     */
156    private function castValue(mixed $value, string $valueType): mixed
157    {
158        switch ($valueType) {
159            case 'bool':
160                return filter_var($value, FILTER_VALIDATE_BOOLEAN);
161            case 'numeric':
162                return (int)$value;
163            case 'string':
164            default:
165                return (string)$value;
166        }
167    }
168}