-
-
Notifications
You must be signed in to change notification settings - Fork 57
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
[RTM] Allow to register hooks listeners as tagged services #1094
[RTM] Allow to register hooks listeners as tagged services #1094
Conversation
|
I very much like this idea! Even thought we agreed not to "improve" the "old" stuff, this will be helpful a lot. However, I would suggest two changes:
|
+1
Since it is part of initializing the Contao 3 framework, it would be ok for me (not ideal of course). |
I thought about it, but wasn't sure how to handle the legacy hooks. To confirm if I got it right: Given are registered hooks in <?php
$GLOBALS['TL_HOOKS']['parseTemplate'] = [
['Foo', 'onParseTemplate'],
['Bar', 'onParseTemplate'],
['Baz', 'onParseTemplate'],
];So your suggestion is, that the What I dislike here that you never know how many hooks are registered. Symfony standard doesn't depend on the number of listeners as well. I suggest handle all legacy hooks as priority |
|
Cool, I like the idea, well done @dmolineus! |
|
I would agree with the priority 0, but I see issues here. The legacy order is important, because extensions are loaded after each other and might depend on it. So we need to make sure the legacy order stays the same. The most simple solution will be to |
|
Regarding initialization in the framework class, let's just make sure we agree on this once and for all, because @Toflar used the hook in the new module as controller pull request to merge legacy with new stuff. Everything should go into the framework class then, and that'll make it huge… |
I would solve it this way:
Another solution could be to to load the legacy hooks and hooks with priority null for each bundle at the same time. So the order would be:
|
Maybe adding an event which is triggered before the initialization hook would allow to handle such tasks as the module as controller feature or the hook listener registration. |
Agree on what? |
He means "agree on not putting everything into the framework class and use listeners/hooks instead". Although this is a special case IMO, because of
|
|
I would still prefer to call an explicit event listener manually instead of having everything in the framework class… Regarding the priority 0, I'm not sure if the order of hooks inside one priority is kept when sorting them by priority? |
|
I reworked the hook registration using the priority attribute. I've chosen this strategy. Priority > 0 will be added before the legacy hooks depending on the priority Hooks with same priority will keep their internal order. |
ed0a7e5
to
3dadd44
Compare
| foreach ($tags as $attributes) { | ||
| $this->guardRequiredAttributesExist($serviceId, $attributes); | ||
|
|
||
| $priority = $attributes['priority'] ?? 0; |
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.
You should probably int-cast this for safety reasons.
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.
Done in ec49f63.
|
|
||
| if (count($hooks) > 0) { | ||
| // Apply priority sorting. | ||
| krsort($hooks); |
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.
are you sure this works? Because it sorts on the key of the hook name, not the priority? Also, you might want to use the \SplPriorityQueue which is exactly meant for this.
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.
Actually I was because I craeted unit tests for it. But I had a closer look. It didn't work. assertEquals does not detect order of keys which I thought it would (and which I was used to do by PHPSpec which I actually prefer).
Fixed it in 802350f. Just used simple arrays as Symfony does the same in the event dispatcher.
| // Apply priority sorting. | ||
| krsort($hooks); | ||
|
|
||
| $container->setParameter('contao.hook_listeners', $hooks); |
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 hooks should not be registered as a parameter that is always available, but we should use dependency injection for it. That means something like $container->getDefinition('contao.framework')->setArgument(6, $hooks).
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.
Implemented in 728799e.
src/Framework/ContaoFramework.php
Outdated
| foreach ($config as $hookName => $priorities) { | ||
| $hooks = []; | ||
|
|
||
| foreach ($priorities as $priority => $listeners) { |
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 think this can be simplified to something like this:
if (isset($GLOBALS['TL_HOOKS'][$hookName])) {
if (isset($priorities[0]) {
$priorities[0] = array_merge($GLOBALS['TL_HOOKS'][$hookName], $priorities[0]);
} else {
$priorities[0] = $GLOBALS['TL_HOOKS'][$hookName];
}
}
$GLOBALS['TL_HOOKS'][$hookName] = array_merge_recursive($priorities);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.
No, because array_merge_recursive would merge like this:
$arrayA = ['foo','bar'];
$arrayB = ['baz'];
// would create [['foo', 'baz'], 'bar]
$merged = array_merge_recursive($arrayA, $arrayB);|
I would have a use case just now again :) Looking forward to this! |
|
I just looked into this again. Came up with the idea whether it might be clever to actually use a Symfony EventDispatcher for hooks? Meaning that for every hook service in the container, we register a closure that calls the eventDispatcher with a GenericEvent ? This way we could actually migrate from our hooks to real events. The only disadvantage would be that we can't have priorities mixed with the existing ones. Legacy hooks always come before or after the event dispatcher. |
What would be the benefit of using a GenericEvent instead of named events for it as prepared in #204? (Ingoring the initial work which has to be done to create it). |
That's a no go because that's exactly what I would use this PR for. |
The difference to me is that you don't need to add event dispatcher code everywhere that hooks are already executed. But I agree with @Toflar that priority settings is somewhat more important. |
src/Framework/ContaoFramework.php
Outdated
| */ | ||
| private function buildHookGlobals(): void | ||
| { | ||
| foreach ($this->hookListeners as $hookName => $priorities) { |
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 thought I already mentioned that somewhere, but can't find it. These lines can be much simplified like this:
foreach ($this->hookListeners as $hookName => $priorities) {
if (isset($GLOBALS['TL_HOOKS'][$hookName]) && is_array($GLOBALS['TL_HOOKS'][$hookName])) {
if (isset($priorities[0]) {
$priorities[0] = array_merge($GLOBALS['TL_HOOKS'][$hookName], $priorities[0]);
} else {
$priorities[0] = $GLOBALS['TL_HOOKS'][$hookName];
}
}
$GLOBALS['TL_HOOKS'][$hookName] = array_merge_recursive($priorities);
}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.
You did, but it's still not true. array_merge_recursive with one argument will do nothing. Or do you mean array_merge_recursive(...$priorities)?
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.
Sorry, the correct call would be call_user_func_array('array_merge', $priorities)
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.
Okay I added your suggest change in 863f813. Think it's not really simplier, but I don't mind.
| use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
|
|
||
| /** | ||
| * Find hook services and store them in an parameter. |
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.
typo
… initialized before.
…y > 0 were added.
… necessary to pass the assertion.
863f813
to
9f549f5
Compare
|
So cool, thank you @dmolineus ! |
|
❤️ |
Description ----------- - Commits ------- 09204b73 Upgrade to PHPStan 0.12
Since #376 it's possible to use services for hooks. This is a great step to using services within Contao.
This PR completes this step by allowing to register hooks as services without using the
config.php. You don't have to manage your hook registration at two places anymore.I'm aware of the above mentioned PR discussed it before and this feature wasn't added because of lack of control the position of the hook.
To gain more control of the position it's possible to set the
beforeattribute so that the hook is registered before the legacy hooks. If you really need more control, well, just use the globals.To keep backward compatibility the registered hooks are added to the
$GLOBALS['TL_HOOKS']array.