In this guide I explain how to render a custom SilverStripe DataObject as an actual page using one of your own custom .ss template files. SilverStripe is built using the MVC architectural pattern which I've given an introduction to in another blog post. In the MVC world a SilverStripe DataObject is essentially a Model definition. Models themselves are only aware of their own manipulative methods and associated database fields, i.e. DateCreated, LastUpdated, Title, Content etc. Rendering a DataObject as a page requires a Controller to interact with the Model, retrieve the relevant info from the database and then render it as a View. This View could be a website page or some other HTTP Response, i.e. a REST API response. We'll leave the latter for another day though.
Presumptions
- You are using at least SilverStripe 4.0.
- For this example we are setting up a blog and wish to render a blog post on it's own page.
- The following classes have been created:
- BlogPageController.php
- BlogPage.php
- BlogPost.php
- A BlogPage has been created in the website SiteTree using /blog as it's URLSegment.
- The BlogPage type within the CMS permits the creation of blog posts.
- A BlogPage can have many BlogPosts associated with it (has_many relationship).
- An individual BlogPost can only be connected with one BlogPage (has_one relationship).
How BlogPageController works
The default behaviour for BlogPageController is to simply render the BlogPage we create in the CMS interface with a template file of the same name with a .ss file extension. Fortunately we don't need to define any of this default behaviour because it is inherited from PageController. No need to re-invent the wheel there thankfully!
This relationships referred to presumptions section will mean that the BlogPage.ss template file will have an awareness of blog posts associated with it. This means a frontend developer will be able to loop through blog posts and display them to the user in a somewhat "Recent Posts" format. However, if the user then wants to only view an individual blog post the BlogPageController will have no idea what is being asked of it and this is where some work is needed.
Defining Controller Actions
An action is basically a task you ask a the controller to carry out and up until this point the only actions our BlogPageController can fulfil are those it has inherited from further up the class hierarchy. What we need to do now is define our own action in order to instruct the controller to display a single blog post as a page.
How does SilverStripe know which Action we are requesting?
When you visit a page on SilverStripe it will look to the URL for cues on what action you are requesting it run. To do this it will split the URL up into four separate parameters.
/Controller/Action/ID/OtherID
Let's break this down:
- Controller:
This is the controller you wish to use. In our case the controller is BlogPostController but as our controller happens to be connected with a page type called BlogPage and we have created a page of that type at the URL /blog, Controller in the URL can use /blog instead. Therefore visiting www.domain.co.uk/blog will use BlogPostController. - Action:
This is the part of the URL in which you tell SilverStripe which action to run on the controller. If you leave this blank then SilverStripe will use run the "index" action, which is the default action on any controller. - ID & OtherID:
Both ID and OtherID are sections of the URL in which you can pass the controller information. Despite what the name might suggest you do not need to pass solely numerical information in the case of wanting an SEO friendly URL.
Now you know how the URL plays a part in SilverStripe let's use this very blog post as an example to better understand what might be going on within the controller.
/blog/post/how-to-render-silverstripe-dataobject-as-a-page
As explained above the first part of the URL is /blog which is telling SilverStripe to use the BlogPageController. The second part /post tells the BlogPageController we want to run the post action. The final part passes the controller an SEO friendly title of the blog post, this will be referred to as the ID parameters within the controller code. The OtherID portion of the URL is omitted in this case.
How to define our custom action?
An action on a controller is a method which matches the action portion of the URL. Here's an example of our post method.
public function post(HTTPRequest $request)
{
//Code Here
}
The HTTPRequest $request
argument within the brackets passes the SilverStripe HTTPRequest object into the $request variable. We are able to use that variable to retrieve the ID and OtherID parameters from the URL and subsequently use them to query our DataObject for any matching results. For the purposes of this example we'll fetch a blog post using an ID of 465 in the database and as a result our post method will now look like this:
public function post(HTTPRequest $request)
{
//Query the BlogPost DataObject for an entry matching the given ID.
$BlogPost = $this->BlogPosts()->byID($request->param("ID"));
//If no blog post is found return a 404 error page.
if(!$BlogPost) {
return $this->httpError(404, 'The requested blog post could not be found');
}
//Otherwise return the blog post to the template
else {
return [
"BlogPost" => $BlogPost
];
}
}
The above code does the following:
- Retrieve all of the blog posts connected with the current BlogPage and fetch one that has the ID passed within the URL's ID parameter, which in our case that's a blog post with an ID of 465. The result of the query is stored within the $BlogPost variable. If no blog post is found then false is given.
- If false is returned render a 404 error page using the given message as the reason for the 404.
- If a blog post with the requested ID does exist, then pass it to the template using the template variable $BlogPost. More on that in a moment.
Allowing the use of our new action
If you were to try and access /blog/post/465 on our website you'd like to hope something would be displayed but in order to prevent the use of unauthorised code SilverStripe will only permit the use of actions we explicitly whitelist. Whitelisting an action is quite straightforward we simply adjust, or add a config variable to the controller itself. So within BlogPostController.php you'd add the following:
$allowed_actions = [
"post"
];
Now that's defined we must visit /dev/build on your SilverStripe installation. Using /dev/build will rebuild SilverStripe's internal models and manifest which will then permit you to access our new action.
Our New BlogPost Template
In our custom post action we returned the BlogPost to the Controller. What that'll do in practice is look for a new template within your theme's Layout folder with the following format PageType_action.ss. In our case that'll be BlogPage_post.ss, so if that template does not exist you'll need to create it first. In the action code we returned the blog post to the template with the name "BlogPost". This means we now access that blog posts data in the following ways:
Option 1 - <% with %>
Wrapping your content with a "with" template tag allows you to simply call $Title, $Content etc from the DataObject itself. As per this example:
<% with BlogPost %>
<h1>$Title</h1>
<p>Published: $Created.Nice</p>
<div class="blog-post-content">$Content</div>
<% end_with %>
Option 2 - Without <% with %>
There's no requirement to use with. This same template file can look like this. You can access subitems of an object with a period/full stop. So in this case we're telling the SilverStripe template engine to look to the BlogPost object to retrieve the where the normal use of Title would otherwise give you the Title of the page.
<h1>$BlogPost.Title</h1>
<p>Published: $BlogPost.Created.Nice</p>
<div class="blog-post-content">$BlogPost.Content</div>
Conclusion
There you have it, you now know how to render your own custom DataObject's as pages and while this example used a blog post you can do this for anything be it a product on an ECommerce store, a Student in a school management platform and so and so forth. If you have any questions on this guide, or have ideas for another guide for SilverStripe then please let me know in the comments section below! :-)