Skip to content

Conversation

@janopae
Copy link

@janopae janopae commented Nov 28, 2025

Q A
Bug fix? yes
New feature? no
Deprecations? no
Documentation? no
Issues Fix #3195
License MIT

When loading Twig Components, code needs a way to identify the component. You can use the identifiers that ComponentFactory internally uses (this will usually be an identifier consisting of the namespaces separated by :). But as the docs still state, past versions of ux-twig-component could also load them by the fully-qualified PHP class name of the component class.

da0537d broke this: From this commit on, ComponentFactory, which is responsible for resolving the identifier the user provided to a Twig template and a class, would only attempt to resolve an FQCN if it couldn't provide metadata for loading the component anonymously (which means without a class, just the Twig template). Because loading the component anonymously always works as long as there is a Twig template, In case the FQCN could also be resolved as a Twig template, ComponentFactory would not resolve FQCNs to a class component, and instead load only the corresponding Twig template as an anonymous component.

The issue affects all versions from 2.20.0 to 2.31.0.

This PR fixes this by trying to resolve the FQCN right at the beginning, makig sure the first attempt to load the component with the class already happens using the right identifier.

This fixes the issue that when relying on the classMap, even components with classes would be loaded as anonymous components, because loading an anonymous template was tried before even checking the classMap.

This fixes the issue by resolving the class map right at the beginning, if possible.

This also allows us to reuse the code that loads the metadata: As we make sure `$name` holds the real config key right at the beginning, we don't have to care anymore about whether the caller provided the internal config key or a class name. The special case for class names can go.

As a side effect, we get a much better error message when attempting to load using an FQCN, because of the code reuse.
@carsonbot carsonbot added Bug Bug Fix TwigComponent Status: Needs Review Needs to be reviewed labels Nov 28, 2025
@janopae
Copy link
Author

janopae commented Nov 28, 2025

I couldn't find a PHP-CS-Fixer config, so I just tried to match the coding style I saw in the rest of the code the best I could

@Kocal Kocal added Status: Waiting feedback Needs feedback from the author and removed Status: Needs Review Needs to be reviewed labels Dec 1, 2025
@smnandre
Copy link
Member

smnandre commented Dec 1, 2025

past versions of ux-twig-component could also load them by the fully-qualified PHP class name of the component

I have no memory of that ... where do we wrote this ?

@janopae
Copy link
Author

janopae commented Dec 1, 2025

I have no memory of that ... where do we wrote this ?

The docs mention it for test code, and this is where the bug occured to me – it broke my tests. https://symfony.com/bundles/ux-twig-component/current/index.html#test-helpers

It seems like ComponentFactory has a special classMap property which gets populated by TwigComponentPass for this sole purpose.

If we come to the conclusion that support for FQCNs for TwigComponents should be dropped, we should remove the population of that property as well as everything in ComponentFactory::metadataFor below

if ($mappedName = $this->classMap[$name] ?? null) {

Copy link
Contributor

@WebMamba WebMamba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to dig more into this I don't understand how this could work. Looks something is broken somewhere

Comment on lines -62 to -68
if ($mappedName = $this->classMap[$name] ?? null) {
if ($config = $this->config[$mappedName] ?? null) {
return new ComponentMetadata($config);
}

throw new \InvalidArgumentException(\sprintf('Unknown component "%s".', $name));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand if you remove all of this this method only search for anonymous component then? It's mean something is wrong with the current architecture and we don't need to separate the finding of the template of the anonymous component and the normal component

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course we do! One is compilable once, the other is/may not be :)

Copy link
Author

@janopae janopae Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand if you remove all of this this method only search for anonymous component then?

No! The first thing it does is trying to create a "normal" component. (see https://github.com/symfony/ux/pull/3196/files#r2591624694) Only if this doesn't work, it tries to create an anonymous component.

Opposed to how it worked before:

First, it tried to create a "normal" component. If that didn't work using the identifier, it tried to load an anonymous component. And only if both of those didn't work, it had the same code for loading a "normal" component again, just this time with the resolved class map.

Now we resolve the class map from the beginning on, so we don't need to repeat the code just to do the same thing again with the name from the resolved class map.

Comment on lines +97 to +100
/**
* @testWith ["bar"]
* ["Foo\\Bar"]
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can remove the comment here

Copy link
Author

@janopae janopae Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand. Should I replace @testWith with an attribute or a dataprovider?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment must be kept for PHPUnit 9 compatibility, but #[TestWith(...)] must be added for PHPUnit 10+ (UX 3.x)

return new ComponentMetadata($this->config[$name]);
}

if ($mappedName = $this->classMap[$name] ?? null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

classMap is usefull

Copy link
Author

@janopae janopae Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why this PR proposes to evaluate it before, not after trying to load the component anonymously

@smnandre
Copy link
Member

smnandre commented Dec 3, 2025

-- Discussion ongoing in issue #3195 --

Comment on lines 51 to 53
if ($config = $this->config[$name] ?? null) {
return new ComponentMetadata($config);
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the place where "normal" (non-anonymous) components get created. If we resolve the classMap first and save it in $name, there's no need for the duplication I removed.

@smnandre
Copy link
Member

smnandre commented Dec 5, 2025

Let's close here, original issue fixed by a configuration update.

@smnandre smnandre closed this Dec 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Bug Fix Status: Waiting feedback Needs feedback from the author TwigComponent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TwigComponents] Using FQCN to load component leads to component being loaded anonymously

5 participants