-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[TwigBundle] Fix bug where namespaced paths don't take parent bundles in account #19586
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[TwigBundle] Fix bug where namespaced paths don't take parent bundles in account #19586
Conversation
7308a17
to
86c9111
Compare
We'll need a test case for this. |
b3d4a15
to
4ddc9ed
Compare
@jakzal I modified the TwigExtensionTest to include a test case for this situation. |
4ddc9ed
to
57eceb7
Compare
AppVeyor ran on an outdated commit, might have to be restarted. |
@@ -92,6 +92,13 @@ public function load(array $configs, ContainerBuilder $container) | |||
$dir = dirname($reflection->getFileName()).'/Resources/views'; | |||
if (is_dir($dir)) { | |||
$this->addTwigPath($twigFilesystemLoaderDefinition, $dir, $bundle); | |||
|
|||
$classMock = new $class(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not really a mock :) what about $object
or just $bundle
? However im not sure we should couple this here, this way. But i guess for now we dont have much choice...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll rename the variable. Don't think there are any options to retrieve the parent in this situation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you could call registerBundles
once =/ Basically new $class()
is somewhat fragile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, and this won't work with a third level (ChildChildTwigBundle). How would you recommend calling registerBundles
here? Is there any way to reach the kernel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kernel
service is out of scope, so i guess not. Maybe you can kick in at TwigBundle::createContainerExtension
, without breaking BC.
This is somewhat related to #18563 in terms of improving the ecosystem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using reflection and newInstanceWithoutConstructor()
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true, but the only ways to solve that issue completely would be to either pass bundles to extension classes or to write some logic that determines the paths during cache warmup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't a parameter returning the bundle map be envisaged?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is that you still would not have access to actual instances of the bundles (so this would not solve #19586 (comment)).
@wesleylancel Do you think you can finish this one? |
@xabbuh: I wish, as this is something that would be extremely useful for us. But, as pointed out by @ro0NL, bundles can extend infinitely A > B > C > D. My PR currently only checks for one level of extending. Ideally, I would be able to access the bundle map that is part of the kernel, but that's not available at this stage yet. If you could point me towards a possible solution I'd be happy to adjust my code accordingly. |
Seeing the related ticket this goes back to 2013. And quite some issues are involved. I've not experienced it myself, but the template problem seems real. Either way allowing kernel introspection would be extremely useful for everybody. Preferably with an immutable kernel. Practically we can move this a few lines up. Relying on a synthetic service before compilation. We also can expose bundles thru parameters, we only need stringish values here. But mayve @ogizanagi has more idea's? As i also liked his approach with a contextual builder. Maybe it can be simplified. |
Unfortunately #19646 was my only attempt to provide a naive feature for sharing some contextual information while the container is building, avoiding the need to rely on pseudo tmp synthetic services. Along with the As you suggested, maybe exposing a simple representation of the bundle map and bundles informations in a kernel parameter could be the way. But I don't remember all the ins and outs and if it was easily feasible at this point. :/ |
Agree 👍 I feel the same about my own attempts now. Eventually having a builder with context can be done in userland easily. But it doesnt solve it for SF. If there arent any other insights, i'd say go for |
Not sure it's doable. Putting
|
It's a stringish map, that's actually cached :) we're not passing bundle instances around here.
Something like that i guess. I mean.. this would lead to a nested |
Ie. looking at the issues, im not really sure we need true bundle instances here. Only metadata from bundle introspection. To build things properly. I truly dont hope we're looking for ways to do this since 2013 😆 as it would definitely require the |
This makes the template compatible with projects disabling the Templating component in recent Symfony versions.
👍 I checked this with the issues described in FriendsOfSymfony/FOSUserBundle#2376 and FriendsOfSymfony/FOSUserBundle#2378 and I can confirm that this patch fixes them. Status: Reviewed |
@xabbuh great news! Could you tell us on which branches this patch will be applied ? |
@Kmelia Once merged, this will be available in the next patch releases of each maintained branch (i.e. from 2.7 to 3.2) and the current development version (3.3). |
@@ -97,6 +97,10 @@ public function load(array $configs, ContainerBuilder $container) | |||
if (is_dir($dir = $bundle['path'].'/Resources/views')) { | |||
$this->addTwigPath($twigFilesystemLoaderDefinition, $dir, $name); | |||
} | |||
|
|||
if (null !== $bundle['parent']) { | |||
$this->prependParentPaths($twigFilesystemLoaderDefinition, $container, $name, $bundle['parent']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this handle a chain of parents ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stof: Yes, this handles that. Should be covered with the changed test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prependParentPaths
was confusing at first. Maybe keep actual path registration explicit in load()
while ($parent) {
// add parent
}
// add self
Basically we dont need any path prepending or recursion, only adding paths in the right calling order.
Maybe utilize with addBundlePaths
or so.
@@ -97,6 +97,10 @@ public function load(array $configs, ContainerBuilder $container) | |||
if (is_dir($dir = $bundle['path'].'/Resources/views')) { | |||
$this->addTwigPath($twigFilesystemLoaderDefinition, $dir, $name); | |||
} | |||
|
|||
if (null !== $bundle['parent']) { | |||
$this->prependParentPaths($twigFilesystemLoaderDefinition, $container, $name, $bundle['parent']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prepending paths looks wrong to me here, as it would overwrite user-configured paths for the same namespace too.
What you should do is collecting the child bundles for each bundle, and then adding paths for child bundles before adding the path of the bundle itself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stof: Did not now about that. That'll require some change to adding the normal paths too, I'll see how to best fix this. Is there an easy way to cover this in the test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a path in the user-configured paths for the same namespace, and ensure that it ends up as the first path in the loader config for this namespace
@chalasr thanks! |
array(__DIR__.'/Fixtures/Bundle/ChildTwigBundle/Resources/views', 'Twig'), | ||
array(__DIR__.'/Fixtures/Bundle/ChildChildTwigBundle/Resources/views', 'ChildTwig'), | ||
array(__DIR__.'/Fixtures/Bundle/ChildChildTwigBundle/Resources/views', 'Twig'), | ||
), $prependPaths); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way these tests are written does not ensure that paths are added in the right order for each namespace. there is nothing guaranteeing that the combinations of appended paths and prepended paths is actually giving us the right priority.
What we should do to have reliable tests is rebuilding the internal structure of Twig_Loader_Filesystem::$path
(i.e. the result of the configuration) and ensuring that it looks good for each namespace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stof: I think whatever gets prepended last will have the most priority. If the array above is in this order, the correct path will end up on top. However, this doesn't keep those user-configured paths in mind. I'm not sure where these get added to the loader, but if they get prepended after the bundle paths get prepended, it should work all right? Not sure if that's testable in the existing test though.
If the Twig internals of that loader do need to be changed, I don't think this can be merged before that is resolved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stof: What if we move these lines https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php#L84-L90 to here https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php#L109 and change the method call to prependPath
instead of addPath
? This should make sure that user-defined paths from config take precedence over anything else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the issue is that the way the test is written does not allow to quickly validate what the final order will be when looking at it. This is why I think the test should not be written this way. We don't want to assert 2 separate lists, as we care about a single list (the one being used at runtime).
thus, the test is even confusing when looking at it, because the list of prependPath
here is in reverse order compared to the order of paths in the loader (as prepending adds at the beginning while you append in $prependPaths
), making it even harder to validate/understand the assertion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stof I'll see if I can collect all the paths before actually adding them to the loader so they can all be tested together in order.
@stof I've added a separate commit so it's easier to see what I've changed (can be squashed later). A hierarchy of bundles is now built first, after that the paths are added to the loader. I'm not sure if I could simplify the code, especially the building of the hierarchy. I could improve the test by asserting the array per namespace if that helps for readability? |
I think an easier implementation could be to have two loops. The first would collect path information for bundles we override without actually registering them. The second would then first register the additional paths from child bundles followed by paths the bundle provides itself. |
@xabbuh: Isn't that what I'm doing now? First the hierarchy is built and then the paths are registered. |
); | ||
} | ||
|
||
if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/'.$name.'/views')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to maintainers: when merging to newer branches, we need to take care of the FileExistenceResource objects here (git conflicts should remind us about this btw)
👍 |
1 similar comment
👍 |
Thank you @wesleylancel. |
…ent bundles in account (wesleylancel) This PR was squashed before being merged into the 2.7 branch (closes #19586). Discussion ---------- [TwigBundle] Fix bug where namespaced paths don't take parent bundles in account | Q | A | | --- | --- | | Branch? | 2.7 | | Bug fix? | yes | | New feature? | no | | BC breaks? | no | | Deprecations? | no | | Tests pass? | yes | | Fixed tickets | #6919 | | License | MIT | | Doc PR | | Currently namespaced paths for templates such as `{% extends '@App/Layout/layout.html.twig' %}` do not work with bundles that have overruled templates using the `getParent()` method in another bundle. See attached ticket. This change prepends the path of the bundle implementing `getParent()` to the paths of the namespace of bundle returned as a parent. Commits ------- 0c77ce2 [TwigBundle] Fix bug where namespaced paths don't take parent bundles in account
@fabpot Do you have any hint about the new release (containing this fix) date? |
@homersimpsons today :) see #21258 |
Currently namespaced paths for templates such as
{% extends '@App/Layout/layout.html.twig' %}
do not work with bundles that have overruled templates using thegetParent()
method in another bundle. See attached ticket. This change prepends the path of the bundle implementinggetParent()
to the paths of the namespace of bundle returned as a parent.