You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf: filter discovery tokenizes the app and system Config/Filters.php on every request #10616
Filters::__construct() calls the deprecated discoverFilters() on every request when filter discovery is on, which it is by default. That method finds every Config/Filters.php across all namespaces. For each file, it calls FileLocator::getClassname(), which runs file_get_contents() and token_get_all() on the file.
In a default app, discovery finds only two files:
app/Config/Filters.php
system/Config/Filters.php
Both get read and tokenized, and then both are skipped, because their class is Config\Filters or CodeIgniter\Config\Filters. So every request pays to parse two files it never uses.
Measurements
PHP 8.3 on macOS, OPcache on, production mode, warm. Each number is averaged over 300 runs.
Case
Time
new Filters(...), discovery on (default)
~103 µs
new Filters(...), discovery off
~0.2 µs
FileLocator::search('Config/Filters.php')
~15 µs
FileLocator::getClassname() per file
~42 µs
For a full request to a bare closure route, Services::filters() is about 126 µs. That's the largest single cost left once #10615 lands, at about 14–18% of the request.
Proposal
In discoverFilters(), compare each found path against APPPATH . 'Config/Filters.php' and SYSTEMPATH . 'Config/Filters.php' before calling getClassname(). Skip those two paths with no file read.
FileLocator::search() already returns real paths, and APPPATH and SYSTEMPATH are real paths too, so a plain string compare works.
Every other file keeps today's class-name check, unchanged. Module and package discovery files are still included as they are now.
With the default setup, the cost drops to the search() call alone, about 15 µs.
Compatibility
No public or protected API changes. discoverFilters() is private.
There's one edge case. Today, if app/Config/Filters.php declared some class other than Config\Filters, discovery would include it. After this change, that file is skipped. This layout breaks the framework's own conventions, and filter discovery has been deprecated since 4.4.2. So no changelog or upgrade-guide entry is planned.
Tests
Add a regression test in tests/system/Filters/FiltersTest.php. It uses a spy FileLocator, injected with Services::injectMock('locator', ...), that records the paths passed to getClassname(). The test asserts that:
getClassname() is never called for the app or system Config/Filters.php.
getClassname() is still called for tests/_support/Config/Filters.php, and the test-customfilter alias it adds is still registered.
The existing discovery test (FiltersTest.php:488) keeps covering the third-party path.
With FileLocatorCached, production apps already avoid these file reads and tokenizations, so I'd expect little benefit there. This could still help apps with locator caching disabled, which is the default.
Activity
michalsn commentedon Oct 10, 2026
With
FileLocatorCached, production apps already avoid these file reads and tokenizations, so I'd expect little benefit there. This could still help apps with locator caching disabled, which is the default.