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

codeigniter4 / CodeIgniter4 / 25902734269

15 May 2026 05:51AM UTC coverage: 88.459% (+0.2%) from 88.299%
25902734269

Pull #10159

github

web-flow
Merge f0573f3e0 into 170b89a6e
Pull Request #10159: feat: Add support for callable TTLs in cache handlers

6 of 10 new or added lines in 3 files covered. (60.0%)

446 existing lines in 24 files now uncovered.

24114 of 27260 relevant lines covered (88.46%)

219.07 hits per line

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

91.03
/system/Config/BaseService.php
1
<?php
2

3
declare(strict_types=1);
4

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

14
namespace CodeIgniter\Config;
15

16
use CodeIgniter\Autoloader\Autoloader;
17
use CodeIgniter\Autoloader\FileLocator;
18
use CodeIgniter\Autoloader\FileLocatorCached;
19
use CodeIgniter\Autoloader\FileLocatorInterface;
20
use CodeIgniter\Cache\CacheInterface;
21
use CodeIgniter\Cache\ResponseCache;
22
use CodeIgniter\CLI\Commands;
23
use CodeIgniter\CodeIgniter;
24
use CodeIgniter\Database\ConnectionInterface;
25
use CodeIgniter\Database\MigrationRunner;
26
use CodeIgniter\Debug\Exceptions;
27
use CodeIgniter\Debug\Iterator;
28
use CodeIgniter\Debug\Timer;
29
use CodeIgniter\Debug\Toolbar;
30
use CodeIgniter\Email\Email;
31
use CodeIgniter\Encryption\EncrypterInterface;
32
use CodeIgniter\EnvironmentDetector;
33
use CodeIgniter\Exceptions\InvalidArgumentException;
34
use CodeIgniter\Filters\Filters;
35
use CodeIgniter\Format\Format;
36
use CodeIgniter\Honeypot\Honeypot;
37
use CodeIgniter\HTTP\CLIRequest;
38
use CodeIgniter\HTTP\ContentSecurityPolicy;
39
use CodeIgniter\HTTP\CURLRequest;
40
use CodeIgniter\HTTP\IncomingRequest;
41
use CodeIgniter\HTTP\Negotiate;
42
use CodeIgniter\HTTP\RedirectResponse;
43
use CodeIgniter\HTTP\Request;
44
use CodeIgniter\HTTP\RequestInterface;
45
use CodeIgniter\HTTP\ResponseInterface;
46
use CodeIgniter\HTTP\SiteURIFactory;
47
use CodeIgniter\HTTP\URI;
48
use CodeIgniter\Images\Handlers\BaseHandler;
49
use CodeIgniter\Input\InputDataFactory;
50
use CodeIgniter\Language\Language;
51
use CodeIgniter\Lock\LockManager;
52
use CodeIgniter\Log\Logger;
53
use CodeIgniter\Pager\Pager;
54
use CodeIgniter\Router\RouteCollection;
55
use CodeIgniter\Router\RouteCollectionInterface;
56
use CodeIgniter\Router\Router;
57
use CodeIgniter\Security\Security;
58
use CodeIgniter\Session\Session;
59
use CodeIgniter\Superglobals;
60
use CodeIgniter\Throttle\Throttler;
61
use CodeIgniter\Typography\Typography;
62
use CodeIgniter\Validation\ValidationInterface;
63
use CodeIgniter\View\Cell;
64
use CodeIgniter\View\Parser;
65
use CodeIgniter\View\RendererInterface;
66
use CodeIgniter\View\View;
67
use Config\App;
68
use Config\Autoload;
69
use Config\Cache;
70
use Config\ContentSecurityPolicy as CSPConfig;
71
use Config\Encryption;
72
use Config\Exceptions as ConfigExceptions;
73
use Config\Filters as ConfigFilters;
74
use Config\Format as ConfigFormat;
75
use Config\Honeypot as ConfigHoneyPot;
76
use Config\Images;
77
use Config\Migrations;
78
use Config\Modules;
79
use Config\Optimize;
80
use Config\Pager as ConfigPager;
81
use Config\Services as AppServices;
82
use Config\Session as ConfigSession;
83
use Config\Toolbar as ConfigToolbar;
84
use Config\Validation as ConfigValidation;
85
use Config\View as ConfigView;
86
use Config\WorkerMode;
87

88
/**
89
 * Services Configuration file.
90
 *
91
 * Services are simply other classes/libraries that the system uses
92
 * to do its job. This is used by CodeIgniter to allow the core of the
93
 * framework to be swapped out easily without affecting the usage within
94
 * the rest of your application.
95
 *
96
 * This is used in place of a Dependency Injection container primarily
97
 * due to its simplicity, which allows a better long-term maintenance
98
 * of the applications built on top of CodeIgniter. A bonus side-effect
99
 * is that IDEs are able to determine what class you are calling
100
 * whereas with DI Containers there usually isn't a way for them to do this.
101
 *
102
 * Warning: To allow overrides by service providers do not use static calls,
103
 * instead call out to \Config\Services (imported as AppServices).
104
 *
105
 * @see http://blog.ircmaxell.com/2015/11/simple-easy-risk-and-change.html
106
 * @see http://www.infoq.com/presentations/Simple-Made-Easy
107
 *
108
 * @method static CacheInterface             cache(Cache $config = null, $getShared = true)
109
 * @method static CLIRequest                 clirequest(App $config = null, $getShared = true)
110
 * @method static CodeIgniter                codeigniter(App $config = null, $getShared = true)
111
 * @method static Commands                   commands($getShared = true)
112
 * @method static void                       createRequest(App $config, bool $isCli = false)
113
 * @method static ContentSecurityPolicy      csp(CSPConfig $config = null, $getShared = true)
114
 * @method static CURLRequest                curlrequest($options = [], ResponseInterface $response = null, App $config = null, $getShared = true)
115
 * @method static Email                      email($config = null, $getShared = true)
116
 * @method static EncrypterInterface         encrypter(Encryption $config = null, $getShared = false)
117
 * @method static EnvironmentDetector        environment(?string $environment = null, bool $getShared = true)
118
 * @method static Exceptions                 exceptions(ConfigExceptions $config = null, $getShared = true)
119
 * @method static Filters                    filters(ConfigFilters $config = null, $getShared = true)
120
 * @method static Format                     format(ConfigFormat $config = null, $getShared = true)
121
 * @method static Honeypot                   honeypot(ConfigHoneyPot $config = null, $getShared = true)
122
 * @method static BaseHandler                image($handler = null, Images $config = null, $getShared = true)
123
 * @method static IncomingRequest            incomingrequest(?App $config = null, bool $getShared = true)
124
 * @method static InputDataFactory           inputdatafactory(bool $getShared = true)
125
 * @method static Iterator                   iterator($getShared = true)
126
 * @method static Language                   language($locale = null, $getShared = true)
127
 * @method static LockManager                locks(?CacheInterface $cache = null, bool $getShared = true)
128
 * @method static Logger                     logger($getShared = true)
129
 * @method static MigrationRunner            migrations(Migrations $config = null, ConnectionInterface $db = null, $getShared = true)
130
 * @method static Negotiate                  negotiator(RequestInterface $request = null, $getShared = true)
131
 * @method static Pager                      pager(ConfigPager $config = null, RendererInterface $view = null, $getShared = true)
132
 * @method static Parser                     parser($viewPath = null, ConfigView $config = null, $getShared = true)
133
 * @method static RedirectResponse           redirectresponse(App $config = null, $getShared = true)
134
 * @method static View                       renderer($viewPath = null, ConfigView $config = null, $getShared = true)
135
 * @method static IncomingRequest|CLIRequest request(App $config = null, $getShared = true)
136
 * @method static ResponseInterface          response(App $config = null, $getShared = true)
137
 * @method static ResponseCache              responsecache(?Cache $config = null, ?CacheInterface $cache = null, bool $getShared = true)
138
 * @method static Router                     router(RouteCollectionInterface $routes = null, Request $request = null, $getShared = true)
139
 * @method static RouteCollection            routes($getShared = true)
140
 * @method static Security                   security(App $config = null, $getShared = true)
141
 * @method static Session                    session(ConfigSession $config = null, $getShared = true)
142
 * @method static SiteURIFactory             siteurifactory(App $config = null, Superglobals $superglobals = null, $getShared = true)
143
 * @method static Superglobals               superglobals(array $server = null, array $get = null, bool $getShared = true)
144
 * @method static Throttler                  throttler($getShared = true)
145
 * @method static Timer                      timer($getShared = true)
146
 * @method static Toolbar                    toolbar(ConfigToolbar $config = null, $getShared = true)
147
 * @method static Typography                 typography($getShared = true)
148
 * @method static URI                        uri($uri = null, $getShared = true)
149
 * @method static ValidationInterface        validation(ConfigValidation $config = null, $getShared = true)
150
 * @method static Cell                       viewcell($getShared = true)
151
 */
152
class BaseService
153
{
154
    /**
155
     * Cache for instance of any services that
156
     * have been requested as a "shared" instance.
157
     * Keys should be lowercase service names.
158
     *
159
     * @var array<string, object> [key => instance]
160
     */
161
    protected static $instances = [];
162

163
    /**
164
     * Factory method list.
165
     *
166
     * @var array<string, (callable(mixed ...$params): object)> [key => callable]
167
     */
168
    protected static array $factories = [];
169

170
    /**
171
     * Mock objects for testing which are returned if exist.
172
     *
173
     * @var array<string, object> [key => instance]
174
     */
175
    protected static $mocks = [];
176

177
    /**
178
     * Have we already discovered other Services?
179
     *
180
     * @var bool
181
     */
182
    protected static $discovered = false;
183

184
    /**
185
     * A cache of the names of services classes found.
186
     *
187
     * @var list<string>
188
     */
189
    private static array $serviceNames = [];
190

191
    /**
192
     * Simple method to get an entry fast.
193
     *
194
     * @param string $key Identifier of the entry to look for.
195
     *
196
     * @return object|null Entry.
197
     */
198
    public static function get(string $key): ?object
199
    {
200
        return static::$instances[$key] ?? static::__callStatic($key, []);
7,858✔
201
    }
202

203
    /**
204
     * Checks if a service instance has been created.
205
     *
206
     * @param string $key Identifier of the entry to check.
207
     *
208
     * @return bool True if the service instance exists, false otherwise.
209
     */
210
    public static function has(string $key): bool
211
    {
212
        return isset(static::$instances[$key]);
78✔
213
    }
214

215
    /**
216
     * Sets an entry.
217
     *
218
     * @param string $key Identifier of the entry.
219
     */
220
    public static function set(string $key, object $value): void
221
    {
UNCOV
222
        if (isset(static::$instances[$key])) {
×
UNCOV
223
            throw new InvalidArgumentException('The entry for "' . $key . '" is already set.');
×
224
        }
225

UNCOV
226
        static::$instances[$key] = $value;
×
227
    }
228

229
    /**
230
     * Overrides an existing entry.
231
     *
232
     * @param string $key Identifier of the entry.
233
     */
234
    public static function override(string $key, object $value): void
235
    {
UNCOV
236
        static::$instances[$key] = $value;
×
237
    }
238

239
    /**
240
     * Returns a shared instance of any of the class' services.
241
     *
242
     * $key must be a name matching a service.
243
     *
244
     * @param array|bool|float|int|object|string|null ...$params
245
     *
246
     * @return object
247
     */
248
    protected static function getSharedInstance(string $key, ...$params)
249
    {
250
        $key = strtolower($key);
7,798✔
251

252
        // Returns mock if exists
253
        if (isset(static::$mocks[$key])) {
7,798✔
254
            return static::$mocks[$key];
542✔
255
        }
256

257
        if (! isset(static::$instances[$key])) {
7,798✔
258
            // Make sure $getShared is false
259
            $params[] = false;
2,255✔
260

261
            static::$instances[$key] = AppServices::$key(...$params);
2,255✔
262
        }
263

264
        return static::$instances[$key];
7,798✔
265
    }
266

267
    /**
268
     * The Autoloader class is the central class that handles our
269
     * spl_autoload_register method, and helper methods.
270
     *
271
     * @return Autoloader
272
     */
273
    public static function autoloader(bool $getShared = true)
274
    {
275
        if ($getShared) {
2,057✔
276
            if (empty(static::$instances['autoloader'])) {
2,054✔
277
                static::$instances['autoloader'] = new Autoloader();
2,048✔
278
            }
279

280
            return static::$instances['autoloader'];
2,054✔
281
        }
282

283
        return new Autoloader();
4✔
284
    }
285

286
    /**
287
     * The file locator provides utility methods for looking for non-classes
288
     * within namespaced folders, as well as convenience methods for
289
     * loading 'helpers', and 'libraries'.
290
     *
291
     * @return FileLocatorInterface
292
     */
293
    public static function locator(bool $getShared = true)
294
    {
295
        if ($getShared) {
2,446✔
296
            if (empty(static::$instances['locator'])) {
2,444✔
297
                $cacheEnabled = class_exists(Optimize::class)
1,920✔
298
                    && (new Optimize())->locatorCacheEnabled;
1,920✔
299

300
                if ($cacheEnabled) {
1,920✔
UNCOV
301
                    static::$instances['locator'] = new FileLocatorCached(new FileLocator(static::autoloader()));
×
302
                } else {
303
                    static::$instances['locator'] = new FileLocator(static::autoloader());
1,920✔
304
                }
305
            }
306

307
            return static::$mocks['locator'] ?? static::$instances['locator'];
2,444✔
308
        }
309

310
        return new FileLocator(static::autoloader());
3✔
311
    }
312

313
    /**
314
     * Provides the ability to perform case-insensitive calling of service
315
     * names.
316
     *
317
     * @return object|null
318
     */
319
    public static function __callStatic(string $name, array $arguments)
320
    {
321
        if (isset(static::$factories[$name])) {
7,798✔
322
            return static::$factories[$name](...$arguments);
7,798✔
323
        }
324

325
        $service = static::serviceExists($name);
2,250✔
326

327
        if ($service === null) {
2,250✔
328
            return null;
1✔
329
        }
330

331
        return $service::$name(...$arguments);
2,250✔
332
    }
333

334
    /**
335
     * Check if the requested service is defined and return the declaring
336
     * class. Return null if not found.
337
     */
338
    public static function serviceExists(string $name): ?string
339
    {
340
        static::buildServicesCache();
2,391✔
341

342
        $services = array_merge(self::$serviceNames, [Services::class]);
2,391✔
343
        $name     = strtolower($name);
2,391✔
344

345
        foreach ($services as $service) {
2,391✔
346
            if (method_exists($service, $name)) {
2,391✔
347
                static::$factories[$name] = [$service, $name];
2,390✔
348

349
                return $service;
2,390✔
350
            }
351
        }
352

353
        return null;
2✔
354
    }
355

356
    /**
357
     * Reset shared instances and mocks for testing.
358
     *
359
     * @return void
360
     *
361
     * @testTag only available to test code
362
     */
363
    public static function reset(bool $initAutoloader = true)
364
    {
365
        static::$mocks     = [];
2,048✔
366
        static::$instances = [];
2,048✔
367
        static::$factories = [];
2,048✔
368

369
        if ($initAutoloader) {
2,048✔
370
            static::autoloader()->initialize(new Autoload(), new Modules());
2,048✔
371
        }
372
    }
373

374
    /**
375
     * Reconnect cache connection for worker mode at the start of a request.
376
     * Checks if cache connection is alive and reconnects if needed.
377
     *
378
     * This should be called at the beginning of each request in worker mode,
379
     * before the application runs.
380
     */
381
    public static function reconnectCacheForWorkerMode(): void
382
    {
383
        if (! isset(static::$instances['cache'])) {
2✔
384
            return;
1✔
385
        }
386

387
        $cache = static::$instances['cache'];
1✔
388

389
        if (! $cache->ping()) {
1✔
UNCOV
390
            $cache->reconnect();
×
391
        }
392
    }
393

394
    /**
395
     * Resets all services except those in the persistent list.
396
     * Used for worker mode to preserve expensive-to-initialize services.
397
     *
398
     * Called at the END of each request to clean up state.
399
     */
400
    public static function resetForWorkerMode(WorkerMode $config): void
401
    {
402
        // Reset mocks (testing only, safe to clear)
403
        static::$mocks = [];
1✔
404

405
        // Reset factories
406
        static::$factories = [];
1✔
407

408
        // Process each service instance
409
        $persistentInstances = [];
1✔
410

411
        foreach (static::$instances as $serviceName => $service) {
1✔
412
            // Persist services in the persistent list
413
            if (in_array($serviceName, $config->persistentServices, true)) {
1✔
414
                $persistentInstances[$serviceName] = $service;
1✔
415
            }
416
        }
417

418
        static::$instances = $persistentInstances;
1✔
419
    }
420

421
    /**
422
     * Resets any mock and shared instances for a single service.
423
     *
424
     * @return void
425
     *
426
     * @testTag only available to test code
427
     */
428
    public static function resetSingle(string $name)
429
    {
430
        $name = strtolower($name);
80✔
431
        unset(static::$mocks[$name], static::$instances[$name]);
80✔
432
    }
433

434
    /**
435
     * Inject mock object for testing.
436
     *
437
     * @param object $mock
438
     *
439
     * @return void
440
     *
441
     * @testTag only available to test code
442
     */
443
    public static function injectMock(string $name, $mock)
444
    {
445
        static::$instances[$name]         = $mock;
7,796✔
446
        static::$mocks[strtolower($name)] = $mock;
7,796✔
447
    }
448

449
    /**
450
     * Resets the service cache.
451
     */
452
    public static function resetServicesCache(): void
453
    {
454
        self::$serviceNames = [];
1✔
455
        static::$discovered = false;
1✔
456
    }
457

458
    protected static function buildServicesCache(): void
459
    {
460
        if (! static::$discovered) {
2,391✔
461
            if ((new Modules())->shouldDiscover('services')) {
1✔
462
                $locator = static::locator();
1✔
463
                $files   = $locator->search('Config/Services');
1✔
464

465
                $systemPath = static::autoloader()->getNamespace('CodeIgniter')[0];
1✔
466

467
                // Get instances of all service classes and cache them locally.
468
                foreach ($files as $file) {
1✔
469
                    // Does not search `CodeIgniter` namespace to prevent from loading twice.
470
                    if (str_starts_with($file, $systemPath)) {
1✔
471
                        continue;
1✔
472
                    }
473

474
                    $classname = $locator->findQualifiedNameFromPath($file);
1✔
475

476
                    if ($classname === false) {
1✔
UNCOV
477
                        continue;
×
478
                    }
479

480
                    if ($classname !== Services::class) {
1✔
481
                        self::$serviceNames[] = $classname;
1✔
482
                    }
483
                }
484
            }
485

486
            static::$discovered = true;
1✔
487
        }
488
    }
489
}
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