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

codeigniter4 / CodeIgniter4 / 22300730863

23 Feb 2026 09:45AM UTC coverage: 86.638% (+0.08%) from 86.559%
22300730863

Pull #9970

github

web-flow
Merge 2f11d7c79 into f733c6ed9
Pull Request #9970: feat: Add Global Context feature

58 of 58 new or added lines in 4 files covered. (100.0%)

96 existing lines in 7 files now uncovered.

22305 of 25745 relevant lines covered (86.64%)

218.33 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\Exceptions\InvalidArgumentException;
33
use CodeIgniter\Filters\Filters;
34
use CodeIgniter\Format\Format;
35
use CodeIgniter\Honeypot\Honeypot;
36
use CodeIgniter\HTTP\CLIRequest;
37
use CodeIgniter\HTTP\ContentSecurityPolicy;
38
use CodeIgniter\HTTP\CURLRequest;
39
use CodeIgniter\HTTP\IncomingRequest;
40
use CodeIgniter\HTTP\Negotiate;
41
use CodeIgniter\HTTP\RedirectResponse;
42
use CodeIgniter\HTTP\Request;
43
use CodeIgniter\HTTP\RequestInterface;
44
use CodeIgniter\HTTP\ResponseInterface;
45
use CodeIgniter\HTTP\SiteURIFactory;
46
use CodeIgniter\HTTP\URI;
47
use CodeIgniter\Images\Handlers\BaseHandler;
48
use CodeIgniter\Language\Language;
49
use CodeIgniter\Log\Logger;
50
use CodeIgniter\Pager\Pager;
51
use CodeIgniter\Router\RouteCollection;
52
use CodeIgniter\Router\RouteCollectionInterface;
53
use CodeIgniter\Router\Router;
54
use CodeIgniter\Security\Security;
55
use CodeIgniter\Session\Session;
56
use CodeIgniter\Superglobals;
57
use CodeIgniter\Throttle\Throttler;
58
use CodeIgniter\Typography\Typography;
59
use CodeIgniter\Validation\ValidationInterface;
60
use CodeIgniter\View\Cell;
61
use CodeIgniter\View\Parser;
62
use CodeIgniter\View\RendererInterface;
63
use CodeIgniter\View\View;
64
use Config\App;
65
use Config\Autoload;
66
use Config\Cache;
67
use Config\ContentSecurityPolicy as CSPConfig;
68
use Config\Encryption;
69
use Config\Exceptions as ConfigExceptions;
70
use Config\Filters as ConfigFilters;
71
use Config\Format as ConfigFormat;
72
use Config\Honeypot as ConfigHoneyPot;
73
use Config\Images;
74
use Config\Migrations;
75
use Config\Modules;
76
use Config\Optimize;
77
use Config\Pager as ConfigPager;
78
use Config\Services as AppServices;
79
use Config\Session as ConfigSession;
80
use Config\Toolbar as ConfigToolbar;
81
use Config\Validation as ConfigValidation;
82
use Config\View as ConfigView;
83
use Config\WorkerMode;
84

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

157
    /**
158
     * Factory method list.
159
     *
160
     * @var array<string, (callable(mixed ...$params): object)> [key => callable]
161
     */
162
    protected static array $factories = [];
163

164
    /**
165
     * Mock objects for testing which are returned if exist.
166
     *
167
     * @var array<string, object> [key => instance]
168
     */
169
    protected static $mocks = [];
170

171
    /**
172
     * Have we already discovered other Services?
173
     *
174
     * @var bool
175
     */
176
    protected static $discovered = false;
177

178
    /**
179
     * A cache of the names of services classes found.
180
     *
181
     * @var list<string>
182
     */
183
    private static array $serviceNames = [];
184

185
    /**
186
     * Simple method to get an entry fast.
187
     *
188
     * @param string $key Identifier of the entry to look for.
189
     *
190
     * @return object|null Entry.
191
     */
192
    public static function get(string $key): ?object
193
    {
194
        return static::$instances[$key] ?? static::__callStatic($key, []);
7,312✔
195
    }
196

197
    /**
198
     * Checks if a service instance has been created.
199
     *
200
     * @param string $key Identifier of the entry to check.
201
     *
202
     * @return bool True if the service instance exists, false otherwise.
203
     */
204
    public static function has(string $key): bool
205
    {
206
        return isset(static::$instances[$key]);
5✔
207
    }
208

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

UNCOV
220
        static::$instances[$key] = $value;
×
221
    }
222

223
    /**
224
     * Overrides an existing entry.
225
     *
226
     * @param string $key Identifier of the entry.
227
     */
228
    public static function override(string $key, object $value): void
229
    {
UNCOV
230
        static::$instances[$key] = $value;
×
231
    }
232

233
    /**
234
     * Returns a shared instance of any of the class' services.
235
     *
236
     * $key must be a name matching a service.
237
     *
238
     * @param array|bool|float|int|object|string|null ...$params
239
     *
240
     * @return object
241
     */
242
    protected static function getSharedInstance(string $key, ...$params)
243
    {
244
        $key = strtolower($key);
7,254✔
245

246
        // Returns mock if exists
247
        if (isset(static::$mocks[$key])) {
7,254✔
248
            return static::$mocks[$key];
472✔
249
        }
250

251
        if (! isset(static::$instances[$key])) {
7,254✔
252
            // Make sure $getShared is false
253
            $params[] = false;
2,115✔
254

255
            static::$instances[$key] = AppServices::$key(...$params);
2,115✔
256
        }
257

258
        return static::$instances[$key];
7,254✔
259
    }
260

261
    /**
262
     * The Autoloader class is the central class that handles our
263
     * spl_autoload_register method, and helper methods.
264
     *
265
     * @return Autoloader
266
     */
267
    public static function autoloader(bool $getShared = true)
268
    {
269
        if ($getShared) {
1,933✔
270
            if (empty(static::$instances['autoloader'])) {
1,930✔
271
                static::$instances['autoloader'] = new Autoloader();
1,923✔
272
            }
273

274
            return static::$instances['autoloader'];
1,930✔
275
        }
276

277
        return new Autoloader();
4✔
278
    }
279

280
    /**
281
     * The file locator provides utility methods for looking for non-classes
282
     * within namespaced folders, as well as convenience methods for
283
     * loading 'helpers', and 'libraries'.
284
     *
285
     * @return FileLocatorInterface
286
     */
287
    public static function locator(bool $getShared = true)
288
    {
289
        if ($getShared) {
2,277✔
290
            if (empty(static::$instances['locator'])) {
2,275✔
291
                $cacheEnabled = class_exists(Optimize::class)
1,800✔
292
                    && (new Optimize())->locatorCacheEnabled;
1,800✔
293

294
                if ($cacheEnabled) {
1,800✔
UNCOV
295
                    static::$instances['locator'] = new FileLocatorCached(new FileLocator(static::autoloader()));
×
296
                } else {
297
                    static::$instances['locator'] = new FileLocator(static::autoloader());
1,800✔
298
                }
299
            }
300

301
            return static::$mocks['locator'] ?? static::$instances['locator'];
2,275✔
302
        }
303

304
        return new FileLocator(static::autoloader());
3✔
305
    }
306

307
    /**
308
     * Provides the ability to perform case-insensitive calling of service
309
     * names.
310
     *
311
     * @return object|null
312
     */
313
    public static function __callStatic(string $name, array $arguments)
314
    {
315
        if (isset(static::$factories[$name])) {
7,254✔
316
            return static::$factories[$name](...$arguments);
7,254✔
317
        }
318

319
        $service = static::serviceExists($name);
2,121✔
320

321
        if ($service === null) {
2,121✔
322
            return null;
1✔
323
        }
324

325
        return $service::$name(...$arguments);
2,121✔
326
    }
327

328
    /**
329
     * Check if the requested service is defined and return the declaring
330
     * class. Return null if not found.
331
     */
332
    public static function serviceExists(string $name): ?string
333
    {
334
        static::buildServicesCache();
2,195✔
335

336
        $services = array_merge(self::$serviceNames, [Services::class]);
2,195✔
337
        $name     = strtolower($name);
2,195✔
338

339
        foreach ($services as $service) {
2,195✔
340
            if (method_exists($service, $name)) {
2,195✔
341
                static::$factories[$name] = [$service, $name];
2,194✔
342

343
                return $service;
2,194✔
344
            }
345
        }
346

347
        return null;
2✔
348
    }
349

350
    /**
351
     * Reset shared instances and mocks for testing.
352
     *
353
     * @return void
354
     *
355
     * @testTag only available to test code
356
     */
357
    public static function reset(bool $initAutoloader = true)
358
    {
359
        static::$mocks     = [];
1,923✔
360
        static::$instances = [];
1,923✔
361
        static::$factories = [];
1,923✔
362

363
        if ($initAutoloader) {
1,923✔
364
            static::autoloader()->initialize(new Autoload(), new Modules());
1,923✔
365
        }
366
    }
367

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

381
        $cache = static::$instances['cache'];
1✔
382

383
        if (! $cache->ping()) {
1✔
UNCOV
384
            $cache->reconnect();
×
385
        }
386
    }
387

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

399
        // Reset factories
400
        static::$factories = [];
1✔
401

402
        // Process each service instance
403
        $persistentInstances = [];
1✔
404

405
        foreach (static::$instances as $serviceName => $service) {
1✔
406
            // Persist services in the persistent list
407
            if (in_array($serviceName, $config->persistentServices, true)) {
1✔
408
                $persistentInstances[$serviceName] = $service;
1✔
409
            }
410
        }
411

412
        static::$instances = $persistentInstances;
1✔
413
    }
414

415
    /**
416
     * Resets any mock and shared instances for a single service.
417
     *
418
     * @return void
419
     *
420
     * @testTag only available to test code
421
     */
422
    public static function resetSingle(string $name)
423
    {
424
        $name = strtolower($name);
16✔
425
        unset(static::$mocks[$name], static::$instances[$name]);
16✔
426
    }
427

428
    /**
429
     * Inject mock object for testing.
430
     *
431
     * @param object $mock
432
     *
433
     * @return void
434
     *
435
     * @testTag only available to test code
436
     */
437
    public static function injectMock(string $name, $mock)
438
    {
439
        static::$instances[$name]         = $mock;
7,252✔
440
        static::$mocks[strtolower($name)] = $mock;
7,252✔
441
    }
442

443
    /**
444
     * Resets the service cache.
445
     */
446
    public static function resetServicesCache(): void
447
    {
448
        self::$serviceNames = [];
1✔
449
        static::$discovered = false;
1✔
450
    }
451

452
    protected static function buildServicesCache(): void
453
    {
454
        if (! static::$discovered) {
2,195✔
455
            if ((new Modules())->shouldDiscover('services')) {
1✔
456
                $locator = static::locator();
1✔
457
                $files   = $locator->search('Config/Services');
1✔
458

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

461
                // Get instances of all service classes and cache them locally.
462
                foreach ($files as $file) {
1✔
463
                    // Does not search `CodeIgniter` namespace to prevent from loading twice.
464
                    if (str_starts_with($file, $systemPath)) {
1✔
465
                        continue;
1✔
466
                    }
467

468
                    $classname = $locator->findQualifiedNameFromPath($file);
1✔
469

470
                    if ($classname === false) {
1✔
UNCOV
471
                        continue;
×
472
                    }
473

474
                    if ($classname !== Services::class) {
1✔
475
                        self::$serviceNames[] = $classname;
1✔
476
                    }
477
                }
478
            }
479

480
            static::$discovered = true;
1✔
481
        }
482
    }
483
}
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