Introduction to controllers
The following example is for a simple Controller
class. When building off the Silverstripe CMS you will
subclass the base Controller
class.
If you're using the cms
module and dealing with SiteTree
records then for your custom page controllers you
would extend ContentController
or PageController
.
// app/src/Control/TeamController.php
namespace App\Control;
use App\Model\Team;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
class TeamController extends Controller
{
private static $allowed_actions = [
'players',
];
public function players(HTTPRequest $request)
{
$this->renderWith(Team::class . '_PlayerList');
}
}
When choosing names for actions, avoid using the same name you've used for relations on the model the controller represents. If you have relations with the same name as controller actions, templates rendered for that controller which refer to the relation won't render as expected - they will attempt to render the action where you expect to be using the relation.
For example if the controller above was for a Team
model which had a Players
relation, the action should not also be named players
. Something like showPlayers
would be more appropriate.
Routing
We need to define the URL that this controller can be accessed on. In our case, the TeamsController
should be visible
at https://www.example.com/teams/
and the players
custom action is at https://www.example.com/team/players/
.
If you're extending ContentController
or PageController
for your SiteTree
records you don't need to define the routes value as the cms
handles
routing for those.
# app/_config/routes.yml
---
Name: approutes
After: '#coreroutes'
---
SilverStripe\Control\Director:
rules:
'teams//$Action/$ID/$Name': 'App\Control\TeamController'
Make sure that after modifying the routes.yml
file you clear your Silverstripe CMS caches using ?flush=1
.
For more information about creating custom routes, see the Routing documentation.
For information about how to get a URL (e.g. to use in templates) for a given controller action, see Getting the URL for a controller action below.
Actions
Actions in controllers are specific routes which are accessible via HTTP or CLI requests. They are often backed by a method, though a method does not need to be declared so long as there is a template available to render for the given action.
Controllers respond by default to an index
action. You don't need to implement this action as a method nor declare it as in allowed_actions
, but you
can implement the index()
method and return custom data to be used in the appropriate template (see Template and Views).
Action methods can return one of four things:
- an array. The appropriate template will be rendered, using the values in the array you provided. The rendered result will be set as the body for the current
HTTPResponse
object. - a string (e.g. JSON or HTML markup). The string will be set as the body for the current
HTTPResponse
object. - an
HTTPResponse
. This can either be a new response or$this->getResponse()
. $this
or$this->customise()
. This will render the controller using the appropriate template and set the rendered result as the body for the currentHTTPResponse
.
There are a couple of things to note here:
- returning
$this
is the equivalent of returning an empty array. - returning
$this->customise()
is the equivalent of returning an array with data.
See templates below for information about declaring what template to use in the above scenarios.
A controller action can also throw an HTTPResponse_Exception
.
This is a special exception that indicates that a specific error HTTP code should be used in the response.
By throwing this exception, the execution pipeline can adapt and use any error handlers (e.g. via the silverstripe/errorpage module).
// app/src/Control/TeamController.php
namespace App\Control;
use SilverStripe\Control\Controller;
// ...
class TeamController extends Controller
{
// ...
/**
* Return some additional data to the current response that is waiting to go out, this makes $Title set to
* 'My Team Name' and continues on with generating the response.
*/
public function index(HTTPRequest $request)
{
// ...
}
/**
* We can manually create a response and return that to ignore any previous data or modifications to the request.
*/
public function someaction(HTTPRequest $request)
{
$this->setResponse(new HTTPResponse());
$this->getResponse()->setStatusCode(400);
$this->getResponse()->setBody('invalid');
return $this->getResponse();
}
/**
* Or, we can modify the response that is waiting to go out.
*/
public function anotheraction(HTTPRequest $request)
{
$this->getResponse()->setStatusCode(400);
return $this->getResponse();
}
/**
* We can render HTML and leave Silverstripe CMS to set the response code and body.
*/
public function htmlaction()
{
return $this->customise(ArrayData::create([
'Title' => 'HTML Action',
]))->renderWith('MyCustomTemplate');
}
/**
* We can send stuff to the browser which isn't HTML
*/
public function ajaxaction()
{
$this->getResponse()->addHeader('Content-type', 'application/json');
return json_encode([
'json' => true,
]);
}
}
For more information on how a URL gets mapped to an action see the Routing documentation.
Security
See the Access Control documentation.
Templates
The template to use for a given action is determined in the following order:
- If a template has been explicitly declared for the action in the
templates
property, it will be used. - If a template has been explicitly declared for the "index" action in the
templates
property, it will be used (regardless of what action is being rendered). - If the
template
property has been set at all, its value will be used. -
If a template exists with the name of this class or any of its ancestors, suffixed with the name of the action name, it will be used.
- e.g. for the
App\Control\TeamController
example, the "showPlayers" action would look for templates namedtemplates/App/Control/TeamController_showPlayers
andtemplates/SilverStripe/Control/Controller_showPlayers
with the relevant file extension. - Note that the "index" action skips this step.
- e.g. for the
-
If a template exists with the name of this class or any of its ancestors (with no suffix), it will be used.
- e.g. for the
App\Control\TeamController
example, it would look for templates namedtemplates/App/Control/TeamController
andtemplates/SilverStripe/Control/Controller
with the relevant file extension.
- e.g. for the
Subclasses of ContentController
additionally check for templates named similarly to the model the controller represents - for example a HomePageController
class which represents a HomePage
model will look for a HomePage_{action}
template after checking HomePageController_{action}
.
You can declare templates to be used for an action by setting the templates
array. The key should be the name of the action,
and the value should be a template name, or array of template names in cascading precedence.
namespace App\Control;
use SilverStripe\Control\Controller;
class TeamController extends Controller
{
protected $templates = [
'showPlayers' => 'TemplateForPlayers',
];
private static $allowed_actions = [
'showPlayers',
];
}
The templates
property is not a configuration property, so if you declare it directly as in the above example you will
override any templates declared in parent classes. If you want to keep template declarations from parent classes, you could
apply new templates in a constructor like so:
namespace App\Control;
class TeamController extends SomeParentController
{
// ...
public function __construct()
{
parent::__construct();
$this->templates['showPlayers'] => 'TemplateForPlayers';
}
}
As mentioned in Actions above, controller actions can return a string or HTTPResponse
to bypass this template selection process.
For more information about templates, inheritance and how to render into views, See the Templates and Views documentation.
Getting the URL for a controller action
Each controller should declare the url_segment
configuration property, using the non-variable portion of that controller's routing rule.
namespace App\Control;
use SilverStripe\Control\Controller;
class TeamController extends Controller
{
private static $url_segment = 'teams';
// ...
}
You can then use the Link()
method to get a relative URL for your controller:
$indexLink = $teamController::Link();
$playersActionLink = $teamController::Link('players');
You can of course also use $Link
in a template.
If you have more complex logic for determining the link for your controller, you can override the Link()
method - in that case you should
be sure to invoke the updateLink
extension method so that extensions can make changes as necessary: $this->extend('updateLink', $link, $action);
Connecting pages to controllers
By default, a SiteTree
subclass will be automatically associated with a controller which is in the same
namespace, and is named the same but suffixed with Controller
. For example, App\PageType\HomePage
will be associated with a App\PageType\HomePageController
if such a class exists.
If there is no controller for a specific page class, that page's ancestors will be checked until a suitable controller is found.
If you find that your controllers are in a different namespace then you'll need to define the correct
controller in the controller_name
configuration property.
Example controller:
namespace App\Control;
use SilverStripe\Control\Controller;
class TeamPageController extends Controller
{
private static $url_segment = 'teams';
// ...
}
Calling $this->Link()
in the above controller will now give a valid relative URL for accessing the controller on your site. If this is a subcontroller or otherwise has some part of its route that is dynamic, you will need to override the Link()
method to resolve the correct URL dynamically.
Example page:
namespace App\PageType;
use App\Control\TeamPageController;
use Page;
class TeamPage extends Page
{
private static $controller_name = TeamPageController::class;
}
You'd now be able to access methods of the controller in the page's template
<%-- TeamPage.ss --%>
<p>{$getExample}</p>