{"id":21458772,"url":"https://github.com/robclancy/xf-support","last_synced_at":"2025-10-30T22:16:46.226Z","repository":{"id":7504531,"uuid":"8854429","full_name":"robclancy/xf-support","owner":"robclancy","description":"This package is a bunch of things to remove a lot of boilerplate when working with XenForo","archived":false,"fork":false,"pushed_at":"2013-06-30T12:58:47.000Z","size":263,"stargazers_count":3,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-07T20:26:37.793Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/robclancy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-03-18T13:13:21.000Z","updated_at":"2014-10-05T20:24:43.000Z","dependencies_parsed_at":"2022-09-05T03:00:54.896Z","dependency_job_id":null,"html_url":"https://github.com/robclancy/xf-support","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/robclancy/xf-support","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robclancy%2Fxf-support","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robclancy%2Fxf-support/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robclancy%2Fxf-support/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robclancy%2Fxf-support/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/robclancy","download_url":"https://codeload.github.com/robclancy/xf-support/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robclancy%2Fxf-support/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265271714,"owners_count":23738305,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-23T06:24:08.576Z","updated_at":"2025-10-12T22:13:13.324Z","avatar_url":"https://github.com/robclancy.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"XenForo Support Package\n==========\n\nThis package is a bunch of things to remove a lot of boilerplate when working with XenForo.\n\n## Installation\nComing soon...\n\n## Examples\n\nNote: 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.\n\n### Installers\n\nThese use forks from illuminate/database to provide a schema builder. \n\nTODO: more information and link to laravel docs\n\nHere is an example installer...\n```php\nclass TestInstaller extends Installer {\n\n\tpublic function up1()\n\t{\n\t\t$this-\u003eschema-\u003ecreate('photos', function($table)\n\t\t{\n\t\t\t$table-\u003eincrements('photo_id');\n\n\t\t\t$table-\u003estring('filename');\n\t\t});\n\t}\n\n\tpublic function down1()\n\t{\n\t\t$this-\u003eschema-\u003edrop('photos');\n\t}\n\n\tpublic function up2()\n\t{\n\t\t$this-\u003eschema-\u003ecreate('albums', function($table)\n\t\t{\n\t\t\t$table-\u003eincrements('album_id');\n\t\t\t$table-\u003estring('name');\n\t\t});\n\n\t\t$this-\u003eschema-\u003etable('photos', function($table)\n\t\t{\n\t\t\t$table-\u003einteger('album_id')-\u003eunsigned()-\u003eafter('photo_id');\n\t\t});\n\t}\n\n\tpublic function down2()\n\t{\n\t\t$this-\u003eschema-\u003etable('photos', function($table)\n\t\t{\n\t\t\t$table-\u003edropColumn('album_id');\n\t\t});\n\n\t\t$this-\u003eschema-\u003edrop('albums');\n\t}\n}\n```\n\n### Data Model\n\nThis 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.\n\n```php\nclass MyModel extends \\Robbo\\Support\\DataModel {\n\n\t$this-\u003e_table = 'my_table';\n\n\t$this-\u003e_key = 'table_id';\n}\n```\n\nAnd that is it to implement everything you see in `Robbo\\Support\\DataModelInterface`. \n\nYou still have to define your own joins, conditions and orders.\n\n```php\nclass MyModel extends \\Robbo\\Support\\DataModel {\n\n\t$this-\u003e_table = 'my_table';\n\n\t$this-\u003e_key = 'table_id';\n\n\tconst FETCH_MY_OTHER_TABLE = 0x01;\n\n\tpublic function getResourceJoinOptions(array $fetchOptions)\n\t{\t\n\t\t$selectFields = '';\n\t\t$joinTables = '';\n\n\t\tif ( ! empty($fetchOptions['join']))\n\t\t{\n\t\t\tif ($fetchOptions['join'] \u0026 self::FETCH_MY_OTHER_TABLE)\n\t\t\t{\n\t\t\t\t$selectFields .= ',\n\t\t\t\t\tother_table.*';\n\n\t\t\t\t$joinTables .= '\n\t\t\t\t\tINNER JOIN other_table ON (my_table.table_id = other_table.table_id)';\n\t\t\t}\n\t\t}\n\n\t\treturn array('selectFields' =\u003e $selectFields, 'joinTables' =\u003e $joinTables);\n\t}\n\n\tpublic function prepareResourceConditions(array $conditions, array \u0026$fetchOptions)\n\t{\n\t\t$db = $this-\u003e_getDb();\n\t\t$sqlConditions = array();\n\n\t\tif (isset($conditions['something']))\n\t\t{\n\t\t\t$sqlConditions[] = 'my_table.something = ' . $db-\u003equote($conditions['something']);\n\t\t}\n\n\t\treturn $this-\u003egetConditionsForClause($sqlConditions);\n\t}\n\n\tprotected function prepareResourceOrderOptions(array \u0026$fetchOptions, $defaultOrderSql = '')\n\t{\n\t\treturn 'TODO: add order example here';\n\t}\n}\n```\n\n### Repositories\n\nThese are essentially wrappers for data models for the purpose of allowing for easier unit testing and cleaner code.\nThere 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.\n\nTo 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.\n\n```php\nclass MyController extends XenForo_ControllerPublic_Abstract {\n\t\n\tpublic function actionIndex()\n\t{\n\t\t$repository = new \\Robbo\\Support\\Repository($this-\u003egetModelFromCache('MyModel'));\n\n\t\treturn $this-\u003eresponseView('mytemplate', array(\n\t\t\t'myData' =\u003e $repository-\u003egetAll()\n\t\t));\n\t}\n\n\tpublic function actionEdit()\n\t{\n\t\t$repository = new \\Robbo\\Support\\Repository($this-\u003egetModelFromCache('MyModel'));\n\t\t$id = $this-\u003e_input-\u003efilterSingle('id', XenForo_Input::UINT);\n\n\t\tif ( ! $resource = $repository-\u003egetById($id))\n\t\t{\n\t\t\treturn $this-\u003eresponseNoPermission();\n\t\t}\n\n\t\treturn $this-\u003eresponseView('mytemplate_edit', array(\n\t\t\t'myData' =\u003e $resource\n\t\t));\n\t}\n\n\tpublic function actionSave()\n\t{\n\t\t$repository = new \\Robbo\\Support\\Repository($this-\u003egetModelFromCache('MyModel'));\n\t\t$input = $this-\u003e_input-\u003efilter(array('one' =\u003e XenForo_Input::UINT, 'two' =\u003e XenForo_Input::STRING));\n\t\t$id = $this-\u003e_input-\u003efilterSingle('id', XenForo_Input::UINT);\n\n\t\tif ( ! $repository-\u003egetById($id))\n\t\t{\n\t\t\treturn $this-\u003eresponseNoPermission();\n\t\t}\n\n\t\t$repository-\u003esave($id, $input);\n\n\t\treturn $this-\u003eresponseRedirect('somewhere');\n\t}\n}\n```\n\nNow there is still a fair bit of boilerplate going on there, which leads me to the next example...\n\n### Controllers\n\nControllers really just have little helpers. For one instead of having to type out `XenForo_Input::UINT` you can do `self::UINT`.\n\nThen 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:\n\n```php\nclass MyController extends XenForo_ControllerPublic_Abstract {\n\t\n\tprotected $_dataModelName = 'MyModel';\n\n\tprotected $_idName = 'id';\n\n\tpublic function actionIndex()\n\t{\n\t\treturn $this-\u003eresponseView('mytemplate', array(\n\t\t\t'myData' =\u003e $this-\u003erepository-\u003egetAll()\n\t\t));\n\t}\n\n\tpublic function actionEdit()\n\t{\n\t\tif ( ! $resource = $this-\u003erepository-\u003egetById($this-\u003e_id)\n\t\t{\n\t\t\treturn $this-\u003eresponseNoPermission();\n\t\t}\n\n\t\treturn $this-\u003eresponseView('mytemplate_edit', array(\n\t\t\t'myData' =\u003e $resource\n\t\t));\n\t}\n\n\tpublic function actionSave()\n\t{\n\t\tif ( ! $this-\u003erepository-\u003egetById($this-\u003e_id))\n\t\t{\n\t\t\treturn $this-\u003eresponseNoPermission();\n\t\t}\n\n\t\t$this-\u003erepository-\u003esave($this-\u003e_id,  $this-\u003e_input-\u003efilter(array('one' =\u003e self::UINT, 'two' =\u003e self::STRING)));\n\n\t\treturn $this-\u003eresponseRedirect('somewhere');\n\t}\n}\n```\n\n### DataWriters\n\nI 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.\n\n```php\nclass MyWriter extends \\Robbo\\Support\\DataWriter {\n\t\n\t/* Old way\n\tprotected function _getFields()\n\t{\n\t\treturn array(\n\t\t\t'merc_gallery_media' =\u003e array(\n\t\t\t\t'media_id' \t\t=\u003e array('type' =\u003e self::TYPE_UINT, \t'autoIncrement' =\u003e true),\n\t\t\t\t'category_id' \t=\u003e array('type' =\u003e self::TYPE_UINT, \t'required' =\u003e true),\n\t\t\t\t'user_id' \t\t=\u003e array('type' =\u003e self::TYPE_UINT, \t'required' =\u003e true),\n\t\t\t\t'username' \t\t=\u003e array('type' =\u003e self::TYPE_STRING, \t'required' =\u003e true, 'maxLength' =\u003e 50),\n\t\t\t\t'ip_id'\t\t\t=\u003e array('type' =\u003e self::TYPE_UINT,   \t'default' =\u003e 0),\n\t\t\t\t'image' \t\t=\u003e array('type' =\u003e self::TYPE_STRING, \t'default' =\u003e '', \t'maxLength' =\u003e 50),\n\t\t\t\t'video' \t\t=\u003e array('type' =\u003e self::TYPE_STRING, \t'default' =\u003e '', \t'maxLength' =\u003e 255),\n\t\t\t\t'title' \t\t=\u003e array('type' =\u003e self::TYPE_STRING, \t'required' =\u003e true, 'maxLength' =\u003e 100),\n\t\t\t\t'description' \t=\u003e array('type' =\u003e self::TYPE_STRING, \t'default' =\u003e ''),\n\t\t\t\t'media_state'  =\u003e array('type' =\u003e self::TYPE_STRING, \t'default' =\u003e 'visible',\n\t\t\t\t\t'allowedValues' =\u003e array('visible', 'moderated', 'deleted')\n\t\t\t\t),\n\t\t\t\t'added_date' \t=\u003e array('type' =\u003e self::TYPE_UINT, \t'default' =\u003e XenForo_Application::$time),\n\t\t\t\t'upload_date' \t=\u003e array('type' =\u003e self::TYPE_UINT, \t'default' =\u003e XenForo_Application::$time),\n\t\t\t\t'view_count' \t=\u003e array('type' =\u003e self::TYPE_UINT, \t'default' =\u003e 0),\n\t\t\t\t'likes' \t\t=\u003e array('type' =\u003e self::TYPE_UINT_FORCED, 'default' =\u003e 0),\n\t\t\t\t'like_users' \t=\u003e array('type' =\u003e self::TYPE_SERIALIZED, 'default' =\u003e 'a:0:{}'),\n\t\t\t)\n\t\t);\n\t}\n\n\tprotected function _getExistingData($data)\n\t{\n\t\tif ( ! $id = $this-\u003e_getExistingPrimaryKey($data))\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\treturn array('merc_gallery_media' =\u003e $this-\u003egetModelFromCache('Merc_Gallery_Model_Media')-\u003egetMediaById($id));\n\t}\n\n\tprotected function _getUpdateCondition($tableName)\n\t{\n\t\treturn 'media_id = ' . $this-\u003e_db-\u003equote($this-\u003egetExisting('media_id'));\n\t}*/\n\n\t// New way starting here...\n\n\tprotected $_table = 'merc_gallery_media';\n\n\tprotected $_key = 'media_id';\n\n\tprotected function _setFields()\n\t{\n\t\t$this-\u003e_field('media_id')-\u003euinteger()-\u003eauto();\n\t\t$this-\u003e_filed('category_id')-\u003euinteger()-\u003erequired();\n\t\t$this-\u003e_field('user_id')-\u003euinteger()-\u003erequired();\n\t\t$this-\u003e_field('username')-\u003estring(50)-\u003erequired();\n\t\t$this-\u003e_field('ip_id')-\u003euinteger()-\u003edefault(0);\n\t\t$this-\u003e_field('image')-\u003estring(50);\n\t\t$this-\u003e_field('video')-\u003estring(255);\n\n\t\t// And so on... this is still fairly tedious so I will be looking at ways to improve it\n\t}\n\n\tprotected function _getExistingData($data)\n\t{\n\t\treturn $this-\u003e_genericExistingData(\n\t\t\t'merc_gallery_media', \n\t\t\t'media_id', \n\t\t\t$this-\u003e_createRepository($this-\u003egetModelFromCache('MyModel')), \n\t\t\t$data\n\t\t);\n\t}\n\n\tprotected function _getUpdateCondition($tableName)\n\t{\n\t\treturn $this-\u003e_genericUpdateCondition($tableName, 'media_id');\n\t}\n}\n```\n\n### Route Prefixes\n\nI 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.\n\nAn old prefix...\n```php\nclass Merc_Sidebar_Route_PrefixAdmin_Blocks implements XenForo_Route_Interface\n{\n\tpublic function match($routePath, Zend_Controller_Request_Http $request, XenForo_Router $router)\n\t{\n\t\t$action = $router-\u003eresolveActionWithIntegerParam($routePath, $request, 'block_id');\n\t\treturn $router-\u003egetRouteMatch('Merc_Sidebar_ControllerAdmin_Block', $action, 'sidebars');\n\t}\n\n\tpublic function buildLink($originalPrefix, $outputPrefix, $action, $extension, $data, array \u0026$extraParams)\n\t{\n\t\treturn XenForo_Link::buildBasicLinkWithIntegerParam($outputPrefix, $action, $extension, $data, 'block_id', 'title');\n\t}\n}\n```\n\nNow the same thing extending the support class instead...\n```php\nclass Merc_Sidebar_Route_PrefixAdmin_Blocks extends \\Robbo\\Support\\RoutePrefix {\n\t\n\tprotected $_controller = 'Merc_Sidebar_ControllerAdmin_Block';\n\n\tprotected $_id = 'block_id';\n\n\tprotected $_group = 'sidebars';\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobclancy%2Fxf-support","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobclancy%2Fxf-support","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobclancy%2Fxf-support/lists"}