XenPlus - the XenForo developer helper library

Robbo

Well-known member
XenPlus is an open source library that I have built to help me reduce having to duplicate code between add-ons and bring objects to things that are static. It creates classes to extend for all listeners (along with documentation advanced IDEs can pickup, copied from the Code Event Listeners section in the ACP), an installer class to help with upgrading (in early stages, might add a heap of helper methods to it or a model) and new autoloaders (experimental) you can inject for things like namespace support. There is also some Zend_Dom extensions to remove bugs and add features.

https://github.com/mercDesign/XenPlus

Bringing Objects Back to Listeners
There is an abstract class to extend (XenPlus_Listener) which will add base functionality to your listener like getModelFromCache, getDb and the fact it is used as an object and not a class (static). There is also a bunch of interfaces to implement which will define the expected signature as defined by the XenForo event listeners in the ACP. When creating a class extend XenPlus_Abstract and implement the corresponding interface (for example, XenPlus_Listener_TemplateHook). Then you will have to define the method run the same as the interface and use it for your listener. When creating the listener in the ACP, listen is the method name. So You_Listener_Name::listen. XenPlus will handle taking that and creating your instance.

Listener Helpers
On top of making your listeners OOP and giving extra functionality like getModelFromCache there are listener helpers much like controller helpers in XenForo. These are used to reduce code duplication and tedious tasks like adding a case for each class you extend. As of writing this, there are only 2 helpers. Template and LoadClass.

Template Helper
Template adds functionality so you can define methods for each hook or template you want to modify. For example, if you were to make a template hook listener you could do this:
Code:
class MyApp_Listener_TemplateHook extends XenPlus_Listener implements XenPlus_Listener_TemplateHook
{
public function run($hookName, &$contents, array $hookParams, XenForo_Template_Abstract $template)
{
	$helper = $this->getHelper('Template');
	if ($helper->getTemplateMethod($hookName))
		$contents = $helper->callTemplateMethod($hookName, $contents, 'extra', 'arguments', 'to', 'be', 'sent');
}
}
Now this hook will look for a method that is $hookName in camel case. For example when the page_container_sidebar hook is called it will look for pageContainerSidebar(...). If it finds it, it will call it. Since according to our definition we set $contents to whatever it returns we have to return the new contents. This isn't a must though, you can pass it by reference if you like. Now to modify the template we have something like this:
Code:
public function pageContainerSidebar($contents, $anotherVarYouWanted)
{
	$newContent = 'do stuff here';
	return $newContent' . $contents;
}

These helpers are designed in a way so you can use them however you like. With the above example you might want to create a template. pageContainerSidebar has no idea about the template so you can either set the template as a variable of the instance (which I recommend doing... so it would be $this->_template) or you can pass it through to the template in the callTemplateMethod helper method.

Load Class
While having to work with a lot of other developers third party add-ons I noticed a few different methods of adding classes to extend less tedious. One method a few developers used was checking for a file that is in the same spot as the XenForo one but a different library. So checking for MyApp_Model_User when XenForo_Model_User is called, if it exists then they add the class to the $extend array. Another popular method was to use an array, I was using this method in an earlier version of XenPlus however it was still tedious and really, you don't need a helper to create a class and check if a key is set to extend so we do nothing here. However we do handle the first method described with this helper. It simply adds methods to test for a class file.
This essentially makes it so you make your loadClass listener once and never have to revisit it. Here is an example:
Code:
class MercSite_Listener_LoadClass extends XenPlus_Listener implements XenPlus_Listener_LoadClass
{
	public function run($class, array &$extend)
	{
		if ($newClass = $this->getHelper('LoadClass')->getExtension($class, 'MercSite_'))
			$extend[] = $newClass;
	}
}
This will look in library/MercSite/ for any files corresponding to library/XenForo/. If it finds something it assumes there is a class in there. The third parameter is an optional $remove parameter which defaults to XenForo_. Use this to remove part of the original class. For example if you wanted to be extending MyApp instead you would put MyApp_ there. Or, if you want all your class extensions in a directory like I have seen a couple developers do, you can just empty the third parameter. With the example above it would then look in MercSite/XenForo/etc...

Installer
I have seen various types of installers, created a few different versions and created complete deployment systems for a XenForo based environment. After doing all this I have found that simple is better. Earlier versions of this installer had things like addContentType however I am now leaning towards leaving the power in the developers hands and having them add them with raw queries, much like XenForo does it with their installer. After making my own upgrade system for deploying websites powered by XenForo I found that all the little shortcuts aren't needed. However, I am not going to say I won't add them or that it is stupid or anything like that. This is my opinion and it is why the installer in the version as of writing this doesn't do everything for you. It does however make installing/upgrading manageable and clean. It does much of what the above does with adding things like getModelFromCache and getDb. It also handles one method per version (if needed) and post execution methods. And most importantly (to me at least) is an actually object orientated and not a messy static class.
The version as of writing this is designed to have one of 3 processes run. Install, upgrade or uninstall.

Install is designed to execute all installing, no matter what. If you add something to version x, you add the changes to the install section and also any upgrades needed instead of the other approach of keeping the install as is and running all upgrade scripts after install. After installing you have _postInstall() and _postInstallAfterTransaction() at your disposal.

Upgrading is done like many other installers around here by calling a methodName{VersionId}. In this case it will call upgrade{$versionId} going from the installed version to the current version. Just like install, you have 2 post upgrade methods. _postUpgrade() and _postUpgradeAfterTransaction().

Uninstalling is like install, just do everything in one go regardless of which version it is. After uninstall you have the same post uninstall methods the same as above.

Along with the possibility of adding things like addContentType I am also thinking about bringing my upgrade deployment system to add-ons. Where the initial install would be like here but also seperate development XML files like XenForo installs/upgrades (this way for multiple developers on one add-ons working with VCS). Then upgrades would be redirected to a system which would work like upgrading XenForo. Same interface going through steps, with AJAX loading the pages and importing data before rebuilding... all very much how XenForo does it.
 
I have stopped work on this and will be using a Merc/Commons library instead for my stuff as this isn't something people would use. My commons library essentially does the same thing but is more just for me and also doesn't have issues with backwards compatibility issues.
 
Top Bottom