Using Controllers in Drupal 8
In Drupal 8 we use Controllers in our Routes. This blog post gives some introduction into the way Controllers can be implemented.
Notice: This blog is not build on the Model, View, Controller (MVC) pattern. I maintain the word Controller in this blog because its used a lot in the classes throughout Drupal Core.
Controllers
A Controller class is the way Drupal is presenting pages through a registred route. They are used all over the core modules. You can look at the book module for example or search for Controller on the Classes page.
A Controller class can be any class. What class is called as the Controller for
the current request is defined by the [module].routing.yml file in your
module. These are automaticly loaded when the request is processed.
My question in this article is: What do I need to do to create a controller and what should be in it?
Constructing a Controller.
A Controller contains at least one method. This method returns the content (html, form, list, ajax, json, anything) of the page request. The naming of the method you want is not important. But something that describes what the method will return is very usefull.
The following example could be a very valid Controller class.
class Example1Controller {<br><br> public function exampleRender() {<br> return "Example of a controller class and method";<br> }<br><br>}
source
Passing arguments to a controller.
This last example implements a simple Controller with no bells and whissles at all. But what do you do when you need more complexity. Then the question arises, what complexity do you want.
Let say for example you want to load something based on the url arguments. In the routing yml file you add a argument to the path. In the Controller class you add a argument to the method with the same name. This makes it beeing passed to the callback. As you see below the order of the method argument can be different than the order of the argument in the route. But the naming should be the same.
example_controller.example2:<br> path: example-controller/example2/{id}/{example_name}<br> defaults:<br> _content: '\Drupal\example_controller\Controller\Example2Controller::exampleRender'<br> requirements:<br> _permission: 'access administration pages'
source
Here is the class that belongs to it.
class Example2Controller {<br><br> public function exampleRender($example_name, $id) {<br> return "This example give '$example_name' as argument with the id ($id)";<br> }<br><br>}
source
Controllers with dependencies on other objects.
A Controller has a lifetime. It is defined in the [module].routing.yml and is
instantiated by the ControllerResolver
class inside the createController method.
A plain Controller is constructed like this.
class Example1Controller {<br><br> public function __construct() {<br> }<br><br>}
A Controller that has required argument for its construct can not be constructed like the previous example. This controller needs a factory-like method to create it. The ContainerInjectionInterface
defines a create method that the ControllerResolver can use.
This example shows what we need to do to create a Controller that is dependend on a database connection. To be able to construct our example object we need the implementation of the create method from the interface.
class Example3Controller implements ContainerInjectionInterface {<br><br> protected $database;<br><br> /**<br> * Construct method.<br> */<br> protected function __construct($database) {<br> $this->database = $database;<br> }<br><br> /**<br> * Implements the interfaces static create method from ContainerInjectionInterface<br> */<br> public static function create(ContainerInterface $container) {<br> return new static(<br> $container->get('database'));<br> }<br><br> /**<br> * Example render callback.<br> */<br> public function exampleRender() {<br> return "ContainerInjectionInterface example.";<br> }<br><br>}
source
Working with the Container inside you Controller.
I might be that you don't need the Controller to depend on objects in the Container. But you might want the Container if you need objects for a special purpose in one or more of the callback methods.
You can do that by using the ContainerAwareInterface.
In this example we use the container to get information about the Request and display that in the response.
class Example5Controller implements ContainerAwareInterface {<br><br> protected $container;<br><br> /**<br> * Example render callback.<br> */<br> public function exampleRender() {<br> $request_uri = $this->container->get('request')->getUri();<br> return "Container example 5. " . $request_uri;<br> }<br><br> /**<br> * @inherit<br> */<br> public function setContainer(ContainerInterface $container = null) {<br> $this->container = $container;<br> }<br><br>}
source
Basic functionality in the ControllerBase
After building several pages you start to figure that there are a lot of default components that Drupal pages need to work nicely. Things like:
- Access to entity's (which have become more prominent all over the place in D8).
- Language information and translation functionality.
- Current user and sessions information.
- Configuration objects.
These are a few things which the abstract ControllerBase
class implements. This gives you a starting point to create extentions on this.
Here is a example of the previous class extended witth the ControllerBase.
class Example4Controller extends ControllerBase implements ContainerInjectionInterface {<br><br> protected $database;<br><br> /**<br> * Construct method.<br> */<br> public function __construct($database) {<br> $this->database = $database;<br> }<br><br> /**<br> * Implements the static interface create method.<br> */<br> public static function create(ContainerInterface $container) {<br> return new static(<br> $container->get('database'));<br> }<br><br> /**<br> * Example render callback.<br> */<br> public function exampleRender() {<br> return "Container example 4.";<br> }<br><br>}
source