Passed
Push — main ( b1635d...673af6 )
by Thierry
01:47
created

ConfigSetter::explodeOptionName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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