First Steps

This series is all about getting started as a complete beginner to the Symfony Framework. One of the hurdles that used to exist in getting started was in getting the framework code downloaded onto your local machine. You would need to download a zipped tarball file, extract it, and then run an installer script.

It was only with Symfony 2.1 onwards that Composer became a 'thing'. Yep, imagine that - using a framework like Symfony without a modern day package manager. Actually, Composer came about as a result of wanting a standard way to manage pulling in third party code (the famed Symfony bundles - which you may have heard about) into existing Symfony projects. Kinda crazy, right?

Anyway, fast-forward to today, and we don't need to worry about any of that, as Symfony has its own installer.

To follow along, you will need to install the installer :) Fortunately, it's copy / paste.

I'm going to assume you have followed the official guide and have the installer available on your local development machine. Note here that you only need the installer on your development machine - not on a production server / your real web server.

To get started then, I would head to a central directory on my computer where I store all my code projects. In my case, this is the /home/chris/Development directory. The home directory of the current user is often shortened to a ~, so /home/chris/Development and ~/Development are one and the same.

Ok, so from my ~/Development directory I would do symfony new contact-form, e.g.:

➜ ~/Development symfony new contact-form

 Downloading Symfony...

        5.5 MiB/5.5 MiB ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  100%

 Preparing project...

 ✔  Symfony 3.2.7 was successfully installed. Now you can:

    * Change your current directory to /home/chris/Development/contact-form

    * Configure your application in app/config/parameters.yml file.

    * Run your application:
        1. Execute the php bin/console server:start command.
        2. Browse to the http://localhost:8000 URL.

    * Read the documentation at

This downloads all the required code and gives you some useful info on getting your new Symfony application up and running.

Let's change into the new directory:

cd contact-form

And list the directory contents:

➜  contact-form ls -la

total 156
drwxr-xr-x  9 chris chris  4096 Apr 10 10:49 .
drwxrwxrwt 15 root  root  28672 Apr 10 10:51 ..
drwxr-xr-x  4 chris chris  4096 Apr 10 10:49 app
drwxr-xr-x  2 chris chris  4096 Apr 10 10:49 bin
-rw-rw-rw-  1 chris chris  2292 Apr 10 10:49 composer.json
-rw-rw-rw-  1 chris chris 78082 Apr 10 10:49 composer.lock
-rw-r--r--  1 chris chris   248 Apr  5 14:09 .gitignore
-rw-r--r--  1 chris chris   978 Apr  5 14:09 phpunit.xml.dist
-rw-rw-rw-  1 chris chris    82 Apr 10 10:49
drwxr-xr-x  3 chris chris  4096 Apr 10 10:49 src
drwxr-xr-x  3 chris chris  4096 Apr 10 10:49 tests
drwxr-xr-x  5 chris chris  4096 Apr 10 10:49 var
drwxr-xr-x 15 chris chris  4096 Apr 10 10:49 vendor
drwxr-xr-x  3 chris chris  4096 Apr  5 14:12 web

There are lots of interesting directories and files in here that we will explore in more depth as we go through this series.

For now, as a quick run down we have:

  • app - contains our project's configuration
  • bin - contains any Symfony specific binaries - such as console
  • `composer.[json|lock] - used by Composer to determine what versions of third party code we use / have installed
  • .gitignore - a useful helper file for git version control, which we do not have setup by default
  • phpunit.xml.dist - a useful helper file for PHPUnit test config
  • - the ahem, readme file :/
  • src - where all our code will live
  • tests - one potential place our test code might live
  • var - contains cache, logs, and sessions directories by default
  • vendor - the directory that composer installs all third party code into
  • web - the publicly visible folder out there on the web (contains images, css, etc)

Whilst in here, the first thing I would do is run a composer update. Even though the Symfony installer will have installed the latest version of the Symfony Framework - 3.2.7 in our case - there may still be dependency updates available. Note that depending on your connection speed, this may take a few minutes:

➜  contact-form composer update

Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 0 installs, 4 updates, 0 removals
  - Updating twig/twig (v1.33.0 => v2.3.0) Downloading: 100%         
  - Updating doctrine/collections (v1.3.0 => v1.4.0) Downloading: 100%         
  - Updating doctrine/annotations (v1.2.7 => v1.4.0) Downloading: 100%         
  - Updating doctrine/common (v2.6.2 => v2.7.2) Downloading: 100%         
Writing lock file
Generating autoload files
> Incenteev\ParameterHandler\ScriptHandler::buildParameters
Updating the "app/config/parameters.yml" file
> Sensio\Bundle\DistributionBundle\Composer\ScriptHandler::buildBootstrap
> Sensio\Bundle\DistributionBundle\Composer\ScriptHandler::clearCache

 // Clearing the cache for the dev environment with debug                       
 // true                                                                        

 [OK] Cache for the "dev" environment (debug=true) was successfully cleared.    

> Sensio\Bundle\DistributionBundle\Composer\ScriptHandler::installAssets

 Trying to install assets as relative symbolic links.

 -- -------- ---------------- 
     Bundle   Method / Error  
 -- -------- ---------------- 

 [OK] All assets were successfully installed.                                   

> Sensio\Bundle\DistributionBundle\Composer\ScriptHandler::installRequirementsFile
> Sensio\Bundle\DistributionBundle\Composer\ScriptHandler::prepareDeploymentTarget

Now this will look a little nicer on your screen. Unfortunately in copy / pasting I do lose all the nice colourings, but hey ho.

Starting The Web Server

We don't need to have Apache, or nginx setup and working to start browsing our new Symfony web site. Starting from Symfony 2.6, we can start web server directly from the command line:

php bin/console server:start

This will start up a web server using PHP's built-in webserver. Be careful here, as it's easy to forget about it and leave it running:

php bin/console server:stop

If you are the forgetful type - like me - then you can use:

php bin/console server:run

This stops you from using the command line whilst the web server is running. You likely will need the command line for other things, so this option is less useful than running the web server in the background (server:start / server:stop).

You can read more about the web server utility by clicking here.

Whichever method you choose, make sure to browse to the given link - by default - and have a look around. We will be looking into the code that renders this page very shortly.

Working With Symfony In PHPStorm

I'm going to use PHPStorm for all my coding tasks throughout this series. It's the IDE I use in every video here on CodeReviewVideos, and the IDE I use in my day-to-day development also.

I have a bunch of videos on using PHPStorm in general, and with Symfony which I would recommend you check out if at all interested.

The only needed step is to start a new project, using the newly downloaded Symfony project as your project's directory. After that, I have no special setup steps - though you may wish to add the Symfony PHPStorm plugin as it comes with a bunch of useful extras to help you along (better auto completion being my favourite).

With my project setup, the first thing I am going to do is ensure we have Bootstrap styling available.

You may have already heard that Symfony has some presets available for styling with Bootstrap (along with Zurb Foundation). To use these styles we do need to have the Bootstrap (or Zurb Foundation) CSS in our project somewhere.

For simplicity, I'm going to pull in the Bootstrap CSS using the official Bootstrap CDN link. This just means I'm going to use the CSS via the web - and in a real project, you'd probably more likely want to have the CSS local to your webserver. This is an area of opinion, and for the moment, one I am going to completely side-step :)

Adding in Bootstrap is really easy. We simply need to grab the link from [the Bootstrap documentation], and copy / paste into our Twig template:

<!DOCTYPE html>
<!-- /app/Resources/views/base.html.twig -->
        <meta charset="UTF-8" />
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
        <!-- Latest compiled and minified CSS -->
        <link rel="stylesheet" href="" 
        <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}

We will cover this template in more detail shortly. For now, just paste in the Bootstrap provided link and save the file.

Whilst changing our templates around, I will also edit the other provided file - /app/Resources/views/default/index.html.twig - to provide a starting point for our project:

<!-- /app/Resources/views/default/index.html.twig -->

{% extends 'base.html.twig' %}

{% block body %}
<div class="container">
   your text goes here
{% endblock %}

{% block stylesheets %}

{% endblock stylesheets %}

Now, you should still be able to view your Symfony-powered website at without having to restart the web server. Just refresh the page and behold, all the default Symfony stuff is gone, and instead, you should be able to see "your text goes here". Amazing! :)

Let's now look at how and why this works.

Default Controller - The 'C' in MVC

One of the terms you will hear frequently when working with a framework like Symfony, Laravel, Rails, or Django is MVC - short for Model View Controller.

This is a software design pattern name given to something you are very likely already doing, even if you don't call it as such.

From a very high level, most of our websites behave like this:

  • A User "requests" a page on our site by browsing to one of our URLs
  • Our code does something mundane, or extremely clever with this request data to figure out what that user wants, and...
  • Returns a response - whether as HTML, or JSON, or XML, or whatever

Taking this over simplified version of things, we could then map:

  • Model - "Our code does something mundane or extremely clever with this request data"
  • View - a "HTML, or JSON, or XML, or whatever" representation of whatever data the "model" returns
  • Controller - Receives the User's request, gets the Model to do the dirty work, and then passes the result onto the view - which the controller then returns to the end user

This is extremely simplified, and - in my opinion - a large part of the confusion around MVC is it's open to a very wide range of interpretations.

Anyway, the Symfony Framework currently comes with this DefaultController class that is the reason why we can fire up the web server and immediately see something interesting.

Given that we have tweaked our templates as above, all we need at this point is the following code:


namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class DefaultController extends Controller
     * @Route("/", name="homepage")
    public function indexAction()
        return $this->render('default/index.html.twig');

Without a whole lot of understanding how this works behind the scenes, it's fairly easy to imagine how it might work:

We have a controller class: the DefaultController.

Somehow Symfony is aware of this class, and will call functions inside it when routes configured inside it are requested.

So how is Symfony aware of this class? Well, Symfony comes pre-configured in this way thanks to an entry in routing.yml:

# /app/config/routing.yml

    resource: "@AppBundle/Controller/"
    type:     annotation

routing.yml is a file that Symfony will look at behind the scenes when loading all the routes in our application. We don't need to do anything to make this work.

What this entry is saying is that the routing loader should look inside the entire /src/AppBundle/Controller directory, find any configured Controller classes, and then look for Route annotations above any of the created controller actions.

We have one of those!

This DefaultController has one public method - the indexAction.

This indexAction has a Route annotation, which has the path of /, and a name we're calling 'homepage':

     * @Route("/", name="homepage") <-- this is the routing annotation
    public function indexAction()

This path of / means when we browse to (the trailing / being the important thing here), we activate this controller action.

If our route pointed to the path of /beans then browsing to would activate this route instead.

The more confusing part - in my opinion - is around the line:

return $this->render('default/index.html.twig');

So my first question might be: where does $this->render come from? We haven't defined this method, so how can call it?

Well, if you're using PHPStorm - or any other decent IDE - you should be able to hold down the ctrl key and click the render method, and be taken to the underlying implementation. This is a pretty good 'tip' to follow whenever you can't figure out how things work. It won't always work, unfortunately, but it will get you most of the way, most of the time.

Up to Symfony 3.2, this would be a protected method on the Controller that the Symfony Framework Bundle provides, and that our DefaultController extends.

If you look at the line:

class DefaultController extends Controller

You can see that our DefaultController extends Controller.

This is inheritance.

Likewise, a few lines up we can see that this Controller class that we are extending from is actually something we are useing:

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

By extending this controller we gain access to all of its public and protected methods, and indirectly its private methods also, should it have any (which it doesn't).

As of Symfony 3.3 onwards this logic has been extracted out into the ControllerTrait, which the base Controller will use. Truthfully this is geeky / nerdy stuff you don't need to know.

Both render implementations are identical, so whichever version of Symfony you are using, this is fundamentally the same.

Now, how these templates get processed and rendered is a video's worth of content in itself. What happens here depends on your environment - dev, or prod, etc.

The important thing to note is that much like how routing.yml is a default path for how routing works, the directory app/Resources/views is also a special path, and will be used behind the scenes when we specify our template location as default/index.html.twig.

I'm willing to go into further details on this template structure if interested - please do leave a comment below. Just be aware that whilst it's interesting, to actually use Symfony on a day to day basis, it is not critical to understand how this works in any way. There's plenty of other 'stuff' to know that is more urgent at this stage, so don't overwhelm yourself.

To finish up here we are going to rename the DefaultController, and associated Twig template to SupportController.php, and rename the index.html.twig file from:




You do not need to do this, however it will make more logical sense to keep things named in line with what function they are expected to do.

We've already started to do a lot of the common things you will do in almost every Symfony application you will ever work with. A large part of feeling confident and comfortable with Symfony is simply using it frequently.

Let's continue on now and make our contact form display in our web browser.

Code For This Course

Get the code for this course.