Symfony: 7 – Controller

Controller

Every request is handled exactly the same way. Instead of individual URLs executing different PHP files, the front controller is always executed, and the routing of different URLs to different parts of your application is done internally. Symfony follows the same simple pattern for every request:

The Symfony Application Flow
The Symfony Application Flow
  • Each request is handled by a single front controller file (e.g. app.php or app_dev.php) that bootstraps the application
  • The Router reads information from the request (e.g. the URI), finds a route that matches that information, and reads the _controller parameter from the route
  • The controller from the matched route is executed and the code inside the controller creates and returns a Response object
  • The HTTP headers and content of the Response object are sent back to the client

Though similarly named, a “front controller” is different from the “controllers”. A front controller is a short PHP file that lives in your web directory and through which all requests are directed. A typical application will have a production front controller (e.g. app.php) and a development front controller (e.g. app_dev.php).

While a controller can be any PHP callable (a function, method on an object, or a Closure), in Symfony2, a controller is usually a single method inside a controller object. Controllers are also called actions.

namespace Test\HelloBundle\Controller;
 
use Symfony\Component\HttpFoundation\Response;
 
class HelloController
{
  public function indexAction($name)
  {
    return new Response('<html><body>Hello '.$name.'!</body></html>');
  }
}

The controller is the indexAction method, which lives inside a controller class (HelloController). Don’t be confused by the naming: a controller class is simply a convenient way to group several controllers/actions together. Typically, the controller class will house several controllers/actions (e.g. updateAction, deleteAction, etc).

The new controller returns a simple HTML page. To actually view this page in your browser, you need to create a route, which maps a specific URL path to the controller:

# app/config/routing.yml
hello:
  path: /hello/{name}
  defaults: { _controller: TestHelloBundle:Hello:index }

This example places the routing configuration directly in the app/config/ directory. A better way to organize your routes is to place each route in the bundle it belongs to.

Every route must have a _controller parameter, which dictates which controller should be executed when that route is matched. This parameter uses a simple string pattern called the logical controller name, which Symfony maps to a specific PHP method and class. The pattern has three parts, each separated by a colon: bundle:controller:action. Symfony adds the string Controller to the class name (Hello => HelloController) and Action to the method name (index => indexAction). Another way of referring to a controller is the method that uses just one colon separator (e.g. service_name:indexAction) and refers to the controller as a service (Symfony Cookbook: Controllers as Services).

When executing your controller, Symfony matches each argument of the controller with a parameter from the matched route:

# app/config/routing.yml
hello:
  path: /hello/{firstName}/{lastName}
  defaults: { _controller: TestHelloBundle:Hello:index, color: green }
 
public function indexAction($firstName, $lastName, $color)
{
  // ...
}

Both placeholder variables ({firstName}, {lastName}) as well as the default color variable are available as arguments in the controller. When a route is matched, the placeholder variables are
merged with the defaults to make one array that’s available to your controller. Keep the following guidelines in mind while you develop:

  • The order of the controller arguments does not matter
  • Each required controller argument must match up with a routing parameter
  • Not all routing parameters need to be arguments on your controller

The first example would throw a RuntimeException because there is no foo parameter defined in the route while the second example would not:

public function indexAction($firstName, $lastName, $color, $foo)
{
  // ...
}
 
public function indexAction($firstName, $lastName, $color, $foo = 'bar')
{
  // ...
}

You can also have Symfony pass you the Request object as an argument to your controller. This is especially convenient when you’re working with forms, for example:

 
use Symfony\Component\HttpFoundation\Request;
 
public function updateAction(Request $request)
{
  $form = $this->createForm(...);
  $form->handleRequest($request);
  // ...
}

If you’re rendering a simple template that doesn’t need any data passed into it, you can avoid creating the controller entirely, by using the built-in FrameworkBundle:Template:template controller. This will simply render whatever template you’ve passed as the template default value:

test_privacy:
  path: /privacy
  defaults:
    _controller: FrameworkBundle:Template:template
    template: 'TestBundle:Static:privacy.html.twig'

Since templates that are rendered in this way are typically static, it might make sense to cache them. By configuring a few other variables in your route, you can control exactly how your page is cached:

test_privacy:
  path: /privacy
  defaults:
  _controller: FrameworkBundle:Template:template
  template: 'TestBundle:Static:privacy.html.twig'
  maxAge: 86400
  sharedMaxAge: 86400

Symfony2 comes with a base Controller class that assists with some of the most common controller tasks and gives your controller class access to any resource it might need. Extending the base class is optional in Symfony. By extending this Controller class, you can take advantage of several helper methods:

namespace Test\HelloBundle\Controller;
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
 
class HelloController extends Controller
{
  public function indexAction($name)
  {
    return new Response('<html><body>Hello '.$name.'!</body></html>');
  }
}

Most controllers will perform the same basic tasks over and over again. These tasks, such as redirecting, forwarding, rendering templates and accessing core services, are very easy to manage. If you want to redirect the user to another page, use the redirect() method. By default, the redirect() method performs a 302 (temporary) redirect. To perform a 301 (permanent) redirect, modify the second argument:

public function indexAction()
{
  return $this->redirect($this->generateUrl('homepage'));
}
 
public function indexAction()
{
  return $this->redirect($this->generateUrl('homepage'), 301);
}

You can also easily forward to another controller internally with the forward() method:

public function indexAction($name)
{
  $response = $this->forward('TestHelloBundle:Hello:fancy', array(
    'name' => $name,
    'color' => 'green',
  ));
 
  // ... further modify the response or return it directly
 
  return $response;
}

Most controllers will ultimately render a template that’s responsible for generating the HTML (or other format) for the controller. The render() method renders a template and returns its content. The templating service can also be used directly:

return $this->render(
  'TestHelloBundle:Hello:index.html.twig',
  array('name' => $name)
);
 
$templating = $this->get('templating');
$content = $templating->render(
  'TestHelloBundle:Hello:index.html.twig',
  array('name' => $name)
);

You can access any service via the get() method:

$request = $this->getRequest();
$templating = $this->get('templating');
$router = $this->get('router');
$mailer = $this->get('mailer');

There are countless other services available. To list all available services, use the container:debug console command:

$ php app/console container:debug

You can also return a 404 response. To do this, you’ll throw a special type of exception:

public function indexAction()
{
  // retrieve the object from database
  $product = ...;
 
  if (!$product) {
    throw $this->createNotFoundException('The product does not exist');
  }
 
  return $this->render(...);
}

Symfony2 provides a nice session object that you can use to store information between requests. By default, Symfony2 stores the attributes in a cookie by using the native PHP sessions. Storing and retrieving information from the session can be easily achieved from any controller:

$session = $this->getRequest()->getSession();
 
// store an attribute for reuse during a later user request
$session->set('foo', 'bar');
 
// in another controller for another request
$foo = $session->get('foo');
 
// use a default value if the key doesn't exist
$filters = $session->get('filters', array());

You can also store small messages that will be stored on the user’s session for exactly one additional request. This is useful when processing a form: you want to redirect and have a special message shown on the next request. These types of messages are called “flash” messages.

public function updateAction()
{
  $form = $this->createForm(...);
 
  $form->handleRequest($this->getRequest());
 
  if ($form->isValid()) {
    // do some sort of processing
    $this->get('session')->getFlashBag()->add(
      'notice',
      'Your changes were saved!'
    );
 
    return $this->redirect($this->generateUrl(...));
  }
 
  return $this->render(...);
}

After processing the request, the controller sets a notice flash message and then redirects. In the template of the next action, the following code could be used to render the notice message:

{% for flashMessage in app.session.flashbag.get('notice') %}
<div class="flash-notice">
    {{ flashMessage }}</div>
{% endfor %}

The Response Object

The only requirement for a controller is to return a Response object.

use Symfony\Component\HttpFoundation\Response;
 
// create a simple Response with a 200 status code (the default)
$response = new Response('Hello '.$name, Response::HTTP_OK);
 
// create a JSON-response with a 200 status code
$response = new Response(json_encode(array('name' => $name)));
$response->headers->set('Content-Type', 'application/json');

The Request Object

Besides the values of the routing placeholders, the controller also has access to the Request object.

use Symfony\Component\HttpFoundation\Request;
 
public function indexAction(Request $request)
{
    $request->isXmlHttpRequest(); // is it an Ajax request?
 
    $request->getPreferredLanguage(array('en', 'fr'));
 
    $request->query->get('page'); // get a $_GET parameter
 
    $request->request->get('page'); // get a $_POST parameter
}

Leave a Reply