Passed
Push — main ( 0f2247...917f8e )
by Thierry
34:19 queued 24:07
created

ConfigSetter::deleteEntries()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
c 0
b 0
f 0
dl 0
loc 20
rs 9.9332
cc 4
nc 4
nop 2
1
<?php
2
3
/**
4
 * ConfigSetter.php
5
 *
6
 * Set values in immutable config objects.
7
 *
8
 * @package jaxon-config
9
 * @author Thierry Feuzeu <[email protected]>
10
 * @copyright 2025 Thierry Feuzeu <[email protected]>
11
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
12
 * @link https://github.com/jaxon-php/jaxon-core
13
 */
14
15
namespace Jaxon\Config;
16
17
use Jaxon\Config\Exception\DataDepth;
18
use Jaxon\Config\Reader\Value;
19
20
use function array_key_exists;
21
use function array_keys;
22
use function array_merge;
23
use function array_pop;
24
use function count;
25
use function implode;
26
use function is_array;
27
use function rtrim;
28
use function strlen;
29
use function substr;
30
use function trim;
31
32
class ConfigSetter
33
{
34
    /**
35
     * Create a new config object
36
     *
37
     * @param array $aOptions The options values to be set
38
     * @param string $sNamePrefix A prefix for the config option names
39
     * @param string $sValuePrefix A prefix of the values in the input array
40
     *
41
     * @return Config
42
     * @throws DataDepth
43
     */
44
    public function newConfig(array $aOptions = [],
45
        string $sNamePrefix = '', string $sValuePrefix = ''): Config
46
    {
47
        return count($aOptions) === 0 ? new Config() :
48
            $this->setOptions(new Config(), $aOptions, $sNamePrefix, $sValuePrefix);
49
    }
50
51
    /**
52
     * Get the last entry from and array and return its length
53
     *
54
     * @param string $sLastName
55
     * @param array $aNames
56
     *
57
     * @return int
58
     */
59
    private function pop(string &$sLastName, array &$aNames): int
60
    {
61
        $sLastName = array_pop($aNames);
62
        return count($aNames);
63
    }
64
65
    /**
66
     * Set the value of a config option
67
     *
68
     * @param array $aValues The current options values
69
     * @param string $sOptionName The option name
70
     * @param mixed $xOptionValue The option value
71
     *
72
     * @return array
73
     */
74
    private function setValue(array $aValues, string $sOptionName, $xOptionValue): array
75
    {
76
        // Given an option name like a.b.c, the values of a and a.b must also be set.
77
        $xValue = $xOptionValue;
78
        $sLastName = '';
79
        $aNames = Value::explodeName($sOptionName);
80
        while($this->pop($sLastName, $aNames) > 0)
81
        {
82
            $sName = implode('.', $aNames);
83
            // The current value is overwritten if it is not an array of options.
84
            $xCurrentValue = isset($aValues[$sName]) &&
85
                Value::containsOptions($aValues[$sName]) ? $aValues[$sName] : [];
86
            $aValues[$sName] = array_merge($xCurrentValue, [$sLastName => $xValue]);
87
            $xValue = $aValues[$sName];
88
        }
89
90
        // Set the input option value.
91
        $aValues[$sOptionName] = $xOptionValue;
92
        return $aValues;
93
    }
94
95
    /**
96
     * Set the value of a config option
97
     *
98
     * @param Config $xConfig
99
     * @param string $sName The option name
100
     * @param mixed $xValue The option value
101
     *
102
     * @return Config
103
     */
104
    public function setOption(Config $xConfig, string $sName, $xValue): Config
105
    {
106
        return new Config($this->setValue($xConfig->getValues(), $sName, $xValue));
107
    }
108
109
    /**
110
     * Recursively set options from a data array
111
     *
112
     * @param array $aValues The current options values
113
     * @param array $aOptions The options values to be set
114
     * @param string $sNamePrefix The prefix for option names
115
     * @param int $nDepth The depth from the first call
116
     *
117
     * @return array
118
     * @throws DataDepth
119
     */
120
    private function setValues(array $aValues, array $aOptions,
121
        string $sNamePrefix = '', int $nDepth = 0): array
122
    {
123
        // Check the max depth
124
        if($nDepth < 0 || $nDepth > 9)
125
        {
126
            throw new DataDepth($sNamePrefix, $nDepth);
127
        }
128
129
        foreach($aOptions as $sName => $xValue)
130
        {
131
            $sName = trim($sName);
132
            if(!Value::containsOptions($xValue))
133
            {
134
                // Save the value of this option
135
                $aValues = $this->setValue($aValues, $sNamePrefix . $sName, $xValue);
136
                continue;
137
            }
138
139
            // Recursively set the options in the array. Important to set a new var.
140
            $sNextPrefix = $sNamePrefix . $sName . '.';
141
            $aValues = $this->setValues($aValues, $xValue, $sNextPrefix, $nDepth + 1);
142
        }
143
        return $aValues;
144
    }
145
146
    /**
147
     * Set the values of an array of config options
148
     *
149
     * @param Config $xConfig
150
     * @param array $aOptions The options values to be set
151
     * @param string $sNamePrefix A prefix for the config option names
152
     * @param string $sValuePrefix A prefix of the values in the input array
153
     *
154
     * @return Config
155
     * @throws DataDepth
156
     */
157
    public function setOptions(Config $xConfig, array $aOptions,
158
        string $sNamePrefix = '', string $sValuePrefix = ''): Config
159
    {
160
        // Find the config array in the input data
161
        $sValuePrefix = trim($sValuePrefix, ' .');
162
        $aKeys = Value::explodeName($sValuePrefix);
163
        foreach($aKeys as $sKey)
164
        {
165
            if(($sKey))
166
            {
167
                if(!isset($aOptions[$sKey]) || !is_array($aOptions[$sKey]))
168
                {
169
                    // No change if the required key is not found.
170
                    return new Config($xConfig->getValues(), false);
171
                }
172
173
                $aOptions = $aOptions[$sKey];
174
            }
175
        }
176
177
        $sNamePrefix = trim($sNamePrefix, ' .');
178
        if(($sNamePrefix))
179
        {
180
            $sNamePrefix .= '.';
181
        }
182
        return new Config($this->setValues($xConfig->getValues(), $aOptions, $sNamePrefix));
183
    }
184
185
    /**
186
     * Unset a config option
187
     *
188
     * @param Config $xConfig
189
     * @param string $sName The option name
190
     *
191
     * @return Config
192
     */
193
    public function unsetOption(Config $xConfig, string $sName): Config
194
    {
195
        return $this->unsetOptions($xConfig, [$sName]);
196
    }
197
198
    /**
199
     * @param array $aValues The option values
200
     * @param string $sName The option names
201
     *
202
     * @return bool
203
     */
204
    private function deleteEntries(array &$aValues, string $sName): bool
205
    {
206
        $sName = rtrim($sName, '.');
207
        if(!array_key_exists($sName, $aValues))
208
        {
209
            return false;
210
        }
211
212
        // Delete all the corresponding entries.
213
        unset($aValues[$sName]);
214
        $sName .= '.';
215
        $nNameLength = strlen($sName);
216
        foreach(array_keys($aValues) as $sOptionName)
217
        {
218
            if(substr($sOptionName, 0, $nNameLength) === $sName)
219
            {
220
                unset($aValues[$sOptionName]);
221
            }
222
        }
223
        return true;
224
    }
225
226
    /**
227
     * Unset an array of config options
228
     *
229
     * @param Config $xConfig
230
     * @param array<string> $aNames The option names
231
     *
232
     * @return Config
233
     */
234
    public function unsetOptions(Config $xConfig, array $aNames): Config
235
    {
236
        $aValues = $xConfig->getValues();
237
        $bChanged = false;
238
        foreach($aNames as $sName)
239
        {
240
            if($this->deleteEntries($aValues, $sName))
241
            {
242
                $bChanged = true;
243
            }
244
        }
245
246
        // $bChanged is false in no entry was deleted.
247
        return new Config($aValues, $bChanged);
248
    }
249
}
250