Controller

A controller is a PHP function you create that reads information from theRequest object and creates and returns a Response object. The response couldbe an HTML page, JSON, XML, a file download, a redirect, a 404 error or anythingelse. The controller executes whatever arbitrary logic your application needsto render the content of a page.

Tip

If you haven't already created your first working page, check outCreate your First Page in Symfony and then come back!

A Simple Controller

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

  1. // src/Controller/LuckyController.php
  2. namespace App\Controller;
  3.  
  4. use Symfony\Component\HttpFoundation\Response;
  5. use Symfony\Component\Routing\Annotation\Route;
  6.  
  7. class LuckyController
  8. {
  9. /**
  10. * @Route("/lucky/number/{max}", name="app_lucky_number")
  11. */
  12. public function number($max)
  13. {
  14. $number = random_int(0, $max);
  15.  
  16. return new Response(
  17. '<html><body>Lucky number: '.$number.'</body></html>'
  18. );
  19. }
  20. }

The controller is the number() method, which lives inside thecontroller class LuckyController.

This controller is pretty straightforward:

  • line 2: Symfony takes advantage of PHP's namespace functionality tonamespace the entire controller class.
  • line 4: Symfony again takes advantage of PHP's namespace functionality:the use keyword imports the Response class, which the controllermust return.
  • line 7: The class can technically be called anything, but it's suffixedwith Controller by convention.
  • line 12: The action method is allowed to have a $max argument thanks to the{max} wildcard in the route.
  • line 16: The controller creates and returns a Response object.

Mapping a URL to a Controller

In order to view the result of this controller, you need to map a URL to it viaa route. This was done above with the @Route("/lucky/number/{max}")route annotation.

To see your page, go to this URL in your browser:

For more information on routing, see Routing.

The Base Controller Class & Services

To aid development, Symfony comes with an optional base controller class calledAbstractController.It can be extended to gain access to helper methods.

Add the use statement atop your controller class and then modifyLuckyController to extend it:

  1. // src/Controller/LuckyController.php
  2. namespace App\Controller;
  3.  
  4. + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  5.  
  6. - class LuckyController
  7. + class LuckyController extends AbstractController
  8. {
  9. // ...
  10. }

That's it! You now have access to methods like $this->render()and many others that you'll learn about next.

Generating URLs

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

  1. $url = $this->generateUrl('app_lucky_number', ['max' => 10]);

Redirecting

If you want to redirect the user to another page, use the redirectToRoute()and redirect() methods:

  1. use Symfony\Component\HttpFoundation\RedirectResponse;
  2.  
  3. // ...
  4. public function index()
  5. {
  6. // redirects to the "homepage" route
  7. return $this->redirectToRoute('homepage');
  8.  
  9. // redirectToRoute is a shortcut for:
  10. // return new RedirectResponse($this->generateUrl('homepage'));
  11.  
  12. // does a permanent - 301 redirect
  13. return $this->redirectToRoute('homepage', [], 301);
  14.  
  15. // redirect to a route with parameters
  16. return $this->redirectToRoute('app_lucky_number', ['max' => 10]);
  17.  
  18. // redirects to a route and maintains the original query string parameters
  19. return $this->redirectToRoute('blog_show', $request->query->all());
  20.  
  21. // redirects externally
  22. return $this->redirect('http://symfony.com/doc');
  23. }

Caution

The redirect() method does not check its destination in any way. If youredirect to a URL provided by end-users, your application may be opento the unvalidated redirects security vulnerability.

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 Responseobject for you:

  1. // renders templates/lucky/number.html.twig
  2. return $this->render('lucky/number.html.twig', ['number' => $number]);

Templating and Twig are explained more in theCreating and Using Templates article.

Fetching Services

Symfony comes packed with a lot of useful objects, called services.These are used for rendering templates, sending emails, querying the database andany other "work" you can think of.

If you need a service in a controller, type-hint an argument with its class(or interface) name. Symfony will automatically pass you the service you need:

  1. use Psr\Log\LoggerInterface;
  2. // ...
  3.  
  4. /**
  5. * @Route("/lucky/number/{max}")
  6. */
  7. public function number($max, LoggerInterface $logger)
  8. {
  9. $logger->info('We are logging!');
  10. // ...
  11. }

Awesome!

What other services can you type-hint? To see them, use the debug:autowiring consolecommand:

  1. $ php bin/console debug:autowiring

If you need control over the exact value of an argument, you can bindthe argument by its name:

  • YAML
  1. # config/services.yaml
  2. services:
  3. # ...
  4.  
  5. # explicitly configure the service
  6. App\Controller\LuckyController:
  7. public: true
  8. bind:
  9. # for any $logger argument, pass this specific service
  10. $logger: '@monolog.logger.doctrine'
  11. # for any $projectDir argument, pass this parameter value
  12. $projectDir: '%kernel.project_dir%'
  • XML
  1. <!-- config/services.xml -->
  2. <?xml version="1.0" encoding="UTF-8" ?>
  3. <container xmlns="http://symfony.com/schema/dic/services"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation="http://symfony.com/schema/dic/services
  6. https://symfony.com/schema/dic/services/services-1.0.xsd">
  7.  
  8. <services>
  9. <!-- ... -->
  10.  
  11. <!-- Explicitly configure the service -->
  12. <service id="App\Controller\LuckyController" public="true">
  13. <bind key="$logger"
  14. type="service"
  15. id="monolog.logger.doctrine"
  16. />
  17. <bind key="$projectDir">%kernel.project_dir%</bind>
  18. </service>
  19. </services>
  20. </container>
  • PHP
  1. // config/services.php
  2. use App\Controller\LuckyController;
  3. use Symfony\Component\DependencyInjection\Reference;
  4.  
  5. $container->register(LuckyController::class)
  6. ->setPublic(true)
  7. ->setBindings([
  8. '$logger' => new Reference('monolog.logger.doctrine'),
  9. '$projectDir' => '%kernel.project_dir%'
  10. ])
  11. ;

Like with all services, you can also use regular constructor injectionin your controllers.

For more information about services, see the Service Container article.

Generating Controllers

To save time, you can install Symfony Maker and tell Symfony to generate anew controller class:

  1. $ php bin/console make:controller BrandNewController
  2.  
  3. created: src/Controller/BrandNewController.php
  4. created: templates/brandnew/index.html.twig

If you want to generate an entire CRUD from a Doctrine entity,use:

  1. $ php bin/console make:crud Product
  2.  
  3. created: src/Controller/ProductController.php
  4. created: src/Form/ProductType.php
  5. created: templates/product/_delete_form.html.twig
  6. created: templates/product/_form.html.twig
  7. created: templates/product/edit.html.twig
  8. created: templates/product/index.html.twig
  9. created: templates/product/new.html.twig
  10. created: templates/product/show.html.twig

New in version 1.2: The make:crud command was introduced in MakerBundle 1.2.

Managing Errors and 404 Pages

When things are not found, you should return a 404 response. To do this, throw aspecial type of exception:

  1. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  2.  
  3. // ...
  4. public function index()
  5. {
  6. // retrieve the object from database
  7. $product = ...;
  8. if (!$product) {
  9. throw $this->createNotFoundException('The product does not exist');
  10.  
  11. // the above is just a shortcut for:
  12. // throw new NotFoundHttpException('The product does not exist');
  13. }
  14.  
  15. return $this->render(...);
  16. }

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

If you throw an exception that extends or is an instance ofHttpException, Symfony willuse the appropriate HTTP status code. Otherwise, the response will have a 500HTTP status code:

  1. // this exception ultimately generates a 500 status error
  2. throw new \Exception('Something went wrong!');

In every case, an error page is shown to the end user and a full debugerror page is shown to the developer (i.e. when you're in "Debug" mode - seeConfiguration Environments).

To customize the error page that's shown to the user, see theHow to Customize Error Pages article.

The Request object as a Controller Argument

What if you need to read query parameters, grab a request header or get accessto an uploaded file? That information is stored in Symfony's Requestobject. To access it in your controller, add it as an argument andtype-hint it with the Request class:

  1. use Symfony\Component\HttpFoundation\Request;
  2.  
  3. public function index(Request $request, $firstName, $lastName)
  4. {
  5. $page = $request->query->get('page', 1);
  6.  
  7. // ...
  8. }

Keep reading for more information about using theRequest object.

Managing the Session

Symfony provides a session service that you can use to store informationabout the user between requests. Session is enabled by default, but will only bestarted if you read or write from it.

Session storage and other configuration can be controlled under theframework.session configuration inconfig/packages/framework.yaml.

To get the session, add an argument and type-hint it withSessionInterface:

  1. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  2.  
  3. public function index(SessionInterface $session)
  4. {
  5. // stores an attribute for reuse during a later user request
  6. $session->set('foo', 'bar');
  7.  
  8. // gets the attribute set by another controller in another request
  9. $foobar = $session->get('foobar');
  10.  
  11. // uses a default value if the attribute doesn't exist
  12. $filters = $session->get('filters', []);
  13. }

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

For more info, see Sessions.

Flash Messages

You can also store special messages, called "flash" messages, on the user'ssession. By design, flash messages are meant to be used exactly once: they vanishfrom 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. use Symfony\Component\HttpFoundation\Request;
  2.  
  3. public function update(Request $request)
  4. {
  5. // ...
  6.  
  7. if ($form->isSubmitted() && $form->isValid()) {
  8. // do some sort of processing
  9.  
  10. $this->addFlash(
  11. 'notice',
  12. 'Your changes were saved!'
  13. );
  14. // $this->addFlash() is equivalent to $request->getSession()->getFlashBag()->add()
  15.  
  16. return $this->redirectToRoute(...);
  17. }
  18.  
  19. return $this->render(...);
  20. }

After processing the request, the controller sets a flash message in the sessionand 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 the flashes() method providedby the Twig global app variable:

  1. {# templates/base.html.twig #}
  2.  
  3. {# read and display just one flash message type #}
  4. {% for message in app.flashes('notice') %}
  5. <div class="flash-notice">
  6. {{ message }}
  7. </div>
  8. {% endfor %}
  9.  
  10. {# read and display several types of flash messages #}
  11. {% for label, messages in app.flashes(['success', 'warning']) %}
  12. {% for message in messages %}
  13. <div class="flash-{{ label }}">
  14. {{ message }}
  15. </div>
  16. {% endfor %}
  17. {% endfor %}
  18.  
  19. {# read and display all flash messages #}
  20. {% for label, messages in app.flashes %}
  21. {% for message in messages %}
  22. <div class="flash-{{ label }}">
  23. {{ message }}
  24. </div>
  25. {% endfor %}
  26. {% endfor %}

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

Tip

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

The Request and Response Object

As mentioned earlier, Symfony willpass the Request object to any controller argument that is type-hinted withthe Request class:

  1. use Symfony\Component\HttpFoundation\Request;
  2.  
  3. public function index(Request $request)
  4. {
  5. $request->isXmlHttpRequest(); // is it an Ajax request?
  6.  
  7. $request->getPreferredLanguage(['en', 'fr']);
  8.  
  9. // retrieves GET and POST variables respectively
  10. $request->query->get('page');
  11. $request->request->get('page');
  12.  
  13. // retrieves SERVER variables
  14. $request->server->get('HTTP_HOST');
  15.  
  16. // retrieves an instance of UploadedFile identified by foo
  17. $request->files->get('foo');
  18.  
  19. // retrieves a COOKIE value
  20. $request->cookies->get('PHPSESSID');
  21.  
  22. // retrieves an HTTP request header, with normalized, lowercase keys
  23. $request->headers->get('host');
  24. $request->headers->get('content-type');
  25. }

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

Like the Request, the Response object has a public headers property.This object is of the type ResponseHeaderBagand provides methods for getting and setting response headers. The header names arenormalized. As a result, the name Content-Type is equivalent tothe name content-type or content_type.

In Symfony, a controller is required to return a Response object:

  1. use Symfony\Component\HttpFoundation\Response;
  2.  
  3. // creates a simple Response with a 200 status code (the default)
  4. $response = new Response('Hello '.$name, Response::HTTP_OK);
  5.  
  6. // creates a CSS-response with a 200 status code
  7. $response = new Response('<style> ... </style>');
  8. $response->headers->set('Content-Type', 'text/css');

To facilitate this, different response objects are included to address differentresponse types. Some of these are mentioned below. To learn more about theRequest and Response (and different Response classes), see theHttpFoundation component documentation.

Accessing Configuration Values

To get the value of any configuration parameterfrom a controller, use the getParameter() helper method:

  1. // ...
  2. public function index()
  3. {
  4. $contentsDir = $this->getParameter('kernel.project_dir').'/contents';
  5. // ...
  6. }

Returning JSON Response

To return JSON from a controller, use the json() helper method. This returns aJsonResponse object that encodes the data automatically:

  1. // ...
  2. public function index()
  3. {
  4. // returns '{"username":"jane.doe"}' and sets the proper Content-Type header
  5. return $this->json(['username' => 'jane.doe']);
  6.  
  7. // the shortcut defines three optional arguments
  8. // return $this->json($data, $status = 200, $headers = [], $context = []);
  9. }

If the serializer service is enabled in yourapplication, it will be used to serialize the data to JSON. Otherwise,the json_encode function is used.

Streaming File Responses

You can use the file()helper to serve a file from inside a controller:

  1. public function download()
  2. {
  3. // send the file contents and force the browser to download it
  4. return $this->file('/path/to/some_file.pdf');
  5. }

The file() helper provides some arguments to configure its behavior:

  1. use Symfony\Component\HttpFoundation\File\File;
  2. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  3.  
  4. public function download()
  5. {
  6. // load the file from the filesystem
  7. $file = new File('/path/to/some_file.pdf');
  8.  
  9. return $this->file($file);
  10.  
  11. // rename the downloaded file
  12. return $this->file($file, 'custom_name.pdf');
  13.  
  14. // display the file contents in the browser instead of downloading it
  15. return $this->file('invoice_3241.pdf', 'my_invoice.pdf', ResponseHeaderBag::DISPOSITION_INLINE);
  16. }

Final Thoughts

In Symfony, a controller is usually a class method which is used to acceptrequests, and return a Response object. When mapped with a URL, a controllerbecomes accessible and its response can be viewed.

To facilitate the development of controllers, Symfony provides anAbstractController. It can be used to extend the controller class allowingaccess to some frequently used utilities such as render() andredirectToRoute(). The AbstractController also provides thecreateNotFoundException() utility which is used to return a page not foundresponse.

In other articles, you'll learn how to use specific services from inside your controllerthat will help you persist and fetch objects from a database, process form submissions,handle caching and more.

Keep Going!

Next, learn all about rendering templates with Twig.

Learn more about Controllers