Create your First Page in Symfony

Create your First Page in Symfony

Creating a new page - whether it's an HTML page or a JSON endpoint - is a simple two-step process:

  1. Create a route: A route is the URL (e.g. /about) to your page and points to a controller;
  2. Create a controller: A controller is the function you write that builds the page. You take the incoming request information and use it to create a Symfony Response object, which can hold HTML content, a JSON string or anything else.

Just like on the web, every interaction is initiated by an HTTP request. Your job is pure and simple: understand that request and return a response.

Creating a Page: Route and Controller

Tip

Before continuing, make sure you've read the Installation chapter and can access your new Symfony app in the browser.

Suppose you want to create a page - /lucky/number - that generates a lucky (well, random) number and prints it. To do that, create a class and a method inside of it that will be executed when someone goes to /lucky/number:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// src/AppBundle/Controller/LuckyController.php
namespace AppBundle\Controller;

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

class LuckyController extends Controller
{
    /**
     * @Route("/lucky/number")
     */
    public function numberAction()
    {
        $number = rand(0, 100);

        return new Response(
            '<html><body>Lucky number: '.$number.'</body></html>'
        );
    }
}

Before diving into this, test it out!

Tip

If you setup a proper virtual host in Apache or Nginx, replace http://localhost:8000 with your host name - like http://symfony.dev/app_dev.php/lucky/number.

If you see a lucky number being printed back to you, congratulations! But before you run off to play the lottery, check out how this works.

The @Route above numberAction() is called an annotation and it defines the URL pattern. You can also write routes in YAML (or other formats): read about this in the routing chapter. Actually, most routing examples in the docs have tabs that show you how each format looks.

The method below the annotation - numberAction - is called the controller and is where you build the page. The only rule is that a controller must return a Symfony Response object (and you'll even learn to bend this rule eventually).

Great question! By including app_dev.php in the URL, you're executing Symfony through a file - web/app_dev.php - that boots it in the dev environment. This enables great debugging tools and rebuilds cached files automatically. For production, you'll use clean URLs - like http://localhost:8000/lucky/number - that execute a different file - app.php - that's optimized for speed. To learn more about this and environments, see Environments.

Creating a JSON Response

The Response object you return in your controller can contain HTML, JSON or even a binary file like an image or PDF. You can easily set HTTP headers or the status code.

Suppose you want to create a JSON endpoint that returns the lucky number. Just add a second method to LuckyController:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/AppBundle/Controller/LuckyController.php
// ...

class LuckyController extends Controller
{
    // ...

    /**
     * @Route("/api/lucky/number")
     */
    public function apiNumberAction()
    {
        $data = array(
            'lucky_number' => rand(0, 100),
        );

        return new Response(
            json_encode($data),
            200,
            array('Content-Type' => 'application/json')
        );
    }
}

Try this out in your browser:

You can even shorten this with the handy JsonResponse:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/AppBundle/Controller/LuckyController.php
// ...

// --> don't forget this new use statement
use Symfony\Component\HttpFoundation\JsonResponse;

class LuckyController extends Controller
{
    // ...

    /**
     * @Route("/api/lucky/number")
     */
    public function apiNumberAction()
    {
        $data = array(
            'lucky_number' => rand(0, 100),
        );

        // calls json_encode and sets the Content-Type header
        return new JsonResponse($data);
    }
}

Dynamic URL Patterns: /lucky/number/{count}

Woh, you're doing great! But Symfony's routing can do a lot more. Suppose now that you want a user to be able to go to /lucky/number/5 to generate 5 lucky numbers at once. Update the route to have a {wildcard} part at the end:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // src/AppBundle/Controller/LuckyController.php
    // ...
    
    class LuckyController extends Controller
    {
        /**
         * @Route("/lucky/number/{count}")
         */
        public function numberAction()
        {
            // ...
        }
    
        // ...
    }
    
  • YAML
    1
    2
    3
    4
    # app/config/routing.yml
    lucky_number:
        path:     /lucky/number/{count}
        defaults: { _controller: AppBundle:Lucky:number }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <!-- src/Acme/DemoBundle/Resources/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="lucky_number" path="/lucky/number/{count}">
            <default key="_controller">AppBundle:Lucky:number</default>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // src/Acme/DemoBundle/Resources/config/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $collection = new RouteCollection();
    $collection->add('lucky_number', new Route('/lucky/number/{count}', array(
        '_controller' => 'AppBundle:Lucky:number',
    )));
    
    return $collection;
    

Because of the {count} "placeholder", the URL to the page is different: it now works for URLs matching /lucky/number/* - for example /lucky/number/5. The best part is that you can access this value and use it in your controller:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/AppBundle/Controller/LuckyController.php
// ...

class LuckyController extends Controller
{

    /**
     * @Route("/lucky/number/{count}")
     */
    public function numberAction($count)
    {
        $numbers = array();
        for ($i = 0; $i < $count; $i++) {
            $numbers[] = rand(0, 100);
        }
        $numbersList = implode(', ', $numbers);

        return new Response(
            '<html><body>Lucky numbers: '.$numbersList.'</body></html>'
        );
    }

    // ...
}

Try it by going to /lucky/number/XX - replacing XX with any number:

You should see 7 lucky numbers printed out! You can get the value of any {placeholder} in your route by adding a $placeholder argument to your controller. Just make sure they have the same name.

The routing system can do a lot more, like supporting multiple placeholders (e.g. /blog/{category}/{page})), making placeholders optional and forcing placeholder to match a regular expression (e.g. so that {count} must be a number).

Find out about all of this and become a routing expert in the Routing chapter.

Rendering a Template (with the Service Container)

If you're returning HTML from your controller, you'll probably want to render a template. Fortunately, Symfony comes with Twig: a templating language that's easy, powerful and actually quite fun.

So far, LuckyController doesn't extend any base class. The easiest way to use Twig - or many other tools in Symfony - is to extend Symfony's base Controller class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// src/AppBundle/Controller/LuckyController.php
// ...

// --> add this new use statement
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class LuckyController extends Controller
{
    // ...
}

Using the templating Service

This doesn't change anything, but it does give you access to Symfony's container: 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.

To render a Twig template, use a service called templating:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/AppBundle/Controller/LuckyController.php
// ...

class LuckyController extends Controller
{
    /**
     * @Route("/lucky/number/{count}")
     */
    public function numberAction($count)
    {
        // ...
        $numbersList = implode(', ', $numbers);

        $html = $this->container->get('templating')->render(
            'lucky/number.html.twig',
            array('luckyNumberList' => $numbersList)
        );

        return new Response($html);
    }

    // ...
}

You'll learn a lot more about the important "service container" as you keep reading. For now, you just need to know that it holds a lot of objects, and you can get() any object by using its nickname, like templating or logger. The templating service is an instance of TwigEngine and this has a render() method.

But this can get even easier! By extending the Controller class, you also get a lot of shortcut methods, like render():

 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
// src/AppBundle/Controller/LuckyController.php
// ...

/**
 * @Route("/lucky/number/{count}")
 */
public function numberAction($count)
{
    // ...

    /*
    $html = $this->container->get('templating')->render(
        'lucky/number.html.twig',
        array('luckyNumberList' => $numbersList)
    );

    return new Response($html);
    */

    // render: a shortcut that does the same as above
    return $this->render(
        'lucky/number.html.twig',
        array('luckyNumberList' => $numbersList)
    );
}

Learn more about these shortcut methods and how they work in the Controller chapter.

Tip

For more advanced users, you can also register your controllers as services.

Create the Template

If you refresh now, you'll get an error:

Unable to find template "lucky/number.html.twig"

Fix that by creating a new app/Resources/views/lucky directory and putting a number.html.twig file inside of it:

  • Twig
    1
    2
    3
    4
    5
    6
    {# app/Resources/views/lucky/number.html.twig #}
    {% extends 'base.html.twig' %}
    
    {% block body %}
        <h1>Lucky Numbers: {{ luckyNumberList }}</h1>
    {% endblock %}
    
  • PHP
    1
    2
    3
    4
    5
    6
    <!-- app/Resources/views/lucky/number.html.php -->
    <?php $view->extend('base.html.php') ?>
    
    <?php $view['slots']->start('body') ?>
        <h1>Lucky Numbers: <?php echo $view->escape($luckyNumberList) ?>
    <?php $view['slots']->stop() ?>
    

Welcome to Twig! This simple file already shows off the basics: like how the {{ variableName }} syntax is used to print something. The luckyNumberList is a variable that you're passing into the template from the render call in your controller.

The {% extends 'base.html.twig' %} points to a layout file that lives at app/Resources/views/base.html.twig and came with your new project. It's really basic (an unstyled HTML structure) and it's yours to customize. The {% block body %} part uses Twig's inheritance system to put the content into the middle of the base.html.twig layout.

Refresh to see your template in action!

If you view the source code, you now have a basic HTML structure thanks to base.html.twig.

This is just the surface of Twig's power. When you're ready to master its syntax, loop over arrays, render other templates and other cool things, read the Templating chapter.

Exploring the Project

You've already created a flexible URL, rendered a template that uses inheritance and created a JSON endpoint. Nice!

It's time to explore and demystify the files in your project. You've already worked inside the two most important directories:

app/
Contains things like configuration and templates. Basically, anything that is not PHP code goes here.
src/
Your PHP code lives here.

99% of the time, you'll be working in src/ (PHP files) or app/ (everything else). As you get more advanced, you'll learn what can be done inside each of these.

The app/ directory also holds a few other things, like the cache directory app/cache/, the logs directory app/logs/ and app/AppKernel.php, which you'll use to enable new bundles (and one of a very short list of PHP files in app/).

The src/ directory has just one directory - src/AppBundle - and everything lives inside of it. A bundle is like a "plugin" and you can find open source bundles and install them into your project. But even your code lives in a bundle - typically AppBundle (though there's nothing special about AppBundle). To find out more about bundles and why you might create multiple bundles (hint: sharing code between projects), see the Bundles chapter.

So what about the other directories in the project?

vendor/
Vendor (i.e. third-party) libraries and bundles are downloaded here by the Composer package manager.
web/
This is the document root for the project and contains any publicly accessible files, like CSS, images and the Symfony front controllers that execute the app (app_dev.php and app.php).
Symfony is flexible. If you need to, you can easily override the default directory structure. See How to Override Symfony's default Directory Structure.

Application Configuration

Symfony comes with several built-in bundles (open your app/AppKernel.php file) and you'll probably install more. The main configuration file for bundles is app/config/config.yml:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    # app/config/config.yml
    # ...
    
    framework:
        secret: "%secret%"
        router:
            resource: "%kernel.root_dir%/config/routing.yml"
        # ...
    
    twig:
        debug:            "%kernel.debug%"
        strict_variables: "%kernel.debug%"
    
    # ...
    
  • XML
     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
    <!-- app/config/config.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"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xmlns:twig="http://symfony.com/schema/dic/twig"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony
            http://symfony.com/schema/dic/symfony/symfony-1.0.xsd
            http://symfony.com/schema/dic/twig
            http://symfony.com/schema/dic/twig/twig-1.0.xsd">
    
        <!-- ... -->
    
        <framework:config secret="%secret%">
            <framework:router resource="%kernel.root_dir%/config/routing.xml" />
            <!-- ... -->
        </framework:config>
    
        <!-- Twig Configuration -->
        <twig:config debug="%kernel.debug%" strict-variables="%kernel.debug%" />
    
        <!-- ... -->
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // app/config/config.php
    // ...
    
    $container->loadFromExtension('framework', array(
        'secret' => '%secret%',
        'router' => array(
            'resource' => '%kernel.root_dir%/config/routing.php',
        ),
        // ...
    ));
    
    // Twig Configuration
    $container->loadFromExtension('twig', array(
        'debug'            => '%kernel.debug%',
        'strict_variables' => '%kernel.debug%',
    ));
    
    // ...
    

The framework key configures FrameworkBundle, the twig key configures TwigBundle and so on. A lot of behavior in Symfony can be controlled just by changing one option in this configuration file. To find out how, see the Configuration Reference section.

Or, to get a big example dump of all of the valid configuration under a key, use the handy app/console command:

1
$ app/console config:dump-reference framework

There's a lot more power behind Symfony's configuration system, including environments, imports and parameters. To learn all of it, see the Configuration chapter.

What's Next?

Congrats! You're already starting to master Symfony and learn a whole new way of building beautiful, functional, fast and maintainable apps.

Ok, time to finish mastering the fundamentals by reading these chapters:

Then, in the Symfony Book, learn about the service container, the form system, using Doctrine (if you need to query a database) and more!

There's also a Cookbook packed with more advanced "how to" articles to solve a lot of problems.

Have fun!

This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License .