PHPackages                             robclancy/xf-support - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. robclancy/xf-support

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

robclancy/xf-support
====================

This package is a bunch of things to remove a lot of boilerplate when working with XenForo.

3742PHP

Since Jun 30Pushed 12y ago3 watchersCompare

[ Source](https://github.com/robclancy/xf-support)[ Packagist](https://packagist.org/packages/robclancy/xf-support)[ RSS](/packages/robclancy-xf-support/feed)WikiDiscussions master Synced 2mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

XenForo Support Package
=======================

[](#xenforo-support-package)

This package is a bunch of things to remove a lot of boilerplate when working with XenForo.

Installation
------------

[](#installation)

Coming soon...

Examples
--------

[](#examples)

Note: a lot of this is still fairly boilerplate and tedious, I will have xf-toolkit generate a lot of code in the future when I get to it.

### Installers

[](#installers)

These use forks from illuminate/database to provide a schema builder.

TODO: more information and link to laravel docs

Here is an example installer...

```
class TestInstaller extends Installer {

	public function up1()
	{
		$this->schema->create('photos', function($table)
		{
			$table->increments('photo_id');

			$table->string('filename');
		});
	}

	public function down1()
	{
		$this->schema->drop('photos');
	}

	public function up2()
	{
		$this->schema->create('albums', function($table)
		{
			$table->increments('album_id');
			$table->string('name');
		});

		$this->schema->table('photos', function($table)
		{
			$table->integer('album_id')->unsigned()->after('photo_id');
		});
	}

	public function down2()
	{
		$this->schema->table('photos', function($table)
		{
			$table->dropColumn('album_id');
		});

		$this->schema->drop('albums');
	}
}
```

### Data Model

[](#data-model)

This is designed to have methods which only work with the database. Other model data, for example resizing a thumbnail should be in a traditional model as these models are designed to work with repositories which are detailed below.

```
class MyModel extends \Robbo\Support\DataModel {

	$this->_table = 'my_table';

	$this->_key = 'table_id';
}
```

And that is it to implement everything you see in `Robbo\Support\DataModelInterface`.

You still have to define your own joins, conditions and orders.

```
class MyModel extends \Robbo\Support\DataModel {

	$this->_table = 'my_table';

	$this->_key = 'table_id';

	const FETCH_MY_OTHER_TABLE = 0x01;

	public function getResourceJoinOptions(array $fetchOptions)
	{
		$selectFields = '';
		$joinTables = '';

		if ( ! empty($fetchOptions['join']))
		{
			if ($fetchOptions['join'] & self::FETCH_MY_OTHER_TABLE)
			{
				$selectFields .= ',
					other_table.*';

				$joinTables .= '
					INNER JOIN other_table ON (my_table.table_id = other_table.table_id)';
			}
		}

		return array('selectFields' => $selectFields, 'joinTables' => $joinTables);
	}

	public function prepareResourceConditions(array $conditions, array &$fetchOptions)
	{
		$db = $this->_getDb();
		$sqlConditions = array();

		if (isset($conditions['something']))
		{
			$sqlConditions[] = 'my_table.something = ' . $db->quote($conditions['something']);
		}

		return $this->getConditionsForClause($sqlConditions);
	}

	protected function prepareResourceOrderOptions(array &$fetchOptions, $defaultOrderSql = '')
	{
		return 'TODO: add order example here';
	}
}
```

### Repositories

[](#repositories)

These are essentially wrappers for data models for the purpose of allowing for easier unit testing and cleaner code. There is a concrete implementation `Robbo\Support\Repository` however you can extend or implement your own as the interface is passed around and not the concrete class.

To use the concrete implementation you need to give it a data model to use. Here is an example in a traditional controller using our above data model.

```
class MyController extends XenForo_ControllerPublic_Abstract {

	public function actionIndex()
	{
		$repository = new \Robbo\Support\Repository($this->getModelFromCache('MyModel'));

		return $this->responseView('mytemplate', array(
			'myData' => $repository->getAll()
		));
	}

	public function actionEdit()
	{
		$repository = new \Robbo\Support\Repository($this->getModelFromCache('MyModel'));
		$id = $this->_input->filterSingle('id', XenForo_Input::UINT);

		if ( ! $resource = $repository->getById($id))
		{
			return $this->responseNoPermission();
		}

		return $this->responseView('mytemplate_edit', array(
			'myData' => $resource
		));
	}

	public function actionSave()
	{
		$repository = new \Robbo\Support\Repository($this->getModelFromCache('MyModel'));
		$input = $this->_input->filter(array('one' => XenForo_Input::UINT, 'two' => XenForo_Input::STRING));
		$id = $this->_input->filterSingle('id', XenForo_Input::UINT);

		if ( ! $repository->getById($id))
		{
			return $this->responseNoPermission();
		}

		$repository->save($id, $input);

		return $this->responseRedirect('somewhere');
	}
}
```

Now there is still a fair bit of boilerplate going on there, which leads me to the next example...

### Controllers

[](#controllers)

Controllers really just have little helpers. For one instead of having to type out `XenForo_Input::UINT` you can do `self::UINT`.

Then there are helpers for creating the repositories and models for you early in the controllers lifespan. So the above controller can be simplified to the following:

```
class MyController extends XenForo_ControllerPublic_Abstract {

	protected $_dataModelName = 'MyModel';

	protected $_idName = 'id';

	public function actionIndex()
	{
		return $this->responseView('mytemplate', array(
			'myData' => $this->repository->getAll()
		));
	}

	public function actionEdit()
	{
		if ( ! $resource = $this->repository->getById($this->_id)
		{
			return $this->responseNoPermission();
		}

		return $this->responseView('mytemplate_edit', array(
			'myData' => $resource
		));
	}

	public function actionSave()
	{
		if ( ! $this->repository->getById($this->_id))
		{
			return $this->responseNoPermission();
		}

		$this->repository->save($this->_id,  $this->_input->filter(array('one' => self::UINT, 'two' => self::STRING)));

		return $this->responseRedirect('somewhere');
	}
}
```

### DataWriters

[](#datawriters)

I hate having to write out all that boilerplate for datawriters just like I had to for models. So I added a few little shortcuts to make it less tedious and even easier to read.

```
class MyWriter extends \Robbo\Support\DataWriter {

	/* Old way
	protected function _getFields()
	{
		return array(
			'merc_gallery_media' => array(
				'media_id' 		=> array('type' => self::TYPE_UINT, 	'autoIncrement' => true),
				'category_id' 	=> array('type' => self::TYPE_UINT, 	'required' => true),
				'user_id' 		=> array('type' => self::TYPE_UINT, 	'required' => true),
				'username' 		=> array('type' => self::TYPE_STRING, 	'required' => true, 'maxLength' => 50),
				'ip_id'			=> array('type' => self::TYPE_UINT,   	'default' => 0),
				'image' 		=> array('type' => self::TYPE_STRING, 	'default' => '', 	'maxLength' => 50),
				'video' 		=> array('type' => self::TYPE_STRING, 	'default' => '', 	'maxLength' => 255),
				'title' 		=> array('type' => self::TYPE_STRING, 	'required' => true, 'maxLength' => 100),
				'description' 	=> array('type' => self::TYPE_STRING, 	'default' => ''),
				'media_state'  => array('type' => self::TYPE_STRING, 	'default' => 'visible',
					'allowedValues' => array('visible', 'moderated', 'deleted')
				),
				'added_date' 	=> array('type' => self::TYPE_UINT, 	'default' => XenForo_Application::$time),
				'upload_date' 	=> array('type' => self::TYPE_UINT, 	'default' => XenForo_Application::$time),
				'view_count' 	=> array('type' => self::TYPE_UINT, 	'default' => 0),
				'likes' 		=> array('type' => self::TYPE_UINT_FORCED, 'default' => 0),
				'like_users' 	=> array('type' => self::TYPE_SERIALIZED, 'default' => 'a:0:{}'),
			)
		);
	}

	protected function _getExistingData($data)
	{
		if ( ! $id = $this->_getExistingPrimaryKey($data))
		{
			return false;
		}

		return array('merc_gallery_media' => $this->getModelFromCache('Merc_Gallery_Model_Media')->getMediaById($id));
	}

	protected function _getUpdateCondition($tableName)
	{
		return 'media_id = ' . $this->_db->quote($this->getExisting('media_id'));
	}*/

	// New way starting here...

	protected $_table = 'merc_gallery_media';

	protected $_key = 'media_id';

	protected function _setFields()
	{
		$this->_field('media_id')->uinteger()->auto();
		$this->_filed('category_id')->uinteger()->required();
		$this->_field('user_id')->uinteger()->required();
		$this->_field('username')->string(50)->required();
		$this->_field('ip_id')->uinteger()->default(0);
		$this->_field('image')->string(50);
		$this->_field('video')->string(255);

		// And so on... this is still fairly tedious so I will be looking at ways to improve it
	}

	protected function _getExistingData($data)
	{
		return $this->_genericExistingData(
			'merc_gallery_media',
			'media_id',
			$this->_createRepository($this->getModelFromCache('MyModel')),
			$data
		);
	}

	protected function _getUpdateCondition($tableName)
	{
		return $this->_genericUpdateCondition($tableName, 'media_id');
	}
}
```

### Route Prefixes

[](#route-prefixes)

I find that most my route prefixes are the same thing over and over. So I made it so I could just define a couple variables.

An old prefix...

```
class Merc_Sidebar_Route_PrefixAdmin_Blocks implements XenForo_Route_Interface
{
	public function match($routePath, Zend_Controller_Request_Http $request, XenForo_Router $router)
	{
		$action = $router->resolveActionWithIntegerParam($routePath, $request, 'block_id');
		return $router->getRouteMatch('Merc_Sidebar_ControllerAdmin_Block', $action, 'sidebars');
	}

	public function buildLink($originalPrefix, $outputPrefix, $action, $extension, $data, array &$extraParams)
	{
		return XenForo_Link::buildBasicLinkWithIntegerParam($outputPrefix, $action, $extension, $data, 'block_id', 'title');
	}
}
```

Now the same thing extending the support class instead...

```
class Merc_Sidebar_Route_PrefixAdmin_Blocks extends \Robbo\Support\RoutePrefix {

	protected $_controller = 'Merc_Sidebar_ControllerAdmin_Block';

	protected $_id = 'block_id';

	protected $_group = 'sidebars';
}
```

###  Health Score

23

—

LowBetter than 27% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity14

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/ac33556a611a70332996beca9c9eb795f85368b32dd49bec5c49bf5181c37e93?d=identicon)[Robbo](/maintainers/Robbo)

---

Top Contributors

[![robclancy-test](https://avatars.githubusercontent.com/u/123053691?v=4)](https://github.com/robclancy-test "robclancy-test (39 commits)")

### Embed Badge

![Health badge](/badges/robclancy-xf-support/health.svg)

```
[![Health](https://phpackages.com/badges/robclancy-xf-support/health.svg)](https://phpackages.com/packages/robclancy-xf-support)
```

###  Alternatives

[kim/activity

Easily retrieve a list of users and guests that are online.

12830.4k](/packages/kim-activity)[nahid/presento

Presento is a package for PHP data presenter

747.2k](/packages/nahid-presento)[kzykhys/text

Simple text manipulation library for PHP5.4

544.5k1](/packages/kzykhys-text)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
