single: Controller
A controller is a PHP function you create that reads information from the Request
object and creates and returns a Response
object. The response could be an HTML page, JSON, XML, a file download, a redirect, a 404 error or anything else you can dream up. The controller executes whatever arbitrary logic your application needs to render the content of a page.
Tip
If you haven't already created your first working page, check out /page_creation
and then come back!
single: Controller; Simple example
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:
// src/Controller/LuckyController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class LuckyController
{
/**
* @Route("/lucky/number/{max}", name="app_lucky_number")
*/
public function number($max)
{
$number = mt_rand(0, $max);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
The controller is the number()
method, which lives inside a controller class LuckyController
.
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 theResponse
class, which the controller must return. - line 7: The class can technically be called anything - but should end in the word
Controller
- line 12: The action method is allowed to have a
$max
argument thanks to the{max}
wildcard in the route </routing>
. - line 16: The controller creates and returns a
Response
object.
single: Controller; Routes and controllers
In order to view the result of this controller, you need to map a URL to it via a route. This was done above with the @Route("/lucky/number/{max}")
route annotation <annotation-routes>
.
To see your page, go to this URL in your browser:
For more information on routing, see /routing
.
single: Controller; Base controller class
To make life nicer, Symfony comes with two optional base Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller
and Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController
. You can extend either to get access to some helper methods.
Add the use
statement atop your controller class and then modify LuckyController
to extend it:
// src/Controller/LuckyController.php
namespace App\Controller;
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
- class LuckyController
+ class LuckyController extends Controller
{
// ...
}
That's it! You now have access to methods like $this->render() <controller-rendering-templates>
and many others that you'll learn about next.
Tip
What's the difference between Controller
or AbstractController
? Not much: both are identical, except that AbstractController
is more restrictive: it does not allow you to access services directly via $this->get()
or $this->container->get()
. This forces you to write more robust code to access services. But if you do need direct access to the container, using Controller
is fine.
single: Controller; Redirecting
The Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::generateUrl
method is just a helper method that generates the URL for a given route:
$url = $this->generateUrl('app_lucky_number', array('max' => 10));
If you want to redirect the user to another page, use the redirectToRoute()
and redirect()
methods:
use Symfony\Component\HttpFoundation\RedirectResponse;
// ...
public function index()
{
// redirects to the "homepage" route
return $this->redirectToRoute('homepage');
// redirectToRoute is a shortcut for:
// return new RedirectResponse($this->generateUrl('homepage'));
// does a permanent - 301 redirect
return $this->redirectToRoute('homepage', array(), 301);
// redirect to a route with parameters
return $this->redirectToRoute('app_lucky_number', array('max' => 10));
// redirects externally
return $this->redirect('http://symfony.com/doc');
}
Caution
The redirect()
method does not check its destination in any way. If you redirect to a URL provided by end-users, your application may be open to the unvalidated redirects security vulnerability.
single: Controller; 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:
// renders templates/lucky/number.html.twig
return $this->render('lucky/number.html.twig', array('name' => $name));
Templating and Twig are explained more in the Creating and Using Templates article </templating>
.
single: Controller; Accessing services
Symfony comes packed with a lot of useful objects, called services </service_container>
. These are used for rendering templates, sending emails, querying the database and any other "work" you can think of.
If you need a service in a controller, just type-hint an argument with its class (or interface) name. Symfony will automatically pass you the service you need:
use Psr\Log\LoggerInterface
// ...
/**
* @Route("/lucky/number/{max}")
*/
public function number($max, LoggerInterface $logger)
{
$logger->info('We are logging!');
// ...
}
Awesome!
What other services can you type-hint? To see them, use the debug:autowiring
console command:
$ php bin/console debug:autowiring
If you need control over the exact value of an argument, you can bind <services-binding>
the argument by its name:
# config/services.yaml
services:
# ...
# explicitly configure the service
App\Controller\LuckyController:
public: true
bind:
# for any $logger argument, pass this specific service
$logger: '@monolog.logger.doctrine'
<!-- config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- ... -->
<!-- Explicitly configure the service -->
<service id="App\Controller\LuckyController" public="true">
<bind key="$logger"
type="service"
id="monolog.logger.doctrine"
/>
</service>
</services>
</container>
// config/services.php
use App\Controller\LuckyController;
use Symfony\Component\DependencyInjection\Reference;
$container->register(LuckyController::class)
->setPublic(true)
->setBindings(array(
'$logger' => new Reference('monolog.logger.doctrine'),
))
;
You can of course also use normal constructor injection <services-constructor-injection>
in your controllers.
Caution
You can only pass services to your controller arguments in this way. It's not possible, for example, to pass a service parameter as a controller argument, even by using bind
. If you need a parameter, use the $this->getParameter('kernel.debug')
shortcut or pass the value through your controller's __construct()
method and specify its value with bind
.
For more information about services, see the /service_container
article.
single: Controller; Managing errors single: Controller; 404 pages
When things are not found, you should return a 404 response. To do this, throw a special type of exception:
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
// ...
public function index()
{
// retrieve the object from database
$product = ...;
if (!$product) {
throw $this->createNotFoundException('The product does not exist');
// the above is just a shortcut for:
// throw new NotFoundHttpException('The product does not exist');
}
return $this->render(...);
}
The Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerTrait::createNotFoundException
method is just a shortcut to create a special Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException
object, which ultimately triggers a 404 HTTP response inside Symfony.
Of course, you can throw any Exception
class in your controller: Symfony will automatically return a 500 HTTP response code.
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 in "Debug" mode - see page-creation-environments
).
To customize the error page that's shown to the user, see the /controller/error_pages
article.
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:
use Symfony\Component\HttpFoundation\Request;
public function index(Request $request, $firstName, $lastName)
{
$page = $request->query->get('page', 1);
// ...
}
Keep reading <request-object-info>
for more information about using the Request object.
single: Controller; The session single: Session
Symfony provides a session service that you can use to store information about the user between requests. Session storage and other configuration can be controlled under the framework.session configuration <config-framework-session>
.
First, activate the session by uncommenting the session
key in config/packages/framework.yaml
:
# config/packages/framework.yaml
framework:
# ...
- #session:
- # # The native PHP session handler will be used
- # handler_id: ~
+ session:
+ # The native PHP session handler will be used
+ handler_id: ~
# ...
To get the session, add an argument and type-hint it with Symfony\\Component\\HttpFoundation\\Session\\SessionInterface
:
use Symfony\Component\HttpFoundation\Session\SessionInterface;
public function index(SessionInterface $session)
{
// stores an attribute for reuse during a later user request
$session->set('foo', 'bar');
// gets the attribute set by another controller in another request
$foobar = $session->get('foobar');
// uses 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.
Tip
Every SessionInterface
implementation is supported. If you have your own implementation, type-hint this in the argument instead.
For more info, see /session
.
single: 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 </forms>
submission:
use Symfony\Component\HttpFoundation\Request;
public function update(Request $request)
{
// ...
if ($form->isSubmitted() && $form->isValid()) {
// do some sort of processing
$this->addFlash(
'notice',
'Your changes were saved!'
);
// $this->addFlash() is equivalent to $request->getSession()->getFlashBag()->add()
return $this->redirectToRoute(...);
}
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 using app.flashes()
:
{# templates/base.html.twig #}
{# you can read and display just one flash message type... #}
{% for message in app.flashes('notice') %}
<div class="flash-notice">
{{ message }}
</div>
{% endfor %}
{# ...or you can read and display every flash message available #}
{% for label, messages in app.flashes %}
{% for message in messages %}
<div class="flash-{{ label }}">
{{ message }}
</div>
{% endfor %}
{% endfor %}
<!-- templates/base.html.php -->
// you can read and display just one flash message type...
<?php foreach ($view['session']->getFlashBag()->get('notice') as $message): ?>
<div class="flash-notice">
<?php echo $message ?>
</div>
<?php endforeach ?>
// ...or you can read and display every flash message available
<?php foreach ($view['session']->getFlashBag()->all() as $type => $flash_messages): ?>
<?php foreach ($flash_messages as $flash_message): ?>
<div class="flash-<?php echo $type ?>">
<?php echo $message ?>
</div>
<?php endforeach ?>
<?php endforeach ?>
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 Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peek
method instead to retrieve the message while keeping it in the bag.
single: Controller; Response object
As mentioned earlier <controller-request-argument>
, Symfony will pass the Request
object to any controller argument that is type-hinted with the Request
class:
use Symfony\Component\HttpFoundation\Request;
public function index(Request $request)
{
$request->isXmlHttpRequest(); // is it an Ajax request?
$request->getPreferredLanguage(array('en', 'fr'));
// retrieves GET and POST variables respectively
$request->query->get('page');
$request->request->get('page');
// retrieves SERVER variables
$request->server->get('HTTP_HOST');
// retrieves an instance of UploadedFile identified by foo
$request->files->get('foo');
// retrieves a COOKIE value
$request->cookies->get('PHPSESSID');
// retrieves 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 Symfony\\Component\\HttpFoundation\\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:
use Symfony\Component\HttpFoundation\Response;
// creates a simple Response with a 200 status code (the default)
$response = new Response('Hello '.$name, Response::HTTP_OK);
// creates a CSS-response with a 200 status code
$response = new Response('<style> ... </style>');
$response->headers->set('Content-Type', 'text/css');
There are special classes that make certain kinds of responses easier. Some of these are mentioned below. To learn more about the Request
and Response
(and special Response
classes), see the HttpFoundation component documentation <component-http-foundation-request>
.
To return JSON from a controller, use the json()
helper method. This returns a special JsonResponse
object that encodes the data automatically:
// ...
public function index()
{
// returns '{"username":"jane.doe"}' and sets the proper Content-Type header
return $this->json(array('username' => 'jane.doe'));
// the shortcut defines three optional arguments
// return $this->json($data, $status = 200, $headers = array(), $context = array());
}
If the serializer service </serializer>
is enabled in your application, it will be used to serialize the data to JSON. Otherwise, the json_encode
function is used.
You can use the Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::file
helper to serve a file from inside a controller:
public function fileAction()
{
// send the file contents and force the browser to download it
return $this->file('/path/to/some_file.pdf');
}
The file()
helper provides some arguments to configure its behavior:
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
public function fileAction()
{
// load the file from the filesystem
$file = new File('/path/to/some_file.pdf');
return $this->file($file);
// rename the downloaded file
return $this->file($file, 'custom_name.pdf');
// display the file contents in the browser instead of downloading it
return $this->file('invoice_3241.pdf', 'my_invoice.pdf', ResponseHeaderBag::DISPOSITION_INLINE);
}
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'll probably extend the base Controller
class because this gives access to shortcut methods (like render()
and redirectToRoute()
).
In other articles, you'll learn how to use specific services from inside your controller that will help you persist and fetch objects from a database, process form submissions, handle caching and more.
Next, learn all about rendering templates with Twig </templating>
.
templating
controller/*