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

Question about resolveDynamicClass()

#1
I've been going through the source so I can understand the XF architecture, in order to prep myself to start making some new add-ons.

I was looking through resolveDynamicClass() in order to understand how the autoloading and class proxy stuff works. I get it now, except for the following snippet:

Code:
                foreach ($extend AS $dynamicClass)
                {
                    // XenForo Class Proxy, in case you're wondering
                    $proxyClass = 'XFCP_' . $dynamicClass;
                    eval('class ' . $proxyClass . ' extends ' . $createClass . ' {}');
                    XenForo_Application::autoload($dynamicClass);
                    $createClass = $dynamicClass;
                }
Does this mean that if you create a bunch of classes that get loaded dynamically via listeners, then the LAST dynamic one is the one that gets returned out from this function, instead of the original?

For example, if the codebase is doing resolveDynamicClass(ControllerPublic_Thread), but I have a class called MyAddon_Controller_Thread that extends it, does that mean this routine will return my child class instead of the originally requested class?

What's the purpose? Is this to allow the system to return a dynamically created child class instead of the original base, to allow for overloads? What if there are multiple products that are attempting to overload a base class's routine, does that mean the last one loaded is the one who wins?
 

Jake Bunce

XenForo moderator
Staff member
#2
The system extends the original class using addon classes. An original class A can be extended with addon classes B and C. If C is the last extension then that is the class that will be returned. Extended classes often call on the parent so that the original code is executed in addition to your code. Here is a code example where my extended class calls on the parent function first, then it executes my code to do additional saving:

http://xenforo.com/community/thread...foro_controlleradmin_forum.35445/#post-402592

In the case of multiple extensions, all of the extended classes can call on the parent, all the way up the inheritance chain.
 
#3
The system extends the original class using addon classes. An original class A can be extended with addon classes B and C. If C is the last extension then that is the class that will be returned. Extended classes often call on the parent so that the original code is executed in addition to your code. Here is a code example where my extended class calls on the parent function first, then it executes my code to do additional saving:

http://xenforo.com/community/thread...foro_controlleradmin_forum.35445/#post-402592

In the case of multiple extensions, all of the extended classes can call on the parent, all the way up the inheritance chain.
My concern is this: if I develop an addon that extends on ControllerPublic_Thread, then it can safely overload an action. However, what happens if I later install an addon which also extends the ControllerPublic to overload an action? Then this add-on will supersede my addon, and my addon will no longer work correctly because it is not the class that gets returned from this routine.

Essentially it seems like it's a race to be last when it comes to overloading XF methods?
 

xf_phantom

Well-known member
#5
Addons should "always" call the parent method, instead of overloading the complete action without calling the parent method.
With calling the parent method, you make sure that other addons code is called.
You can change the routine via

PHP:
function actionFoo(){
$parentReturn = parent::actionFoo();
//do now your stuff;
// you can call methods, change the parent return
//and last but not least, RETURN it again
return $parentReturn;
}
 
//you can also set something before you call the parent, to use it later in the model or datawriter
function actionFoo(){
$inputFoo = $this->_input->filterSingle('foo'.....);
//you can use globals, or the xenforo_application registry, or use e.g. $_POST inside the datawriter
$_GLOBALS['foo'] = $inputFoo;
XenForo_Application::set('foo', $inputFoo);
}


It's IMO a shame, that there's no official tutorial for this:p
 
#6
That is the expectation when an addon extends a controller action. C calls B, B calls A.
I don't think I'm understanding, sorry.

Let's say I have two different addons extending from XenForo_ControllerPublic_Thread. These addons have no knowledge of each other.

With the way the resolve routine is coded, it will return the last class extended from foreach() out to the code. That means only one of the addons will be able to execute its 'unique' code. The other addon will be superseded and will never execute its code, so the addon will presumably fail. Only if the addons knew about each other could we subvert this. We would have to change it to instead of both extending XenForo_ControllerPublic_Thread, only one would extend the XF class and the other would extend the addon class, i.e. class A extends class B extends XenForo_ControllerPublic_Thread. Then each can run its code then call the parent.

But if you install two addons from the resources, presumably they have no idea they both exist and only one would work, right?
 

Jake Bunce

XenForo moderator
Staff member
#7
With the way the resolve routine is coded, it will return the last class extended from foreach() out to the code. That means only one of the addons will be able to execute its 'unique' code. The other addon will be superseded and will never execute its code, so the addon will presumably fail.
No. C extends B extends A. C is returned and instantiated, and C is based on the other two and can call on the other classes through the 'parent' like in my code example:

http://xenforo.com/community/thread...foro_controlleradmin_forum.35445/#post-402592

Notice the parent being called at the beginning of my action:

Code:
		$response = parent::actionSave();
 
#8
No. C extends B extends A. C is returned and instantiated, and C is based on the other two and can call on the other classes through the 'parent' like in my code example:

http://xenforo.com/community/thread...foro_controlleradmin_forum.35445/#post-402592

Notice the parent being called at the beginning of my action:

Code:
$response = parent::actionSave();
I'm at work so I don't have the XF code in front of me to look at. You're saying that if I code up two listeners to setup two classes to extend from XenForo_ControllerPublic_Thread, then it will do

class C extends class B extends class XenForo_ControllerPublic_Thread

return C

and not

class B extends XenForo_ControllerPublic_Thread
class C extends XenForo_ControllerPublic_Thread

return C (masking out B completely)

?

I thought that it would do the latter, which would cause the issues I am describing.
 

tyteen4a03

Well-known member
#11
Addons should "always" call the parent method, instead of overloading the complete action without calling the parent method.
With calling the parent method, you make sure that other addons code is called.
You can change the routine via

PHP:
function actionFoo(){
$parentReturn = parent::actionFoo();
//do now your stuff;
// you can call methods, change the parent return
//and last but not least, RETURN it again
return $parentReturn;
}
 
//you can also set something before you call the parent, to use it later in the model or datawriter
function actionFoo(){
$inputFoo = $this->_input->filterSingle('foo'.....);
//you can use globals, or the xenforo_application registry, or use e.g. $_POST inside the datawriter
$_GLOBALS['foo'] = $inputFoo;
XenForo_Application::set('foo', $inputFoo);
}


It's IMO a shame, that there's no official tutorial for this:p
Plus you have less work when XenForo updates, so you don't have to update the method in case the official one is updated.