Skip to content

#276 - TransformerRegistry: lazy-load transformers through a service locator indexed by code - #293

Merged
njoubert-cleverage merged 3 commits into
mainfrom
276
Oct 14, 2026
Merged

njoubert-cleverage merged 3 commits into
mainfrom
276

Conversation

@njoubert-cleverage

@njoubert-cleverage njoubert-cleverage commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Description

Fixes #276: until now, building TransformerRegistry instantiated every transformer (one addTransformer() call per cleverage.transformer service, added by RegistryCompilerPass), and as TransformerTask and most transformers depend on the registry, running any process built all the transformers of the bundle, the bridges, the application and every generic transformer. They are now fetched lazily from a service locator indexed by code.

Changes

  • Code of a transformer at compile time: new code attribute of the cleverage.transformer tag, and new #[CleverAge\ProcessBundle\Attribute\AsTransformer(code: '...')] attribute (registered with registerAttributeForAutoconfiguration(), adds the tag with its code). TransformerInterface::getCode() is unchanged (a static method would have been a BC break).
  • All the bundle transformers (37, config/services/transformer.yaml) and the generic transformers (CleverAgeProcessExtension) are tagged with their code (the bundle codes were read from getCode(); a test checks that every tag code is the one returned by getCode()).
  • New internal TransformerLocatorCompilerPass (replaces RegistryCompilerPass for the bundle): the tagged services with a code go in a ServiceLocator (ServiceLocatorTagPass::register()), given to the registry. Duplicate codes now fail the container compilation (The transformer code "x" is used by the "a" and "b" services.) instead of the registry construction; an empty / non-string code too. A service with both a tag without code (autoconfiguration on TransformerInterface, DependencyInjection: add FQCN aliases and autoconfiguration for TaskInterface and TransformerInterface #272) and a tag with code (#[AsTransformer]) is registered by its code only.
  • TransformerRegistry: new optional constructor argument ?ServiceProviderInterface $locator. getTransformer() / hasTransformer() use the locator (fetched transformers memoized), check that the service is a TransformerInterface and that the locator code is the one returned by getCode() (explicit exception otherwise); getTransformers() still returns every transformer (it instantiates the locator ones; not used by the bundles).
  • Deprecations (since 5.4), removal in v6.0 (TransformerRegistry: require the code tag attribute and remove eager transformer registration (v6 step of #276) #280, v6.0: remove the deprecated code #292):
    • a transformer tagged without code is still registered eagerly with addTransformer(), which is deprecated: Registering the transformer "x" (Class) with "…\TransformerRegistry::addTransformer()" is deprecated, tag its service with a "code" attribute ({ name: cleverage.transformer, code: x }) or use the #[AsTransformer] attribute instead, so that it is only instantiated when used. It will be required in v6.0.;
    • RegistryCompilerPass (no longer used by the bundle);
    • TransformerInterface::getCode(): the code now comes from the tag / attribute, getCode() only duplicates it; it will be removed from the interface in v6.0 (TransformerRegistry: require the code tag attribute and remove eager transformer registration (v6 step of #276) #280). @deprecated PHPDoc only: neither trigger_deprecation() (interface method) nor Symfony's DebugClassLoader (it does not report the methods of an interface marked @deprecated) can give a runtime notice, it is visible to static analysis and IDEs. Until v6.0 transformers keep implementing it, returning the code of their tag (checked by the registry). In the bundle it is only still called by the deprecated addTransformer(), the registry check, and UnavailableServicesCompilerPass for a definition tagged without code (it now reads the code of the tag).
  • Tests: the tests building a registry use a ServiceLocator instead of the deprecated addTransformer().
  • Docs: quick start, custom transformers (#[AsTransformer], lazy loading, code check, deprecation), common setup (an _instanceof rule cannot give a code per transformer), generic transformer reference.
  • CHANGELOG (Latest).

Bridge bundles: cleverage/soap-process-bundle has a transformer (RequestTransformer, soap_request) tagged without code: it keeps working, with the deprecation, until it gets its code: cleverage/soap-process-bundle#45.

Checks

  • Minimal compiled kernel (FrameworkBundle + MonologBundle + this bundle, application transformers autoconfigured): after the boot only the code-less application transformer is instantiated (deprecated path, with the deprecation); trim is instantiated when a process uses it, trim#1 works; a generic transformer (trim + an application #[AsTransformer] one) works and instantiates its sub-transformers on use; with the serializer disabled, normalize gives the explicit message of CheckSerializerCompilerPass: do not fail the container compilation when the serializer is disabled #278. getTransformers(): 40 transformers.
  • Demo project (main + this change copied in vendor/, then reverted): lint:container OK, demo.transformer, demo.transformer_list, demo.transformers_edge.cached, demo.mapping_array_target_path, demo.file.csv_reader executed (demo.transformers_edge.slugify fails on purpose, invalid transliterator, also without this change).
  • Tests: UnavailableServicesCompilerPassTest (code of the tag, reflection fallback without code), TransformerRegistryTest (lazy instantiation, memoization, missing transformer, code mismatch, not a transformer, getTransformers(), addTransformer() deprecation, duplicate with the locator), TransformerLocatorCompilerPassTest (locator by code, tag with and without code, eager without code, duplicate / invalid code, no registry), CleverAgeProcessExtensionTest (every tagged transformer has the code returned by getCode(), generic transformers, #[AsTransformer]), RegistryCompilerPassTest (deprecation).
  • After composer update (PHP 8.5): 944 tests OK, PHPStan, PHP-CS-Fixer, Rector OK. Lowest dependencies (PHP 8.2): 943 tests OK.

Requirements

  • Documentation updates
    • Reference
    • Cookbooks
    • Changelog
  • Unit tests

Breaking changes

None: transformers without code keep working (deprecated). Side effects (minor):

  • a transformer is no longer built when it is not used (a constructor with side effects is not called anymore);
  • duplicate transformer codes between tagged services with a code fail at compile time instead of at runtime;
  • TransformerRegistry has a new optional constructor argument.

🤖 Generated with Claude Code

…locator indexed by code

- New "code" attribute of the cleverage.transformer tag, and new
  #[AsTransformer(code: '...')] attribute (autoconfiguration) adding it.
- New internal TransformerLocatorCompilerPass (replaces RegistryCompilerPass,
  deprecated): the transformers with a code go in a service locator given to
  TransformerRegistry (new optional constructor argument), and are only
  instantiated when used; duplicate / invalid codes fail the compilation.
  Without code, the transformer is still added eagerly with addTransformer(),
  deprecated since 5.4 (code required in v6.0, #280).
- TransformerRegistry checks that the code of the locator is the one returned
  by getCode(), memoizes the fetched transformers; getTransformers() still
  returns every transformer.
- The 37 bundle transformers and the generic transformers are tagged with
  their code.
- Tests no longer use the deprecated addTransformer(); documentation updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
njoubert-cleverage and others added 2 commits October 14, 2026 18:41
- TransformerInterface::getCode(): @deprecated since 5.4 (the code is given by
  the "code" attribute of the cleverage.transformer tag or #[AsTransformer]),
  removed from the interface in v6.0 (#280). PHPDoc only: no runtime notice is
  possible for an interface method (nor reported by DebugClassLoader).
- UnavailableServicesCompilerPass reads the code of the replaced transformer
  from its tag, getCode() (by reflection) only for a definition tagged without
  code.
- Documentation: custom transformers (getCode() deprecation, paragraph split).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The CI runs PHPUnit with code coverage (beStrictAboutCoverageMetadata):
- testAsTransformerAttributeRegistersTheCode: declare AsTransformer as used;
- the check of the transformer codes runs getCode() of every transformer of
  the bundle: moved to a #[CoversNothing] TransformerCodesTest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TransformerRegistry: lazy-load transformers through a service locator indexed by code

1 participant