PHP in Sulu: How To Write A Custom Controller To Render Any Content
The ideal content management system (CMS) would be able to handle any kind of content out of the box. Unfortunately, such an ideal system could never exist. Have you ever seen a restaurant that serves every meal imaginable? Sulu CMS approaches the challenge of serving any type of content by being incredibly extensible through templates and, if necessary, PHP code. In this article, we are going to show you how to implement dynamic content using a typical PHP-based customization in Sulu: a custom controller.
Check out other options for using code to customize Sulu in PHP in Sulu: The Power Of Code.
Custom controllers in Sulu
If you’re new to Symfony or Sulu, a controller is a component in the Model-View-Controller architecture.
- A model represents structured data, such as a blog page, a calendar, or an address book. Sulu refers to models as entities.
- A view defines how data is rendered at the frontend. Sulu uses Twig templates to build views, or full React apps in headless mode.
- A controller connects models to views. A controller typically receives a request from a view and responds with data taken from a model.
Controllers are the starting points for including custom business logic in your CMS application. Controllers can access entities, invoke custom services, call external APIs, and combine all the results into one for the view to render.
When to use a custom controller
Sulu’s standard functionality can build a fully functional website, but there are some use cases that require a custom controller:
- Deliver automated content. Content isn’t always static. Sometimes, you need to dynamically generate content from frequently updated data. For example, a product catalog that displays the number of items in stock. If standard features like the Automation bundle or target groups do not match the use case, a custom controller can provide highly tailored automation.
- Fetch additional data from the database. Imagine external data synched into your system that requires special treatment. A custom controller can fetch that data for further processing.
- Provide static forms. Typically, a content manager would set up a static form using a page XML and a Twig template. However, if you have specialized forms for a template, such as a wizard or a product configurator, you might want to implement that form using a custom controller.
Sample scenario: What’s the current moon phase?
With the above concepts and use cases in mind, let’s go through a tiny sample project to build our own custom controller. The goal is to display an image of the current phase of the moon on your web page. This information can be calculated on the fly and, therefore, is an ideal case for a custom controller.
The controller:
- Is invoked by a “moon” template
- Calculates the current moon phase,
- Passes the calculated data to the template to render an image of the calculated moon phase.
Step 1: Set up a new Sulu project
We’ll start by setting up a local development environment. You’ll need the standard ingredients:
Using Docker is optional, but it’s a great way of quickly and conveniently spinning up a database. The following steps assume that you use Docker.
We roughly follow the steps in Getting Started. Refer to the docs if you need more details about each step.
Create the project
The project’s starting point is the `sulu/skeleton` project template. Start a shell or prompt window, `cd` into a suitable directory, and run Composer:
composer create-project sulu/skeleton custom-controller
If prompted to install Docker config, respond with ‘yes’.
Start the database
Now we’ll start a database instance.
1. Change into the custom-controller
directory and start the containers:
cd custom-controller
docker-compose up
This process will run in the foreground as long as the database is running. (When you are finished with this tutorial, hit Ctrl+C in this terminal session to power down the Docker compose stack.)
2. To continue with the project, open a second shell. Alternatively, you can run docker-compose up -d
, and also call docker-compose logs
in the same directory to see the log output.
Now you have a MySQL instance running and listening on port 3306. (The compose file also spawns a mailing service that you can ignore for this project.)
Initialize the system
The database is not yet populated. Run the following command in custom-controller
to set up tables and data:
php bin/adminconsole sulu:build dev
Start the CMS
Now let’s test the installation.
1. Start up the web server with the following command:
php -S localhost:8000 -t public/ config/router.php
(Feel free to use another port if port 8000 is already occupied on your computer.)
2. Open http://localhost:8000/admin
in your browser and log in as admin/admin. You should now see the admin UI.
Step 2: Create a custom controller
Now we can start creating a custom controller.
First, create a new file in your editor or IDE.
src/Controller/Website/MoonPhaseController.php
The controller is quite straightforward; below is the complete class. Let’s walk through the code step by step. The numbers in parentheses refer to the respective comments in the code.
We start by declaring the namespace (1). Sulu distinguishes between the visitor-facing web site and the admin UI, hence we choose the Website namespace.
The MoonPhaseController extends Sulu’s WebsiteController (2) and makes use of Sulu’s StructureInterface and Symfony’s Response types.
The controller exposes the indexAction() method (3). A controller that implements a custom action must make use of renderStructure() to craft the HTTP response.
The getAttributes() method (4) is called by the parent WebsiteController class during the rendering process. It allows the child controller to add or modify the attributes passed to the view. In our scenario, it adds a moonphase
attribute with the value returned by the lunarPhase
method (5). This moonphase
attribute gets exposed to the Twig template in the frontend, as we’ll see later.
Finally, method lunarPhase()
calculates the current phase of the moon. (Source: StackOverflow under a CC-BY-SA license. For the visual appeal, we changed the function to return images instead of the text description of the phase.)
Step 3: Integrate the controller with the CMS
At this point, the controller is functioning, but it is neither reachable from the web, nor does it take care of rendering the content it creates. To invoke the controller from the web, we will create a page template to define the page structure and the controller method to be called when opening the page. For rendering, we will create a Twig template.
Create a new page template “moon.xml”
To create a page template:
- Cd into
config/templates/pages
. - Make a copy of
default.xml
. Name itmoon.xml.
- Change the
<key>
tag tomoon
. - Change the
<view>
tag topages/moon
. - Change the controller path to
App\Controller\Website\MoonPhaseController
. - Adjust the property titles accordingly.
Remove the property named “article” from the template because our page is not an article.
Here is the full content of moon.xml, with the changes mentioned above highlighted. Note the <controller>
tag that connects the page to the controller’s indexAction()
method.
Create a Twig template
For the frontend template, go to templates/pages
and make a copy of default.html.twig
. Name the copy moon.html.twig
.
This Twig template takes over the content rendering. Add the following code to the file:
The placeholder {{ moonphase|raw }}
receives the moonphase
attribute from the controller. Apart from that, the template does not do much except create a night sky background and inflate the moon phase emoji to a whopping 10em text size!
Now the coding part is done.
Step 4: Set up the example webspace in the admin UI
Finally, we can create a simple site with a home page and a moon phase page.
- First, clear the caches.
- Open https://localhost:8000/admin in your browser and log in.
- Open the side menu, then click on Webspaces. You will see the example.com webspace.
- Hover over the column to the right of the example.com column. A button with a plus sign will display on top of the column.
- Click the button to create a new page.
6. Click the drop-down menu labeled “Default.” You will see the entry named “Moon Phase” that Sulu created from our moon.xml
template.
7. Select the “Moon Phase” menu item.
The UI will switch to the moon.xml
template.
8. Enter a title for the page, and set the resource locator to “moon”.
9. Save and publish the page.
10. Test it out! Open http://localhost:8000/moon and see the current moon phase.
Your new hobby: creating custom controllers
Now you have entered the world of custom controllers in Sulu, and creating a controller is probably easier than you might have thought. Use your imagination (and your customer’s functional specifications document) to create a Sulu that the world has never seen.
If you would like to learn more about Sulu, try it out or get in contact with us.