Passed
Branch main (e9bbbe)
by Stefan
02:40
created

AbstractConfig::setDateFormat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace SKien\Config;
5
6
/**
7
 * Abstract base class for config components.
8
 *
9
 * #### History
10
 * - *2021-01-01*   initial version
11
 *
12
 * @package SKien/Config
13
 * @version 1.0.0
14
 * @author Stefanius <[email protected]>
15
 * @copyright MIT License - see the LICENSE file for details
16
 */
17
abstract class AbstractConfig implements ConfigInterface
18
{
19
    /** @var array holding the config data    */
20
    protected ?array $aConfig = null;
21
    /** @var string format for date parameters     */
22
    protected string $strDateFormat = 'Y-m-d';
23
    /** @var string format for datetime parameters     */
24
    protected string $strDateTimeFormat = 'Y-m-d H:i';
25
    
26
    /**
27
     * Set the format for date parameters.
28
     * See the formatting options DateTime::createFromFormat. 
29
     * In most cases, the same letters as for the date() can be used. 
30
     * @param string $strFormat
31
     * @link https://www.php.net/manual/en/datetime.createfromformat.php
32
     */
33
    public function setDateFormat(string $strFormat) : void
34
    {
35
        $this->strDateFormat = $strFormat;
36
    }
37
    
38
    /**
39
     * Set the format for datetime parameters.
40
     * See the formatting options DateTime::createFromFormat. 
41
     * In most cases, the same letters as for the date() can be used. 
42
     * @param string $strFormat
43
     * @link https://www.php.net/manual/en/datetime.createfromformat.php
44
     */
45
    public function setDateTimeFormat(string $strFormat) : void
46
    {
47
        $this->strDateTimeFormat = $strFormat;
48
    }
49
    
50
    /**
51
     * Get the value specified by path.
52
     * @param string $strPath
53
     * @param mixed $default
54
     * @return mixed
55
     */
56
    public function getValue(string $strPath, $default = null)
57
    {
58
        if ($this->aConfig === null) {
59
            // without valid config file just return the default value
60
            return $default;
61
        }
62
        $aPath = $this->splitPath($strPath);
63
        $iDepth = count($aPath);
64
        $value = null;
65
        $aValues = $this->aConfig;
66
        for ($i = 0; $i < $iDepth; $i++) {
67
            if (!is_array($aValues)) {
68
                $value = null;
69
                break;
70
            }
71
            $value = $aValues[$aPath[$i]] ?? null;
72
            if ($value === null) {
73
                break;
74
            }
75
            $aValues = $value;
76
        }
77
        return $value ?? $default;
78
    }
79
    
80
    /**
81
     * Get the string value specified by path.
82
     * @param string $strPath
83
     * @param string $strDefault
84
     * @return string
85
     */
86
    public function getString(string $strPath, string $strDefault = '') : string
87
    {
88
        return (string) $this->getValue($strPath, $strDefault);
89
    }
90
    
91
    /**
92
     * Get the integer value specified by path.
93
     * @param string $strPath
94
     * @param int $iDefault
95
     * @return int
96
     */
97
    public function getInt(string $strPath, int $iDefault = 0) : int
98
    {
99
        return intval($this->getValue($strPath, $iDefault));
100
    }
101
    
102
    /**
103
     * Get the integer value specified by path.
104
     * @param string $strPath
105
     * @param float $fltDefault
106
     * @return float
107
     */
108
    public function getFloat(string $strPath, float $fltDefault = 0.0) : float
109
    {
110
        return floatval($this->getValue($strPath, $fltDefault));
111
    }
112
    
113
    /**
114
     * Get the boolean value specified by path.
115
     * @param string $strPath
116
     * @param bool $bDefault
117
     * @return bool
118
     */
119
    public function getBool(string $strPath, bool $bDefault = false) : bool
120
    {
121
        $value = $this->getValue($strPath, $bDefault);
122
        if (!is_bool($value)) {
123
            $value = $this->boolFromString((string) $value, $bDefault);
124
        }
125
        return $value;
126
    }
127
    
128
    /**
129
     * Get the date value specified by path.
130
     * @param string $strPath
131
     * @param mixed $default default value (unix timestamp, DateTime object or date string)
132
     * @return int date as unix timestamp
133
     */
134
    public function getDate(string $strPath, $default = 0) : int
135
    {
136
        $date = (string) $this->getValue($strPath, $default);
137
        if (!ctype_digit($date)) {
138
            $dt = \DateTime::createFromFormat($this->strDateFormat, $date);
139
            $date = $default;
140
            if ($dt !== false) {
141
                $aError = $dt->getLastErrors();
142
                if ($aError['error_count'] == 0) {
143
                    $dt->setTime(0, 0);
144
                    $date = $dt->getTimestamp();
145
                }
146
            }
147
        } else {
148
            $date = intval($date);
149
        }
150
        return $date;
151
    }
152
    
153
    /**
154
     * Get the date and time value specified by path as unix timestamp.
155
     * @param string $strPath
156
     * @param int $default default value (unix timestamp)
157
     * @return int unix timestamp
158
     */
159
    public function getDateTime(string $strPath, $default = 0) : int
160
    {
161
        $date = (string) $this->getValue($strPath, $default);
162
        if (!ctype_digit($date)) {
163
            $dt = \DateTime::createFromFormat($this->strDateTimeFormat, $date);
164
            $date = $default;
165
            if ($dt !== false) {
166
                $aError = $dt->getLastErrors();
167
                if ($aError['error_count'] == 0) {
168
                    $date = $dt->getTimestamp();
169
                }
170
            }
171
        } else {
172
            $date = intval($date);
173
        }
174
        return $date;
175
    }
176
    
177
    /**
178
     * Get the array specified by path.
179
     * @param string $strPath
180
     * @param array $aDefault
181
     * @return array
182
     */
183
    public function getArray(string $strPath, array $aDefault = []) : array
184
    {
185
        $value = $this->getValue($strPath, $aDefault);
186
        return is_array($value) ? $value : $aDefault;
187
    }
188
    
189
    /**
190
     * Returns the internal array.
191
     * @return array
192
     */
193
    public function getConfig() : array
194
    {
195
        return $this->aConfig ?? [];
196
    }
197
    
198
    /**
199
     * Split the given path in its components.
200
     * @param string $strPath
201
     * @return array
202
     */
203
    protected function splitPath(string $strPath) : array
204
    {
205
        return explode('.', $strPath);
206
    }
207
    
208
    /**
209
     * Convert string to bool.
210
     * Accepted values are (case insensitiv): <ul>
211
     * <li> true, on, yes, 1 </li>
212
     * <li> false, off, no, none, 0 </li></ul>
213
     * for all other values the default value is returned!
214
     * @param string $strValue
215
     * @param bool $bDefault
216
     * @return bool
217
     */
218
    protected function boolFromString(string $strValue, bool $bDefault = false) : bool
219
    {
220
        if ($this->isTrue($strValue)) {
221
            return true;
222
        } else if ($this->isFalse($strValue)) {
223
            return false;
224
        } else {
225
            return $bDefault;
226
        }
227
    }
228
    
229
    /**
230
     * Checks whether the passed value is a valid expression for bool 'True'.
231
     * Accepted values for bool 'true' are (case insensitiv): <i>true, on, yes, 1</i>
232
     * @param string $strValue
233
     * @return bool
234
     */
235
    protected function isTrue(string $strValue) : bool
236
    {
237
        $strValue = strtolower($strValue);
238
        return in_array($strValue, ['true', 'on', 'yes', '1']);
239
    }
240
    
241
    /**
242
     * Checks whether the passed value is a valid expression for bool 'False'.
243
     * Accepted values for bool 'false' are (case insensitiv): <i>false, off, no, none, 0</i>
244
     * @param string $strValue
245
     * @return bool
246
     */
247
    protected function isFalse(string $strValue) : bool
248
    {
249
        $strValue = strtolower($strValue);
250
        return in_array($strValue, ['false', 'off', 'no', 'none', '0']);
251
    }
252
    
253
    /**
254
     * Merge this instance with values from onather config.
255
     * Note that the elemenst of the config to merge with has allways higher priority than 
256
     * the elements of this instance. <br/>
257
     * If both config contains elements with the same key, the value of this instance will be
258
     * replaced with the value of the config we merge with. <br/>
259
     * <b>So keep allways the order in wich you merge several configs together in mind.</b>
260
     * @param AbstractConfig $oMerge
261
     */
262
    public function mergeWith(AbstractConfig $oMerge) : void
263
    {
264
        $aMerge = $oMerge->getConfig();
265
        if ($this->aConfig === null) {
266
            $this->aConfig = $aMerge;
267
            return;
268
        }
269
        $this->aConfig = $this->mergeArrays($this->aConfig, $aMerge);
270
    }
271
    
272
    /**
273
     * Merge the values of two array into one resulting array.
274
     * <b>Note: <i>neither array_merge() nor array_merge_recursive() lead to 
275
     * the desired result</i></b><br/><br/>
276
     * Assuming following two config:<pre>
277
     *      $a1 = ["a" => ["c1" => "red", "c2" => "green"]];
278
     *      $a2 = ["a" => ["c2" => "blue", "c3" => "yellow"]]; </pre>
279
     * We expect as result for merge($a1, $a2): <pre>
280
     *      $a3 = ["a" => ["c1" => "red", "c2" => "blue", "c3" => "yellow"]]; </pre>
281
     * => [a][c1] remains on "red", [a][c2] "green" is replaced by "blue" and [a][c3] is supplemented with "yellow" <br/><br/>
282
     * But <ol>
283
     * <li><b>$a3 = array_merge($a1, $a2)</b> will result in: <pre>
284
     *      $a3 = ["a" => ["c2" => "blue", "c3" => "yellow"]]; </pre>
285
     * => the entire element [a] is replaced by the content of $a2 - the sub-elements 
286
     * of $a1 that are not contained in $a2 are lost! <br/><br/>
287
     * </li>
288
     * <li><b>$a3 = array_merge_recursive($a1, $a2)</b> will result in: <pre>
289
     *      $a3 = ["a" => ["c1" => red, "c2" => ["green", "blue"], "c3" => "yellow"]]</pre> 
290
     * => [a][c2] changes from string to an array ["green", "blue"]!
291
     * </li></ol>
292
     * @param array $aBase
293
     * @param array $aMerge
294
     */
295
    protected function mergeArrays(array $aBase, array $aMerge) : array
296
    {
297
        foreach ($aMerge as $keyMerge => $valueMerge) {
298
            if (isset($aBase[$keyMerge]) && is_array($aBase[$keyMerge]) && is_array($valueMerge)) {
299
                // The element is available in the basic configuration and both elements contains
300
                // an array 
301
                // -> call mergeArray () recursively, unless it is a zero index based array in both cases
302
                if ($this->isAssoc($aBase[$keyMerge]) || $this->isAssoc($valueMerge)) {
303
                    $aBase[$keyMerge] = $this->mergeArrays($aBase[$keyMerge], $valueMerge);
304
                    continue;
305
                }
306
            }
307
            // in all other cases either the element from the array that is to be merged is inserted 
308
            // or it has priority over the original element
309
            $aBase[$keyMerge] = $valueMerge;
310
        }
311
        return $aBase;
312
    }
313
 
314
    /**
315
     * Check if given array is associative.
316
     * Only if the array exactly has sequential numeric keys, starting from 0, the
317
     * array is NOT associative. 
318
     * @param array $a
319
     * @return bool
320
     */
321
    protected function isAssoc(array $a) : bool
322
    {
323
        if ($a === []) {
324
            return false;
325
        }
326
        return array_keys($a) !== range(0, count($a) - 1);
327
    }
328
}