Skip to content

Commit b53c917

Browse files
authored
Merge pull request #504 from ExpressionEngine/feature/7.x/add-on-controllers
ee7 docs for add-on controllers feature by @mithra62
2 parents 6c1119f + 3a08f49 commit b53c917

File tree

6 files changed

+494
-0
lines changed

6 files changed

+494
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Add-on Controllers
2+
3+
Add-on Controllers allows Add-on developers to create their Add-on methods using an abstracted object layer while keeping backwards compatibility intact. It covers Extensions, Modules (Action and Tags), and the Control Panel layers. It's very simple to implement (just inherit an object), while maintaining the legacy implementation in play.
4+
5+
To implement, you simply set your Add-on `mod.`, `ext.`, and/or `mcp.` files to inherit from a base object (depending on the implementation).
6+
7+
## Add-on File Examples
8+
9+
Here's a breakdown for how the Add-on files would look for an Add-on named `Custom_addon`.
10+
11+
> Note that this works by using set or determined namespaces for a given Add-on. By default, this Controller implementation will use the Add-on name to get the configured namespace within the Add-on. For this example of `Custom_addon`, it's `YourAddon`; all route namespaces are determined by that.
12+
13+
### Module
14+
15+
The `mod` file would consist exclusively of the below.
16+
17+
```php
18+
use ExpressionEngine\Service\Addon\Module;
19+
20+
class Custom_addon extends Module
21+
{
22+
protected $addon_name = 'custom_addon';
23+
}
24+
```
25+
26+
Once that's done, your Add-on Module is now set to look for objects for all calls to it. Those objects depend on what "type" (Tag or Action). Using the configured namespace for the Add-on, the Module routes would be looked for in YourAddon\Module\Tags|Action`.
27+
28+
#### Template Tag
29+
30+
For a template tag called as `{exp:custom_addon:test_template_tag}`, you'd setup your Tag object like the below:
31+
32+
```php
33+
namespace YourAddon\Module\Tags;
34+
35+
use ExpressionEngine\Service\Addon\Controllers\Tag\AbstractRoute;
36+
37+
class TestTemplateTag extends AbstractRoute
38+
{
39+
public function process()
40+
{
41+
42+
}
43+
}
44+
```
45+
46+
#### Actions
47+
Just like with Template Tags, Actions get stored in the `Actions` namespace. The below is for an Action called `my-test-action`.
48+
49+
```php
50+
<?php
51+
namespace YourAddon\Module\Actions;
52+
53+
use ExpressionEngine\Service\Addon\Controllers\Action\AbstractRoute;
54+
55+
class MyTestAction extends AbstractRoute
56+
{
57+
public function process()
58+
{
59+
60+
}
61+
}
62+
```
63+
64+
### Extension
65+
66+
The `ext` is structured just like the others:
67+
68+
```php
69+
use ExpressionEngine\Service\Addon\Extension;
70+
71+
class Custom_addon_ext extends Extension
72+
{
73+
protected $addon_name = 'custom_addon';
74+
}
75+
```
76+
77+
Again, just like the others, Extensions are structured similarly.
78+
79+
```php
80+
<?php
81+
namespace YourAddon\Extensions;
82+
83+
use ExpressionEngine\Service\Addon\Controllers\Extension\AbstractRoute;
84+
85+
class TestExtension extends AbstractRoute
86+
{
87+
public function process()
88+
{
89+
90+
}
91+
}
92+
```
93+
94+
Note that the above `process` method allows you to accept variable parameters (just like regular Extensions). For example, to build an Extension around `template_post_parse`, you'd write your Extension like the below:
95+
96+
```php
97+
namespace YourAddon\Extensions;
98+
99+
use ExpressionEngine\Service\Addon\Controllers\Extension\AbstractRoute;
100+
101+
class TestExtension extends AbstractRoute
102+
{
103+
public function process(string $final_template, bool $is_partial, string $site_id, array $currentTemplateInfo): string
104+
{
105+
return $final_template;
106+
}
107+
}
108+
```
109+
110+
### Module Control Panel (MCP)
111+
112+
The `mcp` layer acts more as a Controller/Router than the others in that the CP is supposed to be rendered as Views. To start, just like all the others, you extend from the `Mcp` object. Once that's done, all requests will be routed to the object layer.
113+
114+
```php
115+
use ExpressionEngine\Service\Addon\Mcp;
116+
117+
class Custom_addon_mcp extends Mcp
118+
{
119+
protected $addon_name = 'custom_addon';
120+
}
121+
```
122+
123+
#### Route Examples
124+
125+
A basic 'index' route (normally done through a method attached to the `mcp` object) would look like the below:
126+
127+
```php
128+
namespace YourAddon\Mcp;
129+
130+
use ExpressionEngine\Service\Addon\Controllers\Mcp\AbstractRoute;
131+
132+
class Index extends AbstractRoute
133+
{
134+
/**
135+
* @var string
136+
*/
137+
protected $route_path = 'index';
138+
139+
/**
140+
* @var string
141+
*/
142+
protected $cp_page_title = 'home';
143+
144+
/**
145+
* @param false $id
146+
* @return AbstractRoute
147+
*/
148+
public function process($id = false): AbstractRoute
149+
{
150+
return $this;
151+
}
152+
}
153+
```
154+
155+
The generation of the CP array format is done automatically by the Controller so all the devs really do is assign variables and ensure their `process` method returns an instance of their Route:
156+
157+
```php
158+
namespace YourAddon\Mcp;
159+
160+
use ExpressionEngine\Service\Addon\Controllers\Mcp\AbstractRoute;
161+
162+
class Index extends AbstractRoute
163+
{
164+
/**
165+
* @var string
166+
*/
167+
protected $route_path = 'index';
168+
169+
/**
170+
* @var string
171+
*/
172+
protected $cp_page_title = 'home';
173+
174+
/**
175+
* @param false $id
176+
* @return AbstractRoute
177+
*/
178+
public function process($id = false): AbstractRoute
179+
{
180+
$this->addBreadcrumb('test-link', 'test-link')
181+
->addBreadcrumb('test-link2', 'test-link2');
182+
183+
$variables = [];
184+
$this->setBody('my-view', $variables);
185+
return $this;
186+
}
187+
}
188+
```
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# Control Panel
2+
3+
Just like with Extension and Module routing, you have to extend your ExpressionEngine Addon file to use the corresponding Add-on Controller Base route. In this case, that'll be `ExpressionEngine\Service\Addon\Mcp`.
4+
5+
You'll also have to add a new property to your Control Panel object called `$route_namespace` which is the namespace for route objects.
6+
7+
> Note the below example uses the `setRouteNamespace` method to set the `$route_namespace` property.
8+
9+
### Update Control Panel File
10+
11+
```php
12+
use ExpressionEngine\Service\Addon\Mcp;
13+
14+
class My_addon_mcp extends Cp
15+
{
16+
protected $module_name = 'my_addon';
17+
18+
public function __construct()
19+
{
20+
$this->setRouteNamespace('MyAddon\Addon\Controllers');
21+
}
22+
23+
public function index()
24+
{
25+
return $this->route('index', func_get_args());
26+
}
27+
}
28+
29+
```
30+
31+
#### About `$route_namespace`
32+
33+
Note that `$route_namespace` isn't directly one to one. The Add-on Controller does some magic to keep things compartmentalized. Using the below example, the namespace used to locate your Route objects would be `Namespace\For\Your\Controllers\Cp\Routes`.
34+
35+
#### About `$module_name`
36+
37+
This value should contain the internal value ExpressionEngine uses to classify your parent Addon.
38+
39+
> An extra special note about the use of `func_get_args()`: you should 100% keep doing that if you want Routing to be consistent. Basically, every declaration of a `route()` call should look like: `return $this->route('ROUTE_NAME', func_get_args());`
40+
41+
### Create Control Panel Routes
42+
43+
Unlike any other Controller Route, Control Panel routes require an addition to your ExpressionEngine Control Panel object. The idea is you tell ExpressionEngine to your object then the Router takes over. For example, in the above example, we define an `index` method and then route it internally.
44+
45+
The route object itself would look like the below:
46+
47+
```php
48+
namespace YourAddon`\Addon\Controllers\Cp\Routes;
49+
50+
use ExpressionEngine\Service\Addon\Mcp\AbstractRoute;
51+
52+
class Index extends AbstractRoute
53+
{
54+
protected $route_path = 'index';
55+
56+
public function process($id = false): AbstractRoute
57+
{
58+
return $this;
59+
}
60+
}
61+
```
62+
63+
You may have noticed that unlike traditional ExpressionEngine Control Panel methods, you don't return an array; you return an instance of your route instead. To generate output, the Router will take everything you setup within your object and pass that to ExpressionEngine.
64+
65+
#### About `$route_path`
66+
67+
The value MUST contain the `noun|/verb` your Route is accessed through. See the for more details.
68+
69+
#### Full Example
70+
71+
```php
72+
namespace YourAddon\Controllers\Mcp\Routes\ControllersExamples;
73+
74+
use ExpressionEngine\Service\Addon\Mcp\AbstractRoute;
75+
76+
class Cp extends AbstractRoute
77+
{
78+
protected $route_path = 'controllers-examples/cp';
79+
80+
public function process($id = false): AbstractRoute
81+
{
82+
$this->setBody('test-route/my-action', []);
83+
$this->setHeading('cp');
84+
85+
$this->addBreadcrumb($this->url('controllers-examples'), 'eo.cp.nav.controller.examples');
86+
return $this;
87+
}
88+
}
89+
```
90+
91+
92+
## Helper Functionality
93+
94+
### Sidebar Generator
95+
96+
To automatically generate your sidebar, you'll need to add a property to your Route object called `$sidebar_data` that contains an array that outlines your sidebar.
97+
98+
```php
99+
protected $sidebar_data = [
100+
'eo.cp.nav.controller.examples' => [
101+
'path' => 'controllers-examples',
102+
'list' => [
103+
'cp' => 'controllers-examples/cp',
104+
'module' => 'controllers-examples/mod',
105+
]
106+
],
107+
'eo.cp.nav.forms' => [
108+
'path' => '',
109+
'list' => [
110+
'eo.cp.nav.example' => 'forms/example'
111+
]
112+
],
113+
'eo.cp.nav.members' => [
114+
'path' => '',
115+
'list' => [
116+
'eo.cp.nav.example' => 'members'
117+
]
118+
],
119+
'eo.cp.nav.entries' => [
120+
'path' => '',
121+
'list' => [
122+
'eo.cp.nav.example' => 'entries'
123+
]
124+
]
125+
];
126+
```
127+
128+
Note the `$active_sidebar` property can be used to specify a specific sidebar node as having an `active` state. Otherwise, that'll be determined through the `$route_path` property.
129+
130+
### URL Helper
131+
132+
To remove a considerable amount of keystrokes, you can use the `url($path, $with_base, $query)` method. What this does is allow you to create Control Panel URLs using the short syntax of a Route as well as any URL.
133+
134+
```php
135+
protected function url(string $path, bool $with_base = true, array $query = []): string
136+
```
137+
#### `$path`
138+
139+
Either the Router shortname for the route you want, or a full URL to the destination.
140+
141+
#### `$with_base`
142+
143+
Boolean to compile the URL along with the `$base_url` property. If your link is to anything BUT a Route, you'll want this to be false.
144+
145+
#### `$query`
146+
147+
An array of key=>values you want to use for the query string on the URL.
148+
149+
### Breadcrumbs
150+
151+
You apply breadcrumbs using either the `addBreadcrumb($url, $text)` or the `setBreadcrumbs(array $breadcrumbs = [])` method(s). Note that `setBreadcrumbs` will completely reset any previously compiled `breadcrumb` items.
152+
153+
```php
154+
protected function setBreadcrumbs(array $breadcrumbs = []): AbstractRoute
155+
protected function addBreadcrumb(string $url, string $text): AbstractRoute
156+
```
157+
158+
### Body Content
159+
160+
To output content within the ExpressionEngine Control Panel, you have to dictate both a view script to use and the variables to pass to it.
161+
162+
```php
163+
public function setBody(string $view, array $variables = []): AbstractRoute
164+
```
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Extensions
2+
3+
Just like with Module and Control Panel routing, you have to extend your ExpressionEngine Addon file to use the cooresponding Add-on Controller Base route. In this case, that'll be `ExpressionEngine\Service\Addon\Extension`.
4+
5+
You'll also have to add a new property to your Extension called `$route_namespace` which is the namespace for route objects.
6+
7+
### Update Extension File
8+
9+
```php
10+
use ExpressionEngine\Service\Addon\Extension;
11+
12+
class Your_addon_ext extends Extension
13+
{
14+
protected $route_namespace = 'Namespace\For\Your\Controllers';
15+
}
16+
```
17+
18+
#### About `$route_namespace`
19+
20+
Note that `$route_namespace` isn't directly one to one. The Add-on Controller does some magic to keep things compartmentalized. Using the below example, the namespace used to locate your Route objects would be `Namespace\For\Your\Controllers\Extensions\Routes`.
21+
22+
### Create Route
23+
24+
Note that your `process` method's signature should match up with the ExpressionEngine hooks passed parameters.
25+
26+
```php
27+
namespace YourAddon\Addon\Controllers\Extension\Routes;
28+
29+
use ExpressionEngine\Service\Addon\Extension\AbstractRoute;
30+
31+
class TemplatePostParse extends AbstractRoute
32+
{
33+
public function process(string $final_template, bool $is_partial, int $site_id, array $currentTemplateInfo): string
34+
{
35+
return $final_template;
36+
}
37+
}
38+
```

0 commit comments

Comments
 (0)