Development Wiki


From phpBB Development Wiki

Revision as of 21:06, 17 September 2012 by Imkingdavid (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 Controller routing system 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 the phpbb_controller_interface and is responsible for serving the user content based on what they 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.

#Comments use hash tags
#Do NOT use tabs for indentation. Instead, 4 spaces = 1 tab
    class: phpbb_my_class
        - @request
        - literal_text
        - %core.php_ext%

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 and controller method are instantiated using the required dependencies.

#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/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/url) it will give it the default value of "bar".

Note that the method name is optional; if it is not specified, the default handle() method will be called. However, because the handle() method is defined without arguments in the phpbb_controller_interface class, you cannot route to that method if you are specifying arguments in the URL pattern. Also keep in mind that 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, and will then 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.

All controller methods must return a Response instance. 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 - Should be an empty string on success; otherwise, its error message will be sent to trigger_error() automatically. Do not make direct calls to trigger_error from within your controller.
  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.

A controller method that does nothing but return an successful response looks like this:

public function controller()
    return new