1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Odd Listener Execution Order Behavior

Discussion in 'XenForo Development Discussions' started by Snog, Aug 18, 2016.

  1. Snog

    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?
  2. Jake B.

    Jake B. Well-Known Member

    Is one of them using load_class and the other using load_class_controller? Maybe that would have something to do with it
  3. Chris D

    Chris D XenForo Developer Staff Member

    Also if one of the listeners is using an event hint and one of them isn't.

    Listeners with hints are processed first.
  4. Snog

    Snog Well-Known Member


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

    Both are using an event hint.
  5. Snog

    Snog Well-Known Member

    Hmm, switching add-on 1 to load_class_controller still has the same result.
  6. Jake B.

    Jake B. Well-Known Member

    Can probably do some debugging on the load order in the
    foreach ($extend AS $dynamicClass) 
    loop inside XenForo_Application::resolveDynamicClass
  7. Snog

    Snog Well-Known Member

    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';

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

    And they never execute their code below that.

    Always dies with add-on 2.
  8. Snog

    Snog Well-Known Member

    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: Aug 18, 2016
  9. Snog

    Snog Well-Known Member

    @Chris D or @Mike am I losing my mind or do I just not understand something here?
  10. Snog

    Snog Well-Known Member

    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:

    Attached Files:

  11. Snog

    Snog Well-Known Member

    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..

    I do believe we may have a bug.
  12. Chris D

    Chris D XenForo Developer Staff Member

    Yeah with class extensions you sort of need to think about it in the opposite order.

    This may help to explain it:

    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 :)
    Snog likes this.
  13. Snog

    Snog Well-Known Member

    Yes it does!

    Thank you very much for the explanation. I thought I was losing my mind. :)
    Chris D likes this.
  14. Chris D

    Chris D XenForo Developer Staff Member

    Well, I never said you weren't ;)
    Amaury and Snog like this.
  15. Snog

    Snog Well-Known Member

    OK, well at least I recovered part of it now. :D
    Chris D likes this.
  16. Snog

    Snog Well-Known Member

    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.
  17. Chris D

    Chris D XenForo Developer Staff Member

    Yeah, basically.
    Snog likes this.
  18. Xon

    Xon Well-Known Member

    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
    $n = get_class($this);
    echo "$n\n";
    while($n = get_class_parent($n))
    echo "$n\n";
    Very handy for debugging :p

    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: Aug 19, 2016
  19. Snog

    Snog Well-Known Member

    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.

Share This Page