From 652264ebc6147595b89112568b9f49e09a7e8298 Mon Sep 17 00:00:00 2001 From: Glenn <1964713+pyromanci@users.noreply.github.com> Date: Wed, 15 May 2024 09:46:19 -0400 Subject: [PATCH 01/10] feat: Adding BaseService::updateServicesCache refreshes and looks for new services class post Autoload. --- system/Config/BaseService.php | 31 +++++++++++++++++++ .../AfterAutoloadModule/Config/Services.php | 29 +++++++++++++++++ .../Test/AfterAutoloadModule/Test.php | 18 +++++++++++ tests/system/Config/ServicesTest.php | 21 +++++++++++++ user_guide_src/source/changelogs/v4.6.0.rst | 3 ++ user_guide_src/source/concepts/services.rst | 3 ++ 6 files changed, 105 insertions(+) create mode 100644 tests/_support/Test/AfterAutoloadModule/Config/Services.php create mode 100644 tests/_support/Test/AfterAutoloadModule/Test.php diff --git a/system/Config/BaseService.php b/system/Config/BaseService.php index 68885acd0e54..8254b0036dcc 100644 --- a/system/Config/BaseService.php +++ b/system/Config/BaseService.php @@ -419,4 +419,35 @@ protected static function buildServicesCache(): void static::$discovered = true; } } + + /** + * Update the services cache. + */ + public static function updateServicesCache(): void + { + if ((new Modules())->shouldDiscover('services')) { + $locator = static::locator(); + $files = $locator->search('Config/Services'); + + $systemPath = static::autoloader()->getNamespace('CodeIgniter')[0]; + + // Get instances of all service classes and cache them locally. + foreach ($files as $file) { + // Does not search `CodeIgniter` namespace to prevent from loading twice. + if (str_starts_with($file, $systemPath)) { + continue; + } + + $classname = $locator->findQualifiedNameFromPath($file); + + if ($classname === false) { + continue; + } + + if ($classname !== Services::class && ! in_array($classname, self::$serviceNames, true)) { + self::$serviceNames[] = $classname; + } + } + } + } } diff --git a/tests/_support/Test/AfterAutoloadModule/Config/Services.php b/tests/_support/Test/AfterAutoloadModule/Config/Services.php new file mode 100644 index 000000000000..c62b436488ad --- /dev/null +++ b/tests/_support/Test/AfterAutoloadModule/Config/Services.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace AfterAutoloadModule\Config; + +use AfterAutoloadModule\Test; +use CodeIgniter\Config\BaseService; + +class Services extends BaseService +{ + public static function test(bool $getShared = true): Test + { + if ($getShared) { + return static::getSharedInstance('test'); + } + + return new Test(); + } +} diff --git a/tests/_support/Test/AfterAutoloadModule/Test.php b/tests/_support/Test/AfterAutoloadModule/Test.php new file mode 100644 index 000000000000..8fab895b1545 --- /dev/null +++ b/tests/_support/Test/AfterAutoloadModule/Test.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace AfterAutoloadModule; + +class Test +{ +} diff --git a/tests/system/Config/ServicesTest.php b/tests/system/Config/ServicesTest.php index 008f2804c9f2..321017363c45 100644 --- a/tests/system/Config/ServicesTest.php +++ b/tests/system/Config/ServicesTest.php @@ -13,6 +13,7 @@ namespace CodeIgniter\Config; +use AfterAutoloadModule\Test; use CodeIgniter\Autoloader\Autoloader; use CodeIgniter\Autoloader\FileLocator; use CodeIgniter\Database\MigrationRunner; @@ -350,6 +351,26 @@ public function testResetSingleCaseInsensitive(): void $this->assertNotInstanceOf(MockResponse::class, $someService); } + #[PreserveGlobalState(false)] + #[RunInSeparateProcess] + public function testUpdateServiceCache(): void + { + Services::injectMock('response', new MockResponse(new App())); + $someService = service('response'); + $this->assertInstanceOf(MockResponse::class, $someService); + service('response')->setStatusCode(200); + + Services::autoloader()->addNamespace('AfterAutoloadModule', TESTPATH . '_support/Test/AfterAutoloadModule/'); + Services::updateServicesCache(); + + $someService = service('response'); + $this->assertInstanceOf(MockResponse::class, $someService); + $this->assertSame(200, $someService->getStatusCode()); + + $someService = service('test'); + $this->assertInstanceOf(Test::class, $someService); + } + public function testFilters(): void { $result = Services::filters(); diff --git a/user_guide_src/source/changelogs/v4.6.0.rst b/user_guide_src/source/changelogs/v4.6.0.rst index fe3e80b9b4fc..9c6672349911 100644 --- a/user_guide_src/source/changelogs/v4.6.0.rst +++ b/user_guide_src/source/changelogs/v4.6.0.rst @@ -154,6 +154,9 @@ Helpers and Functions Others ====== +- **Services:** Added ``BaseService::updateServicesCache`` method to allow + updating the services cache file post autoloading. This will only look for + new services and add them to the class name cache. - **Filters:** Now you can execute a filter more than once with the different arguments in before or after. diff --git a/user_guide_src/source/concepts/services.rst b/user_guide_src/source/concepts/services.rst index 92eec136e1c8..8c478dc20c67 100644 --- a/user_guide_src/source/concepts/services.rst +++ b/user_guide_src/source/concepts/services.rst @@ -173,3 +173,6 @@ would simply use the framework's ``Config\Services`` class to grab your service: .. literalinclude:: services/012.php .. note:: If multiple Services files have the same method name, the first one found will be the instance returned. + +There may be times when you need to have Service Discovery refresh it's cache after the inital autoload proccess. This can be done by running :php:meth:`Config\\Services::updateServicesCache()`. +This will force the service discovery to re-scan the directories for any new services files. From f89a7001005d4ca8bcd3e648126bf9294af20298 Mon Sep 17 00:00:00 2001 From: Glenn <1964713+pyromanci@users.noreply.github.com> Date: Wed, 15 May 2024 12:18:27 -0400 Subject: [PATCH 02/10] chore: added comments to the test classes for BaseService::updateServicesCache() --- .../_support/Test/AfterAutoloadModule/Config/Services.php | 8 ++++++++ tests/_support/Test/AfterAutoloadModule/Test.php | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/tests/_support/Test/AfterAutoloadModule/Config/Services.php b/tests/_support/Test/AfterAutoloadModule/Config/Services.php index c62b436488ad..56a399a6962f 100644 --- a/tests/_support/Test/AfterAutoloadModule/Config/Services.php +++ b/tests/_support/Test/AfterAutoloadModule/Config/Services.php @@ -16,8 +16,16 @@ use AfterAutoloadModule\Test; use CodeIgniter\Config\BaseService; +/** + * Services for testing BaseService::updateServicesCache() + * + * This class should not be discovered by the autoloader until the test adds this namespace to the autoloader. + */ class Services extends BaseService { + /** + * Return a shared instance of the Test class for testing + */ public static function test(bool $getShared = true): Test { if ($getShared) { diff --git a/tests/_support/Test/AfterAutoloadModule/Test.php b/tests/_support/Test/AfterAutoloadModule/Test.php index 8fab895b1545..961be4895160 100644 --- a/tests/_support/Test/AfterAutoloadModule/Test.php +++ b/tests/_support/Test/AfterAutoloadModule/Test.php @@ -13,6 +13,11 @@ namespace AfterAutoloadModule; +/** + * A simple class for testing BaseService::updateServicesCache() + * + * This class should not be discovered by the autoloader until the test adds this namespace to the autoloader. + */ class Test { } From b077db595238f30fed9ee25fd05fbb7da82be077 Mon Sep 17 00:00:00 2001 From: Glenn <1964713+pyromanci@users.noreply.github.com> Date: Wed, 15 May 2024 12:24:30 -0400 Subject: [PATCH 03/10] fix: Added missing class to codeigniter.additionalServices for testing. --- phpstan.neon.dist | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index eccd0f88db75..3577b0c768c4 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -42,3 +42,6 @@ parameters: booleansInConditions: true disallowedConstructs: true matchingInheritedMethodNames: true + codeigniter: + additionalServices: + - AfterAutoloadModule\Config\Services From 0070a7b61108e958717ff2c87051c0c29d74e198 Mon Sep 17 00:00:00 2001 From: Glenn <1964713+pyromanci@users.noreply.github.com> Date: Wed, 15 May 2024 12:40:49 -0400 Subject: [PATCH 04/10] fix: Forgot to update the exlude list from the fork rest. --- tests/system/CommonSingleServiceTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/system/CommonSingleServiceTest.php b/tests/system/CommonSingleServiceTest.php index a8b4c6dabc32..71c92260a9b7 100644 --- a/tests/system/CommonSingleServiceTest.php +++ b/tests/system/CommonSingleServiceTest.php @@ -111,6 +111,7 @@ public static function provideServiceNames(): iterable 'reset', 'resetSingle', 'injectMock', + 'updateServicesCache', 'encrypter', // Encrypter needs a starter key 'session', // Headers already sent ]; From 97cb84a7a13a117cd2ee949efe4c0fadcaed28c0 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 30 Jun 2024 10:51:28 +0900 Subject: [PATCH 05/10] feat: replace updateServicesCache() with resetServicesCache() --- system/Config/BaseService.php | 40 ++++++------------------ tests/system/CommonSingleServiceTest.php | 2 +- tests/system/Config/ServicesTest.php | 4 +-- 3 files changed, 12 insertions(+), 34 deletions(-) diff --git a/system/Config/BaseService.php b/system/Config/BaseService.php index 8254b0036dcc..2fc079d3c964 100644 --- a/system/Config/BaseService.php +++ b/system/Config/BaseService.php @@ -388,6 +388,15 @@ public static function injectMock(string $name, $mock) static::$mocks[strtolower($name)] = $mock; } + /** + * Resets the service cache. + */ + public static function resetServicesCache(): void + { + self::$serviceNames = []; + static::$discovered = false; + } + protected static function buildServicesCache(): void { if (! static::$discovered) { @@ -419,35 +428,4 @@ protected static function buildServicesCache(): void static::$discovered = true; } } - - /** - * Update the services cache. - */ - public static function updateServicesCache(): void - { - if ((new Modules())->shouldDiscover('services')) { - $locator = static::locator(); - $files = $locator->search('Config/Services'); - - $systemPath = static::autoloader()->getNamespace('CodeIgniter')[0]; - - // Get instances of all service classes and cache them locally. - foreach ($files as $file) { - // Does not search `CodeIgniter` namespace to prevent from loading twice. - if (str_starts_with($file, $systemPath)) { - continue; - } - - $classname = $locator->findQualifiedNameFromPath($file); - - if ($classname === false) { - continue; - } - - if ($classname !== Services::class && ! in_array($classname, self::$serviceNames, true)) { - self::$serviceNames[] = $classname; - } - } - } - } } diff --git a/tests/system/CommonSingleServiceTest.php b/tests/system/CommonSingleServiceTest.php index 71c92260a9b7..343f17e22f5a 100644 --- a/tests/system/CommonSingleServiceTest.php +++ b/tests/system/CommonSingleServiceTest.php @@ -110,8 +110,8 @@ public static function provideServiceNames(): iterable 'serviceExists', 'reset', 'resetSingle', + 'resetServicesCache', 'injectMock', - 'updateServicesCache', 'encrypter', // Encrypter needs a starter key 'session', // Headers already sent ]; diff --git a/tests/system/Config/ServicesTest.php b/tests/system/Config/ServicesTest.php index 321017363c45..4b4574f5c29a 100644 --- a/tests/system/Config/ServicesTest.php +++ b/tests/system/Config/ServicesTest.php @@ -353,7 +353,7 @@ public function testResetSingleCaseInsensitive(): void #[PreserveGlobalState(false)] #[RunInSeparateProcess] - public function testUpdateServiceCache(): void + public function testResetServiceCache(): void { Services::injectMock('response', new MockResponse(new App())); $someService = service('response'); @@ -361,7 +361,7 @@ public function testUpdateServiceCache(): void service('response')->setStatusCode(200); Services::autoloader()->addNamespace('AfterAutoloadModule', TESTPATH . '_support/Test/AfterAutoloadModule/'); - Services::updateServicesCache(); + Services::resetServicesCache(); $someService = service('response'); $this->assertInstanceOf(MockResponse::class, $someService); From 8a2b115ce820d2999f0640a5b5d8366593ffcf87 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 30 Jun 2024 11:04:34 +0900 Subject: [PATCH 06/10] docs: update docs --- user_guide_src/source/changelogs/v4.6.0.rst | 5 ++--- user_guide_src/source/concepts/services.rst | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/user_guide_src/source/changelogs/v4.6.0.rst b/user_guide_src/source/changelogs/v4.6.0.rst index 9c6672349911..e675b871a9c3 100644 --- a/user_guide_src/source/changelogs/v4.6.0.rst +++ b/user_guide_src/source/changelogs/v4.6.0.rst @@ -154,12 +154,11 @@ Helpers and Functions Others ====== -- **Services:** Added ``BaseService::updateServicesCache`` method to allow - updating the services cache file post autoloading. This will only look for - new services and add them to the class name cache. - **Filters:** Now you can execute a filter more than once with the different arguments in before or after. +- **Services:** Added ``BaseService::resetServicesCache()`` method to reset + the services cache. See :ref:`resetting-services-cache`. *************** Message Changes diff --git a/user_guide_src/source/concepts/services.rst b/user_guide_src/source/concepts/services.rst index 8c478dc20c67..ec5a9832b77f 100644 --- a/user_guide_src/source/concepts/services.rst +++ b/user_guide_src/source/concepts/services.rst @@ -174,5 +174,18 @@ would simply use the framework's ``Config\Services`` class to grab your service: .. note:: If multiple Services files have the same method name, the first one found will be the instance returned. -There may be times when you need to have Service Discovery refresh it's cache after the inital autoload proccess. This can be done by running :php:meth:`Config\\Services::updateServicesCache()`. -This will force the service discovery to re-scan the directories for any new services files. +.. _resetting-services-cache: + +Resetting Services Cache +======================== + +.. versionadded:: 4.6.0 + +When Services is first called fairly early in the framework initialization process, +the Services classes discovered by auto-discovery is cached in a property. + +If modules are dynamically loaded later, and there are Services in the modules, +the cache must be updated. + +This can be done by running ``Config\Services::resetServicesCache()``. This will +clear the cache, and force the service discovery again when needed. From bae26f058c7cc8479d292ad44d29cea22fef3df6 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 30 Jun 2024 11:10:41 +0900 Subject: [PATCH 07/10] test: rename varible names --- tests/system/Config/ServicesTest.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/system/Config/ServicesTest.php b/tests/system/Config/ServicesTest.php index 4b4574f5c29a..ed7078514317 100644 --- a/tests/system/Config/ServicesTest.php +++ b/tests/system/Config/ServicesTest.php @@ -356,19 +356,22 @@ public function testResetSingleCaseInsensitive(): void public function testResetServiceCache(): void { Services::injectMock('response', new MockResponse(new App())); - $someService = service('response'); - $this->assertInstanceOf(MockResponse::class, $someService); + $response = service('response'); + $this->assertInstanceOf(MockResponse::class, $response); service('response')->setStatusCode(200); - Services::autoloader()->addNamespace('AfterAutoloadModule', TESTPATH . '_support/Test/AfterAutoloadModule/'); + Services::autoloader()->addNamespace( + 'AfterAutoloadModule', + TESTPATH . '_support/Test/AfterAutoloadModule/' + ); Services::resetServicesCache(); - $someService = service('response'); - $this->assertInstanceOf(MockResponse::class, $someService); - $this->assertSame(200, $someService->getStatusCode()); + $response = service('response'); + $this->assertInstanceOf(MockResponse::class, $response); + $this->assertSame(200, $response->getStatusCode()); - $someService = service('test'); - $this->assertInstanceOf(Test::class, $someService); + $test = service('test'); + $this->assertInstanceOf(Test::class, $test); } public function testFilters(): void From 94e43fdbe33b10c15d0a10ebf4e01b77d7232126 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 30 Jun 2024 11:15:00 +0900 Subject: [PATCH 08/10] test: update comments --- tests/_support/Test/AfterAutoloadModule/Config/Services.php | 5 +++-- tests/_support/Test/AfterAutoloadModule/Test.php | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/_support/Test/AfterAutoloadModule/Config/Services.php b/tests/_support/Test/AfterAutoloadModule/Config/Services.php index 56a399a6962f..34dc48476628 100644 --- a/tests/_support/Test/AfterAutoloadModule/Config/Services.php +++ b/tests/_support/Test/AfterAutoloadModule/Config/Services.php @@ -17,9 +17,10 @@ use CodeIgniter\Config\BaseService; /** - * Services for testing BaseService::updateServicesCache() + * Services for testing BaseService::resetServicesCache(). * - * This class should not be discovered by the autoloader until the test adds this namespace to the autoloader. + * This class should not be discovered by the autoloader until the test adds + * this namespace to the autoloader. */ class Services extends BaseService { diff --git a/tests/_support/Test/AfterAutoloadModule/Test.php b/tests/_support/Test/AfterAutoloadModule/Test.php index 961be4895160..3664f1c7c85d 100644 --- a/tests/_support/Test/AfterAutoloadModule/Test.php +++ b/tests/_support/Test/AfterAutoloadModule/Test.php @@ -14,9 +14,10 @@ namespace AfterAutoloadModule; /** - * A simple class for testing BaseService::updateServicesCache() + * A simple class for testing BaseService::resetServicesCache(). * - * This class should not be discovered by the autoloader until the test adds this namespace to the autoloader. + * This class should not be discovered by the autoloader until the test adds + * this namespace to the autoloader. */ class Test { From 0edfd6f516492645574b74840aec7ef928dc33c4 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 30 Jun 2024 11:23:54 +0900 Subject: [PATCH 09/10] test: move class files for testing The module will be added dynamically. So it is special. --- .../Config/Services.php | 0 .../{Test/AfterAutoloadModule => _AfterAutoloadModule}/Test.php | 0 tests/system/Config/ServicesTest.php | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename tests/_support/{Test/AfterAutoloadModule => _AfterAutoloadModule}/Config/Services.php (100%) rename tests/_support/{Test/AfterAutoloadModule => _AfterAutoloadModule}/Test.php (100%) diff --git a/tests/_support/Test/AfterAutoloadModule/Config/Services.php b/tests/_support/_AfterAutoloadModule/Config/Services.php similarity index 100% rename from tests/_support/Test/AfterAutoloadModule/Config/Services.php rename to tests/_support/_AfterAutoloadModule/Config/Services.php diff --git a/tests/_support/Test/AfterAutoloadModule/Test.php b/tests/_support/_AfterAutoloadModule/Test.php similarity index 100% rename from tests/_support/Test/AfterAutoloadModule/Test.php rename to tests/_support/_AfterAutoloadModule/Test.php diff --git a/tests/system/Config/ServicesTest.php b/tests/system/Config/ServicesTest.php index ed7078514317..4beaed54bfc7 100644 --- a/tests/system/Config/ServicesTest.php +++ b/tests/system/Config/ServicesTest.php @@ -362,7 +362,7 @@ public function testResetServiceCache(): void Services::autoloader()->addNamespace( 'AfterAutoloadModule', - TESTPATH . '_support/Test/AfterAutoloadModule/' + SUPPORTPATH . '_AfterAutoloadModule/' ); Services::resetServicesCache(); From a4563e53b7cd6197df337af7aa2130129662d3fe Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 30 Jun 2024 11:52:05 +0900 Subject: [PATCH 10/10] docs: fix description --- user_guide_src/source/concepts/services.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/user_guide_src/source/concepts/services.rst b/user_guide_src/source/concepts/services.rst index ec5a9832b77f..55809de99796 100644 --- a/user_guide_src/source/concepts/services.rst +++ b/user_guide_src/source/concepts/services.rst @@ -181,8 +181,9 @@ Resetting Services Cache .. versionadded:: 4.6.0 -When Services is first called fairly early in the framework initialization process, -the Services classes discovered by auto-discovery is cached in a property. +When the Services class is first called fairly early in the framework initialization +process, the Services classes discovered by auto-discovery are cached in a property, +and it will not be updated. If modules are dynamically loaded later, and there are Services in the modules, the cache must be updated.