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. The controller runs 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 :doc:`/page_creation` and then come back! A Basic 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 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(int $max): Response { $number = random_int(0, $max); return new Response( 'Lucky number: '.$number.'' ); } } The controller is the ``number()`` method, which lives inside the 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 the ``Response`` class, which the controller must return. * *line 7*: The class can technically be called anything, but it's suffixed with ``Controller`` by convention. * *line 12*: The action method is allowed to have a ``$max`` argument thanks to the ``{max}`` :doc:`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 via a route. This was done above with the ``@Route("/lucky/number/{max}")`` :ref:`route annotation `. To see your page, go to this URL in your browser: http://localhost:8000/lucky/number/100 For more information on routing, see :doc:`/routing`. .. _the-base-controller-class-services: .. _the-base-controller-classes-services: The Base Controller Class & Services ------------------------------------ To aid development, Symfony comes with an optional base controller class called :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`. It can be extended to gain access to helper methods. Add the ``use`` statement atop your controller class and then modify ``LuckyController`` to extend it: .. code-block:: diff // src/Controller/LuckyController.php namespace App\Controller; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - class LuckyController + class LuckyController extends AbstractController { // ... } That's it! You now have access to methods like :ref:`$this->render() ` and many others that you'll learn about next. Generating URLs ~~~~~~~~~~~~~~~ The :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::generateUrl` method is just a helper method that generates the URL for a given route:: $url = $this->generateUrl('app_lucky_number', ['max' => 10]); .. _controller-redirect: Redirecting ~~~~~~~~~~~ If you want to redirect the user to another page, use the ``redirectToRoute()`` and ``redirect()`` methods:: use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; // ... public function index(): RedirectResponse { // redirects to the "homepage" route return $this->redirectToRoute('homepage'); // redirectToRoute is a shortcut for: // return new RedirectResponse($this->generateUrl('homepage')); // does a permanent HTTP 301 redirect return $this->redirectToRoute('homepage', [], 301); // if you prefer, you can use PHP constants instead of hardcoded numbers return $this->redirectToRoute('homepage', [], Response::HTTP_MOVED_PERMANENTLY); // redirect to a route with parameters return $this->redirectToRoute('app_lucky_number', ['max' => 10]); // redirects to a route and maintains the original query string parameters return $this->redirectToRoute('blog_show', $request->query->all()); // redirects to the current route (e.g. for Post/Redirect/Get pattern): return $this->redirectToRoute($request->attributes->get('_route')); // redirects externally return $this->redirect('http://symfony.com/doc'); } .. danger:: 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`_. .. _controller-rendering-templates: 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', ['number' => $number]); Templating and Twig are explained more in the :doc:`Creating and Using Templates article `. .. _controller-accessing-services: .. _accessing-other-services: Fetching Services ~~~~~~~~~~~~~~~~~ Symfony comes *packed* with a lot of useful classes and functionalities, called :doc:`services `. 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, type-hint an argument with its class (or interface) name and Symfony will inject it automatically. This requires your :doc:`controller to be registered as a service `:: use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Response; // ... /** * @Route("/lucky/number/{max}") */ public function number(int $max, LoggerInterface $logger): Response { $logger->info('We are logging!'); // ... } Awesome! What other services can you type-hint? To see them, use the ``debug:autowiring`` console command: .. code-block:: terminal $ php bin/console debug:autowiring If you need control over the *exact* value of an argument, you can :ref:`bind ` the argument by its name: .. configuration-block:: .. code-block:: yaml # config/services.yaml services: # ... # explicitly configure the service App\Controller\LuckyController: tags: [controller.service_arguments] bind: # for any $logger argument, pass this specific service $logger: '@monolog.logger.doctrine' # for any $projectDir argument, pass this parameter value $projectDir: '%kernel.project_dir%' .. code-block:: xml %kernel.project_dir% .. code-block:: php // config/services.php use App\Controller\LuckyController; use Symfony\Component\DependencyInjection\Reference; $container->register(LuckyController::class) ->addTag('controller.service_arguments') ->setBindings([ '$logger' => new Reference('monolog.logger.doctrine'), '$projectDir' => '%kernel.project_dir%', ]) ; Like with all services, you can also use regular :ref:`constructor injection ` in your controllers. For more information about services, see the :doc:`/service_container` article. Generating Controllers ---------------------- To save time, you can install `Symfony Maker`_ and tell Symfony to generate a new controller class: .. code-block:: terminal $ php bin/console make:controller BrandNewController created: src/Controller/BrandNewController.php created: templates/brandnew/index.html.twig If you want to generate an entire CRUD from a Doctrine :doc:`entity `, use: .. code-block:: terminal $ php bin/console make:crud Product created: src/Controller/ProductController.php created: src/Form/ProductType.php created: templates/product/_delete_form.html.twig created: templates/product/_form.html.twig created: templates/product/edit.html.twig created: templates/product/index.html.twig created: templates/product/new.html.twig created: templates/product/show.html.twig Managing Errors and 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\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; // ... public function index(): Response { // 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 :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::createNotFoundException` method is just a shortcut to create a special :class:`Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException` object, which ultimately triggers a 404 HTTP response inside Symfony. If you throw an exception that extends or is an instance of :class:`Symfony\\Component\\HttpKernel\\Exception\\HttpException`, Symfony will use the appropriate HTTP status code. Otherwise, the response will have a 500 HTTP status code:: // this exception ultimately generates a 500 status error 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 :ref:`page-creation-environments`). To customize the error page that's shown to the user, see the :doc:`/controller/error_pages` article. .. _controller-request-argument: 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? That information is stored in Symfony's ``Request`` object. To access it in your controller, add it as an argument and **type-hint it with the Request class**:: use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; // ... public function index(Request $request): Response { $page = $request->query->get('page', 1); // ... } :ref:`Keep reading ` for more information about using the Request object. Managing the Session -------------------- You can 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 :doc:`form ` submission:: .. configuration-block:: .. code-block:: php-symfony use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; // ... public function update(Request $request): Response { // ... 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(/* ... */); } :ref:`Reading ` for more information about using Sessions. .. _request-object-info: The Request and Response Object ------------------------------- As mentioned :ref:`earlier `, Symfony will pass the ``Request`` object to any controller argument that is type-hinted with the ``Request`` class:: use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; public function index(Request $request): Response { $request->isXmlHttpRequest(); // is it an Ajax request? $request->getPreferredLanguage(['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 a public ``headers`` property. This object is of the type :class:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag` and provides methods for getting and setting response headers. The header names are normalized. As a result, the name ``Content-Type`` is equivalent to the name ``content-type`` or ``content_type``. In Symfony, a controller is required 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(''); $response->headers->set('Content-Type', 'text/css'); To facilitate this, different response objects are included to address different response types. Some of these are mentioned below. To learn more about the ``Request`` and ``Response`` (and different ``Response`` classes), see the :ref:`HttpFoundation component documentation `. .. note:: Technically, a controller can return a value other than a ``Response``. However, your application is responsible for transforming that value into a ``Response`` object. This is handled using :doc:`events ` (specifically the :ref:`kernel.view event `), an advanced feature you'll learn about later. Accessing Configuration Values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To get the value of any :ref:`configuration parameter ` from a controller, use the ``getParameter()`` helper method:: // ... public function index(): Response { $contentsDir = $this->getParameter('kernel.project_dir').'/contents'; // ... } Returning JSON Response ~~~~~~~~~~~~~~~~~~~~~~~ To return JSON from a controller, use the ``json()`` helper method. This returns a ``JsonResponse`` object that encodes the data automatically:: use Symfony\Component\HttpFoundation\JsonResponse; // ... public function index(): JsonResponse { // returns '{"username":"jane.doe"}' and sets the proper Content-Type header return $this->json(['username' => 'jane.doe']); // the shortcut defines three optional arguments // return $this->json($data, $status = 200, $headers = [], $context = []); } If the :doc:`serializer service ` is enabled in your application, it will be used to serialize the data to JSON. Otherwise, the :phpfunction:`json_encode` function is used. Streaming File Responses ~~~~~~~~~~~~~~~~~~~~~~~~ You can use the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::file` helper to serve a file from inside a controller:: use Symfony\Component\HttpFoundation\BinaryFileResponse; // ... public function download(): BinaryFileResponse { // 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 download(): BinaryFileResponse { // 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); } Final Thoughts -------------- In Symfony, a controller is usually a class method which is used to accept requests, and return a ``Response`` object. When mapped with a URL, a controller becomes accessible and its response can be viewed. To facilitate the development of controllers, Symfony provides an ``AbstractController``. It can be used to extend the controller class allowing access to some frequently used utilities such as ``render()`` and ``redirectToRoute()``. The ``AbstractController`` also provides the ``createNotFoundException()`` utility which is used to return a page not found response. 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. Keep Going! ----------- Next, learn all about :doc:`rendering templates with Twig `. Learn more about Controllers ---------------------------- .. toctree:: :maxdepth: 1 :glob: controller/* .. _`Symfony Maker`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html .. _`unvalidated redirects security vulnerability`: https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html