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

codeigniter4 / CodeIgniter4 / 21507617415

30 Jan 2026 07:11AM UTC coverage: 85.382% (-0.1%) from 85.527%
21507617415

push

github

web-flow
feat: FrankenPHP Worker Mode (#9889)

Co-authored-by: John Paul E. Balandan, CPA <paulbalandan@gmail.com>
Co-authored-by: neznaika0 <ozornick.ks@gmail.com>

153 of 243 new or added lines in 19 files covered. (62.96%)

1 existing line in 1 file now uncovered.

22119 of 25906 relevant lines covered (85.38%)

205.24 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 other service classes we've found.
180
     *
181
     * @var array
182
     *
183
     * @deprecated 4.5.0 No longer used.
184
     */
185
    protected static $services = [];
186

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

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

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

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

229
        static::$instances[$key] = $value;
×
230
    }
231

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

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

255
        // Returns mock if exists
256
        if (isset(static::$mocks[$key])) {
7,159✔
257
            return static::$mocks[$key];
473✔
258
        }
259

260
        if (! isset(static::$instances[$key])) {
7,159✔
261
            // Make sure $getShared is false
262
            $params[] = false;
2,053✔
263

264
            static::$instances[$key] = AppServices::$key(...$params);
2,053✔
265
        }
266

267
        return static::$instances[$key];
7,159✔
268
    }
269

270
    /**
271
     * The Autoloader class is the central class that handles our
272
     * spl_autoload_register method, and helper methods.
273
     *
274
     * @return Autoloader
275
     */
276
    public static function autoloader(bool $getShared = true)
277
    {
278
        if ($getShared) {
1,879✔
279
            if (empty(static::$instances['autoloader'])) {
1,876✔
280
                static::$instances['autoloader'] = new Autoloader();
1,869✔
281
            }
282

283
            return static::$instances['autoloader'];
1,876✔
284
        }
285

286
        return new Autoloader();
4✔
287
    }
288

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

303
                if ($cacheEnabled) {
1,762✔
304
                    static::$instances['locator'] = new FileLocatorCached(new FileLocator(static::autoloader()));
×
305
                } else {
306
                    static::$instances['locator'] = new FileLocator(static::autoloader());
1,762✔
307
                }
308
            }
309

310
            return static::$mocks['locator'] ?? static::$instances['locator'];
2,232✔
311
        }
312

313
        return new FileLocator(static::autoloader());
3✔
314
    }
315

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

328
        $service = static::serviceExists($name);
2,063✔
329

330
        if ($service === null) {
2,063✔
331
            return null;
1✔
332
        }
333

334
        return $service::$name(...$arguments);
2,063✔
335
    }
336

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

345
        $services = array_merge(self::$serviceNames, [Services::class]);
2,135✔
346
        $name     = strtolower($name);
2,135✔
347

348
        foreach ($services as $service) {
2,135✔
349
            if (method_exists($service, $name)) {
2,135✔
350
                static::$factories[$name] = [$service, $name];
2,134✔
351

352
                return $service;
2,134✔
353
            }
354
        }
355

356
        return null;
2✔
357
    }
358

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

372
        if ($initAutoloader) {
1,869✔
373
            static::autoloader()->initialize(new Autoload(), new Modules());
1,869✔
374
        }
375
    }
376

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

390
        $cache = static::$instances['cache'];
1✔
391

392
        if (! $cache->ping()) {
1✔
NEW
393
            $cache->reconnect();
×
394
        }
395
    }
396

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

408
        // Reset factories
409
        static::$factories = [];
1✔
410

411
        // Process each service instance
412
        $persistentInstances = [];
1✔
413

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

421
        static::$instances = $persistentInstances;
1✔
422
    }
423

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

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

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

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

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

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

477
                    $classname = $locator->findQualifiedNameFromPath($file);
1✔
478

479
                    if ($classname === false) {
1✔
480
                        continue;
×
481
                    }
482

483
                    if ($classname !== Services::class) {
1✔
484
                        self::$serviceNames[] = $classname;
1✔
485
                    }
486
                }
487
            }
488

489
            static::$discovered = true;
1✔
490
        }
491
    }
492
}
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