Odd Listener Execution Order Behavior

Snog

Well-known member
This one seems to be very odd to me.

Two add-ons have listeners for ControllerPublic/Forum. Both add-ons extend actionAddThread().

Add-on 1 changes a parameter passed to actionAddThread.
Add-on 2 pretty much replaces actionAddThread.

The execution order for add-on 1's listener is set to 1.
The execution order for add-on 2's listener is set to 10.

When actionAddThread is called, the listener for add-on 1 does not execute at all and XF goes directly to add-on 2's listener.

Is this a bug, limitation or something I'm totally missing when I look at them?
 
Is one of them using load_class and the other using load_class_controller? Maybe that would have something to do with it
 
Also if one of the listeners is using an event hint and one of them isn't.

Listeners with hints are processed first.
 
Is one of them using load_class and the other using load_class_controller? Maybe that would have something to do with it
Bingo!

Add-on 1 is using load_class, add-on 2 is using load_class_controller

Both are using an event hint.
 
Hmm, switching add-on 1 to load_class_controller still has the same result.

Can probably do some debugging on the load order in the
Code:
foreach ($extend AS $dynamicClass)
loop inside XenForo_Application::resolveDynamicClass
 
I don't think I need to go that far.

I added this to add-on 1's extended class as the first 2 lines:
echo 'IN class 1';
die();

And this to add-on 2:
echo 'IN class 2';
die();

And they never execute their code below that.

Always dies with add-on 2.
 
I've tried this with several different add-ons. Only the last extended class in execution order is being executed.

If I swap execution order, the same result. The last one is the one that executes.

I would expect it to halt on the first extension in execution order.

Maybe I don't understand how the execution takes place?
 
Last edited:
If another developer wants to play around with this, the attached file contains 2 add-ons that do nothing except echo their ID when you try to post a thread.

Upload all of the files to your development server, install both XMLs. Then try to post a thread with developer tools open to the console window in your browser.

Oddly enough, if I let the second add-on in execution order continue through with return parent::actionAddThread, the first add-on in execution order runs. So they seem to be reversed. I'm so confused. :confused:
 

Attachments

For a second there I thought I was going to answer my own question with 'Execution order = priority where a higher number means higher priority', but that's not the case..

Lower execution orders will run first. Note that listeners that specify an event hint will always run after listeners that don't.

I do believe we may have a bug.
 
Yeah with class extensions you sort of need to think about it in the opposite order.

This may help to explain it:
https://xenforo.com/community/resources/understanding-the-xenforo-class-proxy-system.2080

The listeners do run in the order you specify (lowest first), but given this example where each of these is extending XenForo_ControllerPublic_Thread:
  1. First listener, $extend[] = 'Class1';
  2. Second listener, $extend[] = 'Class2';
  3. Third listener, $extend[] = 'Class3';
As these classes get resolved, effectively, Class3 ends up at the top of the pile. So an over-simplified way of looking at it is that Class3 extends Class2 which extends Class1 which extends XenForo_ControllerPublic_Thread.

If the execution orders were all flipped so:
  1. Class3
  2. Class2
  3. Class1
Then (again, over-simplified) Class1 extends Class2 which extends Class3 which extends XenForo_ControllerPublic_Thread.

I say over-simplified just because of the fake proxy classes.

In reality, in the most recent example it's Class1 extends XFCP_Class1 which extends Class2 which extends XFCP_Class2 etc.

Hope that makes more sense now :)
 
One more thing...

Would it be fair to say that load_class should only be used when there's not a listener for the class being extended?

As in not in this list:
  • load_class_bb_code
  • load_class_controller
  • load_class_datawriter
  • load_class_importer
  • load_class_mail
  • load_class_model
  • load_class_route_prefix
  • load_class_search_data
  • load_class_view
  • load_class_widget_renderer
That's the way I've always done it.
 
I tended to just use load_class, but I can see why using the load_class_xxx would be better for compatibility. Especially as one of my add-ons was doing something funky with it's priorities.

If you are getting confusing by the class hierarchy ordering ; stick this code somewhere in the execution path
Code:
$n = get_class($this);
echo "$n\n";
while($n = get_class_parent($n))
{
echo "$n\n";
}
Very handy for debugging :p

Would it be fair to say that load_class should only be used when there's not a listener for the class being extended?
If you are replacing XenForo code (ie not calling parent), you want to use a low priority value(< 10) using load_class so your code runs after most other plugins. Otherwise a higher priority.

load_class_xxx is useful if you know someone is replacing code and just uses the stock priority or some higher one than what you use.
 
Last edited:
I almost always use load_class_xxx because in most cases the possibility of the parent being called exists. There are exceptions, but they are few and far between.
 
Top Bottom