Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • SensioLabs Professional services to help you with Symfony
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by SensioLabs
  1. Home
  2. Documentation
  3. Book
  4. Controller
  • Documentation
  • Book
  • Reference
  • Bundles
  • Cloud

Table of Contents

  • Requests, Controller, Response Lifecycle
  • A Simple Controller
  • Mapping a URL to a Controller
    • Route Parameters as Controller Arguments
  • The Base Controller Class
    • Generating URLs
    • Redirecting
    • Rendering Templates
    • Accessing other Services
  • Managing Errors and 404 Pages
  • The Request object as a Controller Argument
  • Managing the Session
    • Flash Messages
  • The Request and Response Object
  • Creating Static Pages
  • Forwarding to Another Controller
  • Checking the Validity of a CSRF Token inside Controller
  • Final Thoughts
  • Learn more from the Cookbook

Controller

Edit this page

Warning: You are browsing the documentation for Symfony 2.3, which is no longer maintained.

Read the updated version of this page for Symfony 6.2 (the current stable version).

Controller

A controller is a PHP callable you create that takes information from the HTTP request and creates and returns an HTTP response (as a Symfony Response object). The response could be an HTML page, an XML document, a serialized JSON array, an image, a redirect, a 404 error or anything else you can dream up. The controller contains whatever arbitrary logic your application needs to render the content of a page.

See how simple this is by looking at a Symfony controller in action. This renders a page that prints the famous Hello world!:

1
2
3
4
5
6
use Symfony\Component\HttpFoundation\Response;

public function helloAction()
{
    return new Response('Hello world!');
}

The goal of a controller is always the same: create and return a Response object. Along the way, it might read information from the request, load a database resource, send an email, or set information on the user's session. But in all cases, the controller will eventually return the Response object that will be delivered back to the client.

There's no magic and no other requirements to worry about! Here are a few common examples:

  • Controller A prepares a Response object representing the content for the homepage of the site.
  • Controller B reads the {slug} placeholder from the request to load a blog entry from the database and creates a Response object displaying that blog. If the {slug} can't be found in the database, it creates and returns a Response object with a 404 status code.
  • Controller C handles the form submission of a contact form. It reads the form information from the request, saves the contact information to the database and emails the contact information to you. Finally, it creates a Response object that redirects the client's browser to the contact form "thank you" page.

Requests, Controller, Response Lifecycle

Every request handled by a Symfony project goes through the same simple lifecycle. The framework takes care of all the repetitive stuff: you just need to write your custom code in the controller function:

  1. Each request executes a single front controller file (e.g. app.php on production or app_dev.php on development) that bootstraps the application;
  2. The front controller's only job is to initialize Symfony's engine (called the Kernel) and pass it a Request object to handle;
  3. The Symfony core asks the router to inspect the request;
  4. The router matches the incoming URL to a specific route and returns information about the route, including the controller that should be executed;
  5. The correct controller from the matched route is executed and the code inside the controller creates and returns the appropriate Response object;
  6. The HTTP headers and content of the Response object are sent back to the client.

Creating a page is as easy as creating a controller (#5) and making a route that maps a URL to that controller (#4).

Note

Though similarly named, a "front controller" is different from the PHP functions called "controllers" talked about in this chapter. A front controller is a short PHP file that lives in your web/ directory 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). You'll likely never need to edit, view or worry about the front controllers in your application. The "controller class" is a convenient way to group several "controllers", also called actions, together in one class (e.g. updateAction(), deleteAction(), etc). So, a controller is a method inside a controller class. They hold your code which creates and returns the appropriate Response object.

A Simple Controller

While a controller can be any PHP callable (a function, method on an object, or a Closure), a controller is usually a method inside a controller class:

1
2
3
4
5
6
7
8
9
10
11
12
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\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.

This controller is pretty straightforward:

  • line 2: Symfony takes advantage of PHP's namespace functionality to namespace the entire controller class.
  • line 4: Symfony again takes advantage of PHP's namespace functionality: the use keyword imports the Response class, which the controller must return.
  • line 6: The class name is the concatenation of a name for the controller class (i.e. Hello) and the word Controller. This is a convention that provides consistency to controllers and allows them to be referenced only by the first part of the name (i.e. Hello) in the routing configuration.
  • line 8: Each action in a controller class is suffixed with Action and is referenced in the routing configuration by the action's name (e.g. index). In the next section, you'll create a route that maps a URI to this action. You'll learn how the route's placeholders ({name}) become arguments to the controller method ($name).
  • line 10: The controller creates and returns a Response object.

Mapping a URL to a Controller

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:

  • Annotations
  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class HelloController
{
    /**
     * @Route("/hello/{name}", name="hello")
     */
    public function indexAction($name)
    {
        return new Response('<html><body>Hello '.$name.'!</body></html>');
    }
}
1
2
3
4
5
# app/config/routing.yml
hello:
    path:      /hello/{name}
    # uses a special syntax to point to the controller - see note below
    defaults:  { _controller: AppBundle:Hello:index }
1
2
3
4
5
6
7
8
9
10
11
12
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing
        http://symfony.com/schema/routing/routing-1.0.xsd">

    <route id="hello" path="/hello/{name}">
        <!-- uses a special syntax to point to the controller - see note below -->
        <default key="_controller">AppBundle:Hello:index</default>
    </route>
</routes>
1
2
3
4
5
6
7
8
9
10
11
// app/config/routing.php
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

$collection = new RouteCollection();
$collection->add('hello', new Route('/hello/{name}', array(
    // uses a special syntax to point to the controller - see note below
    '_controller' => 'AppBundle:Hello:index',
)));

return $collection;

Now, you can go to /hello/ryan (e.g. http://localhost:8000/hello/ryan if you're using the built-in web server) and Symfony will execute the HelloController::indexAction() controller and pass in ryan for the $name variable. Creating a "page" means simply creating a controller method and an associated route.

Simple, right?

The AppBundle:Hello:index controller syntax

If you use the YAML or XML formats, you'll refer to the controller using a special shortcut syntax called the logical controller name which, for example, looks like AppBundle:Hello:index. For more details on the controller format, read Routing subtitle of the Routing chapter.

Route Parameters as Controller Arguments

You already know that the route points to the HelloController::indexAction() controller method that lives inside AppBundle. What's more interesting is the argument that is passed to that controller method:

1
2
3
4
5
6
7
8
9
10
11
// src/AppBundle/Controller/HelloController.php
// ...
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

/**
 * @Route("/hello/{name}", name="hello")
 */
public function indexAction($name)
{
    // ...
}

The controller has a single argument, $name, which corresponds to the {name} placeholder from the matched route (e.g. ryan if you go to /hello/ryan). When executing the controller, Symfony matches each argument with a placeholder from the route. So the value for {name} is passed to $name. Just make sure they the name of the placeholder is the same as the name of the argument variable.

Take the following more-interesting example, where the controller has two arguments:

  • Annotations
  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/AppBundle/Controller/HelloController.php
// ...

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class HelloController
{
    /**
     * @Route("/hello/{firstName}/{lastName}", name="hello")
     */
    public function indexAction($firstName, $lastName)
    {
        // ...
    }
}
1
2
3
4
# app/config/routing.yml
hello:
    path:      /hello/{firstName}/{lastName}
    defaults:  { _controller: AppBundle:Hello:index }
1
2
3
4
5
6
7
8
9
10
11
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing
        http://symfony.com/schema/routing/routing-1.0.xsd">

    <route id="hello" path="/hello/{firstName}/{lastName}">
        <default key="_controller">AppBundle:Hello:index</default>
    </route>
</routes>
1
2
3
4
5
6
7
8
9
10
// app/config/routing.php
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

$collection = new RouteCollection();
$collection->add('hello', new Route('/hello/{firstName}/{lastName}', array(
    '_controller' => 'AppBundle:Hello:index',
)));

return $collection;

Mapping route parameters to controller arguments is easy and flexible. Keep the following guidelines in mind while you develop.

  1. The order of the controller arguments does not matter

    Symfony matches the parameter names from the route to the variable names of the controller. The arguments of the controller could be totally reordered and still work perfectly:

    1
    2
    3
    4
    public function indexAction($lastName, $firstName)
    {
        // ...
    }
  2. Each required controller argument must match up with a routing parameter

    The following would throw a RuntimeException because there is no foo parameter defined in the route:

    1
    2
    3
    4
    public function indexAction($firstName, $lastName, $foo)
    {
        // ...
    }

    Making the argument optional, however, is perfectly ok. The following example would not throw an exception:

    1
    2
    3
    4
    public function indexAction($firstName, $lastName, $foo = 'bar')
    {
        // ...
    }
  3. Not all routing parameters need to be arguments on your controller

    If, for example, the lastName weren't important for your controller, you could omit it entirely:

    1
    2
    3
    4
    public function indexAction($firstName)
    {
        // ...
    }

Tip

You can also pass other variables from your route to your controller arguments. See How to Pass Extra Information from a Route to a Controller.

The Base Controller Class

For convenience, Symfony comes with an optional base Controller class. If you extend it, this won't change anything about how your controller works, but you'll get access to a number of helper methods and the service container (see Controller): an array-like object that gives you access to every useful object in the system. These useful objects are called services, and Symfony ships with a service object that can render Twig templates, another that can log messages and many more.

Add the use statement atop the Controller class and then modify HelloController to extend it:

1
2
3
4
5
6
7
8
9
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class HelloController extends Controller
{
    // ...
}

Helper methods are just shortcuts to using core Symfony functionality that's available to you with or without the use of the base Controller class. A great way to see the core functionality in action is to look in the Controller class.

See also

If you're curious about how a controller would work that did not extend this base Controller class, check out cookbook article Controllers as Services. This is optional, but can give you more control over the exact objects/dependencies that are injected into your controller.

Generating URLs

The generateUrl() method is just a helper method that generates the URL for a given route.

Redirecting

To redirect the user's browser to another page of your app, use the generateUrl() method in combination with another helper method called redirect() which takes a URL as an argument:

1
2
3
4
public function indexAction()
{
    return $this->redirect($this->generateUrl('homepage'));
}

By default, the redirect() method performs a 302 (temporary) redirect. To perform a 301 (permanent) redirect, modify the second argument:

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

To redirect to an external site, use redirect() and pass it the external URL:

1
2
3
4
public function indexAction()
{
    return $this->redirect('http://symfony.com/doc');
}

For more information, see the Routing chapter.

Tip

The redirect() method is simply a shortcut that creates a Response object that specializes in redirecting the user. It's equivalent to:

1
2
3
use Symfony\Component\HttpFoundation\RedirectResponse;

return new RedirectResponse($this->generateUrl('homepage'));

Rendering Templates

If you're serving HTML, you'll want to render a template. The render() method renders a template and puts that content into a Response object for you:

1
2
// renders app/Resources/views/hello/index.html.twig
return $this->render('hello/index.html.twig', array('name' => $name));

Templates can also live in deeper sub-directories. Just try to avoid creating unnecessarily deep structures:

1
2
3
4
// renders app/Resources/views/hello/greetings/index.html.twig
return $this->render('hello/greetings/index.html.twig', array(
    'name' => $name
));

Templates are a generic way to render content in any format. And while in most cases you'll use templates to render HTML content, a template can just as easily generate JavaScript, CSS, XML or any other format you can dream of. To learn how to render different templating formats read the Creating and Using Templates section of the Creating and Using Templates chapter.

The Symfony templating engine is explained in great detail in the Creating and Using Templates chapter.

Templating Naming Pattern

You can also put templates in the Resources/views directory of a bundle and reference them with a special shortcut syntax like @App/Hello/index.html.twig or @App/layout.html.twig. These would live in at Resources/views/Hello/index.html.twig and Resources/views/layout.html.twig inside the bundle respectively.

Accessing other Services

Symfony comes packed with a lot of useful objects, called services. These are used for rendering templates, sending emails, querying the database and any other "work" you can think of. When you install a new bundle, it probably brings in even more services.

When extending the base controller class, you can access any Symfony service via the get() method of the Controller class. Here are several common services you might need:

1
2
3
4
5
$templating = $this->get('templating');

$router = $this->get('router');

$mailer = $this->get('mailer');

What other services exist? To list all services, use the container:debug console command:

1
$ php app/console container:debug

For more information, see the Service Container chapter.

Managing Errors and 404 Pages

When things are not found, you should play well with the HTTP protocol and return a 404 response. To do this, you'll throw a special type of exception. If you're extending the base Controller class, do the following:

1
2
3
4
5
6
7
8
9
10
public function indexAction()
{
    // retrieve the object from database
    $product = ...;
    if (!$product) {
        throw $this->createNotFoundException('The product does not exist');
    }

    return $this->render(...);
}

The createNotFoundException() method is just a shortcut to create a special NotFoundHttpException object, which ultimately triggers a 404 HTTP response inside Symfony.

Of course, you're free to throw any Exception class in your controller - Symfony will automatically return a 500 HTTP response code.

1
throw new \Exception('Something went wrong!');

In every case, an error page is shown to the end user and a full debug error page is shown to the developer (i.e. when you're using the app_dev.php front controller - see Configuring Symfony (and Environments)).

You'll want to customize the error page your user sees. To do that, see the "How to Customize Error Pages" cookbook recipe.

The Request object as a Controller Argument

What if you need to read query parameters, grab a request header or get access to an uploaded file? All of that information is stored in Symfony's Request object. To get it in your controller, just add it as an argument and type-hint it with the Request class:

1
2
3
4
5
6
7
8
use Symfony\Component\HttpFoundation\Request;

public function indexAction($firstName, $lastName, Request $request)
{
    $page = $request->query->get('page', 1);

    // ...
}

Managing the Session

Symfony provides a nice session object that you can use to store information about the user (be it a real person using a browser, a bot, or a web service) between requests. By default, Symfony stores the attributes in a cookie by using the native PHP sessions.

To retrieve the session, call getSession() method on the Request object. This method returns a SessionInterface with easy methods for storing and fetching things from the session:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Symfony\Component\HttpFoundation\Request;

public function indexAction(Request $request)
{
    $session = $request->getSession();

    // store an attribute for reuse during a later user request
    $session->set('foo', 'bar');

    // get the attribute set by another controller in another request
    $foobar = $session->get('foobar');

    // use a default value if the attribute doesn't exist
    $filters = $session->get('filters', array());
}

Stored attributes remain in the session for the remainder of that user's session.

Flash Messages

You can also store special messages, called "flash" messages, on the user's session. By design, flash messages are meant to be used exactly once: they vanish from the session automatically as soon as you retrieve them. This feature makes "flash" messages particularly great for storing user notifications.

For example, imagine you're processing a form submission:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use Symfony\Component\HttpFoundation\Request;

public function updateAction(Request $request)
{
    $form = $this->createForm(...);

    $form->handleRequest($request);

    if ($form->isValid()) {
        // do some sort of processing

        $request->getSession()->getFlashBag()->add(
            'notice',
            'Your changes were saved!'
        );

        return $this->redirect($this->generateUrl(...));
    }

    return $this->render(...);
}

After processing the request, the controller sets a flash message in the session and then redirects. The message key (notice in this example) can be anything: you'll use this key to retrieve the message.

In the template of the next page (or even better, in your base layout template), read any flash messages from the session:

  • Twig
  • PHP
1
2
3
4
5
{% for flash_message in app.session.flashBag.get('notice') %}
    <div class="flash-notice">
        {{ flash_message }}
    </div>
{% endfor %}
1
2
3
4
5
<?php foreach ($view['session']->getFlash('notice') as $message): ?>
    <div class="flash-notice">
        <?php echo "<div class='flash-error'>$message</div>" ?>
    </div>
<?php endforeach ?>

Note

It's common to use notice, warning and error as the keys of the different types of flash messages, but you can use any key that fits your needs.

Tip

You can use the peek() method instead to retrieve the message while keeping it in the bag.

The Request and Response Object

As mentioned earlier, the framework will pass the Request object to any controller argument that is type-hinted with the Request class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use Symfony\Component\HttpFoundation\Request;

public function indexAction(Request $request)
{
    $request->isXmlHttpRequest(); // is it an Ajax request?

    $request->getPreferredLanguage(array('en', 'fr'));

    // retrieve GET and POST variables respectively
    $request->query->get('page');
    $request->request->get('page');

    // retrieve SERVER variables
    $request->server->get('HTTP_HOST');

    // retrieves an instance of UploadedFile identified by foo
    $request->files->get('foo');

    // retrieve a COOKIE value
    $request->cookies->get('PHPSESSID');

    // retrieve an HTTP request header, with normalized, lowercase keys
    $request->headers->get('host');
    $request->headers->get('content_type');
}

The Request class has several public properties and methods that return any information you need about the request.

Like the Request, the Response object has also a public headers property. This is a ResponseHeaderBag that has some nice methods for getting and setting response headers. The header names are normalized so that using Content-Type is equivalent to content-type or even content_type.

The only requirement for a controller is to return a Response object. The Response class is an abstraction around the HTTP response - the text-based message filled with headers and content that's sent back to the client:

1
2
3
4
5
6
7
8
use Symfony\Component\HttpFoundation\Response;

// create a simple Response with a 200 status code (the default)
$response = new Response('Hello '.$name, 200);

// create a JSON-response with a 200 status code
$response = new Response(json_encode(array('name' => $name)));
$response->headers->set('Content-Type', 'application/json');

There are also special classes to make certain kinds of responses easier:

  • For JSON, there is JsonResponse. See The HttpFoundation Component.
  • For files, there is BinaryFileResponse. See The HttpFoundation Component.
  • For streamed responses, there is StreamedResponse. See The HttpFoundation Component.

See also

Now that you know the basics you can continue your research on Symfony Request and Response object in the HttpFoundation component documentation.

Creating Static Pages

You can create a static page without even creating a controller (only a route and template are needed). See cookbook article How to Render a Template without a custom Controller.

Forwarding to Another Controller

Though not very common, you can also forward to another controller internally with the forward() method. Instead of redirecting the user's browser, this makes an "internal" sub-request and calls the defined controller. The forward() method returns the Response object that's returned from that controller:

1
2
3
4
5
6
7
8
9
10
11
public function indexAction($name)
{
    $response = $this->forward('AppBundle:Something:fancy', array(
        'name'  => $name,
        'color' => 'green',
    ));

    // ... further modify the response or return it directly

    return $response;
}

The array passed to the method becomes the arguments for the resulting controller. The target controller method might look something like this:

1
2
3
4
public function fancyAction($name, $color)
{
    // ... create and return a Response object
}

Just like when creating a controller for a route, the order of the arguments of fancyAction() doesn't matter: the matching is done by name.

Checking the Validity of a CSRF Token inside Controller

You may sometimes want to use CSRF protection in a controller where you don't have a Symfony form.

If, for example, you're doing a DELETE action, you can use the isCsrfTokenValid() method to check the CSRF token:

1
2
3
4
5
6
7
8
$csrf = $this->container->get('form.csrf_provider');

$intention = 'authenticate';
$token = $csrf->generateCsrfToken($intention);

if (!$csrf->isCsrfTokenValid($intention, $token)) {
    // CSRF token invalid! Do something, like redirect with an error.
}

Final Thoughts

Whenever you create a page, you'll ultimately need to write some code that contains the logic for that page. In Symfony, this is called a controller, and it's a PHP function where you can do anything in order to return the final Response object that will be returned to the user.

To make life easier, you can choose to extend a base Controller class, which contains shortcut methods for many common controller tasks. For example, since you don't want to put HTML code in your controller, you can use the render() method to render and return the content from a template.

In other chapters, you'll see how the controller can be used to persist and fetch objects from a database, process form submissions, handle caching and more.

Learn more from the Cookbook

  • How to Customize Error Pages
  • How to Define Controllers as Services
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version
    We stand with Ukraine.
    Version:
    Take the exam at home

    Take the exam at home

    Code consumes server resources. Blackfire tells you how

    Code consumes server resources. Blackfire tells you how

    Symfony footer

    ↓ Our footer now uses the colors of the Ukrainian flag because Symfony stands with the people of Ukraine.

    Avatar of Xavier Lacot, a Symfony contributor

    Thanks Xavier Lacot (@xavier) for being a Symfony contributor

    2 commits • 28 lines changed

    View all contributors that help us make Symfony

    Become a Symfony contributor

    Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

    Learn how to contribute

    Symfony™ is a trademark of Symfony SAS. All rights reserved.

    • What is Symfony?

      • Symfony at a Glance
      • Symfony Components
      • Case Studies
      • Symfony Releases
      • Security Policy
      • Logo & Screenshots
      • Trademark & Licenses
      • symfony1 Legacy
    • Learn Symfony

      • Symfony Docs
      • Symfony Book
      • Reference
      • Bundles
      • Best Practices
      • Training
      • eLearning Platform
      • Certification
    • Screencasts

      • Learn Symfony
      • Learn PHP
      • Learn JavaScript
      • Learn Drupal
      • Learn RESTful APIs
    • Community

      • SymfonyConnect
      • Support
      • How to be Involved
      • Code of Conduct
      • Events & Meetups
      • Projects using Symfony
      • Downloads Stats
      • Contributors
      • Backers
    • Blog

      • Events & Meetups
      • A week of symfony
      • Case studies
      • Cloud
      • Community
      • Conferences
      • Diversity
      • Documentation
      • Living on the edge
      • Releases
      • Security Advisories
      • SymfonyInsight
      • Twig
      • SensioLabs
    • Services

      • SensioLabs services
      • Train developers
      • Manage your project quality
      • Improve your project performance
      • Host Symfony projects

      Deployed on

    Follow Symfony

    Search by Algolia