Development Wiki


From phpBB Development Wiki

Revision as of 15:54, 9 January 2013 by Marc (Talk | contribs)

This page aims to clearly explain the process of creating and using a controller for extensions as well as for core phpBB functionality.

Why Controllers?

For extensions, a controller is the only way to allow a user to access a custom "front-facing" page. In 3.0, a Modification could simply upload a file to the board's root directory. However, because Extensions are self-contained in their own isolated directories and cannot add any files outsides of those directories, we integrated Symfony's HttpKernel, Controller, and Routing systems to allow extensions to continue to have pages that can be easily accessed by end-users.

It is also simply a good programming practice to route all page requests for the software through a single location. In fact, we aim to eventually port all core front-facing files (e.g. viewforum.php, viewtopic.php, etc.) to controllers as well.

What does this look like for an end user?

A new file, called app.php, was added to the root board directory. This is our routing file. All extension controllers will be routed through that file. By default, PHP allows us to use a URL syntax such as ./app.php/controller_name. For servers that support it, we have implemented some URL Rewriting rules (for Apache in the .htaccess file, and for IIS 7+ in the web.config file) to make it much nicer: ./controller_name

Creating a Controller

A controller consists of 3 components at a minimum:

  1. The service file - ./config/services.yml - This file contains a list of services; a service is basically the definition of a class and its dependencies (For more information read Dependency Injection Container and its linked pages).
  2. The routing file - ./config/routing.yml - This file contains a list of controller access names (i.e. what the user puts in the URL) associated with the service
  3. The controller class - (filename and location can vary) - This is a class that implements phpbb_controller_interface and is responsible for serving content to the user based on what was requested in the URL.

The Service File

Each extension that wishes to use a controller must contain its own ./config/services.yml file. Core services are defined by the ./config/services.yml file in the root phpBB directory; all core services should be defined there.

Following the implementation of a Dependency Injection Container, core phpBB objects are now available as services. To simplify the process of specifying dependencies for controllers, we require all controller classes be defined as a service as well. This allows you to easily define what objects and information your controller will need using the container without having to mess with global variables.

Here is the syntax used for defining a service. For actual examples, view the services.yml file in the root config directory.

# This is a comment
# You must indent with 4 spaces instead of 1 tab
    class: phpbb_my_class
        - @request # The phpbb_request class will be instantiated for this argument
        - literal_text # The string 'literal text' will be sent to this argument
        - %core.php_ext% # This contains the value of $phpEx

One very important point to note here is that the order of arguments here should match the order of parameters on your __construct() method definition. However, the order does not matter in any other way as long as the two match.

The Routing File

Each extension that wishes to use a controller must also contain its own ./config/routing.yml file. Core routes are defined by the ./config/routing.yml file in the root phpBB directory.

This file is responsible for associating a controller's access name (i.e. what is typed in the URL) with its service (i.e. what we covered in the previous section). When someone types the access_name in the URL, the service is located and the associated class is instantiated the controller method given method is called.

# This is also YML, so the comments above apply here
    pattern: /url/{foo}
    defaults: { _controller: service_name:method_name, foo: "bar" }

The above example says that when the user goes to the url /app.php?controller=url/blah it should load the service_name service and call the method_name method, giving the value of the {foo} "slug" to the $foo argument (the names must match). If no value is given for {foo} (i.e. the URL is /app.php?controller=url) it will give it the default value of "bar".

You must specify at least as many "slugs" (URL variables) as there are required parameters, and at most as many as there are total parameters. Optional parameters do not have to be provided in the Routing definition, in which case they will take the default value given in the method definition.

There are a lot more things you can do to create powerful and flexible routes. For more information about how to take full advantage of this system, view Symfony's Routing Documentation.

The Controller Class

The final component of a controller is the class. For extensions, this should be located in a ./controller/ directory of your extension package. Core controllers should be in ./includes/controller/. While there are no hard-coded rules about this within the code, this helps keeps things organized and easily maintainable. This way, anyone will know where to look if they wish to change something, instead of having to search for it.

The controller class name should follow the class naming guidelines, and should match the class specified in the service definition. For an extension, the class should be called something like phpbb_ext_myext_controller_main if your controller is located at ./ext/myext/controller/main.php. Every controller class "must" implement the phpbb_controller_interface interface. The interface requires one method called handle()

Every controller should contain at least two methods. The first is the __construct() method. This is optional if you have no dependencies defined for your service; otherwise, this method must contain the same arguments as are listed for your service, in the same order. To go along with the above example, an appropriate __construct() method might look like:

public function __construct(phpbb_request $request$my_variable 'literal_text'$phpEx '.php')
$this->request $request;
$this->my_variable $my_variable;
$this->php_ext $phpEx;

The second method, which is absolutely required (because it is in the implemented interface), is handle(). You do not have to use the handle() method as long as you specify a method for each route in your routing.yml file. However, you must have a handle() method, even if it is empty, to satisfy the interface.

Controller methods must follow these guidelines:

  1. The method must be publicly accessible
  2. The method must return a Response object

The Response Object

If you choose to not use the Helper Object (see below), you will need to manually return a Symfony Response object. To use the Response object, you should have the following code at the top of your class file just above the class definition:

use Symfony\Component\HttpFoundation\Response;

By using the above statement, you can simply refer to the Response object as Reponse without having to resolve the namespace on every usage.

The Reponse object takes two arguments:

  1. Response message - This should be the full, rendered page source that will be output on the screen. There are more details about this below.
  2. Status code - This defaults to 200, which is the status code \"OK\". If you are sending a response about being unable to find some information, you would use the 404 (\"Not Found\") status. 403 would be used if the user lacks the appropriate permissions, and 500 would be for an unknown error.

The Helper Class

A new phpbb_controller_helper class is available to make it easier to do some common tasks.

The first method provided is called render(). It takes the template filename, the page title, and the status code as its arguments. The page title defaults to an empty string and the status code defaults to 200.

The second method is a shortcut for outputting an error called error(). It takes the error message and status code as its arguments. The status code defaults to 500. This should be used instead of trigger_error().

public function foo()

    if (
true === false)
$this->helper->error('True is somehow identical to false. The world is over.'500);

$this->helper->render('foo_body.html''Page Title');
// That is identical to doing:
    // page_header('Page Title');
    // $this->template->set_filenames(array(
    //     'body' => 'foo_body.html',
    // ));
    // page_footer(true, false, false);
    // return new Response($this->template->return_display('body'), 200);

As you can see, the helper class makes it much easier to perform common tasks with less lines of code. NOTE: The phpbb_controller_helper::render_template() method returns a Response object. You are welcome to get that value in a variable, manipulate the response, and then return the variable.