Symfony has its own template engine called TWIG. It is a simple scripting language with a few tags and only three main rules:
Whatever goes between {% %} should be executed
Whatever is expressed via {{ }} should be printed
Whatever is enclosed by {# #} is just a comment
As we continue, we will see how to use TWIG to create sophisticated and dynamic templates based on our project needs. For now, let's just see what a TWIG file looks like.
The render() method from the previous topic has two parameters: the path to our TWIG template and its parameter. By default, all templates are in the app/Resources/views folder. If you go there, you will find another folder called default. That's why the middle part of the path parameter is default:
Obviously, in the default folder, we have the template file itself. So basically, we follow the [subdirectory in /Resources/views]/[template name] format to access our templates.
There are two questions here:
Why didn't we mention the full path as Resources/views/Default?
By default, Symfony knows that all templates should be organized in Resources/views, so we can ignore that part and keep references nice and short.
Why do we even need a subfolder in Resources/views? Wouldn't it be cleaner and shorter if we keep every template in the root of Resources/views?
Yes, you can, but it is not very well organized. For example, imagine that we have several routes for different menu items: /about, /about/{name} and /project, /project/{id}. You can keep templates for these routes in the root and give them unique names, or you can create subfolders About/ and Project/ and keep the related templates in each of them.
Controller/View interaction
Let's add a new controller action in AppBundle and call it aboutAction(). This method will receive a name and says something about it:
// mava/src/AppBundle/Controller/DefaultController.php
class DefaultController extends Controller
{
// ...
/**
* @Route("/about/{name}", name="aboutpage")
*/
public function aboutAction($name)
{
return $this->render('about/index.html.twig', array('name' => $name));
}
}
The new @Route() annotation for this method suggests that we need a new folder called about/ and an index template as follows:
As you can see, we can decorate the contents of a .twig file in any way you like. For example, we can capitalize the name using the capitalize filter and show the date by applying the date() filter to the current timestamp.
Note
There is a lot to say about TWIG and I will show you how to use it practically in the chapters to come.
User is one of the key entities of our project. They will be recognized as team members and organized in several different groups. Keeping this in mind, let's create an about page and see how we can see details about a specific user.
Conditional templates
In the previous example, imagine that we want to make the {name} parameters optional. In other words, if there is a name in the URL, then we want to see a name-related message, and if there is no name, then we want to see a general message.
Let's start by changing the @Route() annotation:
// mava/src/AppBundle/Controller/DefaultController.php
class DefaultController extends Controller
{
// . . .
/**
* @Route("/about/{name}", name="aboutpage", defaults={"name":null})
*/
public function aboutAction($name)
{
return $this->render('about/index.html.twig', array('name' => $name));
}
}
The defaults parameter nominates a default value for the name variable. If we don't set a value for name, then it will be set to null. So now our aboutAction() can receive requests from both /about and about/{name}.
Let's see how the template can handle these requests. Get rid of the previous contents of About/index.html.twig and replace them with the following blocks:
{# mava/app/Resources/views/About/index.html.twig #}
{% if name %}
{{name}} is a member of our team.
{% else %}
mava is a web app for task management and team collaboration. <br/>
{% endif %}
As you noticed, I used the {% if <condition> %} tag to create a conditional structure. The idea is to create one template to handle various routes. Sure, we could create two separate templates and routes for /about and /about/name, but that's how we can work smarter and not harder. So basically, our template says that if I see a value for the name variable, I will go in the if block; otherwise, I will follow the else block.
Make it dynamic
So far, it was about a static controller dealing with a static template. Let's see how we can feed our template with data from a database. Instead of handling database queries directly, we will use an Object Relational Mapper (ORM).
Note
Doctrine is the ORM that we are using in this book. It is powerful and by default integrated into Symfony, which makes it very convenient to use.
The Doctrine's job is to treat PHP classes and objects like they are tables and records. This way, we don't need to write SQL queries for Create, Read, Update, and Delete (CRUD) actions. All we need to do is ask our ORM to do the job for us. That makes coding a lot easier and fun.
Database configurations
Before using Doctrine, we need to make sure that our database settings are correct. Make sure that you have installed MySQL and its PHP drivers already and you have a valid MySQL username and password. You might find database management applications handy. I use MySQL Workbench, but feel free to choose anything that appeals to you.
To check the database configuration in your Symfony project, open the app/config/parameters.yml file and set your own db username and password:
As you can see, the database name for our project is mava. To create this database, run the following command:
$ bin/console doctrine:database:create
Generating an entity
The database is created and we can create our tables in it. In Doctrine terminology, we don't call them tables anymore. Technically, they are PHP classes called entities. To generate an entity named User, run the following command:
$ bin/console doctrine:generate:entity
Then, follow the interactive steps as follows:
The Entity shortcut name: AppBundle:UserDetermine the format to use for the mapping information.Configuration format (yml, xml, php, or annotation) [annotation]:
We only need three fields for our entity:
New field name (press <return> to stop adding fields): nameField type [string]: Field length [255]: Is nullable [false]: Unique [false]: New field name (press <return> to stop adding fields): bioField type [string]: textIs nullable [false]: Unique [false]: New field name (press <return> to stop adding fields): emailField type [string]: Field length [255]: Is nullable [false]: Unique [false]:
If you check your database, you won't see the new table yet but there are some changes in our bundle directory.
There is a new Entity/ folder in our bundle and a PHP class called User.php in it. This file contains some property definitions and getter and setter methods for each property:
/**
* @var string
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* Set name
* @param string $name
* @return User
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
* @return string
*/
public function getName()
{
return $this->name;
}
The comments before the variable and method definition are not just ordinary comments. They are generated by console (when we choose an annotation) and a way of communication between our entity and Doctrine. For example, take a look at this comment:
It tells Doctrine that we need a column called name with a string (255) type. Now that we have our entity defined, it is time to generate the related table in our database:
To play with our new entity, we need some records. We can add records manually or we can ask Symfony's console to do the job for us. These sample records are called data fixtures and there is a bundle to load and use fixtures. This bundle is called doctrine-fixtures-bundle and this is how we install it:
In the root of your project open composer.json file and add the following entry to it: