• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 20410659201

21 Dec 2025 01:38PM UTC coverage: 84.51% (-0.02%) from 84.53%
20410659201

Pull #9853

github

web-flow
Merge 68e05ba3b into 23b64e61a
Pull Request #9853: feat(encryption): Add previous keys fallback feature

43 of 54 new or added lines in 4 files covered. (79.63%)

3 existing lines in 2 files now uncovered.

21518 of 25462 relevant lines covered (84.51%)

196.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.57
/system/Config/BaseConfig.php
1
<?php
2

3
/**
4
 * This file is part of CodeIgniter 4 framework.
5
 *
6
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11

12
namespace CodeIgniter\Config;
13

14
use CodeIgniter\Autoloader\FileLocatorInterface;
15
use CodeIgniter\Exceptions\ConfigException;
16
use CodeIgniter\Exceptions\RuntimeException;
17
use Config\Encryption;
18
use Config\Modules;
19
use ReflectionClass;
20
use ReflectionException;
21

22
/**
23
 * Class BaseConfig
24
 *
25
 * Not intended to be used on its own, this class will attempt to
26
 * automatically populate the child class' properties with values
27
 * from the environment.
28
 *
29
 * These can be set within the .env file.
30
 *
31
 * @phpstan-consistent-constructor
32
 * @see \CodeIgniter\Config\BaseConfigTest
33
 */
34
class BaseConfig
35
{
36
    /**
37
     * An optional array of classes that will act as Registrars
38
     * for rapidly setting config class properties.
39
     *
40
     * @var array
41
     */
42
    public static $registrars = [];
43

44
    /**
45
     * Whether to override properties by Env vars and Registrars.
46
     */
47
    public static bool $override = true;
48

49
    /**
50
     * Has module discovery completed?
51
     *
52
     * @var bool
53
     */
54
    protected static $didDiscovery = false;
55

56
    /**
57
     * Is module discovery running or not?
58
     */
59
    protected static bool $discovering = false;
60

61
    /**
62
     * The processing Registrar file for error message.
63
     */
64
    protected static string $registrarFile = '';
65

66
    /**
67
     * The modules configuration.
68
     *
69
     * @var Modules|null
70
     */
71
    protected static $moduleConfig;
72

73
    public static function __set_state(array $array)
74
    {
75
        static::$override = false;
2✔
76
        $obj              = new static();
2✔
77
        static::$override = true;
2✔
78

79
        $properties = array_keys(get_object_vars($obj));
2✔
80

81
        foreach ($properties as $property) {
2✔
82
            $obj->{$property} = $array[$property];
2✔
83
        }
84

85
        return $obj;
2✔
86
    }
87

88
    /**
89
     * @internal For testing purposes only.
90
     * @testTag
91
     */
92
    public static function setModules(Modules $modules): void
93
    {
94
        static::$moduleConfig = $modules;
1✔
95
    }
96

97
    /**
98
     * @internal For testing purposes only.
99
     * @testTag
100
     */
101
    public static function reset(): void
102
    {
103
        static::$registrars   = [];
18✔
104
        static::$override     = true;
18✔
105
        static::$didDiscovery = false;
18✔
106
        static::$moduleConfig = null;
18✔
107
    }
108

109
    /**
110
     * Will attempt to get environment variables with names
111
     * that match the properties of the child class.
112
     *
113
     * The "shortPrefix" is the lowercase-only config class name.
114
     */
115
    public function __construct()
116
    {
117
        static::$moduleConfig ??= new Modules();
7,017✔
118

119
        if (! static::$override) {
7,017✔
120
            return;
6✔
121
        }
122

123
        $this->registerProperties();
7,013✔
124

125
        $properties  = array_keys(get_object_vars($this));
7,013✔
126
        $prefix      = static::class;
7,013✔
127
        $slashAt     = strrpos($prefix, '\\');
7,013✔
128
        $shortPrefix = strtolower(substr($prefix, $slashAt === false ? 0 : $slashAt + 1));
7,013✔
129

130
        foreach ($properties as $property) {
7,013✔
131
            $this->initEnvValue($this->{$property}, $property, $prefix, $shortPrefix);
7,013✔
132

133
            if ($this instanceof Encryption) {
7,013✔
134
                if ($property === 'key') {
34✔
135
                    $this->{$property} = $this->parseEncryptionKey($this->{$property});
34✔
136
                } elseif ($property === 'previousKeysFallbackEnabled') {
34✔
137
                    // previousKeysFallbackEnabled must be boolean
138
                    $this->{$property} = (bool) $this->{$property};
34✔
139
                } elseif ($property === 'previousKeys') {
34✔
140
                    // previousKeys must be an array
141
                    if (is_string($this->{$property})) {
34✔
NEW
142
                        $this->{$property} = array_map(fn ($item): string => $this->parseEncryptionKey($item), explode(',', $this->{$property}));
×
143
                    }
144
                }
145
            }
146
        }
147
    }
148

149
    protected function parseEncryptionKey(string $key): string
150
    {
151
        if (str_starts_with($key, 'hex2bin:')) {
34✔
152
            return hex2bin(substr($key, 8));
2✔
153
        }
154
        if (str_starts_with($key, 'base64:')) {
32✔
155
            return base64_decode(substr($key, 7), true);
2✔
156
        }
157

158
        return $key;
30✔
159
    }
160

161
    /**
162
     * Initialization an environment-specific configuration setting
163
     *
164
     * @param array|bool|float|int|string|null $property
165
     *
166
     * @return void
167
     */
168
    protected function initEnvValue(&$property, string $name, string $prefix, string $shortPrefix)
169
    {
170
        if (is_array($property)) {
7,013✔
171
            foreach (array_keys($property) as $key) {
7,013✔
172
                $this->initEnvValue($property[$key], "{$name}.{$key}", $prefix, $shortPrefix);
7,006✔
173
            }
174
        } elseif (($value = $this->getEnvValue($name, $prefix, $shortPrefix)) !== false && $value !== null) {
7,013✔
175
            if ($value === 'false') {
6,944✔
176
                $value = false;
10✔
177
            } elseif ($value === 'true') {
6,944✔
178
                $value = true;
10✔
179
            }
180
            if (is_bool($value)) {
6,944✔
181
                $property = $value;
10✔
182

183
                return;
10✔
184
            }
185

186
            $value = trim($value, '\'"');
6,944✔
187

188
            if (is_int($property)) {
6,944✔
189
                $value = (int) $value;
10✔
190
            } elseif (is_float($property)) {
6,944✔
191
                $value = (float) $value;
10✔
192
            }
193

194
            // If the default value of the property is `null` and the type is not
195
            // `string`, TypeError will happen.
196
            // So cannot set `declare(strict_types=1)` in this file.
197
            $property = $value;
6,944✔
198
        }
199
    }
200

201
    /**
202
     * Retrieve an environment-specific configuration setting
203
     *
204
     * @return string|null
205
     */
206
    protected function getEnvValue(string $property, string $prefix, string $shortPrefix)
207
    {
208
        $shortPrefix        = ltrim($shortPrefix, '\\');
7,013✔
209
        $underscoreProperty = str_replace('.', '_', $property);
7,013✔
210

211
        switch (true) {
212
            case array_key_exists("{$shortPrefix}.{$property}", $_ENV):
7,013✔
213
                return $_ENV["{$shortPrefix}.{$property}"];
12✔
214

215
            case array_key_exists("{$shortPrefix}_{$underscoreProperty}", $_ENV):
7,013✔
216
                return $_ENV["{$shortPrefix}_{$underscoreProperty}"];
10✔
217

218
            case array_key_exists("{$shortPrefix}.{$property}", $_SERVER):
7,013✔
219
                return $_SERVER["{$shortPrefix}.{$property}"];
6,944✔
220

221
            case array_key_exists("{$shortPrefix}_{$underscoreProperty}", $_SERVER):
7,013✔
222
                return $_SERVER["{$shortPrefix}_{$underscoreProperty}"];
×
223

224
            case array_key_exists("{$prefix}.{$property}", $_ENV):
7,013✔
225
                return $_ENV["{$prefix}.{$property}"];
10✔
226

227
            case array_key_exists("{$prefix}_{$underscoreProperty}", $_ENV):
7,013✔
228
                return $_ENV["{$prefix}_{$underscoreProperty}"];
10✔
229

230
            case array_key_exists("{$prefix}.{$property}", $_SERVER):
7,013✔
231
                return $_SERVER["{$prefix}.{$property}"];
1✔
232

233
            case array_key_exists("{$prefix}_{$underscoreProperty}", $_SERVER):
7,013✔
234
                return $_SERVER["{$prefix}_{$underscoreProperty}"];
×
235

236
            default:
237
                $value = getenv("{$shortPrefix}.{$property}");
7,013✔
238
                $value = $value === false ? getenv("{$shortPrefix}_{$underscoreProperty}") : $value;
7,013✔
239
                $value = $value === false ? getenv("{$prefix}.{$property}") : $value;
7,013✔
240
                $value = $value === false ? getenv("{$prefix}_{$underscoreProperty}") : $value;
7,013✔
241

242
                return $value === false ? null : $value;
7,013✔
243
        }
244
    }
245

246
    /**
247
     * Provides external libraries a simple way to register one or more
248
     * options into a config file.
249
     *
250
     * @return void
251
     *
252
     * @throws ReflectionException
253
     */
254
    protected function registerProperties()
255
    {
256
        if (! static::$moduleConfig->shouldDiscover('registrars')) {
7,013✔
257
            return;
1✔
258
        }
259

260
        if (! static::$didDiscovery) {
7,013✔
261
            // Discovery must be completed before the first instantiation of any Config class.
262
            if (static::$discovering) {
19✔
263
                throw new ConfigException(
×
264
                    'During Auto-Discovery of Registrars,'
×
265
                    . ' "' . static::class . '" executes Auto-Discovery again.'
×
266
                    . ' "' . clean_path(static::$registrarFile) . '" seems to have bad code.',
×
267
                );
×
268
            }
269

270
            static::$discovering = true;
19✔
271

272
            /** @var FileLocatorInterface */
273
            $locator         = service('locator');
19✔
274
            $registrarsFiles = $locator->search('Config/Registrar.php');
19✔
275

276
            foreach ($registrarsFiles as $file) {
19✔
277
                // Saves the file for error message.
278
                static::$registrarFile = $file;
19✔
279

280
                $className = $locator->findQualifiedNameFromPath($file);
19✔
281

282
                if ($className === false) {
19✔
283
                    continue;
×
284
                }
285

286
                static::$registrars[] = new $className();
19✔
287
            }
288

289
            static::$didDiscovery = true;
19✔
290
            static::$discovering  = false;
19✔
291
        }
292

293
        $shortName = (new ReflectionClass($this))->getShortName();
7,013✔
294

295
        // Check the registrar class for a method named after this class' shortName
296
        foreach (static::$registrars as $callable) {
7,013✔
297
            // ignore non-applicable registrars
298
            if (! method_exists($callable, $shortName)) {
7,013✔
299
                continue; // @codeCoverageIgnore
7,013✔
300
            }
301

302
            $properties = $callable::$shortName();
812✔
303

304
            if (! is_array($properties)) {
812✔
305
                throw new RuntimeException('Registrars must return an array of properties and their values.');
1✔
306
            }
307

308
            foreach ($properties as $property => $value) {
811✔
309
                if (isset($this->{$property}) && is_array($this->{$property}) && is_array($value)) {
811✔
310
                    $this->{$property} = array_merge($this->{$property}, $value);
811✔
311
                } else {
312
                    $this->{$property} = $value;
×
313
                }
314
            }
315
        }
316
    }
317
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc