From f2541baf5469a54d2e1c0caa13da57fac7651db3 Mon Sep 17 00:00:00 2001 From: Nicolas Joubert Date: Mon, 19 Oct 2026 11:48:40 +0200 Subject: [PATCH] #179 - Load the service definitions with a glob pattern instead of a Finder - CleverAgeUiProcessExtension::findServices() imports the service definition files with a glob pattern ($loader->import('*.yaml', 'glob')) instead of a Finder and one load() call per file, as cleverage/process-bundle#271. Same signature, same files. - symfony/finder stays required: PurgeLogsCommand uses it. - Test that every service of config/services/*.yaml is registered. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 1 + .../CleverAgeUiProcessExtension.php | 10 ++---- .../CleverAgeUiProcessExtensionTest.php | 34 +++++++++++++++++++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e8f4d4..a88e819 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Latest ------ ## Changes +* [#179](https://github.com/cleverage/ui-process-bundle/issues/179) `CleverAgeUiProcessExtension::findServices()` loads the service definition files with a glob pattern instead of a `Finder` (same files, as [cleverage/process-bundle#271](https://github.com/cleverage/process-bundle/issues/271)). Add tests. * [#172](https://github.com/cleverage/ui-process-bundle/issues/172) Prefix the route names of the bundle with `cleverage_ui_process_`, as required by the Symfony best practices for bundles: `cleverage_ui_process_login`, `cleverage_ui_process_logout`, `cleverage_ui_process_list`, `cleverage_ui_process_launch`, `cleverage_ui_process_http_execute`, and the dashboard `cleverage_ui_process`, so that the routes generated by EasyAdmin for the CRUD pages are `cleverage_ui_process__`. The former names (`process`, `process__`, `process_login`, `process_logout`, `process_list`, `process_launch`, `http_process_execute`) are deprecated route aliases (new `config/routes/deprecated_aliases.yaml`, loaded by the documented import, and internal `DeprecatedEasyAdminRouteAliasLoader` for the routes generated by EasyAdmin), removed in 4.0; `?routeName=` URLs keep working. Document that `easyadmin.routes` generates the routes of every dashboard. Update documentation and UPGRADE.md, add tests. * [#166](https://github.com/cleverage/ui-process-bundle/issues/166) Doctrine ORM 2: the entities have readonly properties (since v3.2), which the legacy proxies of ORM 2 cannot handle (`Cannot unset readonly property ... from scope Proxies\__CG__\...` when an entity proxy is loaded). The container compilation now fails with an explicit message when ORM 2 is used without `doctrine.orm.enable_lazy_ghost_objects: true` (new internal `CheckDoctrineLazyGhostObjectsPass`); nothing changes with ORM 3. Update documentation, add tests. * [#164](https://github.com/cleverage/ui-process-bundle/issues/164) Require `symfony/polyfill-php83`, `symfony/polyfill-php84` and `symfony/polyfill-php85`: the functions of PHP 8.3 to 8.5 (`json_validate()`, `array_find()`, `array_first()`...) can be used while supporting PHP 8.2 (see `CONTRIBUTING.md`). Rector is configured for PHP 8.2 (was 8.5, which allowed it to introduce PHP 8.3+ functions without polyfill) with the `PHP_POLYFILLS` set (`withPhpSets()` without argument), it does not change the code. `array_last()` replaces `end()` in a test. Tests: lazy ghost proxies with Doctrine ORM 2 (now installed by the lowest dependencies job), as configured by the Symfony recipe: the legacy proxies cannot unset the readonly properties of the entities. diff --git a/src/DependencyInjection/CleverAgeUiProcessExtension.php b/src/DependencyInjection/CleverAgeUiProcessExtension.php index 2e57df3..ee7a738 100644 --- a/src/DependencyInjection/CleverAgeUiProcessExtension.php +++ b/src/DependencyInjection/CleverAgeUiProcessExtension.php @@ -26,7 +26,6 @@ use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -use Symfony\Component\Finder\Finder; use Symfony\Component\Notifier\NotifierInterface; final class CleverAgeUiProcessExtension extends Extension implements PrependExtensionInterface @@ -255,16 +254,11 @@ private function configureSecurity(ContainerBuilder $container): void } /** - * Recursively import config files into container. + * Import the service definition files of a directory into the container. */ protected function findServices(ContainerBuilder $container, string $path, string $extension = 'yaml'): void { - $finder = new Finder(); - $finder->in($path) - ->name('*.'.$extension)->files(); $loader = new YamlFileLoader($container, new FileLocator($path)); - foreach ($finder as $file) { - $loader->load($file->getFilename()); - } + $loader->import('*.'.$extension, 'glob'); } } diff --git a/tests/DependencyInjection/CleverAgeUiProcessExtensionTest.php b/tests/DependencyInjection/CleverAgeUiProcessExtensionTest.php index f822de1..15ffcae 100644 --- a/tests/DependencyInjection/CleverAgeUiProcessExtensionTest.php +++ b/tests/DependencyInjection/CleverAgeUiProcessExtensionTest.php @@ -31,12 +31,46 @@ use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\Yaml\Yaml; #[CoversClass(CleverAgeUiProcessExtension::class)] #[UsesClass(Configuration::class)] #[UsesClass(NotificationTrigger::class)] class CleverAgeUiProcessExtensionTest extends TestCase { + public function testEveryServiceDefinitionFileIsLoaded(): void + { + $container = $this->createContainer('prod'); + (new CleverAgeUiProcessExtension())->load([], $container); + + $files = glob(\dirname(__DIR__, 2).'/config/services/*.yaml'); + self::assertIsArray($files); + self::assertNotEmpty($files); + foreach ($files as $file) { + $config = Yaml::parseFile($file, Yaml::PARSE_CUSTOM_TAGS); + self::assertIsArray($config); + if (!isset($config['services'])) { + continue; + } + self::assertIsArray($config['services']); + foreach (array_keys($config['services']) as $id) { + if ('_defaults' === $id) { + continue; + } + if (str_ends_with((string) $id, '\\')) { + // Resource: at least one service registered under the namespace prefix + $registered = array_filter( + array_keys($container->getDefinitions()), + static fn (string $definitionId): bool => str_starts_with($definitionId, (string) $id) + ); + self::assertNotEmpty($registered, \sprintf('No service registered for the "%s" resource of %s', $id, basename($file))); + continue; + } + self::assertTrue($container->has((string) $id), \sprintf('Service "%s" of %s is not registered', $id, basename($file))); + } + } + } + public function testLoadWithDefaultConfiguration(): void { $container = $this->createContainer('prod');