Você está na página 1de 292

Symfony Certification

Unofficial self-study guide

Raúl Fraile
This book is for sale at http://leanpub.com/symfony-selfstudy

This version was published on 2015-08-18

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.

©2015 Raúl Fraile


Tweet This Book!
Please help Raúl Fraile by spreading the word about this book on Twitter!
The suggested hashtag for this book is #symfony_selfstudy.
Find out what other people are saying about the book by clicking on this link to search for this
hashtag on Twitter:
https://twitter.com/search?q=#symfony_selfstudy
“The only true wisdom is in knowing you know nothing.” ― Socrates
Contents

Special thanks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i

The Symfony certification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii


Why become a Symfony certified developer . . . . . . . . . . . . . . . . . . . . . . . . . . ii
How to become certified . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii
Who should buy this book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii
How this book is organized . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iv
Conventions used in this book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
Copyright notice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v

Training questions and takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


1. PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

2. HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

3. Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

4. Standardization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
CONTENTS

Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Further reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

5. The Bundles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

6. The Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

7. Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112

8. Twig . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

9. Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144

10. Validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145


Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

11. Dependency Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157


Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
CONTENTS

12. Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170


Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182

13. HTTP Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183


Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Further reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196

14. The command line . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197


Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205

15. Automated Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207


Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Further reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215

16. Miscellaneous . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216


Exam goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Takeaways . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
Further reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224

Extra materials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225


Hands-on exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
1. Custom autoloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
2. Overriding default locations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
3. Twig extensions playground . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
4. The playful bundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
5. Controlling the framework with HTTP headers . . . . . . . . . . . . . . . . . . . . . . 251
CONTENTS

Appendixes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
Answer sheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271

List of figures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278


Special thanks
I would like to thank to Baptiste Dupuch, Alfonso Machado, Miguel Vilata, Edgar Tebar, David
Velasco, Ziad Jammal, Jose Luis Laso, Adrian Caliman, Ariel Ferrandini, Jakub Łukasik, Miguel
Garcia, Joeri Timmermans, Miguel Angel Garzón, Alexey Egorov, Philippe Segatori, Christian
Flothmann and Stephan Wentz for taking the time to review some of the questions and suggest
changes and improvements. This book is definitely better thanks to you.
I am also very grateful to Javier Eguiluz for all the help during the process of getting the authorization
for publishing this book.
The Symfony certification
It’s been a few years since the version 2 of the Symfony framework came to life. SensioLabs¹, the
company behind Symfony, and especially its creator and lead developer, Fabien Potencier, took a
considerable risk when they decided to start over and write the new version of Symfony from scratch
and changing almost everything. PHP had evolved and was mature enough to have a more serious
object oriented code. And Symfony took the plunge.
Today, all PHP community is taking advantage of that risky movement. They decided that the
framework should be only the glue between a set of reusable components, following the Unix
philosophy of having small yet capable software, which can be combined to create complex systems.
Today, many Symfony components are being used in other frameworks, CMS and third-party
libraries. Quality was also taken very seriously, enforcing testing and following design patterns
not too common in the PHP world at that time, such as Dependency Injection.
All of these led to a more professional, but inevitably complex framework. The learning curve is
not necessarily much steeper than the one to learn Symfony 1, but the minimum requirements to
be efficient and make things in the Symfony way are definitely stronger. SensioLabs has developed
the Symfony certification exam to validate that you have the skills necessary to work with Symfony
and extend it to fulfill your needs.

Why become a Symfony certified developer


It’s obvious that the ultimate goal when preparing a certification exam is to master the underlying
topic. It’s a great way to evaluate your skills, and even if you already have a deep understanding
on how Symfony works, you can always find better, cleaner or more efficient ways to do stuff you
wouldn’t know otherwise.
The exam is hard. That’s the real truth. Actually, quite hard. I still remember when I got certified back
in 2012. At that time, you could only get certified in conferences, and I took the exam at deSymfony²,
the unofficial Spanish Symfony Conference. They announced the exam only a few days before the
conference, so I only had time to read the documentation during my train trip. 12 people did the
exam in a university class, in front of Fabien. Only 3 of us passed the exam, and according to different
people I have been talking during these years, the percentage remains similar.
Due to the difficulty of the exam, being a Symfony certified developer gives you visibility, relevance
and maybe a better job. But remember, it’s just a piece of paper. At the end, what you learn in the
process is what really matters and the exam just acknowledges that you entered into a new level.
¹http://sensiolabs.com
²http://desymfony.com/
The Symfony certification iii

The goal of this book is to help you pass the exam by having a way to validate your knowledge as
you study for the exam and point you to the right direction.

How to become certified


The Symfony certification exam contains 75 questions from 16 categories, which must be answered
in 90 minutes. At the time of writing, the exam covers the most recent Long Term Support (LTS)
release: Symfony 2.3 LTS.
There are two different grades: advanced and expert. If you pass the exam, you will get one of
them, depending on your score. Required scores to pass the exam or get the expert grade are not
public, but what we know for sure is that is a hard certification.
The exam contains only three types of questions:

True/false or Yes/no questions.


Questions with two possible answers (True/false or Yes/no), and only one of them is valid.

Single answer questions.


Only one of all possible answers is valid.

Multiple answers question.


At least one of the possible answers is valid, but there could be more, or even all of them.
They are probably the hardest questions, as even if there are obvious incorrect answers, the
rest could be valid.

The exam does not contain open questions or code to be written.


As the goal of the book is that you are well prepared for the exam, all questions can have multiple
correct answers, so you have to think about all possible answers. Keep in mind that some of the
questions included in the book are probably much harder than the ones you will find at the exam,
but they will give you a better understanding about the underlying topic.

Who should buy this book


While this book is aimed to people who wants to pass the certification exam, it can be useful for any
PHP and Symfony developer willing to test their skills. Even if you are already certified or with a
depth understanding of Symfony, you will learn at least one or two things. The book is not intended
to be a replacement of the official documentation, it is just an additional resource, and hopefully
useful, to prepare the exam.
The Symfony certification iv

How this book is organized


The certification exam for Symfony 2.3 LTS covers 16 different topics. In the book, each of the
exam topics is covered in a different chapter, including sample questions with detailed answers and
takeaways. The chapters are organized as follows:

• Chapter 1, “PHP”, includes questions about PHP in general, especially those related to OOP
Object Oriented Programming (OOP), namespaces, interfaces, anonymous functions and
abstract classes.
• Chapter 2, “HTTP”, focuses on questions about the Hypertext Transfer Protocol (HTTP):
requests and responses, status codes and client/server interaction.
• Chapter 3, “Symfony2 Architecture”, tests your knowledge about the Symfony architecture:
components, bridges and bundles, as well as how code is organized, including configuration.
• Chapter 4, “Standardization”, validates your knowledge about how all different elements glue
together: naming conventions, coding standards, Composer packages.
• Chapter 5, “The Bundles”, contains questions about how bundles are organized and naming
conventions.
• Chapter 6, “The Controllers”, focuses on how to work with controllers in Symfony, what the
base controller offers and in general how to handle requests to convert them into proper
responses.
• Chapter 7, “Routing”, checks your understanding about the Symfony routing system, which
is based on the Routing component
• Chapter 8, “Twig”, includes questions about the Twig templating engine, from how to create
and reuse templates to adding extensions.
• Chapter 9, “Forms”, validates your knowledge about the Form component and how is
integrated with the full-stack framework. There will be questions about creating and handling
forms, as well as how to render them from Twig.
• Chapter 10, “Validation”, contains questions about the Validator component and how objects
can be validated.
• Chapter 11, “Dependency Injection”, focuses on the Dependency Injection pattern, and how to
work with the Dependency Injection Container from Symfony.
• Chapter 12, “Security”, is about the Security component, differences between Authentication
and Authorization, as well as important concepts such as firewalls, user providers, access
control rules or roles.
• Chapter 13, “HTTP Cache”, focuses on HTTP caching strategies and Edge Side Includes.
• Chapter 14, “The command line”, includes questions both about the Console component and
built-in Symfony commands.
• Chapter 15, “Automated Tests”, evaluates your knowledge about unit and functional testing,
from the point of view of a Symfony developer.
• Chapter 16, “Miscellaneous”, includes questions and takeaways about error handling and
debugging.
The Symfony certification v

Finally, the book includes 5 hands-on exercises that will force you to deep dive into the internals of
Symfony with the aim of getting a better understanding of some important concepts.

Conventions used in this book


This book uses certain typographic styles in order to help you quickly identify important informa-
tion.

• A monospaced font indicates filenames, commands, URLs, variables or source code.


• Italized text indicates key terms.
• Italized text indicates important concepts and ideas.

In addition to this, some special blocks of text are used to emphasize information:

Warning
Warnings describe potential pitfalls or dangers.

Tip
Tips provide useful information related to the topic being discussed.

Extra work
Indicate extra work that can be done to improve the knowledge about a specific concept,
but that is not covered in the book.

Copyright notice
Symfony is a registered trademark of Fabien Potencier. This book is not endorsed or sponsored
by Fabien Potencier or SensioLabs. Sample questions, explanations and other testing elements
included in this book are not derived from the actual exam questions, so you shouldn’t try
to memorize them but learn the underlying topic. Learning how Symfony works in depth is
the best way to pass the exam and become a better developer, which is the ultimate goal of a
certification.
The cover image, “Calculator”³ is copyright (c) 2007 Anssi Koskinen and made available under a
Attribution 2.0 Generic (CC BY 2.0) license⁴.
³https://www.flickr.com/photos/ansik/304526237
⁴https://creativecommons.org/licenses/by/2.0/
The Symfony certification vi

The icons used in diagrams belong to the “small-n-flat” icon set⁵, are copyright (c) Paomedia and
made available under a Attribution 3.0 Unported (CC BY 3.0) license⁶.

⁵https://github.com/paomedia/small-n-flat
⁶http://creativecommons.org/licenses/by/3.0/
Training questions and takeaways

The book contains 275 questions and 159 takeaways grouped in 16 different topics, as well as 5
hands-on exercises. You can choose your own path to prepare the exam, but I would recommend
the following strategy:

1. Print the Answer sheet section so you don’t have to go back and forth for each question.
2. For each exam topic:
1. Study the related material.
2. Try to answer all questions of the given topic without reading the answers.
3. Check the number of hits.
4. Read the detailed answers.
5. Study the takeaways.
3. For each exercise:
1. Try to do it by yourself, understanding what you are doing.
2. Check the solution.
4. If there are topics that you don’t fully understand, answer the questions again. The Index
section contains all the questions related to different Symfony components, bundles, PHP
functions and Twig-related stuff.

Work hard, good luck and May the Force be with you!
1. PHP
Exam goals
1.1. Object Oriented Programming
1.2. Namespaces
1.3. Interfaces
1.4. Anonymous functions and closures
1.5. Abstract classes

Questions
1. Which of the following PHP versions can execute this script?

// script.php
$data = [1, 2, 3];
echo $data[0];

1. 5.3+
2. 5.4+
3. 5.5+
4. 5.6+

2. In the following list there are two features that have never been available in PHP (up to 5.6).
What are they?

1. Variadic functions
2. Generators
3. Named parameters
4. Generics

3. Can you create a class with the name trait in PHP?

1. Yes, class names can be any string starting with a letter


2. Yes, but only before PHP 5.4
1. PHP 3

3. Yes, but only in PHP 5.4 and up


4. No, function names cannot be used as class names

4. Is strtolower a valid class name in PHP?

1. No, built-in function names cannot be used as class names


2. No, unless you disable the function strtolower() in the disable_functions directive of the
php.ini file
3. No, unless you define it in a different namespace than the global one
4. Yes

5. Can interfaces inherit from another interfaces in PHP?

1. Yes
2. No

6. In PHP, what is the value of MyClass::class_name?

1. Since PHP 5.5, ::class_name contains the fully qualified name of the class
2. Since PHP 5.5, ::class_name contains the name of the class (without the namespace)
3. Since PHP 5.5, ::class_name contains a reference to the class itself
4. Unless it’s defined, ::class_name won’t exist

7. Can interfaces define constants in PHP?

1. Yes
2. No

8. In PHP, can classes/interfaces extending from other classes/interfaces override constants


from the parent one?

1. Yes
2. Only classes can override constants
3. Only interfaces can override constants
4. No, constants can never be overriden

9. What are the effects of declaring a class as final in PHP?

1. Final classes cannot be extended


1. PHP 4

2. Final classes can be extended but child classes won’t be able to override methods
3. Final classes cannot be instantiated
4. The final keyword does not exist

10. Is multiple inheritance supported in PHP?

1. Yes, it’s supported in classes and interfaces


2. It’s only supported in classes
3. It’s only supported in interfaces
4. No

11. What is wrong with the following PHP code?

// example.php
use Symfony\Component\HttpFoundation\Request;

final class MyRequest extends Request


{
protected function test() {}
}

$request = new MyRequest();

1. Final classes cannot be instantiated


2. Final classes cannot define protected methods
3. Final classes cannot extend from other class
4. Nothing, the code is correct

12. How can you check the syntax of a PHP script without executing it?

1. Using the php_check_syntax() function


2. Using the eval() function
3. Using the PHP binary with the -l option
4. Using the built-in webserver in PHP 5.4+

13. Is there any problem with the following PHP code?


1. PHP 5

// script.php
class Book {
var $title = 'Title';
}

$book = new Book();


echo $book->title;

1. Yes, properties must be declared as public, protected or private, not var


2. Yes, properties declared with var are like private, so $book->title throws a fatal error
3. Yes, property title is not initialized
4. No

14. Which of the following sentences are true about abstract classes in PHP?

1. They cannot be instantiated


2. They support multiple inheritance like interfaces
3. They can be declared as final
4. They can include abstract methods

15. Does the following class definition throw an error?

// abstract_method.php
class Book {
abstract function getTitle();
}

1. Yes
2. No

16. What is the minimum version to use traits in PHP?

1. 5.3
2. 5.4
3. 5.5
4. 5.6

17. Which of the following function calls are valid if the function definition is map(callable
$callback)? (assuming PHP 5.4+)
1. PHP 6

1. map(function() {})
2. map('rand')
3. map(create_function('', ''))
4. map([new SplFixedArray(10), 'count'])

18. Which of the following elements are affected by namespaces in PHP?

1. Classes
2. Interfaces
3. Functions
4. Variables

19. Is there any error in the following code?

// script.php
namespace Entity {
class Book {}
}

namespace Service {
class Book {}
}

namespace {
$book1 = new Entity\Book();
$book2 = new Service\Book();
}

1. Yes, the namespace declaration statement has to be the very first statement in the script
2. Yes, only one namespace can be declared in a file
3. Yes, the Service\Book class cannot be found
4. No

20. What is the output of the following code?

// script.php
namespace Bundle\Entity;

echo __NAMESPACE__;
1. PHP 7

1. Entity
2. Bundle
3. Bundle\Entity
4. Fatal error, __NAMESPACE__ is not defined

21. What is the output of echo print("hello");?

1. hello
2. hello0
3. hello1
4. hello5

22. If an interface defines the method test(array $data) and the class implementing the
interface defines the method test($data). Does PHP throw an error?

1. Yes
2. No

23. What is the only case in which a class defining an interface, it doesn’t have to implement
all its methods?

1. Final classes
2. Abstract classes
3. SPL classes
4. Exception classes

24. What needs to be done to make the following code work as expected (and print out 3)?

// mylist.php
class MyList
{
public $items;

public function __construct($items)


{
$this->items = $items;
}
}

$list = new MyList([1, 2, 3]);


echo count($list);
1. PHP 8

1. Redeclare the count() function to accept objects of the type MyList


2. Add a custom error handler to catch errors generated when using count() with objects
3. Implement the Iterator interface
4. Implement the Countable interface

25. Given the file test.php, what happens if you run php test.php?

// test.php
unlink(__FILE__);
echo 'hello';

1. Removes the test.php file and then prints hello


2. Removes the test.php and ends the execution
3. Removes the test.php file and generates a IO error
4. It generates a fatal error as it is not allowed to remove the current script

26. Which of the following sentences are true about PHAR files?

1. They store opcodes instead of raw PHP code


2. The stub must always end with a call to the __halt_compiler() function
3. They can only be executed when the phar.require_hash option is set to on
4. It allows incremental updates, so there is no need to download the whole file again

27. How can you create a Symfony\Component\HttpFoundation\Response object without writing


the full namespace if there is already a use Symfony\Component\HttpFoundation instruction?

1. new Response()
2. new HttpFoundation\Response()
3. new HttpFoundation::Response()
4. It is not possible

28. When would you use the levenshtein() function?

1. To detect misspelled words


2. To tokenize a string
3. To merge values from different configuration files
4. To parse annotations

29. Which of the following pairs of words are not related?

1. spl_autoload_register() - PSR-4
2. closure - abstract class
3. phar - __halt_compiler()
4. __sleep() - serialization

30. What is the output of the following PHP script?


1. PHP 9

error_reporting(E_ALL ^ E_NOTICE);
$value = 1;
$get = function() {
echo $value;
};

echo 2;
$get();

1. 1
2. 2
3. 12
4. 21

31. What is the value of __NAMESPACE__ when used in global code?

1. /
2. \
3. Empty string
4. It is not defined and cannot be used

32. Given the following code, what is the value of the $value variable?

namespace One;

class SplMinHeap {}

$heap = new namespace\SplMinHeap();


var_dump(get_class($heap));

1. One\SplMinHeap
2. \SplMinHeap
3. Error: Syntax error, unexpected 'namespace'
4. Error: Class 'One\namespace\SplMinHeap' not found

33. If a namespaced function does not exist, does PHP fallback to the global function?

1. Yes
2. No

34. What is the output of the following code?


1. PHP 10

$function = function() {
return 1;
};

echo get_class($function);

1. Resource
2. Closure
3. Function
4. Error: ‘get_class() expects parameter 1 to be object

Answers
1. Which of the following PHP versions can execute this script?
Answers 2, 3 and 4 are correct. Support for array short syntax was introduced in PHP 5.4, and
previous versions throw a parse error. As Symfony 2.3 LTS supports PHP versions from 5.3.3, this
syntax is not used at all in the Symfony code, but that doesn’t mean that it can’t be used in your
own code if your PHP version is 5.4+.

PHP version
Remember that you can get the current PHP version by using the phpinfo() or
phpversion() built-in functions, or PHP_VERSION constants (PHP_MAJOR_VERSION, PHP_-
MINOR_VERSION, PHP_RELEASE_VERSION, PHP_VERSION_ID and PHP_EXTRA_VERSION). From
the command line, running php -v prints out the PHP version too.

2. In the following list there are two features that have never been available in PHP (up to 5.6).
What are they?
Answers 3 and 4 are correct. Named parameters and generics have never been available in PHP.
Generators were introduced in PHP 5.5, while variadic functions (indefinite number of parameters)
were already available but PHP 5.6 introduced the ... operator to make them more explicit and
easier to handle, as there is no need to use the func_get_args() anymore.
Named parameters would allow to pass arguments to a function in any order by using a name:
1. PHP 11

// named_parameters.php
// positional parameters (haystack, needle)
strpos('this is a test', 'test');

// named parameters
strpos(needle => 'test', haystack => 'this is a test');

Named parameters in Twig


Twig supports named parameters since 1.12, and they are useful to make the meaning of
the passed arguments more explicit or to skip some of them to use the default values.

Generics would allow classes and methods to be parameterized. For example, instead of writing sev-
eral classes like ParameterBag, HeaderBag, FileBag or ServerBag, if they have similar functionality
and only changes the type of the bag, a generic class named Bag could be created which accepts the
type as parameter.

Generics support in Hack


Generics are supported in the Hack language, using a syntax similar to C++ templates:
class Bag<T> { ... }.

3. Can you create a class with the name trait in PHP?


Only answer 2 is correct. Reserved words cannot be used as class names, but as traits were introduced
in PHP 5.4, previous versions don’t have trait keyword in the list of reserved words.
Function names can be used as class names, but trait is not a function in any PHP version. For
example, the following code is valid:

// function_names_as_class_names.php
class strpos {}
$strpos = new strpos();

4. Is strtolower a valid class name in PHP?


Answer 4 is valid. Function names are not reserved words in PHP, so strtolower can be used as
a class name. In PHP, functions and classes are handled differently, so you can have in the same
1. PHP 12

namespace functions and classes with the same name, as shown in the following script:

// script.php
namespace Test;

function test() {}

class test {}

5. Can interfaces inherit from another interfaces in PHP?


Answer 1 is correct, an interface can indeed inherit from another interface. They can be extended
like classes using the extends operator.

6. In PHP, what is the value of MyClass::class_name?


Answer 4 is correct. In the exam, you should be aware of tricky questions like this one. Since PHP
5.5, there is a new constant to get the fully qualified name of a class, but its name is ::class, not
::class_name. So, unless you define the :class_name constant, it won’t be available.

7. Can interfaces define constants in PHP?


Answer 1 is correct, interfaces can indeed define constants. In fact, they are heavily used in Symfony.
For example, the UrlGeneratorInterface interface of the Routing component defines constants such
as ABSOLUTE_URL or ABSOLUTE_PATH.

8. In PHP, can classes/interfaces extending from other classes/interfaces override constants


from the parent one?
Answer 2 is correct, only extending classes can override constants from the parent class. That is not
allowed in interfaces.

9. What are the effects of declaring a class as final in PHP?


Only answer 1 is correct, class definitions prefixed with the final keyword cannot be extended. It
can also be used with methods preventing them from being overriden. For example, the following
1. PHP 13

two examples generate fatal errors (Class TechnicalBook may not inherit from final class
and Cannot override final method Book::getTitle()).

// final_class.php
final class Book {}

class TechnicalBook extends Book {}

// final_method.php
class Book {
final public function getTitle() {}
}

class TechnicalBook extends Book {


public function getTitle() {}
}

10. Is multiple inheritance supported in PHP?


PHP only supports multiple inheritance for interfaces, so only answer 3 is correct. Classes cannot
extend from more than 1 parent class.
In the following example, when implementing the BookCategoryInterface interface, getTitle(),
getCategory() and getPrice() have to be defined:

// interface_multiple_inheritance.php
interface BookInterface
{
public function getTitle();
}

interface CategoryInterface
{
public function getCategory();
}

interface BookCategoryInterface extends BookInterface, CategoryInterface


{
public function getPrice();
}
1. PHP 14

11. What is wrong with the following PHP code?


The code is correct, so answer 4 is the right one. The only limitation that final classes have is
that it cannot have child classes extending from it, but they can be instantiated and even define
protected methods. While it does not make much sense to define protected methods in final classes,
it’s completely valid.

12. How can you check the syntax of a PHP script without executing it?
Answer 3 is correct. The PHP binary can check the syntax (lint) of a PHP file when using the -l
option. While eval() can be used to check if a given PHP code is correct, it does it by executing it,
which is quite different. Take the following code as an example:

// example.php
echo lower('HELLO');

The syntax is valid, so php -l will not detect any error. If you pass that code to eval() it will throw
an error as the lower() function is undefined.
The php_check_syntax() function was removed in PHP 5.0.5. Despite having that name, it used to
check the syntax but also executed the code.

Check several PHP files


The php -l command only checks 1 file. If you are using a Unix-like system and want
to check all PHP files of the project, you can combine it with the find command: find .
-type f -name "*.php" -exec php -l {} \;.

13. Is there any problem with the following PHP code?


Answer 4 is correct, the code works as expected and the output is Title. To keep backward
compatibility with PHP 4, PHP 5 still accepts the keyword var, and PHP treats those properties
as public.

14. Which of the following sentences are true about abstract classes in PHP?
Answers 1 and 4 are correct. Abstract classes cannot be instantiated and they can include abstract
1. PHP 15

methods which must be implemented in child classes.


PHP does not support multiple inheritance for classes, even for abstract classes. Only interfaces can
extend from multiple interfaces. It does not make sense to declare an abstract class as final, as they
need to be extended in order to be instantiated. In fact, PHP does not allow declaring abstract classes
as final.

15. Does the following class definition throw an error?


Answer 1 is correct, it throws a fatal error. In PHP, if one of the methods is declared as abstract,
the class must be abstract as well. The following code would be correct:

// abstract_method.php
abstract class Book {
abstract function getTitle();
}

16. What is the minimum version to use traits in PHP?


Answer 2 is correct. Traits were introduced in PHP 5.4. In single inheritance languages like PHP,
traits are used as a mechanism for code reuse.

17. Which of the following function calls are valid if the function definition is map(callable
$callback)? (assuming PHP 5.4+)

All answers are correct! The callable typehint was introduced in PHP 5.4 and accepts anything
that can be executed as a function. For example:

• Function names of built-in and user defined functions.


• An array containing an instantiated object at position 0 and the method name at position 1.
• For static class methods: an array containing a class name at position 0 and the method name
at position 1 or just className::methodName().
• Anonymous functions.

In this question, anonymous functions are used in answers 1 and 3. Answer 2 uses a string with a
built-in function name. And answer 4 uses an array with an object of the class SplFixedArray and
the method count.
1. PHP 16

18. Which of the following elements are affected by namespaces in PHP?


Answers 1, 2 and 3 are correct. In PHP, namespaces affect classes, interfaces, functions, constants
and traits. Variables are not affected by namespaces. For example, the following code displays 1 as
a result:

// variable_namespace.php
namespace Foo {
$number = 1;
}

namespace Bar {
echo $number;
}

19. Is there any error in the following code?


Answer 4 is correct, there are no errrors. PHP supports defining multiple namespaces in the same
file. While you can use the usual syntax, the alternative bracketed syntax is recommended in these
cases. When the namespace has no name, it assumes that is the global namespace.
Symfony makes use of this syntax in the app/bootstrap.php.cache, which contains a copy of
several classes and interfaces that will be used for sure, reducing the number of input/output
operations.

20. What is the output of the following code?


Answer 3 is correct, the __NAMESPACE__ magic constant contains the FQN (Fully Qualified Name) of
the current namespace. If the current namespace is the global one, __NAMESPACE__ will be empty.

21. What is the output of echo print("hello");?


Answer 3 is correct. Both echo and print are language constructs that output a string, but they are
slightly different. When using print, it always returns 1, no matter what the input is (yay!). In the
example, print first outputs the string hello and then returns 1, which is printed by echo. Not to
be confused with the printf function, which returns the length of the input string:
1. PHP 17

// print.php
// output: hello1
echo print('hello');

// output: hello5
echo printf('hello');

// parse error
echo echo 'hello';

22. If an interface defines the method test(array $data) and the class implementing the
interface defines the method test($data). Does PHP throw an error?
Answer 1 is correct. Classes implementing an interface must define all methods using the exact same
method name and typehints. Interestingly enough, default values can differ. If we take the following
interface as an example:

// book_interface.php
interface BookInterface
{
public function buy($price = 20);
}

public function buy($price = 5) would be valid, but not public function buy($price)

23. What is the only case in which a class defining an interface, it doesn’t have to implement
all its methods?
Answer 2 is correct. Abstract classes can implement an interface and not define all its methods. Child
classes will have to do it in order to be instantiated. For example, any non-abstract class extending
from MyList will have to implement the method count():

// abstract_interface.php
class MyList implements \Countable {}

24. What needs to be done to make the following code work as expected (and print out 3)?
1. PHP 18

Answer 4 is correct. The count() function will check if the object implements the Countable
interface (wich defines only one method, count()), and in that case will return the result of that
method.
Other answers are not valid. It’s impossible to redeclare functions in PHP, at least without using
third-party extensions such as APD⁷. The Iterator interface is meant for objects that want to be
iterated using a foreach loop for example. Finally, even if you could add a custom handler to catch
that kind of errors, it would not work as the count() function does not throw any exception or error
when an object that doesn’t implement the Countable interface is passed, it just returns 1.

25. Given the file test.php, what happens if you run php test.php?
Answer 1 is correct. To understand this behaviour, you first need to know how the PHP parser works.
When there is only one file with no include’s or require’s, PHP reads the whole file and generates a
stream of tokens, which are then converted into a list of instructions called opcodes. These opcodes
are stored in memory, so even if you delete the current file with unlink(__FILE__), the execution
can continue. For example, this is the list of opcodes for the sample code:

• SEND_VAL(‘test.php’)
• DO_FCALL(‘unlink’)
• ECHO(‘hello’)

This list is stored in memory, so PHP just needs to execute it without worrying about the original
file.
When there are instructions like include or require, the process is similar, as they are only parsed
when the interpreter reaches the opcode to do it. So, the following code generates a warning because
the file notfound.php doesn’t exist, but first prints out the string hello:

// test_include.php
echo 'hello';
include 'notfound.php';

26. Which of the following sentences are true about PHAR files?
Only answer 2 is correct. The phar extension provides a way to put entire PHP applications into
a single file. As you can see in the following hex dump taken from a simple PHAR file, they are
composed of 4 main parts: stub, manifest, file contents and signatures.
⁷http://php.net/manual/en/book.apd.php
1. PHP 19

Figure 1. PHAR files

The first part, the stub, usually contains loader functionality and it always must end with a call the
__halt_compiler() function. This function, which you can use in your own scripts as well, tells
PHP to stop parsing the contents of the file. That way, additional data can be included in the PHAR
file. The manifest describes the contents of the files, such as filenames, sizes or timestamps. The
file contents section contains the actual contents of the files, and the signature is used to check the
integrity of the PHAR file before getting executed.
Other answers are not correct. They store PHP files as raw text, not as opcodes. Incremental updates
are not supported and they can be executed even if phar.require_hash is set to off.

27. How can you create a Symfony\Component\HttpFoundation\Response object without writing


the full namespace if there is already a use Symfony\Component\HttpFoundation instruction?
Answer 2 is correct. PHP matches the first part of HttpFoundation\Response with the last part
of Symfony\Component\HttpFoundation, so the FQN that receives the autoloading function is
Symfony\Component\HttpFoundation\Response.

Aliases work similarly:

// use_alias.php
use Symfony\Component\HttpFoundation as Http;

$response = new Http\Response();


1. PHP 20

28. When would you use the levenshtein() function?


Answer 1 is correct. The levenshtein() function calculates the Levenshtein distance⁸ between two
strings, which is the minimal number of characters you have to replace, insert or delete to transform
one string into another. That means that the distance between similar words will be smaller, so it is
useful to detect misspelled words and suggest alternatives, like in the following example:

// levenshtein.php
$commands = [
'start',
'stop',
'reload'
];

// $input = 'statr';

if (false === in_array($input, $commands)) {


foreach ($commands as $command) {
if (levenshtein($input, $command) < 3) {
echo 'Did you mean ' . $command . '?';
exit();
}
}
}

// ...

In the example, if the user enters statr instead of start, as the Levenshtein distance is 2, the script
suggests using start.
Symfony uses this function in two components: Console and DependencyInjection. Every time
you mispell a command, for example, php app/console cache:clean instead of php app/console
cache:clear, the Console component checks for similar commands to suggest alternatives. The
same happens if you try to get an unknown service from the DIC.

⁸http://en.wikipedia.org/wiki/Levenshtein_distance
1. PHP 21

$ php app/console cache:clean

[InvalidArgumentException]
Command "cache:clean" is not defined.
Did you mean this?
cache:clear

29. Which of the following pairs of words are not related?


Answer 2 is correct. Closures are anonymous functions, and they don’t have nothing to do
with abstract classes, which are classes that cannot be instantiated and can have methods not
implemented yet.
The rest of pairs are closely related:

• The spl_autoload_register() function allows to register a custom function that will be


called every time you use a class or an interface that have not been include‘d or require‘d
yet. The goal of the function is to convert a FQN into a file path, and load it, based on a series
of rules. The PSR-4 standard defines a series of rules to convert a FQN into a file path.
• PHAR files must always have a call to the __halt_compiler() function, so PHP knows that
it doesn’t have to keep parsing the file.
• With the __sleep() magic method you can return an array with the names of all variables of
the object that should be serialized.

30. What is the output of the following PHP script?


Answer 2 is correct. The body of anonymous functions is only executed when the function is called,
so the script first prints out 2 and then calls the function. Then, the anonymous function tries to
output the value of $value, but it doesn’t have access to it (it wasn’t imported with a use statement).
As notices were muted in the first line, it doesn’t display the Undefined variable: value message.
To fix the script so the output is 21, you would need to add a use statement:
1. PHP 22

$value = 1;
$get = function() use ($value) {
echo $value;
};

echo 2;
$get();

31. What is the value of __NAMESPACE__ when used in global code?


Answer 3 is correct. When used in the global namespace, __NAMESPACE__ can still be used but it’s
empty.

// namespace_constant.php
// empty
var_dump(__NAMESPACE__);

namespace {
// empty
var_dump(__NAMESPACE__);
}

namespace Test {
// Test
var_dump(__NAMESPACE__);
}

32. Given the following code, what is the value of the $value variable?
Answer 1 is correct. While not very common, the namespace operator can be used to explicitly
request an element from the current namespace (or subnamespace).

33. If a namespaced function does not exist, does PHP fallback to the global function?
Answer 1 is correct. PHP has different rules to fallback to the global namespace if a namespaced
element does not exist:

• Classes: Class names always resolve to the current namespace name, there is no fallback to
1. PHP 23

global namespace.
• Functions and constants: It will fallback to global functions/constants if a namespaced
function/constant does not exist.

It is easier to see with the following examples:

// namespace_fallback.php
namespace One;

$class = new Exception();


$function = strlen('hello');
$constant = PHP_EOL;

The new Exception() instruction will fail, as the One\Exception class doesn’t exist, and PHP
doesn’t try to fallback to the global namespace. For classes and functions is different, even though
One\strlen and One\PHP_EOL don’t exist, PHP fallbacks to \strlen and \PHP_EOL.

34. What is the output of the following code?


Answer 2 is correct. PHP automatically converts anonymous functions stored in variables into
instances of the Closure internal class.

Takeaways
• Object Oriented Programming
– Reserved words cannot be used as class names.
– Final classes cannot be extended, while final methods cannot be overriden.
• Namespaces
– Namespaces affect classes, interfaces, functions, constants and traits. Variables are not
affected by namespaces.
– Multiple namespaces can be defined in the same file.
– The __NAMESPACE__ magic constant contains the current namespace name. When used
in global code, its empty.
– The namespace operator can be used to explicitly request an element from the current
namespace or subnamespace.
1. PHP 24

– When functions and constants don’t exist in the current namespace, PHP fallbacks to
the global namespace
• Interfaces
– Interfaces support multiple inheritance.
– Interfaces cannot define protected or private methods. They are always public
methods.
– The class implementing the interface must use the exact same method signatures for all
methods. A fatal error is generated otherwise.
• Anonymous functions and closures
– Anonymous functions stored in variables are converted into Closure instances.
• Abstract classes
– Abstract classes cannot be instantiated.
– Unlike normal classes, abstract classes can contain abstract methods and they don’t have
to implement all methods from imported interfaces.
• PHP features
– PHP 5.3 introduced namespaces, closures, nowdoc syntax and late static binding.
– PHP 5.4 introduced traits, short array syntax and the built-in webserver, among others.
– PHP 5.5 introduced generators, the finally keyword, a new password hashing API and
class name resolution via ::class.
– PHP 5.6 introduced the ... operator for variadic functions, use const and use function,
the phpdbg debugger and expression in constants.
• Other
– Current PHP version can be retrieved with the phpinfo() or phpversion() built-in
functions, PHP_VERSION constants or with php -v from CLI.
2. HTTP
Exam goals
2.1. Client/Server interaction
2.2. HTTP request
2.3. HTTP response
2.4. Status codes

Questions
1. Which class of HTTP status codes is used to indicate a client error?

1. 1xx class
2. 3xx class
3. 4xx class
4. 4xx or 5xx classes, depending on the type of the error

2. Which of the following sentences about the HTTP protocol are true?

1. It’s a stateless protocol


2. It’s a binary-based protocol
3. GET, POST and DELETE are idempotent methods
4. GET, HEAD and OPTIONS are safe methods

3. What is the purpose of the TRACE method?

1. Performance
2. Debugging
3. Security
4. Caching

4. Given the following HTTP request, what is the value of Request::getScheme()?


2. HTTP 26

GET / HTTP/1.1
Host: localhost:8000
Accept: text/html
X-Scheme: tcp

1. tcp
2. localhost
3. 8000
4. http

5. If you go to https://example.com, what is the port that the webserver is listening to?

1. 21
2. 80
3. 443
4. 8080

6. Are responses to the OPTIONS method cacheable?

1. Yes
2. No

7. Which of the following steps must be done in order to serve gzipped responses from
Symfony apps?

1. Call to the setCompression() method of the Response object, passing the value gzip
2. Check if application/gzip is in the array returned by the getAcceptableContentTypes()
method of the Request object, and in that case, gzip the contents with gzencode()
3. Set the option framework.request.compression to gzip.
4. Nothing must be done, this is handled by the web server.

8. If the webserver returns the CSS file styles.css compressed with GZIP, what is the value
of the Content-type header?

1. application/gzip
2. text/css
3. text/css+gzip
4. deflate
2. HTTP 27

9. How does the browser communicate to the server what is the preferred language of the
user?

1. The main language is included in the User-Agent header


2. User languages preferences are sent using the Accept-Language header
3. The main language is sent using the Locale header, while secondary languages are usually
included in the X-Locale-Alternate header.
4. The browser doesn’t communicate the language of the user, it’s inferred from the user IP

10. Given the following controller, what is the content returned by a HEAD request?

// AppBundle/Controller/BookController.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class BookController
{
public function indexAction(Request $request)
{
return new Response($request->getHttpHost());
}
}

1. HEAD
2. GET
3. HTTP
4. Empty response

11. Which of the following sentences are true about the DELETE method?

1. It asks the server to delete a resource


2. It may not be available in all browsers
3. It must return 105 Deleted as status code.
4. It was removed in HTTP 1.1

12. What HTTP status code would you return if http://example.com/1 is not available
temporarily and want to redirect all requests to http://example.com/2 until it gets back?

1. 200
2. 204
2. HTTP 28

3. 301
4. 302

13. In the HTTP protocol, can the same URI accept more than one method?

1. Yes
2. No

14. What exception must be thrown in Symfony to generate a HTTP 404 status code?

1. Symfony\Component\HttpKernel\Exception\NotFoundHttpException
2. Symfony\Component\HttpKernel\Exception\ConflictHttpException
3. Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
4. Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException

Answers
1. Which class of HTTP status codes is used to indicate a client error?
Answer 3 is correct. In the HTTP protocol, the first digit of the status code defines the class of the
response. For client errors such as bad syntax, 4xx status codes are used:

Class Description
1xx Informational
2xx Success
3xx Redirection
4xx Client error
5xx Server error

There are tens of status codes⁹. For the exam, you should at least try to know the most important
ones:

⁹https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
2. HTTP 29

Status code Description Usage


200 OK GET and HEAD responses
201 Created POST responses
204 No content PUT or PATCH responses
301 Moved permanently URL has changed permanently, for example
a domain change
304 Not Modified For validation caching, as a response of
If-None-Match or If-Modified-Since
headers
400 Bad request For example, bad parameters sent by the
client
403 Forbidden User is not authorized to access to the
resource
404 Not found The resource does not exist
405 Method Not Allowed The HTTP method is not accepted. For
example, the TRACE method is refused by
most servers
500 Internal Server Error General error in the server

2. Which of the following sentences about the HTTP protocol are true?
Answers 1 and 4 are correct. The HTTP protocol is stateless, that’s why additional tools are needed
to maintain the session, like cookies or querystring parameters. It is also a text-based protocol, which
makes it easier to debug but also less efficient.

HTTP/2
Unlike HTTP 1, HTTP/2 is a binary protocol. Methods, status codes, header fields and URIs
will remain the same, but there are several improvements that will make the web faster.

There are two important concepts for HTTP methods: idempotency and safety. Safe methods are
the ones that do not modify resources (or at least they shoudn’t), such as GET, HEAD and OPTIONS.
In the other hand, idempotent methods can be called several times without different outcomes. For
example, POST is not idempotent, as it creates a new resource in each call. In the other hand, PUT
is idempotent, as it modifies a resource providing the newly-updated representation of the original
resource, so calling it multiple times will end up with the same representation as the first call. GET,
HEAD, OPTIONS, PUT and DELETE are idempotent methods.

3. What is the purpose of the TRACE method?


Answer 2 is correct. The TRACE method is used for debugging and echoes back the client request
(including cookies and credentials). It is useful to know if any intermediate proxy is modifying the
2. HTTP 30

request, but unfortunately is switched off in most servers as there is a vulneraibilty which exploits
this method, Cross Site Tracing¹⁰. You can try it out with your local server:

$ curl -X TRACE 127.0.0.1


TRACE / HTTP/1.1
User-Agent: curl/7.37.1
Host: 127.0.0.1
Accept: */*

$ curl -X TRACE -H "Cookie: id=123" 127.0.0.1


TRACE / HTTP/1.1
User-Agent: curl/7.37.1
Host: 127.0.0.1
Accept: */*
Cookie: id=123

4. Given the following HTTP request, what is the value of Request::getScheme()?


Answer 4 is correct. The getScheme() method returns the request’s scheme, in this case, http. The
method only returns http or https, and as the request is not secured, the value is http:

// Request.php
public function getScheme()
{
return $this->isSecure() ? 'https' : 'http';
}

The X-Scheme request header is made up and is not taken into account.

5. If you go to https://example.com, what is the port that the webserver is listening to?
Answer 3 is correct. By default, HTTP requests are sent to the port 80, and HTTPS ones to 443.

6. Are responses to the OPTIONS method cacheable?


Answer 2 is correct. The OPTIONS allows you to get a list of what HTTP methods the current resource
¹⁰https://www.owasp.org/index.php/Cross_Site_Tracing
2. HTTP 31

provides, and is not cacheable. For example, if you send the following HTTP request:

OPTIONS /dist/httpd HTTP/1.1


Host: apache.org

You get as a response the Allow HTTP header with the value GET,HEAD,POST,OPTIONS,TRACE. That
means that the URI apache.org/dist/httpd accepts all those methods. In Symfony you would have
to use the methods option to accept only a subset of HTTP methods, otherwise it accepts any method.

7. Which of the following steps must be done in order to serve gzipped responses from
Symfony apps?
Answer 4 is correct. Compressing the body of HTTP responses with GZIP is usually done on the
fly by the webserver, as long as the Accept-Encoding request header contains gzip or deflate. The
following diagram shows how GZIP compression works with the HTTP protocol:

Figure 2. HTTP + GZIP

1. The user wants to go to http://example.com.


2. The browser, as is able to decompress GZIP responses, it adds the header Accept-Encoding:
gzip.
3. The web server (i.e. Apache or nginx) asks PHP/Symfony for the response for the given
request.
4. The Symfony application returns a HTTP response with HTML code (usually) in its body.
5. As the browser sent the Accept-Encoding: gzip header, the web server compresses the
response body on the fly and adds the Content-Encoding: gzip header (Content-Type must
2. HTTP 32

contain the type of the uncompressed contents


6. The browser checks that the Content-Encoding: gzip header is present, and uncompress the
body on the fly.

8. If the webserver returns the CSS file styles.css compressed with GZIP, what is the value
of the Content-type header?
Answer 2 is correct. When a resource is compressed, the Content-type header must remain the
same, so the client knows what type of content will be dealing with once it gets uncompressed. To
let the client know that the resource has been compressed with GZIP, the Content-Encoding header
is used. This header is used as a modifier to the media-type, and indicates additional content codings
that have been applied to the body. For the current example, the following headers would be valid:

Content-Type: text/css
Content-Encoding: gzip

Why GZIP?
GZIP has become the de-facto lossless compression method for text data in websites, but
that doesn’t mean that is the best compresion algorithm. There are methods with better
compression ratios, but GZIP provides a good-enough compression ratio in most situations
and it’s really fast, both for compression and decompression. If you think about it, the
process is completely transparent for the user: the webserver compress resources on the
fly (unless it’s done offline) and the browser decompress it on the fly as well when the
Content-Encoding header is present.

9. How does the browser communicate to the server what is the preferred language of the
user?
Answer 2 is correct. In each request, the browser fills the Accept-Language header with the
languages configured by the user. It may also contain a parameter indicating the priority, for
example:

GET / HTTP/1.1
Host: localhost:8000
Accept-Language:es,en;q=0.8,en-US;q=0.6

In the previous example, Spanish is set as the main language, so it is expected that websites return
2. HTTP 33

the Spanish-version of the resource if available. English and English-US, in that order, are set as
secondary languages.
The Request object in Symfony provides a way to get the list of languages and even get the preferred
language based on the user preferences from the ones supported by the website/app:

// AppBundle/Controller/BookController.php
use Symfony\Component\HttpFoundation\Request;

class BookController
{
public function indexAction(Request $request)
{
$languages = $request->getLanguages();

$preferred = $request->getPreferredLanguage(['en', 'fr']);

// ...
}
}

The $languages variable will contain the user language preferences in the correct order, ['es',
'en', 'en_US']. The $preferred variable will contain en, as it is supported by the website/app and
the client.

10. Given the following controller, what is the content returned by a HEAD request?
Answer 4 is correct. The HEAD method is identical to GET, except that the server must return an
empty body. The metainformation contained in the HTTP headers should also be identical to the
ones returned for the GET method. The HEAD method is useful for testing links.
In Symfony, responses to HEAD requests have an extra post-processing step. The Response::prepare()
method, which is always called before sending the response, and is responsible of tweaking the
Response object to ensure that it is compliant with RFC 2616 (HTTP/1.1). One of the things that this
method takes care of is to remove the body when the request method was HEAD:
2. HTTP 34

// Symfony/Components/HttpFoundation/Response.php
namespace Symfony\Component\HttpFoundation;

class Response
{
// ...

public function prepare(Request $request)


{
// ...

if ($request->isMethod('HEAD')) {
$length = $headers->get('Content-Length');
$this->setContent(null);
if ($length) {
$headers->set('Content-Length', $length);
}
}

// ...
}

// ...
}

11. Which of the following sentences are true about the DELETE method?
Answers 1 and 2 are correct. The DELETE method is defined in the HTTP specification and is
submitted by clients to remove a resource from the server. Despite this, it is possible that some
browsers don’t support it. In those cases, Symfony uses a workaround (_method parameter) to
simulate it.

12. What HTTP status code would you return if http://example.com/1 is not available
temporarily and want to redirect all requests to http://example.com/2 until it gets back?
Answer 4 is correct. The 302 HTTP status code is meant to let the client know that the resource is
temporarily under a different URI, but it should keep using the original URI in future requests. The
301 status code is for resources that have been moved permanently. In both cases, the redirection
URI is set in the Location header:
2. HTTP 35

HTTP/1.1 302 FOUND


Location: http://example.com/2

13. In the HTTP protocol, can the same URI accept more than one method?
Answer 1 is correct, it can accept more than one method. In fact, it is common to accept several
methods. For example, it is common that forms use the same URI for both displaying the form and
receiving the data:

<form method="post" action="/index.php">


<!-- ... -->
</form>

The /index.php resource will accept GET requests to display the form (and probably HEAD requests
as well) and POST requests to handle the sent data.
In Symfony, routes accept any HTTP method by default unless the methods option is used.

14. What exception must be thrown in Symfony to generate a HTTP 404 status code?
Answer 1 is correct. Throwing a NotFoundHttpException exception from a controller will make
Symfony to convert it into a 404 Not Found response. If you extend from the base controller, this is
exactly what the createNotFoundException() does:

// FrameworkBundle/Controller/Controller.php
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class Controller extends ContainerAware


{

// ...

public function createNotFoundException(


$message = 'Not Found',
\Exception $previous = null
) {
return new NotFoundHttpException($message, $previous);
}
2. HTTP 36

The HttpKernel component provides several exceptions for common HTTP status codes (the
following exception classes must be prefixed with Symfony\Component\HttpKernel\Exception):

Exception HTTP status code


AccessDeniedHttpException 403
BadRequestHttpException 400
ConflictHttpException 409
GoneHttpException 410
LengthRequiredHttpException 411
MethodNotAllowedHttpException 405
NotAcceptableHttpException 406
NotFoundHttpException 404
PreconditionFailedHttpException 412
PreconditionRequiredHttpException 428
ServiceUnavailableHttpException 503
TooManyRequestsHttpException 429
UnauthorizedHttpException 401
UnsupportedMediaTypeHttpException 415

Takeaways
• HTTP methods
– Safe methods should not modify resources. GET, HEAD and OPTIONS are safe methods.
– Idempotent methods can be called several times without different outcomes. That is, the
side-effects of several identical requests is the same as for a single request. GET, HEAD,
OPTIONS, PUT and DELETE are idempotent methods.
– Symfony removes the response body for HEAD requests.
– Some browsers only accept the GET and POST methods. Symfony uses the _method
parameter as a workaround.
• Status codes
– Classes
* 1xx class: Informational
* 2xx class: Success
* 3xx class: Redirection
2. HTTP 37

* 4xx class: Client error


* 5xx class: Server error
– The HttpKernel provides exceptions for common HTTP errors that generate the proper
status code.
• Request headers
– Accept-Language is used to communicate the language preferences of the user, ordered
by priority.
– Host specifies the host (domain) and the port number.
– Accept-Encoding: gzip is sent by clients that support GZIP compression.
• Response headers
– In compressed responses, Content-Encoding is used to inform the client about the
compression algorithm used, while Content-Type contains the original content type
before compressed.
3. Architecture
Exam goals
3.1. Standard edition of Symfony2
3.2. Components
3.3. Bundles
3.4. Bridges
3.5. Configuration
3.6. Code organization
3.7. Request handling

Questions
1. Which of the following classes do not belong to the HttpFoundation component?

1. Request
2. Response
3. Firewall
4. Session

2. Is it possible to disable the kernel.terminate event?

1. Yes
2. No

3. Which of the following sentences defines better what a bridge is?

1. Bridges are a set of classes to extend third-party libraries into Symfony


2. Bridges contain glue code between Symfony components and the full-stack framework
3. Bridges contain common interfaces and abstract classes to add flexibility
4. Bridges are special bundles that extend from the FrameworkBundle bundle

4. Which of the following files are front controllers?


3. Architecture 39

1. app/console
2. web/app.php
3. web/app_dev.php
4. Symfony\Bundle\FrameworkBundle\Controller

5. What of the following Symfony components can be used standalone?

1. Form
2. HttpKernel
3. Security
4. DependencyInjection

6. Which of the following statements are true when the debug mode is enabled?

1. The application runs slower.


2. The Symfony cache is flushed automatically when changes are detected.
3. The Symfony cache is disabled.
4. The event kernel.debug is enabled.

7. Classes implementing the HttpKernelInterface interface handle a Request to convert it to


a Response

1. True
2. False

8. What is the correct order of the following kernel events?

1. kernel.request, kernel.view, kernel.controller, kernel.response, kernel.terminate.


2. kernel.request, kernel.controller, kernel.view, kernel.terminate, kernel.response.
3. kernel.request, kernel.view, kernel.controller, kernel.terminate, kernel.response.
4. kernel.request, kernel.controller, kernel.view, kernel.response, kernel.terminate.

9. How can you get the controller name from a listener subscribed to the kernel.request event?

1. $event->getController()
2. $event->getRequest()->get('_controller')
3. $event->getRequest()->attributes->get('_controller')
4. It’s not possible to get the controller name.
3. Architecture 40

10. What event can be used to dynamically change the controller to be executed?

1. kernel.request
2. kernel.route
3. kernel.controller
4. kernel.view

11. What kernel event could be used to add an special HTTP header to some responses?

1. kernel.request
2. kernel.view
3. kernel.response
4. kernel.terminate

12. Which of the following directories are not inside the app directory?

1. cache
2. web
3. config
4. vendor

13. Symfony 3 will use a slightly different directory structure. Which of the following files or
directories will exist?

1. /bin/console
2. /var/cache
3. /var/logs
4. /app/config

14. What are the advantages of using the Symfony Filesystem component instead of PHP
built-in functions such as mkdir() or file_put_contents()?

1. Better portability and error handling


2. Provides abstractions such as File or Directory
3. If available, makes use of Linux utilities such as cp or rm
4. It can dump atomically content into a file with the dumpFile() method

15. In the Process component, the method Process::isSuccessful() returns true if the
command…
3. Architecture 41

1. … status code is 0
2. … status code is different than 0
3. … error output is empty
4. … finished in less seconds than the timeout value

16. In the following list there is one component that is not available in Symfony. Which one
is it?

1. OptionsResolver
2. ExpressionLanguage
3. Finder
4. Stopwatch

17. What is the design pattern that implements the EventDispatcher component?

1. Strategy
2. Factory
3. Adapter
4. Mediator

18. Why the Process component doesn’t work on Windows?

1. Because proc_open() is not available on Windows


2. Because chdir() contains a bug in most Windows versions
3. Because PHP has to be compiled with the --enable-maintainer-zts flag
4. The Process component works on Windows

19. What is the output of the following code?

// php_process.php
use Symfony\Component\Process\PhpProcess;

$message = 'Hello world';


$process = new PhpProcess('<?php echo $message;');
$process->run();

echo $process->getOutput();

1. Hello world
3. Architecture 42

2. Notice: Undefined variable: message


3. echo Hello world;
4. echo $message;

20. Internally, all translation files are converted into *.po files so gettext can be used

1. True
2. False

21. If you define a kernel.request listener with maximum priority so it is the first one to
be executed, will other kernel.request listeners get executed if you set the response using
setResponse()?

1. Yes, always
2. Yes, unless you execute stopPropagation()
3. Yes, unless you return false
4. No

Answers
1. Which of the following classes do not belong to the HttpFoundation component?
Answer 3 is correct, the Firewall class does not belong to the HttpFoundation component, but the
Security one. In this component, firewalls are used to authenticate a user. Request and Response
are probably the two most important classes of the HttpFoundation, but the component contains
code for other actions such as managing sessions and file uploads.

2. Is it possible to disable the kernel.terminate event?


Answer 1 is correct. The kernel.terminate event is only dispatched when the kernel imple-
ments the TerminableInterface interface. By default, the AppKernel class extends from Sym-
fony\Component\HttpKernel\Kernel, which already implements that interface, but it can be dis-
abled by using a different class (as long as it implements Symfony\Component\HttpKernel\HttpKernelInterface)
or overriding the terminate() method as in the following example:
3. Architecture 43

// AppKernel.php
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class AppKernel extends Kernel


{

// ...

public function terminate(Request $request, Response $response)


{
return;
}
}

3. Which of the following sentences defines better what a bridge is?


Answer 1 is correct. A bridge fills the gap between third-party libraries like Doctrine, Twig or
Swiftmailer, and Symfony. For example, the Swiftmailer bridge adds a data collector to intercept
messages so they can be viewed from the profiler.

4. Which of the following files are front controllers?


Answers 1, 2 and 3 are correct. A front controller handles all requests of a given application. Symfony
provides 3 front controllers: web/app.php and web/app_dev.php for HTTP requests and app/console
for the console tool.
In Symfony, front controllers are responsible of loading the app/bootstrap.php.cache file (which
in turn loads the Composer autoload file) and bootstrapping the kernel.

5. What of the following Symfony components can be used standalone?


All answers are correct. All Symfony components can be used standalone (even the most complex
ones like Form or Security), so you can take any of them and add it to your own project or even
create a new framework.
3. Architecture 44

6. Which of the following statements are true when the debug mode is enabled?
Answers 1 and 2 are correct. When the debug mode is enabled, the internal cache is flushed
automatically when changes in configuration files or code are detected. This process makes the
application run slower.

7. Classes implementing the HttpKernelInterface interface handle a Request to convert it to


a Response
Answer 1 is correct. The kernel implements the HttpKernelInterface interface, which only defines
the handle() method:

// HttpKernelInterface.php
function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true);

The handle() method of kernels implementing this interface converts the Request object into a
proper Response object.

8. What is the correct order of the following kernel events?


Answer 4 is correct. The following diagram shows the events and the order they are generated:
3. Architecture 45

Figure 3. Kernel events

1. As soon as the Request object is handled by the kernel, a kernel.request event is dispatched.
2. Then, once the controller has been resolved, a kernel.controller event is generated.
3. If the controller does not return a proper Response object, a kernel.view event is dispatched
so listeners can convert what the controller has returned into a Response object.
4. When the Response object is ready to be sent, a kernel.response event is dispatched.
5. Just after the response has been sent to the browser, the kernel.terminate event is generated
for expensive tasks.
6. If an error is produced, a kernel.exception event is dispatched so listener can generate a
helpful response for the user or the developer.

If you see the implementation of the kernel, the order of the events can be seen quite easily:
3. Architecture 46

// HttpKernel/HttpKernel.php
class HttpKernel implements HttpKernelInterface, TerminableInterface
{

public function terminate(Request $request, Response $response)


{
// kernel.terminate
$this->dispatcher->dispatch(
KernelEvents::TERMINATE,
new PostResponseEvent($this, $request, $response
));
}

private function handleRaw(Request $request, $type = self::MASTER_REQUEST)


{
// ...

// kernel.request
$this->dispatcher->dispatch(KernelEvents::REQUEST, $event);

if ($event->hasResponse()) {
// if a listener returns a response, it is sent
// ...
}

// resolve controller
if (false === $controller = $this->resolver->getController($request)) {
// ...
}

// kernel.controller
$this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event);

// ...

// execute controller
$response = call_user_func_array($controller, $arguments);

// kernel.view if not a proper response


if (!$response instanceof Response) {
// kernel.view
$this->dispatcher->dispatch(KernelEvents::VIEW, $event);
3. Architecture 47

// ...
}

return $this->filterResponse($response, $request, $type);


}

private function filterResponse(Response $response, Request $request, $type)


{
// ...

// kernel.response
$this->dispatcher->dispatch(KernelEvents::RESPONSE, $event);

return $event->getResponse();
}

private function handleException(\Exception $e, $request, $type)


{
// ...

// kernel.exception
$this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event);

// ...
}

The HttpKernel::terminate() method is called explicitly from the front controller:

// web/app.php

// ...

$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
3. Architecture 48

9. How can you get the controller name from a listener subscribed to the kernel.request event?
Answer 4 is correct. It’s actually impossible to get the controller name from a listener subscribed to
the kernel.request event, as it is dispatched before the controller resolver. It would be possible to
get it from the kernel.controller event, and even change it.

10. What event can be used to dynamically change the controller to be executed?
Answer 3 is correct. Once the controller to be used is resolved, the kernel generates a ker-
nel.controller event, so listeners can change it if they want. Once all listeners have been executed
(or one of them stops propagation), the controller arguments are resolved and is executed.
For example, the following event listener changes the controller if the site is under maintenance
mode:

// AppBundle/EventListener/MaintenanceListener.php
use AppBundle\Controller\MaintenanceController;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;

class MaintenanceListener
{
public function onKernelController(FilterControllerEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {

if (true === $this->maintenance) {


$event->setController([
new MaintenanceController(),
'indexAction'
]);
}
}
}
}

11. What kernel event could be used to add an special HTTP header to some responses?
Answer 3 is correct. The right place to do it would be after the response object has been created
and before it is sent to the browser, so the kernel.response event is the only one that fulfills those
3. Architecture 49

requeriments.
In the following code, the X-Certification: Pass HTTP header is added to all responses with the
200 status code:

// AppBundle/EventListener/HeadersListener.php
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;

class HeadersListener
{
public function onKernelResponse(FilterResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
$response = $event->getResponse();

if (200 === $response->getStatusCode()) {


$response->headers->set('X-Certification', 'Pass');
$event->setResponse($response);
}
}

}
}

And the header is there:

$ curl -I http://localhost:8000/app_dev.php/test
HTTP/1.1 200 OK
Host: localhost:8000
Content-Type: text/html; charset=UTF-8
X-Certification: Pass

12. Which of the following directories are not inside the app directory?
Answers 2 and 4 are correct. By default, the app directory contains 4 subdirectories:

• cache: cache files.


• config: configuration files for all environments.
• logs: log files.
3. Architecture 50

• Resources: templates and translation files.

The vendor directory, which contains all Composer dependencies, is located in the root directory of
the project. The same applies for the web directory.

13. Symfony 3 will use a slightly different directory structure. Which of the following files or
directories will exist?
All answers are correct. In Symfony 3, the cache and logs directory will live inside /var, and the
console file will be moved to the /bin directory:

• app
– app/config
– app/Resources
• bin
– bin/console
• src
• var
– var/cache
– var/logs
• vendor
• web

14. What are the advantages of using the Symfony Filesystem component instead of PHP
built-in functions such as mkdir() or file_put_contents()?
Answers 1 and 4 are correct. There are 3 main advantages of using the Symfony Filesystem
component over PHP functions:

• Portability:
• Ease of use: Methods such as mkdir() or exists() accept arrays and objects implementing
the Traversable interface (IteratorAggregate or Iterator).
• Error handling: Unlike PHP filesystem-related functions, it throws exceptions.
• Unit testing: It makes unit testing easier

One of its methods, dumpFile(), dumps atomically content into a file. That means that you will
never see a partially-written file, as it writes a temporary file first and then moves it to the new file
location when it’s finished.
3. Architecture 51

It does not provide higher abstractions such as File or Directory. Also, it doesn’t make use of Linux
utilities. In fact, there is not a single call to exec(), shell_exec(), passthru() or similar functions.

15. In the Process component, the method Process::isSuccessful() returns true if the
command…
Answer 1 is correct. If the status code returned by the command is 0, the method returns true:

// Process.php
public function isSuccessful()
{
return 0 === $this->getExitCode();
}

Status code from PHP processes


When executing a PHP script from the command line, if there are no errors, PHP returns 0
as the status code, but you can change it with the exit() function. This function terminates
the current script and optionally accepts a status code. In fact, this function is used by the
Console component to return different status codes from an application.

16. In the following list there is one component that is not available in Symfony. Which one
is it?
Sorry for this question, but the correct answer is 2. Keep in mind that the exam is about Symfony 2.3
LTS, and the ExpressionLanguage component didn’t exist when the 2.3 LTS version was released.
Always double check your answers before submitting the exam :)

17. What is the design pattern that implements the EventDispatcher component?
Answer 4 is correct. Basically, the Mediator pattern decouples a Producer from a Consumer. As com-
munication between objects is encapsulated with a mediator object, they no longer communicate
directly with each other, but instead through the mediator. The following diagram shows how it
works:
3. Architecture 52

Figure 4. Mediator pattern

Consumers (event listeners) ask to the mediator object to be informed when a given event is
dispatched. Then, when the producer generates a new event, the mediator informs to all consumers
listening to that event.

Difference between Mediator and Observer


There is a subtle difference between the Mediator and Observer patterns. While Mediator
promotes loose coupling by keeping objects from referring to each other explicitly (that’s
the task of the Mediator object), Observer defines a one-to-many dependency between
objects.

18. Why the Process component doesn’t work on Windows?


Answer 4 is correct. The Process component, as any other Symfony component, works on Windows.
While it is true that it makes use of the proc_open() function, it is part of the PHP core and available
both in Windows and Unix-like systems.
Internally, the Process class makes use of the proc_open() function to execute a command and open
file pointers for input/output:
3. Architecture 53

// Process.php
public function start($callback = null)
{
// ...
$this->process = proc_open(
$commandline,
$descriptors,
$this->processPipes->pipes,
$this->cwd,
$this->env,
$this->options);

// ...
}

The descriptor specification is an indexed array to tell the function how it must to handle stdin,
stdout and stderr. By default, pipes are used, but it can be configured to use a file for stdout
instead. These are the parameters that proc_open() receives when executing ls -lh:

string(6) "ls -lh"

array(3) {
[0] =>
array(2) {
[0] => string(4) "pipe"
[1] => string(1) "r"
}
[1] =>
array(2) {
[0] => string(4) "pipe"
[1] => string(1) "w"
}
[2] =>
array(2) {
[0] => string(4) "pipe"
[1] => string(1) "w"
}
}

array(0) {}

string(31) "/home/raulfraile/tests"
3. Architecture 54

NULL

array(2) {
'suppress_errors' => bool(true)
'binary_pipes' => bool(true)
}

The suppress_errors is only for Windows systems and suppresses errors generated by the proc_-
open() function, while binary_pipes forces to open pipes in binary mode, instead of using the usual
stream_encoding.

19. What is the output of the following code?


Answer 2 is correct. PhpProcess provides a way to execute PHP code in isolation. That means that
it is run in a different process so no variables or open resources are shared between them. Internally,
the component makes use of the PHP binary to execute the code, passing it through stdin:

// PhpProcess.php
namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\RuntimeException;

class PhpProcess extends Process


{
public function __construct(
$script,
$cwd = null,
array $env = [],
$timeout = 60,
array $options []
) {
$executableFinder = new PhpExecutableFinder();
if (false === $php = $executableFinder->find()) {
$php = null;
}

parent::__construct($php, $cwd, $env, $script, $timeout, $options);


}

// ...
3. Architecture 55

As you can see, PhpProcess just finds the PHP binary to work like any other command. In fact, if
you know where your PHP binary is, it would be the same as doing this (in bash):

// PhpProcess.php
use Symfony\Component\Process\Process;

$process = new Process('/usr/local/bin/php <<< "<?php echo 'Hello world';"');


$process->run();

echo $process->getOutput();

20. Internally, all translation files are converted into *.po files so gettext can be used
Answer 2 is correct. Translation files, regardless of their format, are converted into plain PHP files in
the cache directory (translations subdirectory). These PHP files create MessageCatalogue objects:

// app/cache/dev/translations/catalogue.es.1cd7e...php
use Symfony\Component\Translation\MessageCatalogue;

$catalogue = new MessageCatalogue('es', array (


'validators' =>
array (
'This value should be false.' => 'Este valor debería ser falso.',
// ...
)
));
$catalogue->addFallbackCatalogue($catalogueEn);

return $catalogue;

21. If you define a kernel.request listener with maximum priority so it is the first one to
be executed, will other kernel.request listeners get executed if you set the response using
setResponse()?
Answer 4 is correct. When using the setResponse() method of the GetResponseEvent class, it
automatically stops the propagation:
3. Architecture 56

// Symfony/Component/HttpKernel/Event/GetResponseEvent.php
namespace Symfony\Component\HttpKernel\Event;

use Symfony\Component\HttpFoundation\Response;

class GetResponseEvent extends KernelEvent


{
// ...

public function setResponse(Response $response)


{
$this->response = $response;

$this->stopPropagation();
}
}

For kernel.view events, as GetResponseForControllerResultEvent is used (which extends from


GetResponseEvent), the same behaviour is expected.

Takeaways
• Architecture of the full-stack framework
– Components are the building blocks of the framework. They solve common problems in
web development and can be used standalone, even the most complex ones.
– Bundles are a set of PHP classes and configuration files, with a well-known structure,
that provide some functionality.
– Bridges are set of classes to extend third-party library into Symfony.
– Vendors are third-party libraries needed by the framework or the application.
– Symfony provides 3 front controllers: web/app.php and web/app_dev.php for HTTP
requests and app/console for the console tool.
• Kernel events
– The default implementation of the kernel dispatches 6 types of events:
* kernel.request: dispatched as soon as the request arrives. Listeners can return a
Response and “end” the execution.
* kernel.controller: dispatched once the controller has been resolved. Listeners can
manipulate the controller callable.
3. Architecture 57

* kernel.view: dispatched only if the controller does not return a Response object.
* kernel.response: allows to modify or replace the Response object after its creation.
* kernel.terminate: dispatched once the response has been sent. Allows to run
expensive post-response jobs.
* kernel.exception: dispatched if there is an uncaught exception. It is the last chance
to convert an Exception object into a proper Response object.
4. Standardization
Exam goals
4.1. Naming conventions
4.2. Coding standards
4.3. Integration of third-party libraries
4.4. Composer packages handling
4.5. Development best practices
4.6. Framework overload

Questions
1. Which of these are recommended ways to install Symfony?

1. Symfony installer
2. Composer
3. Downloading zip/tgz file from symfony.com
4. apt-get / yum

2. When using the Symfony installer, is it required to have Composer installed to start working
on a new project?

1. No
2. Yes, as it’s required for the autoloading system
3. Yes, it’s neded for the post-install scripts
4. Yes, but only if there are missing dependencies

3. Why are Symfony releases digitally signed using GPG?

1. To generate stronger CSRF tokens in forms


2. To check that the file was downloaded without errors
3. To check that the downloaded file was not modified by any malicious user
4. To avoid using HTTPS
4. Standardization 59

4. What file should contain infrastructure-related configuration options such as database


connection parameters?

1. app/config/parameters.yml
2. app/config/config.yml
3. app/config/config_dev.yml
4. app/config/routing.yml

5. What happens if you add a new parameter to the app/config/parameters.yml.dist file and
then run composer update?

1. It is copied to app/config/parameters.yml
2. Composer asks for the value of the new parameter and then adds it to the app/config/pa-
rameters.yml file.
3. Composer refuses to update dependencies until app/config/parameters.yml.dist and ap-
p/config/parameters.yml have the same options (no matter if different values).
4. Nothing.

6. In terms of performance, is it better to use XML files for configuration files over YAML?

1. Yes
2. No

7. Why is it not recommended to define parameters for the classes of your services, like in the
following example?

# app/config/services.yml
parameters:
importer.class: AppBundle\Importer\Importer

services:
app.importer:
class: "%importer.class%"

1. The parameters are dumped to the compiled class and it makes the file larger for no good
reason.
2. It allows to override a service by just setting its *.class parameter.
3. The compiled container can contain up to 1024 parameters, throwing an exception if there are
more parameters defined.
4. It adds an extra lookup when getting the service from the container.
4. Standardization 60

8. What is PSR-2?

1. A common logger interface


2. An standard way to convert fully qualified names into file paths
3. An utility to convert non-namespaced PHP classes into namespaced ones
4. A coding style guide

9. What is wrong with the following Symfony controller?

// src/BookBundle/Controller/BookController.php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BookController {
function ImageAction() {
$return_response = new Response('hello');

return $return_response;
}
}

1. It won’t work due to syntax issues


2. It won’t work due to autoloading issues
3. It will work, but needs some coding style fixes
4. There is nothing wrong

10. Why is it not recommended to use the @Template annotation to configure the template that
must be used by the controller?

1. It can only be used with Twig templates


2. It makes it more difficult to know which template is being rendered
3. It involves more magic
4. It can’t be used to generate streamed responses

11. In Composer, which of the following requirement is equivalent to ∼1.2?

1. >=1.2
2. >=1.2 <2.0
3. >=1.2 <=1.3
4. >=1.2 <1.3
4. Standardization 61

12. What are the advantages of commiting the composer.lock file into your version control
repository?

1. All developers will work with the exact same version of the dependencies
2. It is easier to work with legacy projects that use older versions of dependencies
3. It is faster to add new dependencies as most of the dependency graph is already calculated
4. There are no important advantages and is not recommended at all

13. If you accidently delete the file vendor/composer/autoload_real.php, what is the best way
to get it back when working in a development environment?

1. Running composer self-update


2. Running composer self-update --rollback
3. Running composer dump-autoload
4. Running composer dump-autoload --optimize

14. Given the following composer.json file, how many packages will end up in the vendor
directory?

{
"require": {
"php": ">=5.3.3",
"symfony/framework-bundle": "2.3.*",
"twig/extensions": "1.0.*"
}
}

1. 0
2. 1
3. 2
4. More than 2

15. Is it possible to use PSR-7 with Symfony 2.3 LTS?

1. Yes
2. No
4. Standardization 62

Answers
1. Which of these are recommended ways to install Symfony?
Only answer 1 is correct. Currently, the Symfony Installer is the only recommended way to create
new Symfony applications. When using the Symfony installer, it automatically downloads a zip or
tar.gz file (depending on your machine’s capabilities) that contains all the required dependencies,
so it’s not necessary to run composer install or composer update to start working. It also has
additional goodies such as generating a strong value for secret parameter.

2. When using the Symfony installer, is it required to have Composer installed to start working
on a new project?
Answer 1 is correct, it is not necessary. It is true that the autoloading system is tied to Composer,
but all the required code is inside the vendor/composer directory, so the Composer binary is not
required at all. Post-install scripts are not executed as Composer itself is not executed, and cannot
be missing dependencies in the downloaded package, unless there is an error.

Composer with the Symfony installer


While Composer is not required to start working in a new project, it will be needed as soon
as you add new dependencies or update the current ones.

3. Why are Symfony releases digitally signed using GPG?


Answer 3 is correct. The sensiolabs/checksums¹¹ repository contains GPG¹² signatures for different
projects, including Symfony. These signatures are generated using a private key, and they are meant
to be used to check that the downloaded file has not been modified by a malicious user to inject
code.

4. What file should contain infrastructure-related configuration options such as database


connection parameters?
Answer 1 is correct. The app/config/config.yml file should contain only application-related
configuration options, as the application doesn’t need to know anything about how to connect to the
database. When the application is deployed, the app/config/parameters.yml can just be changed
for the one containing the right options for the production server.
¹¹https://github.com/sensiolabs/checksums
¹²http://en.wikipedia.org/wiki/GNU_Privacy_Guard
4. Standardization 63

5. What happens if you add a new parameter to the app/config/parameters.yml.dist file and
then run composer update?
Answer 2 is correct. The app/config/parameters.yml.dist contains the canonical list of configu-
ration parameters for the application. By default, the composer.json file is configured to run some
scripts after the install (post-install-cmd) and update (post-update-cmd) commands. One of
this scripts is Incenteev/ParameterHandler/ScriptHandler::buildParameters, which compares
if app/config/parameters.yml.dist and app/config/parameters.yml contain the same options.
In case there is an option defined in app/config/parameters.yml.dist but not in app/config/pa-
rameters.yml, it asks for it.

6. In terms of performance, is it better to use XML files for configuration files over YAML?
Answer 2 is correct, there is no difference in terms of performance between using XML or any
other available format. At least, there is no difference after the first request, once they have been
“compiled” into plain PHP.

7. Why is it not recommended to define parameters for the classes of your services, like in the
following example?
Answers 1 and 2 are correct. Even though it was a common practice, Symony 3 will not include
any *.class parameters as they are dumped to the compiled container making the file larger than
needed without adding any benefit. Also, the possibility to override a service by just changing its
*.class parameter has been abused and is not considered a good practice anymore.
Using the container:debug command, you can check the huge amount of .class-like parameters
defined:

$ php app/console container:debug --parameters | grep ".class" | wc -l


377

$ php app/console container:debug --parameters | grep ".class"


...
cache_clearer.class Symfony\Component\HttpKernel\CacheClearer\ChainC\
acheClearer
cache_warmer.class Symfony\Component\HttpKernel\CacheWarmer\CacheWa\
rmerAggregate
controller_name_converter.class Symfony\Bundle\FrameworkBundle\Controller\Contro\
llerNameParser
controller_resolver.class Symfony\Bundle\FrameworkBundle\Controller\Contro\
4. Standardization 64

llerResolver
data_collector.config.class Symfony\Component\HttpKernel\DataCollector\Confi\
gDataCollector
data_collector.events.class Symfony\Component\HttpKernel\DataCollector\Event\
DataCollector
data_collector.exception.class Symfony\Component\HttpKernel\DataCollector\Excep\
tionDataCollector
data_collector.logger.class Symfony\Component\HttpKernel\DataCollector\Logge\
rDataCollector
data_collector.memory.class Symfony\Component\HttpKernel\DataCollector\Memor\
yDataCollector
data_collector.request.class Symfony\Component\HttpKernel\DataCollector\Reque\
stDataCollector
data_collector.router.class Symfony\Bundle\FrameworkBundle\DataCollector\Rou\
terDataCollector
data_collector.security.class Symfony\Bundle\SecurityBundle\DataCollector\Secu\
rityDataCollector
...

But having the option to change completely how the framework works just by overriding a single
parameter is probably the main reason to get rid of them:

<parameters>
<parameter key="router.class">MyRouter\Router</parameter>
</parameters>

8. What is PSR-2?
Answer 4 is correct. PSR-2 complements PSR-1 (coding standard), to have a common style in projects
that adhere to this standard. At the time of writing, 6 PSR have been accepted:

Name Description
PSR-0 Autoloading Standard (deprecated in favor of PSR-4)
PSR-1 Basic Coding Standard
PSR-2 Coding Style Guide
PSR-3 Logger Interface
PSR-4 Autoloading Standard
PSR-7 HTTP message interfaces
4. Standardization 65

9. What is wrong with the following Symfony controller?


Answer 3 is correct. The code is perfectly valid, but it’s not PSR-1 and PSR-2 compliant. The official
best practices recommend to follow both standards for any PHP code in Symfony projects.
The same code following the PSR-1 and PSR-2 standards:

// src/BookBundle/Controller/BookController.php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BookController
{
public function imageAction()
{
$returnResponse = new Response('hello');

return $returnResponse;
}
}

10. Why is it not recommended to use the @Template annotation to configure the template that
must be used by the controller?
Answers 2 and 3 are correct. It obviously involves more magic and as it can be used without
arguments, it makes it more difficult to know which template will be rendered.

11. In Composer, which of the following requirement is equivalent to ∼1.2?


Answer 2 is correct. The tilde operator (∼) is useful for projects following semantic versioning, as
its goal is to accept any version that does not include backwards compatibility breaks.

12. What are the advantages of commiting the composer.lock file into your version control
repository?
Answers 1 and 2 are correct. Anyone that sets up the project and executes composer install will
work with the exact same version of the dependencies, even when deploying the project. If there
are bugs, anyone can reproduce it. In addition, when working with legacy projects that work with
older versions of dependencies, you can be confident that even if you start working on it again after
4. Standardization 66

a few months, it will still work.


Answer 3 is not correct, as Composer needs to recalculate all the dependency graph when adding
new dependencies. Even though the composer.lock file contains the exact versions to use, if you
add a new one, it could affect to any of the current versions.

13. If you accidently delete the file vendor/composer/autoload_real.php, what is the best way
to get it back when working in a development environment?
Answer 3 is correct. The composer dump-autoload command updates the autoloader files without
installing or updating the dependencies. Answer 4 would also work, but it is less convenient for a
development environment. The --optimize (or -o) option generates an optimized autoloader as it
creates a classmap of all PSR-0 and PSR-4 packages:

// vendor/composer/autoload_classmap.php
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
// ...
'Symfony\\Component\\HttpFoundation\\Request' => $vendorDir . '/symfony/symf\
ony/src/Symfony/Component/HttpFoundation/Request.php'
// ...
);

This takes more time, but once calculated, the autoload process is much faster, as they are already
precalculated in a big array. This is recommended for production environments.
The composer self-update (or selfupdate) just updates Composer itself. The --rollback option
is useful if there is an error in the latest version of Composer, to rollback to the previously installed
version.

14. Given the following composer.json file, how many packages will end up in the vendor
directory?
Answer 4 is correct. From the 3 required dependencies, only two are “real”: symfony/framework-
bundle and twig/extensions. As both have other dependencies as well, you will end up for sure
with more than 2 packages. In fact, you end up with 21 packages if you run composer install with
that composer.json file.
4. Standardization 67

15. Is it possible to use PSR-7 with Symfony 2.3 LTS?


Answer 1 is correct. Even though PSR-7 has been accepted recently, it is avaible thanks to
the SensioFrameworkExtraBundle bundle and installing two new packages: symfony/psr-http-
message-bridge and zendframework/zend-diactoros:

// BookBundle/Controller/BookController.php
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response;
use Zend\Diactoros\Stream;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BookController extends Controller


{
public function testAction(ServerRequestInterface $request)
{
$body = new Stream('php://temp', 'w');
$body->write('PSR-7 works!');

return new Response($body);


}
}

Takeaways
• Composer
– The composer dump-autoload command updates the autoloader files without installing
or updating the dependencies.
– The Next Significant Release operators are useful for projects following semantic
versioning¹³:
* The ∼ operator fixes the minimum version and accepts any version up to the next
significan release (without including it), so ∼1.2 is equivalent to >=1.2 <2.0.0, and
∼1.2.3 to >=1.2.3 <1.3.0.
* The ˆ operator works in a very similar way, it fixes the minimum version but aims
for maximum interoperability (recommended for libraries). ˆ1.2 is equivalent to
>=1.2 <2.0.0 and ˆ1.2.3 to >=1.2.3 <2.0.0,
¹³http://semver.org/
4. Standardization 68

• Best practices
– The only recommended way to install Symfony is through the Symfony installer.
– The app/config/config.yml file should contain only application-related configuration
options.
– While recommended in the past, it is no longer considered a good practice to use *.class
parameters.

Further reading
• Composer documentation. https://getcomposer.org/doc
• Semantic versioning. http://semver.org
• PHP Framework Interop Group. http://php-fig.org
5. The Bundles
Exam goals
5.1. Naming conventions
5.2. Code organization
5.3. Controllers
5.4. Views
5.5. Resources

Questions
1. Which of the following directories of a bundle don’t follow the standard naming?

1. Controller
2. Resources
3. Listener
4. DependencyInjection
5. Config

2. If the AppBundle bundle needs to write temporary files, what is the best place to do it?

1. src/AppBundle/Resources/temp
2. src/AppBundle/cache
3. app/cache
4. web/temp

3. Which of the following actions are mandatory to use a third-party bundle?

1. Install the bundle via Composer


2. Register the bundle in the AppKernel::registerBundles() method
3. Add the bundle configuration in app/config/config.yml or app/config/config_*.yml
4. In the prod environment, execute the command php app/console container:reload

4. If a bundle needs to use the jQuery library, where should be placed the jquery.js file?
5. The Bundles 70

1. Resources/public/js
2. Resources/public/js/vendor
3. Resources/public/vendor
4. None of the above are correct

5. What kind of help provides the config:dump-reference command?

1. Dumps the default configuration of a bundle


2. Dumps the current configuration of a bundle
3. Dumps the current configuration in .htaccess format using the php_value directive
4. Adds the default configuration of a bundle in the app/config/config.yml file

6. Is this the right way to register the AppBundle bundle as a parent of the BackendBundle
bundle?

// BackendBundle/BackendBundle.php
namespace BackendBundle;

use AppBundle\AppBundle;

class BackendBundle extends AppBundle


{

1. Yes
2. No, the AppBundle bundle cannot be a parent of any bundle
3. No, the parent bundle must be configured with the getParent() method
4. No, the BackendBundle bundle must be in a subnamespace of AppBundle

7. How can you get the root directory of the AppBundle bundle from a controller?

1. $this->get('kernel')->getRootDir() . '/src/AppBundle'
2. $this->get('kernel')->getBundle('AppBundle')->getPath()
3. $this->get('bundle.app')->getRootDir()
4. It’s not possible to get the root directory of a bundle from a controller

8. Is there any problem in the following code?


5. The Bundles 71

// app/AppKernel.php
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Bundle;

class AppKernel extends Kernel


{
public function registerBundles()
{
$bundles = [];

if ('prod' === $this->getEnvironment()) {


$bundles = [
new Bundle\FrameworkBundle\FrameworkBundle(),
// ...
];
}

if (in_array($this->getEnvironment(), array('dev', 'test'))) {


$bundles[] = new Bundle\WebProfilerBundle\WebProfilerBundle();
}

return $bundles;
}

// ...
}

1. Yes, the registerBundles() method must return an object implementing the BundleListIn-
terface, not an array
2. Yes, the WebProfilerBundle bundle should be enabled in the prod environment too
3. Yes, the test environment won’t work properly
4. No

9. What is the recommended naming convention for template file names and directories?

1. camelCase
2. snake_case
3. StudlyCaps
4. None of the above are correct

10. What method must be overriden in the bundle class to register compiler passes?
5. The Bundles 72

1. boot()
2. getCompilerPasses()
3. build()
4. init()

11. Why the base class which all bundles extend from by default makes use of the Finder
component?

1. To find configuration files like services.yml or routing.xml inside the bundle


2. To find translation files included in the bundle
3. To find custom validators defined in the bundle
4. To find console commands defined in the bundle

Answers
1. Which of the following directories of a bundle don’t follow the standard naming?
Answers 3 and 5 are correct, as they are not standard directories. The directory where event listeners
are located must be called EventListener, and configuration files are placed in the Resources/con-
fig directory. The following table describes the recommended directories for common PHP classes
and files:
Directory Description
Command Custom commands
Controller Controllers
DependencyInjection Service container extensions
EventListener Event listeners
Model In reusable bundles, model classes
Resources/config Configuration files: services and routes
Resources/doc Documentation
Resources/meta Metainformation, such as bundle license
Resources/public Web assets: JS/CSS files, fonts or images
Resources/translations Translation files
Resources/views PHP or Twig templates, form themes or macros
Tests Unit and functional tests

2. If the AppBundle bundle needs to write temporary files, what is the best place to do it?
5. The Bundles 73

Answer 3 is correct. Bundles should be read-only. That means that no files should be generated
inside the bundle unless they are going to be part of the repository. You can get the cache directory
using the kernel service:

// src/BookBundle/Controller/BookController.php
class BookController
{
public function detailAction()
{
$cacheDir = $this->get('kernel')->getCacheDir();

// ...
}
}

3. Which of the following actions are mandatory to use a third-party bundle?


Only answer 2 is correct. To use a bundle it must be registered first in the kernel. For that, it must
be included in the array returned by the AppKernel::registerBundles() method.
Answer 4 is wrong. The container:reload command doesn’t exist, but you should clear the cache
when adding a new bundle. Answers 1 and 3 are most of the time true, but they are not mandatory.
While not recommended, you could install bundles without using Composer (for example, using git
submodules). Regarding the configuration, bundles can provide sensible defaults so in some cases
you don’t even have to add any extra configuration.

4. If a bundle needs to use the jQuery library, where should be placed the jquery.js file?
Answer 4 is correct. Bundles should not embed third-party libraries.

5. What kind of help provides the config:dump-reference command?


Answer 1 is correct. The config:dump-reference command outputs the default configuration of
a bundle, which can be really useful to know what are the options that the bundle provides. For
example, this is the default configuration for the TwigBundle bundle:
5. The Bundles 74

twig:
exception_controller: twig.controller.exception:showAction
form:
resources:

# Default:
- form_div_layout.html.twig

# Example:
- MyBundle::form.html.twig
globals:

# Examples:
foo: "@bar"
pi: 3.14

# Prototype
key:
id: ~
type: ~
value: ~
autoescape:

# Defaults:
- Symfony\Bundle\TwigBundle\TwigDefaultEscapingStrategy
- guess
autoescape_service: ~
autoescape_service_method: ~
base_template_class: ~ # Example: Twig_Template
cache: %kernel.cache_dir%/twig
charset: %kernel.charset%
debug: %kernel.debug%
strict_variables: ~
auto_reload: ~
optimizations: ~
paths:

# Prototype
paths: []
5. The Bundles 75

6. Is this the right way to register the AppBundle bundle as a parent of the BackendBundle
bundle?
Answer 3 is correct. In Symfony, there is no real parent/child relationship between bundles, it is just
a way to extend and override certain parts of the parent bundle. To register a bundle as a child, the
getParent() method must be used, and it has to return the short name of the parent bundle:

// BackendBundle/BackendBundle.php
namespace BackendBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class BackendBundle extends Bundle


{
public function getParent()
{
return 'AppBundle';
}
}

7. How can you get the root directory of the AppBundle bundle from a controller?
Answer 2 is correct. The abstract kernel class that AppKernel extends from, Symfony\Component\HttpKernel\Kernel
implements the getBundle() method, so you can get any of the registered Bundle objects:

// HttpKernel/Kernel.php
namespace Symfony\Component\HttpKernel;

abstract class Kernel implements KernelInterface, TerminableInterface


{
// ...

public function getBundle($name, $first = true)


{
// ...

return $this->bundleMap[$name];
}

// ...
}
5. The Bundles 76

As bundles extend from Symfony\Component\HttpKernel\Bundle\Bundle, which implements the


method getPath(), you can get the location of any bundle in the filesystem. This method makes use
of Reflection (specifically ReflectionObject) to get the location of the PHP class file, and then its
parent directory with the dirname() function:

// HttpKernel/Kernel.php
namespace Symfony\Component\HttpKernel\Bundle;

use Symfony\Component\DependencyInjection\ContainerAware;

abstract class Bundle extends ContainerAware implements BundleInterface


{
// ...

public function getPath()


{
if (null === $this->reflected) {
$this->reflected = new \ReflectionObject($this);
}

return dirname($this->reflected->getFileName());
}

// ...
}

8. Is there any problem in the following code?


Answer 3 is correct. When the environment is dev or test, required bundles such as FrameworkBun-
dle won’t be loaded. The FrameworkBundle is the glue between all Symfony components and the
full-stack framework can’t work without it.

9. What is the recommended naming convention for template file names and directories?
Answer 2 is correct. For directory and template names, lowered snake_case is recommended for
composed names. For example, if you create a template to list books, list_books.html.twig would
be a good name using the lowered snake_case convention.
5. The Bundles 77

10. What method must be overriden in the bundle class to register compiler passes?
Answer 3 is correct. The build() method is only called when the cache is empty and can be used to
change how the container is built, adding compiler passes.

11. Why the base class which all bundles extend from by default makes use of the Finder
component?
Answer 4 is correct. The default implementation of a bundle makes use of the Finder component to
look for console commands, as they are loaded by convention.

// HttpKernel/Bundle/Bundle.php
namespace Symfony\Component\HttpKernel\Bundle;

use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Console\Application;
use Symfony\Component\Finder\Finder;

abstract class Bundle extends ContainerAware implements BundleInterface


{

// ...

public function registerCommands(Application $application)


{
if (!is_dir($dir = $this->getPath().'/Command')) {
return;
}

$finder = new Finder();


$finder->files()->name('*Command.php')->in($dir);

$prefix = $this->getNamespace().'\\Command';
foreach ($finder as $file) {
// ...
$application->add($r->newInstance());
}
}
}
5. The Bundles 78

Takeaways
• General
– The config:dump-reference command prints out the default configuration for a bundle.
– Compiler passes are registered by overriding the build() method of the bundle class.
• Naming convention and organization
– Some bundle directories have standard names, such as Command, Controller, Dependen-
cyInjection, EventListener, Model, Resources or Tests. Some of them are required in
order to be found, while others are just a naming convention.
– For template file names and directories, lowered snake_case is recommended.
– Bundles should be read-only and not embed third-party libraries.
6. The Controllers
Exam goals
6.1. Naming conventions
6.2. Get the request
6.3. Generate the response
6.4. The cookies
6.5. The session
6.6. Session flashbag
6.7. Redirects
6.8. Internal redirects
6.9. Generate 404 pages

Questions
1. Is it possible to return binary data such as images or videos from a controller?

1. Yes
2. No

2. Given the route /book/{authorSlug}/{titleSlug}, that is pointing to AppBundle:Book:detail,


which of the following method signatures would throw an exception?

1. public function detailAction($authorSlug, $titleSlug)


2. public function detailAction($titleSlug, $authorSlug)
3. public function detailAction($authorSlug)
4. public function detailAction($authorSlug, $page)

3. From a controller, which of the following lines get the slug parameter from the route
/book/{slug}?

1. $this->getRequest()->get('slug')
2. $this->getRequest()->query->get('slug')
3. $this->getRequest()->route->get('slug')
4. $this->getRequest()->attributes->get('slug')
6. The Controllers 80

5. $this->getRequest()->attributes->get('_route_params')['slug']

4. Given the route /book/{slug} that is pointing to AppBundle:Book:detail, what is the output
of the following controller when going to http://example.com/book/test-title?slug=test-
title2?

// AppBundle/Controllers/BookController.php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

class BookController
{
public function detailAction(Request $r)
{
return new Response($r->get('slug'));
}
}

1. test-title
2. test-title2
3. Empty response
4. Error: Controller requires that you provide a value for the "$r" argument

5. Which of the following parameter bags are not available in the Request object?

1. request
2. response
3. server
4. controller

6. Are the variables value1, value2 and value3 equal?


6. The Controllers 81

// AppBundle/Controllers/BookController.php
use Symfony\Component\HttpFoundation\Request;

class BookController
{
public function detailAction(Request $request, $_route)
{
$value1 = $_route;

$value2 = $request->attributes->get('_route');

$newRequest = Request::createFromGlobals();
$value3 = $newRequest->attributes->get('_route');

// ...
}
}

1. Yes
2. No

7. What is the main goal of the base controller class?

1. To intercept errors and exceptions


2. To allow to define controllers as services
3. To add an extra layer of security by protecting the Request object
4. To provide helpers methods that child controllers can use

8. In a controller that extends from the base controller, what is the difference between the
render() and renderView() methods?

1. There is no difference
2. render() returns a Response object, and renderView() a string
3. render() returns a string, and renderView() a Response object
4. The renderView() method does not exist

9. What would you need to do in order to make this controller work?


6. The Controllers 82

// AppBundle/Controllers/BookController.php
class BookController
{
public function helloAction()
{
return 'hello';
}
}

1. Create a Response object setting the content to hello


2. Register a listener for the kernel.view event and convert the string into a Response object
3. Use renderView('hello') to get a proper Response object
4. Nothing. The controller works without any change

10. What is wrong with the following controller?

// AppBundle/Controllers/BookController.php
class BookController
{
public function helloAction()
{
return $this->container->get('templating')
->renderResponse('AppBundle::hello.html.twig');
}
}

1. Controllers must extend from the base controller


2. The templating service cannot be used directly as it does not know the templating engine
3. renderResponse() returns a string instead of a Response object
4. Nothing

11. Which of the following sentences are not true about defining controllers as services?

1. They are easier to test


2. It is more decoupled from the framework
3. They must be defined in abstract classes
4. They cannot have access to the container

12. How can you generate a 404 response from a controller that extends from the base
controller?
6. The Controllers 83

1. $this->generateException(404, 'Not found')


2. throw $this->createNotFoundException('Not found')
3. throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException('Not found',
null);
4. return \Symfony\Component\HttpKernelInterface::NOT_FOUND

13. What HTTP status code will generate Symfony if a controller contains throw new
\RuntimeException('Error')?

1. 200
2. 400
3. 404
4. 500

14. What advantages provides the JsonResponse class over the base Response class?

1. It sets the Content-type header to application/json


2. It serializes the input data so you don’t have to execute the json_encode() function manually
3. A callback function can be set when using JSONP
4. It can transpose JSON on demand

15. What is the main purpose of the built-in FrameworkBundle:Template:template controller?

1. Render custom error templates


2. Provide information about the template being rendered for the profiler
3. Render templates that don’t require a controller, such as static pages
4. Extract translation keys/strings from templates

16. What is the output of the following controller?

// AppBundle/Controllers/BookController.php
class BookController
{
public function testAction()
{
$filename = tempnam(sys_get_temp_dir(), 'sf');
file_put_contents($filename, 'hello {{ 1 + 1 }}');

return $this->forward('FrameworkBundle:Template:template', [
'template' => $filename
]);
}
}
6. The Controllers 84

1. hello
2. hello 2
3. Error: The controller must return a response
4. Error: Twig files must have the .twig extension
5. Error: Route "FrameworkBundle:Template:template" does not exist.
6. Error: Controller requires that you provide a value for the "$variables" argument.

17. How can you store a value in the session from a controller?

1. $this->get('session')->put()
2. $this->get('session')->store()
3. $this->get('session')->save()
4. $this->get('session')->set()

18. What is the value of $value after executing the following action exactly once?

// AppBundle/Controllers/BookController.php
class BookController
{
public function testAction()
{
$this->get('session')->set('test', 1);
$value = $this->get('session')->get('test', 2);

// ...
}
}

1. 1
2. 2
3. null
4. Empty string

19. Given the following 3 controllers, what is the output if you go to /example1, /example2,
/example3, in that specific order?
6. The Controllers 85

// AppBundle/Controllers/BookController.php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class BookController
{
/**
* @Route("/example1", name="example1")
*/
public function exampleOneAction(Request $request)
{
$request->getSession()->getFlashBag()->add('test', 'ok');

return new Response('ok');


}

/**
* @Route("/example2", name="example2")
*/
public function exampleTwoAction(Request $request)
{
return new Response('ok');
}

/**
* @Route("/example3", name="example3")
*/
public function exampleThreeAction(Request $request)
{
$flashbag = $request->getSession()->getFlashBag();

return new Response($flashbag->get('test', ['error'])[0]);


}
}

1. ok, ok, ok
2. ok, ok, error
3. ok, ok and empty response
4. ok, ok and fatal error

20. What information does the Request::isXmlHttpRequest() method use to know if it’s an
AJAX request?
6. The Controllers 86

1. The user agent


2. The Accept HTTP header.
3. The Content-type HTTP header
4. The X-Requested-With HTTP header

21. Assuming that the locale is set in the URL using the special _locale parameter, will the
following code translate hello into Spanish if the current locale is en?

// AppBundle/Controllers/BookController.php
class BookController
{
public function testAction()
{
$request->setLocale('es');

$translator = $this->get('translator');
$value = $translator->trans('hello');

// ...
}

1. Yes
2. No

22. If the current locale is en, what will be the value of the $str variable?

// AppBundle/Controllers/BookController.php
class BookController
{
public function testAction()
{
$str = $this->get('translator')
->transChoice('One book|%count% books', 2);
}

1. 2 books
2. One book
6. The Controllers 87

3. %count% books
4. One book|%count% books

Answers
1. Is it possible to return binary data such as images or videos from a controller?
Answer 1 is correct, controllers can return anything, even binary data. You can do it by setting the
right HTTP headers (at least Content-type) or using the BinaryFileResponse class
Return the file web/favicon.ico using a Response object:

// AppBundle/Controller/BookController.php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BookController extends Controller


{
public function imageAction()
{
$filename = $this->get('kernel')->getRootDir() . '/../web/favicon.ico';

$response = new Response();

$response->headers->set('Content-type', 'image/x-icon');
$response->headers->set('Content-length', filesize($filename));

$response->setContent(readfile($filename));

return $response;
}
}

Return the file web/favicon.ico using a BinaryFileResponse object:


6. The Controllers 88

// AppBundle/Controller/BookController.php
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BookController extends Controller


{
public function imageAction()
{
$filename = $this->get('kernel')->getRootDir() . '/../web/favicon.ico';

return new BinaryFileResponse($filename);


}
}

2. Given the route /book/{authorSlug}/{titleSlug}, that is pointing to AppBundle:Book:detail,


which of the following method signatures would throw an exception?
Only answer 4 would throw an exception. Symfony maps route parameters to method arguments
by name, so $authorSlug and $titleSlug contain the values of {authorSlug} and {titleSlug}.
The order of the controller parameters does not matter, so answers 1 and 2 work equally well. Also,
not all routing parameters need to be in the arguments list, but if there is an argument, it must be
declared as a route parameter or be part of the defaults collection. That’s why answer 3 works but
answer 4 throws an exception, as $page is not a route parameter and we assume that there is not in
the defaults collection.

3. From a controller, which of the following lines get the slug parameter from the route
/book/{slug}?

Answers 1, 4 and 5 are correct. With $this->getRequest() you can get the Request object, which
contains all the information related to the request. This object includes an special bag called
attributes, that stores additional data such as the route name (_route) or route parameters (in
_route_params and directly in the bag):
6. The Controllers 89

array [
'_controller' => string 'AppBundle\Controller\BookController::detailAction'
'slug' => 'test-title'
'_route' => 'book_detail'
'_route_params' => [
'slug' => 'test-title'
]
]

Answer 5 is directly using the _route_params array from the attributes bag to get the value,
while answers 1 and 4 are get the value indirectly (not recommended, as it is slower). $this-
>getRequest()->get() makes use of the query, attributes and request bags (in that order), so
it ends up using $this->getRequest()->attributes->get(), which gets the attribute slug stored
in the bag.

4. Given the route /book/{slug} that is pointing to AppBundle:Book:detail, what is the output
of the following controller when going to http://example.com/book/test-title?slug=test-
title2?

Answer 2 is correct. As the Request::get() method makes use of the query, attributes and
request bags, in that specific order, it will find first the querystring parameter slug and won’t
keep checking the rest of the bags. If the querystring parameter would have a different name, the
output would be test-title. And in case the slug route parameter would not exist, you would get
an empty response. Finally, you can use $r instead of $request for the Request object, as unlike
normal route parameters, it only requires that you type-hint the argument. It is probably a useless
example, but you can even have more than 1 Request arguments:

// AppBundle/Controllers/BookController.php
use Symfony\Component\HttpFoundation\Request;

class BookController
{
public function detailAction(Request $r, Request $request)
{
// ...
}
}
6. The Controllers 90

5. Which of the following parameter bags are not available in the Request object?
Answers 2 and 4 are correct. The Request object contains 7 parameter bags: attributes, request,
query, server, files, cookies and headers.

6. Are the variables value1, value2 and value3 equal?


They are not equal, so answer 2 is correct. In the example, value1 and value2 will contain the
route name, as they use two valid methods to get it: adding the argument $_route or through
the attributes bag of the Request object. In the other hand, the new request created with
Request::createFromGlobals() will contain the same information in all parameters bags, except in
attributes. This special bag is populated through several listeners dispatched by the kernel before
being available to the controller, so $newRequest->attributes will be empty here.

7. What is the main goal of the base controller class?


Answer 4 is correct. The base controller provides useful helpers such as generateUrl(), render()
or createNotFoundException(). All other answers are wrong. Errors and exceptions are handled
by listeners attached to the kernel.exception event. Controllers defined as services will most likely
not extend the base controller. And answer 3 doesn’t even make sense.

8. In a controller that extends from the base controller, what is the difference between the
render() and renderView() methods?

Answer 2 is correct. Both methods are almost identical, they render a template, but they differ
in the return value. render() returns a Response object, while renderView() returns a string.
renderView() can be useful for example to render emails.

9. What would you need to do in order to make this controller work?


Answers 1 and 2 are correct. Controllers must return always a Response object. In case that a
controller returns something different, like a string, the kernel dispatches the kernel.view event, so
listeners can convert the return value of the controller into a Response object.

10. What is wrong with the following controller?


Answer 4 is correct, there is nothing wrong about the controller. As it does not extend from the base
controller (not mandatory), it needs to use the templating service directly. In fact, it does the same
6. The Controllers 91

as the render() method of the base controller does:

// Symfony/Bundle/FrameworkBundle/Controller/Controller.php
namespace Symfony\Bundle\FrameworkBundle\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\DependencyInjection\ContainerAware;
// ...

class Controller extends ContainerAware


{
// ...

public function render($view, array $params = [], Response $response = null)


{
return $this->container->get('templating')
->renderResponse($view, $params, $response);
}

// ...
}

11. Which of the following sentences are not true about defining controllers as services?
Answers 3 and 4 are correct. Defining controllers as services usually takes a little extra work,
but provides some very interesting advantages. As all dependencies are injected and declared in
the method definition, they are easier to test and they don’t depend on the base controller or the
container. It is a good practice to inject only the services required by the controller instead of the
full container, but it can be done too.

12. How can you generate a 404 response from a controller that extends from the base
controller?
Answers 2 and 3 are correct. The base controller defines the createNotFoundException() method to
generate 404 responses. Internally, it creates a Symfony\Component\HttpKernel\Exception\NotFoundHttpException
exception, exactly as it is done in the answer 3.

13. What HTTP status code will generate Symfony if a controller contains throw new
6. The Controllers 92

\RuntimeException('Error')?

Answer 4 is correct, a 500 HTTP response is sent to the client. Generic exceptions such as
Exception or RuntimeException are intercepted by Symfony and converted into a Response
object with a 500 HTTP status code. Other exceptions generate different HTTP status codes,
such as Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException (403) or Sym-
fony\Component\HttpKernel\Exception\NotFoundHttpException (404). All these HTTP-related
exceptions extend from Symfony\Component\HttpKernel\Exception\HttpException.

14. What advantages provides the JsonResponse class over the base Response class?
Answers 1, 2 and 3 are correct. When generating JSON responses, JsonResponse provides some
interesting advantages so you don’t have to do it manually, such as setting the Content-type header
to application/json (or text/javascript when using JSONP), serializing the data or setting the
callback function for JSONP.

15. What is the main purpose of the built-in FrameworkBundle:Template:template controller?


Answer 3 is correct. The built-in FrameworkBundle:Template:template controller just renders the
template passed as {template}, with no variables, so it is especially useful for static pages that don’t
require a custom controller.
For example, the following route makes use of the FrameworkBundle:Template:template controller
to generate the /about_us static page:

# AppBundle/Resources/config/routing.yml
about_us_static:
path: /about_us
defaults:
_controller: FrameworkBundle:Template:template
template: AppBundle:static:about_us.html.twig

16. What is the output of the following controller?


Answer 2 is correct. The code is valid, but you will hardly use it in that way. First, the controller
creates a temporary file that contains hello {{ 1 + 1 }}, and then forwards the request to the
built-in FrameworkBundle:Template:template controller, that just renders the template file set at
the {template} parameter.
6. The Controllers 93

17. How can you store a value in the session from a controller?
Answer 4 is correct. To store a value in the session, the set() method of the session service can
be used. When extending from the base controller, the getSession() method returns the session
service.

18. What is the value of $value after executing the following action exactly once?
Answer 1 is correct. The first line of the controller action adds to the session the attribute test with
the value 1, which is read in the second line. The second parameter is the default value, but as test
has been set, it just returns its value.

19. Given the following 3 controllers, what is the output if you go to /example1, /example2,
/example3, in that specific order?

Answer 1 is correct. Flash messages are meant to live for exactly one request, but there is no special
mechanism to delete the messages from the session, they are deleted when you get them:

// HttpFoundation/Session/FlashBag.php
public function get($type, array $default = array())
{
if (!$this->has($type)) {
return $default;
}

$return = $this->flashes[$type];

unset($this->flashes[$type]);

return $return;
}

As you can see, once you read messages, they are unset from the flashes array. So, as they live
in the session, if they are not read, they will be available until the session expires, even if there is
more than one request. In the example, going to /example2 a hundred times would not change the
response of example3.
Now that you know how flash messages are removed, you can see that the following code would
return okerror:
6. The Controllers 94

$flashbag = $request->getSession()->getFlashBag() ;
$flashbag->add('test', 'ok');

$a = $flashbag->get('test', ['error'])[0];
$b = $flashbag->get('test', ['error'])[0];

return new Response($a . $b);

To keep the flash messages, peek() can be used instead of get().

20. What information does the Request::isXmlHttpRequest() method use to know if it’s an
AJAX request?
Answer 4 is correct. The isXmlHttpRequest() method just checks whether the value of the X-
Requested-With header is XMLHttpRequest:

// HttpFoundation/Request.php
public function isXmlHttpRequest()
{
return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
}

This header is set by most JavaScript frameworks when sending AJAX requests and is useful to
generate different responses for AJAX and non-AJAX requests.

21. Assuming that the locale is set in the URL using the special _locale parameter, will the
following code translate hello into Spanish if the current locale is en?
Answer 2 is correct. Changing the locale from the request in the controller does not have any
effect on the translation service, as the locale is already loaded. In fact, if you call $translator-
>getLocale() after $request->setLocale('es'), it will still return en. To change the locale in the
controller there are two options:
6. The Controllers 95

// AppBundle/Controllers/BookController.php
// option 1: change locale of the service
$translator->setLocale('es');
$value = $translator->trans('hello');

// option 2: temporary change


$value = $translator->trans('hello', [], null, 'es');

Internally, Symfony registers an event listener (LocaleListener in the HttpKernel component) to


set the default locale.

22. If the current locale is en, what will be the value of the $str variable?
Answer 3 is correct. The second argument is taken into account to determine which fragment must
be used. Only for that, it’s not used to replace placeholders by their values. To return 2 books, the
following code should be used:

// AppBundle/Controllers/BookController.php
class BookController
{
public function testAction()
{
$number = 2;

$str = $this->get('translator')
->transChoice('One book|%count% books', $number, [
'%count%' => $number
]);
}
}

Takeaways
• Controllers
– The base controller class defines helpers methods that child controllers can use.
6. The Controllers 96

– Defining controllers as services usually takes a little extra time but provides several
advantages like testability and decoupling.
• Request
– There are different ways to get the Request object: type-hinting an argument of the
method or calling $this->getRequest().
– Request::get() checks the query, attributes and request bags, in that order.
– Request::isXmlHttpRequest() checks the value of the X-Requested-With HTTP header
to determine whether the request is a XMLHttpRequest (AJAX). This header is set by
most JavaScript frameworks.
• Response
– BinaryFileResponse can be used to return binary data from controllers.
– JsonResponse can be used to generate JSON responses, and automatically sets the right
Content-type header, encodes the data into JSON and allows to configure a callback
function for JSONP.
– The kernel.view event is dispatched when a controller does not return a Response
object.
– The built-in FrameworkBundle:Template:template controller just renders the template
file set in the {template} parameter, with no extra variables. It is useful for static pages.
• Error pages
– Exceptions are converted into Response objects with a specific HTTP status code.
7. Routing
Exam goals
7.1. Configuration (YAML / XML / PHP & annotations)
7.2. Restrict URL parameters
7.3. Set default values to URL parameters
7.4. Generate URL parameters
7.5. Trigger redirections

Questions
1. The Symfony routing component maps URL paths to…

1. controllers.
2. views.
3. event listeners.
4. .htaccess rewrite rules.

2. What is the value of the variable $slug in the following code for the URL http://example.com/books/test/2?

// src/AppBundle/Controller/BookController.php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BookController extends Controller


{
/**
* @Route("/books/{slug}", name="book_detail")
*/
public function detailAction($slug)
{
// ...
}
}

1. test
7. Routing 98

2. test/2
3. null
4. It throws an exception

3. By default, where is defined the _profiler route to access to the profiler in Symfony?

1. /app/config/config.yml
2. /app/config/config_dev.yml
3. /app/config/routing.yml
4. /app/config/routing_dev.yml

4. Given the following two routes, what controller will be executed for the URL /book/123?

# app/config/routing.yml
book_detail_section:
path: /book/{id}/{section}
defaults: { _controller: AppBundle:Book:detail, section: home }

book_detail:
path: /book/{id}
defaults: { _controller: AppBundle:Book:detailSection }

1. AppBundle:Book:detail
2. AppBundle:Book:detailSection
3. Error: No route found
4. Error: The routing file contains unsupported keys for "defaults"

5. What is the most likely place in which you can find this kind of code?

if (preg_match('#^/books/(?P<slug>[^/]++)$#s', $pathinfo, $matches)) {


return $this->mergeDefaults(
array_replace($matches, ['_route' => 'book_detail']),
['_controller' => 'AppBundle\\Controller\\BookController::detailActi\
on']
);
}

1. /app/config/routing.php
2. /app/cache/dev/appDevUrlMatcher.php
3. /app/bootstrap.php.cache
4. web/app_dev.php

6. Given the following routes, what controller will be executed for the URL /book/test?
7. Routing 99

# app/config/routing.yml
book_list:
path: /books
defaults: { _controller: AppBundle:Default:list }

book_detail:
path: /books/{slug}
defaults: { _controller: AppBundle:Default:detail }

book_list:
path: /books/{slug}/download
defaults: { _controller: AppBundle:Default:download }

1. AppBundle:Book:list
2. AppBundle:Book:detail
3. AppBundle:Book:download
4. Error: No route found

7. Can two routes contain the same path value?

1. Yes
2. No

8. Given the following route, will the AppBundle:Home:index controller be executed if you go
to https://m.example.com?

<!-- app/config/routing.xml -->


<route id="home" path="/" host="{subdomain}.example.com">
<default key="_controller">AppBundle:Home:index</default>
</route>

1. Yes
2. No, as the host option does not accept placeholders.
3. No, as the schemes option has not been set to https
4. No, as m is not a valid subdomain.

9. What command can be used to get a list of all routes?

1. framework:routes
2. router:list
7. Routing 100

3. router:debug
4. container:routes

10. If no methods are specified for a route, what methods will be matched?

1. GET
2. GET or POST
3. Only safe methods: GET or HEAD
4. All methods

11. Given the definition of the route book_list, what will be the value of the variable $url?

# app/config/routing.yml
book_list:
path: /books
defaults: { _controller: AppBundle:Default:list }
methods: [POST]

// src/AppBundle/Controller/HomeController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class HomeController extends Controller


{
public function indexAction()
{
$url = $this->generateUrl('book_list', ['page' => 1]);

// ...
}
}

1. /books?page=1
2. /books?_page=1
3. /books
4. http://example.com/books?page=1
5. http://example.com/books?_page=1
6. http://example.com/books
7. Error: Parameter "page" is not defined.

12. Given the following route definition, what URL would you get if you use the UrlGenera-
torInterface::NETWORK_PATH constant?
7. Routing 101

# app/config/routing.yml
book_list:
path: /books
defaults: { _controller: AppBundle:Default:list }
methods: [POST]

1. /books
2. //example.com/books
3. http://example.com/books
4. ///books

13. When generating an absolute URL, where does the domain name come from?

1. It is set in the /app/config/parameters.yml file.


2. From $_SERVER['HTTP_REFERER']
3. From the Request object
4. It is passed as parameter to generateUrl()

14. Once you have dumped all routes as Apache rewrite rules with the router:dump-apache
command and stored in the .htaccess file, which of the following sentences are true?

1. Front controllers are automatically generated for each route (for example, /web/_book_-
list.php).
2. The Symfony routing system is still used to match routes.
3. Rewrite rules contain information about the controller that must be executed.
4. Routes for development tools such as _profiler are never dumped.

15. What is the output of the command route:debug book_detail?

1. A list of all current routes.


2. Information about the book_detail route definition.
3. Information about all the routes with a name starting with book_detail
4. An error, the command should be router:debug book_detail

16. Is the following route definition valid?


7. Routing 102

# app/config/routing.yml
test_route:
path: /test
defaults: { _controller: 'strtolower', str: 'HELLO WORLD' }

1. No
2. Yes, it would return a valid Response object containing hello world
3. Yes, it would return the string hello world, which can be converted later into a response from
an event listener
4. Yes, but only if there is a service registered as strtolower and that implements the __invoke()
magic function

17. Given the following route definition, what URLs will be matched?

# app/config/routing.yml
book_title:
path: /book/{title}
defaults: { _controller: AppBundle:Book:detail }
requirements:
title: .+

1. /book
2. /book/123
3. /book/123/456
4. /book/123/456/789

Answers
1. The Symfony routing component maps URL paths to…
Answer 1 is correct. URL paths are just maps to controllers. When matched, Symfony executes
a route using the call_user_func_array() function. As the call_user_func_array() function
accepts a callable, you can use some valid forms for callables to execute the controller method.
For example, all these requests are equivalent:
7. Routing 103

# app/config/config.yml

# bundle:controller:action notation
book_list:
path: /books
defaults: { _controller: AppBundle:Book:list }

# class::method notation
book_list:
path: /books
defaults: { _controller: 'AppBundle\Controller\BookController::listAction' }

# service:method notation (controllers as services)


book_list:
path: /books
defaults: { _controller: 'book.controller::list' }

You can even go further and use any class or service to generate responses. With the following route
definition, the static method create() from Symfony\Component\HttpFoundation\Response is used
to generate a valid response containing Hello world! as its body:

# app/config/config.yml
dummy:
path: /dummy
defaults:
_controller: 'Symfony\Component\HttpFoundation\Response::create'
content: 'Hello world!'

2. What is the value of the variable $slug in the following code for the URL http://example.com/books/test/2?
Answer 4 is correct, as there is no valid route for that URL. As the routing component uses
/ as the placeholder separator, the route definition would need two placeholders. URLs like
http://example.com/books/test-2 or http://example.com/books/test_2 would work though,
and the value of $slug would be test-2 or test_2.

route:match command

The route:match command can be used to see if a given URL can be matched against any
of the defined routes.
7. Routing 104

3. By default, where is defined the _profiler route to access to the profiler in Symfony?
Answer 4 is correct. By default, routes needed only for the dev environment such as _profiler or
_wdt are defined in the /app/config/routing_dev.yml file. This main routing file, which includes
any other routing files (imports the main routing.yml file), is defined in /app/config/config_-
dev.yml using the setting framework.router.resource:

# app/config/config_dev.yml
framework:
router:
resource: "%kernel.root_dir%/config/routing_dev.yml"

If you want to use another format, it would be as simple as changing it here. For example, to use
XML for route definitions:

# app/config/config_dev.yml
framework:
router:
resource: "%kernel.root_dir%/config/routing_dev.xml"

Symfony processes all configuration files in any of the supported formats and generate PHP code
for them.

4. Given the following two routes, what controller will be executed for the URL /book/123?
Answer 1 is correct. In Symfony, earlier routes always win. As the section placeholder for the
book_detail_section route is optional (it contains a default value: home), the URL /book/123
matches the first route, so following ones are simply ignored. As a rule of thumb, more specific
routes should appear first.

5. What is the most likely place in which you can find this kind of code?
Answer 2 is correct. For performance reasons, all route definitions are compiled down into a plain
PHP class, which makes use of functions like strpos(), in_array() or preg_match() to match a
route.
7. Routing 105

6. Given the following routes, what controller will be executed for the URL /book/test?
Answer 4 is correct. You may have noticed that there are two routes with the same name: book_list.
This is actually a limitation of the YAML format. If you try to parse the given route definitions with
the Yaml component, you get the following array:

[
'book_list' => [
'path' => '/books/{slug}/download'
'defaults' => [
'_controller' => 'AppBundle:Default:download'
]
],
'book_detail' => [
'path' => '/books/{slug}'
'defaults' => [
'_controller' => string 'AppBundle:Default:detail' (length=24)
]
]
]

You get only two route definitions, as the third route overwrites the first one:

// app/cache/dev/appDevUrlMatcher.php
if (0 === strpos($pathinfo, '/books')) {
// book_list
if (preg_match('#^/books/(?P<slug>[^/]++)/download$#s', $pathinfo, $match)) {
return $this->mergeDefaults(
array_replace($match, ['_route' => 'book_list']),
['_controller' => 'AppBundle\\Controller\\DefaultController::downloa\
dAction']
);
}

// book_detail
if (preg_match('#^/books/(?P<slug>[^/]++)$#s', $pathinfo, $match)) {
return $this->mergeDefaults(
array_replace($match, ['_route' => 'book_detail']),
['_controller' => 'AppBundle\\Controller\\DefaultController::indexAc\
tion']
);
}
}
7. Routing 106

7. Can two routes contain the same path value?


Answer 1 is correct. You can have as much routes with the same path value as you want. Just keep
in mind that if the first one has no extra restrictions (i.e. methods or requirements), the other ones
will never be executed.

8. Given the following route, will the AppBundle:Home:index controller be executed if you go
to https://m.example.com?
Answer 1 is correct. The host option allows you to match a route based on the host. It accepts
placeholders, using the same syntax as the path matching system.
It is true that with the schemes option you can force routes to always use HTTP or HTTPS, but if
not set, any scheme will be accepted.

9. What command can be used to get a list of all routes?


Answer 3 is correct. The router:debug command prints out all registered routes for the given
environment (dev unless --env or -e is used

10. If no methods are specified for a route, what methods will be matched?
Answer 4 is correct. If you don’t specify any method, ANY will be used instead, meaning that any
method will be matched. For example, check the following 3 route definitions:

# app/config/routing.yml
example_any:
path: /example
defaults: { _controller: AppBundle:Example:example }

example_get_only:
path: /example
defaults: { _controller: AppBundle:Example:example }
methods: ["GET"]

example_get_post:
path: /example
defaults: { _controller: AppBundle:Example:example }
methods: ["GET", "POST"]
7. Routing 107

If you execute the router:debug command, you get the following list:

Name Method Scheme Host Path


example_any ANY ANY ANY /example
example_get_only GET ANY ANY /example
example_get_post GET|POST ANY ANY /example

The three routes point to the same controller, but the first one would work for any method, the
second one only for GET and HEAD (GET and HEAD are considered equivalent in the RFC), and the last
one for GET, HEAD and POST. If you check the compiled router file in the cache directory, this is what
you find:

// app/cache/dev/appDevUrlMatcher.php

// ...

// example_any
if ($pathinfo === '/example') {
return [
'_controller' => 'AppBundle:Default:index',
'_route' => 'example_any'
];
}

// example_get_only
if ($pathinfo === '/example') {
if (!in_array($this->context->getMethod(), ['GET', 'HEAD'])) {
$allow = array_merge($allow, ['GET', 'HEAD']);
goto not_example_get_only;
}

return [
'_controller' => 'AppBundle:Default:index',
'_route' => 'example_get_only'
];
}
not_example_get_only:

// example_get_post
if ($pathinfo === '/example') {
if (!in_array($this->context->getMethod(), ['GET', 'POST', 'HEAD'])) {
$allow = array_merge($allow, ['GET', 'POST', 'HEAD']);
7. Routing 108

goto not_example_get_post;
}

return [
'_controller' => 'AppBundle:Default:index',
'_route' => 'example_get_post'
];
}
not_example_get_post:

// ...

As you can see, the first route only checks the path, while in the other two checks that the method
is a valid one (context is an instance of Symfony\Component\Routing\RequestContext).

11. Given the definition of the route book_list, what will be the value of the variable $url?
Answer 1 is correct. The route generator takes as an input an array of parameters and replaces by
name all placeholders, but in case that you pass extra parameters, they are added as querystring
parameters. The generateUrl() method takes an optional parameter to generate full URLs, but by
default generates paths. That is why answer 4 is wrong:

// src/AppBundle/Controller/HomeController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class HomeController extends Controller


{
public function indexAction()
{
// /books?page=1
$url = $this->generateUrl('book_list', ['page' => 1]);

// /books?page=1
$this->generateUrl(
'book_list',
['page' => 1],
UrlGeneratorInterface::ABSOLUTE_PATH
);

// http://example.com/books?page=1
7. Routing 109

$this->generateUrl(
'book_list',
['page' => 1],
UrlGeneratorInterface::ABSOLUTE_URL
);

// ../books?page=1
$this->generateUrl(
'book_list',
['page' => 1],
UrlGeneratorInterface::RELATIVE_PATH
);

// //example.com/books?page=1
$this->generateUrl(
'book_list',
['page' => 1],
UrlGeneratorInterface::NETWORK_PATH
);
}
}

The value of UrlGeneratorInterface::ABSOLUTE_URL is true, so you can get the absolute URL using
true as well:

// src/AppBundle/Controller/HomeController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class HomeController extends Controller


{
public function indexAction()
{
// http://example.com/books?page=1
$this->generateUrl('book_list', ['page' => 1], true);
}
}

12. Given the following route definition, what URL would you get if you use the UrlGenera-
7. Routing 110

torInterface::NETWORK_PATH constant?

Answer 2 is correct. When using the UrlGeneratorInterface::NETWORK_PATH constant, a network


path is generated, which reuses the current scheme but specifies the host.

13. When generating an absolute URL, where does the domain name come from?
Answer 3 is correct, the domain name (host) is extracted from the current Request object, in
particular, from the Host HTTP header.

14. Once you have dumped all routes as Apache rewrite rules with the router:dump-apache
command and stored in the .htaccess file, which of the following sentences are true?
Only answer 3 is correct. When dumping routes as Apache rewrite rules, they already contain
information about the controller that must be executed, as well as the route name and parameters.
Take as an example the following route definition:

# /app/config/routing.yml
book_detail:
path: /books/{slug}
defaults: { _controller: AppBundle:Book:detail }

That definition, when dumped as Apache rewrite rules, looks like this:

# .htaccess
# book_detail
RewriteCond %{REQUEST_URI} ^/books/([^/]++)$
RewriteRule .* help [QSA, L, E=_ROUTING_route:book_detail, E=_ROUTING_param_slug\
:%1, E=_ROUTING_default__controller:AppBundle\\Controller\\BookController\:\:det\
ailAction]

You can see that all the information is passed to the Symfony front controller (app.php or app_-
dev.php) using environment variables (E flag), bypassing the routing system.

It is important to note that the router:dump-apache command dumps all routes, even the ones from
the Symfony development tools such as _profiler or _wdt. It doesn’t make any distinction.

15. What is the output of the command route:debug book_detail?


7. Routing 111

Answer 2 is valid. Without arguments, router:debug displays a list of all current routes, but when
a route name is passed as an argument, it shows information about it:

[router] Route "book_detail"


Name book_detail
Path /books/{slug}
Host ANY
Scheme ANY
Method ANY
Class Symfony\Component\Routing\Route
Defaults _controller: AppBundle\Controller\BookController::detailAction
Requirements NO CUSTOM
Options compiler_class: Symfony\Component\Routing\RouteCompiler
Path-Regex #^/books/(?P<slug>[^/]++)$#s

Even though the right command is route:debug, there is no need to type the full command, as
long as it is not ambigous. So, if there is not any command namespace starting by r, you could
type r:debug. r:d would not work as there would be two possible commands: route:debug and
route:dump-apache, so the shortest unambiguous name would be r:de.

16. Is the following route definition valid?


Answer 3 is correct. As controllers are executed with the call_user_func_array() function, any
valid callable can be used. Remember that strings containing function names are considered as
valid callables (is_callable("strtolower") returns true), so the following code is basically what
Symfony ends up executing in this case:

// function_name_callable.php
// returns "hello"
call_user_func_array('strtolower', ['HELLO']);

17. Given the following route definition, what URLs will be matched?
Answers 2, 3 and 4 are correct. As the requirement of the parameter title is .+, / can be part of
it. So, in the answer 2, title will be 123, 123/456 in answer 3 and 123/456/789 in answer 4. If you
don’t specify the requirements for a given parameter, [ˆ/]+ is used.
7. Routing 112

Takeaways
• Route definition
– By default, development routes like /_profiler or _wdt are defined in the file app/con-
fig/routing_dev.yml.
– If no methods are defined, ANY is used.
• Visualizing and debugging
– router:debug displays all current routes and provides more information about specific
ones.
• URL generation
– Additional parameters are added to the URI as query strings.
– When generating absolute URLs, the Request object is used to get the domain name.
8. Twig
Exam goals
8.1. Auto escape
8.2. Template inheritance
8.3. Global functions
8.4. Filters
8.5. Template includes
8.6. Control statements (loops and conditions)
8.7. URLs generation
8.8. Call a controller from a view
8.9. Translations

Questions
1. Which of the following sentences are true about Twig?

1. The templating engine does not process PHP tags


2. Templates are compiled down to native PHP code at runtime
3. Templates are required to have the extension .twig
4. path() and url() are custom Twig functions added by Symfony

2. In Twig, are comments rendered when debug mode is enabled?

1. Yes
2. No
3. Only in the dev environment
4. Only when twig.debug is set to true

3. What is the syntax for comments in Twig?

1. {{ ... }}
2. {# ... #}
3. {* ... *}
8. Twig 114

4. {- ... -}

4. Are Twig templates recompiled when changes are made when debug mode is enabled?

1. Yes
2. No
3. Only in the dev environment
4. Only when apc.stat is set to 1 in the PHP settings

5. Output escaping in Twig…

1. … is disabled by default
2. … is enabled by default
3. … can be disabled with the e filter
4. … is necessary to prevent XSS attacks

6. If the current time is 16:22:42 Europe/Madrid, does the following template print 16:22?

{# time.html.twig #}
{{ now | date(timezone="Europe/Madrid", format="H:i") }}

1. Yes
2. No

7. Where is defined the url() function?

1. It belongs to the Twig core


2. In the official Twig extensions repository
3. Twig bridge
4. TwigBundle

8. What is the command to check the syntax of one or more Twig files?

1. twig:validate
2. twig:syntax
3. twig:lint
4. There is not such a command

9. What is the command to convert individual Twig templates to PHP ones?


8. Twig 115

1. twig:export
2. twig:convert
3. twig:compile
4. There is not such a command

10. What is the output of the following code inside a controller that extends from the base
controller?

return $this->render('{{ var }}', ['var2' => 'hello']);

1. No errors but empty response as var is not defined


2. Error: The variable var is not defined
3. Error: The render() method expects a file path, not Twig code
4. Error: The render() method does not exist, it must be renderView()

11. How are variables declared in Twig templates?

1. {{ define var = 'hello' }}


2. {% define var = 'hello' %}
3. {{ set var = 'hello' }}
4. {% set var = 'hello' %}

12. Which of the following variable assignations are valid in Twig?

1. {% set name = 'John' %}


2. {% set name %}John{% endset %}
3. {% set name %}{{ 'John' }}{% endset %}
4. {% set name, name = 'John', 'John' %}

13. How can we get the current route name from Twig?

1. {{ app.request.attributes._route }}
2. {{ app.request.attributes._routeName }}
3. {{ app.routing.route }}
4. {{ app.routing.routeName }}

14. What application specific variables are available in the app global variable?

1. app.security
8. Twig 116

2. app.user
3. app.request
4. app.session

15. How can you get the name of the current environment from a Twig template?

1. {{ env.name }}
2. {{ app.env.name }}
3. {{ app.environment }}
4. {{ app.environment.name }}

16. How can you get the HTTP method of the current request from a Twig template?

1. {{ app.request.method }}
2. {{ app.request.getMethod() }}
3. {{ app.request.http }}
4. It is not possible to get the HTTP method from Twig without custom extensions

17. How can you get the current charset from a Twig template?

1. {{ app.request.charset }}
2. {{ app.request.attributes._charset }}
3. {{ _charset }}
4. It is not possible to get the current charset from Twig

18. What is the output of the following Twig template?

// IndexController.php
$data = [
'first' => 0,
'first-page' => 1
];

return $this->render('index.html.twig', [
'page' => 5,
'data' => $data
]);
8. Twig 117

{# index.html.twig #}
{{ data.first-page }}

1. No output (empty response)


2. 0
3. 1
4. -5

19. Would the following Twig template throw an error?

return $this->render('index.html.twig', []);

{# index.html.twig #}
{{ data }}

1. No. Twig never throws errors for undefined variables


2. Only when the twig.strict_variables is true
3. Only when the twig.strict_variables is false
4. Yes. Twig always throws errors for undefined variables

20. Which of the following filters are not available in Twig?

1. strtolower
2. lowercase
3. lower
4. lowerize

21. Is it possible to define additional global variables to be available in all Twig templates?

1. Yes, adding them to twig.globals in config.yml


2. Yes, registering a listener for the kernel.view event and injecting the variables before the
template is parsed
3. Yes, creating a Twig extension and making use of the method getGlobals()
4. No

22. Which of the following solutions would be better to have a variable called env in all Twig
templates containing the current environment name? (Only 1 answer)
8. Twig 118

1. Adding {% set env = app.environment %} in the base template which every other template
inherits from
2. Adding env: "%kernel.environment%" to twig.globals
3. Overriding the render() method of the base controller
4. Looking for the definition of the twig service in TwigBundle and adding <call method="addGlobal">
<argument>env</argument> <argument>%kernel.environment%</argument> </call>

23. Given the following Twig template, what would be its output?

{# spaceless.html.twig #}
{% spaceless %}
<div id="hello">
<span >Hello world</span>
</div>
{% endspaceless %}

1. <div id="hello"><span>Hello world</span></div>


2. <div id="hello"><span>Helloworld</span></div>
3. <divid="hello"><span>Hello world</span></div>
4. <div   id="hello"><span >Hello world</span></div>

24. In Twig, for loops create a special variable to get some information such as the current
iteration or whether the current iteration is the first/last. What is its name?

1. loop
2. index
3. for
4. app

25. What error do you get if you render the following Twig template from a controller, if the
route name is book_list?

{# render_controller.html.twig #}
{% render(controller(app.request.attributes.get("_route"))) %}

1. Maximum function nesting level reached


2. Unable to parse the controller name
3. Class "book" does not exist
4. None of the above are correct
8. Twig 119

26. Which of the following are good use cases for the cycle function in Twig?

1. Zebra stripes on HTML tables


2. HTML nested menus
3. Previous/Next links in pagination
4. Breadcrumbs

27. What is the output of the following Twig template?

{# object_first.html.twig #}
{{ {one: 1, two: 2} | first }}

1. one
2. 1
3. Error: Array to string conversion
4. Error: Object to string conversion

28. The goal of the following template is to choose a different layout for AJAX requests. Would
it work?

{# conditional_layout.html.twig #}
{% set ajax = app.request.xmlHttpRequest %}
{% set prefix = 'AppBundle::' %}
{% extends prefix ~ (ajax ? "layout_ajax.html.twig" : "layout.html.twig") %}
{# ... #}

1. No, the method xmlHttpRequest doesn’t exist


2. No, the extends instruction cannot depend on runtime variables
3. No, when present, extends must be the first instruction
4. Yes

29. In nested loops, how can you access to the loop.index variable of the parent loop?

1. {{ parent.index }}
2. {{ parent.loop.index }}
3. {{ loop.parent.loop.index }}
4. {{ loop.loop.parent.loop.index }}

30. Given the following Twig extension, what would be the output of {{ hello("bye") }}?
8. Twig 120

// AppBundle/Twig/Extension/MagicExtension.php
class MagicExtension extends \Twig_Extension
{

public function getFunctions()


{
return [
new \Twig_SimpleFunction(
'*',
[$this, 'getMagic'],
['is_safe' => ['html']]
)
];
}

public function getMagic()


{
return implode(':', func_get_args());
}

public function getName()


{
return 'magic';
}
}

1. bye
2. hello:bye
3. bye:hello
4. Error: The function "hello" does not exist

31. What is the output of the following template if not_found.html.twig doesn’t exist and
error.html.twig contains An error occurred?

{# test.html.twig #}
{{ include('not_found.html.twig') | default('error.html.twig') }}

1. An error occurred
2. Error: The filter "default" does not exist
3. Error: Unable to find template "not_found.html.twig"
4. Error: Unexpected token "An" of value "error"
8. Twig 121

32. In which of the following scenarios would you use the Twig verbatim tag?

1. To output Twig code without parsing it


2. To optimize the template by removing unnecessary code
3. To define additional blocks in the parent template
4. None of the above are correct

33. What is the logical name of a Twig template located at app/Resources/views/layout.html.twig?

1. Framework::layout.html.twig
2. :views:layout.html
3. app::layout.html.twig
4. ::layout.html.twig

Answers
1. Which of the following sentences are true about Twig?
Answers 1, 2 and 4 are correct. Twig does not process PHP tags, it just prints out the PHP code as if
it were plain text. Before using Twig templates, they are compiled down to native PHP and cached
for future use, so the performance impact is really low compared to plain PHP code.

2. In Twig, are comments rendered when debug mode is enabled?


Answer 2 is correct. Comments are never rendered in Twig, they are just ignored.

Executing Twig code online


TwigFiddle¹⁴ is an exellent tool to execute Twig code online against different versions of
Twig. If you want to make a quick test, it is probably the fastest option.

3. What is the syntax for comments in Twig?


Answer 2 is correct. Comments in Twig templates are wrapped between {# and #}. They can be
¹⁴http://twigfiddle.com/
8. Twig 122

multiline and their content is never rendered.


Like with PHP, unclosed comments throw an exception (a Twig_Error_Syntax exception):

{# unclosed_comment.html.twig #}
{#
{{ 'hello' }}

4. Are Twig templates recompiled when changes are made when debug mode is enabled?
Answer 1 is correct. When the debug mode is enabled, Twig templates are recompiled if the
templating engine detects that the file has changed. This is the default mode in the dev environment,
but it can be used in any other environment, even with prod. The apc.stat setting has no effect
when compiling Twig templates, as APC (or OpCache) only caches PHP code (but when apc.stat
is 0, clearing the template cache won’t update the APC cache).
In Symfony, Twig templates are compiled down in the twig directory of the environment cache (i.e.
app/cache/dev). For example, the following simple template:

{# test.html.twig #}
{# print something #}

{{ 'hello world' }}

Is compiled down to:

// app/cache/dev/twig/8a/89/24ac44...php
/* AppBundle::test.html.twig */
class __TwigTemplate_8a8924ac extends Twig_Template
{
public function __construct(Twig_Environment $env)
{
parent::__construct($env);

$this->parent = false;

$this->blocks = [];
}

protected function doDisplay(array $context, array $blocks = array())


{
8. Twig 123

// line 2
echo "";

// line 3
echo "hello world";
}

public function getTemplateName()


{
return "AppBundle::test.html.twig";
}

public function getDebugInfo()


{
return array ( 22 => 3, 19 => 2,);
}
}

As you can see, it’s plain PHP code. The class extends from Twig_Template and the doDisplay()
method is responsible or rendering the template. The {{ 'hello' }} instruction has been converted
to a simple echo, while the comment has just been ignored. The getDebugInfo() method is used for
debugging purposes, and maps lines of the PHP compiled template and the Twig template. Here, the
3rd line of the template corresponds with the 22nd line of the PHP file.

5. Output escaping in Twig…


Answers 2 and 4 are correct. Output escaping is enabled by default in Twig templates and it
can be disabled per instruction with the raw filter. It also prevents XSS¹⁵ attacks: user-provided
content will be escaped so HTML tags won’t be parsed by the browser. Internally, escape uses the
htmlspecialchars() PHP function.

For example, imagine that you have a forum where users can post messages. If HTML code is inserted
in the message and are rendered unescaped, they would be interpreted by the browser. With output
escaping, if the variable code contains <a href="http://example.com"></a>, {{ code }} is escaped
and the browser doesn’t interpret it as HTML code:

&lt;a href=&quot;http://example.com&quot;&gt;&lt;/a&gt;gt;

¹⁵http://en.wikipedia.org/wiki/Cross-site_scripting
8. Twig 124

6. If the current time is 16:22:42 Europe/Madrid, does the following template print 16:22?
Answer 2 is correct, as there is an error in the template. The variable now doesn’t exist, it should be
the string "now", which is interpreted by the date‘ filter as the current date and time:

{# date.html.twig #}
{{ "now" | date(timezone="Europe/Madrid", format="H:i") }}

In case you are wondering, named arguments are supported since Twig 1.12. The previous code is
equivalent to these two:

{# date.html.twig #}
{{ "now" | date("H:i", "Europe/Madrid") }}

{{ "now" | date("H:i", timezone="Europe/Madrid") }}

date input values

Internally, the date filter makes use of the date() PHP function, so you can use any
value that is accepted by the function, such as +1 hour. {{ "+1 hour"|date("H:i",
"Europe/Madrid") would print 17:22.

7. Where is defined the url() function?


Answer 3 is correct. The Twig bridge provides integration for Twig with different Symfony
components, such as the Routing component. Twig functions such as url() or path() are defined
there.

Twig bridge
In addition to url() and path(), the Twig bridge integrates Twig with other components
like Form, HttpKernel, Security and Yaml. All functions and filters that are useful in a
Symfony project but not for a templating engine in isolation, are probaby defined here:
form-related functions, the trans and transchoice filters or other important functions
like render(), controller() or is_granted().

8. What is the command to check the syntax of one or more Twig files?
8. Twig 125

Answer 3 is correct. The TwigBundle bundle defines the command twig:lint to check the syntax
of Twig files. For the validation process, it first tokenizes the contents of the Twig file and then tries
to parse it. If an error is thrown, the file contains a syntax error. Remember that linters don’t check
other errors, like undefined variables or wrong array positions, so the following template will be
valid for the linter but will throw an error when is executed:

{# no_array_key.html.twig #}
{% set data = [1, 2, 3] %}
{{ data[4] }}

Unlike the PHP linter, the twig:lint command takes into account all tags, functions and filters
defined by bundles.

9. What is the command to convert individual Twig templates to PHP ones?


Answer 4 is correct. Out of the box, there is no command to convert Twig templates to PHP ones.
By the way, the TwigBundle bundle defines a cache warmer to compile all twig templates and store
them in cache when executing cache:warm.

10. What is the output of the following code inside a controller that extends from the base
controller?
Answer 3 is correct. The render() method defined in the base controller throws an exception as it
expects a file path, not actual Twig code.

11. How are variables declared in Twig templates?


Answer 4 is correct. The set tag is used to declare variables in Twig templates. The assigned value
can be any valid Twig expression and several variables can be assigned in the same instruction.

set in loops

In Twig, loops are scoped, so any variable declared inside a loop won’t be available outside.

12. Which of the following variable assignations are valid in Twig?


All answers are valid, and in all of them, the name variable contains John. The set tag can also be
8. Twig 126

used to capture the output generated by its body ‘capture’ chunks of text, that’s why answers 2 and
3 work. Answer 4 works as well because in Twig you can set the same variable more than once.

13. How can we get the current route name from Twig?
Answer 1 is correct. The app global variable provides the Request object in app.request. This object
contains a parameter bag called attributes, with information about the controller (_controller),
the route (_route) and the route parameters (_route_params).

14. What application specific variables are available in the app global variable?
All answers are correct. The app global variable provides 6 application specific variables: security,
user, request, session, environment and debug.

15. How can you get the name of the current environment from a Twig template?
Answer 3 is correct. The app.environment variable contains a string with the current environment:
dev, prod, etc. While there are better ways (data collectors or HTTP headers), it can be used to output
additional information for debugging purposes:

{# layout.html.twig #}
{# ... #}

{% if app.environment == 'dev' %}
IP: {{ app.request.clientIp }}
{% endif %}

{# ... #}

16. How can you get the HTTP method of the current request from a Twig template?
Answers 1 and 2 are both correct. As app.request contains the Request object, you can get the cur-
rent HTTP method using the getMethod() method. In addition, when using app.request.method,
Twig does the following checks:

1. method is a valid property


2. method is a valid method
8. Twig 127

3. getMethod() is a valid method


4. isMethod() is a valid method

As Request::getMethod() exists, {{ app.request.method }} returns the same method as {{


app.request.getMethod() }}.

17. How can you get the current charset from a Twig template?
Answer 3 is correct. In addition to the app global variable (defined by Symfony), self, context and
charset (defined by Twig itself) are always available in Twig templates.

18. What is the output of the following Twig template?


Answer 4 is correct. When attributes contain special characters such as -, that can be interpreted as
the minus operator, the attributes function must be used. In this example, Twig is interpreting the
code as {{ data.first - page }}.

19. Would the following Twig template throw an error?


Answer 2 is correct. Errors for undefined variables are only thrown when twig.strict_variables
is true, otherwise it just returns null. By default, Symfony uses %kernel.debug% to set the value of
twig.strict_variables, so errors will be thrown in the dev environment, but not in prod.

20. Which of the following filters are not available in Twig?


Answers 1, 2 and 4 are correct. To make a string lowercase, Twig provides the lower filter, which
makes use of the strtolower() PHP function under the hood.
Similarly, upper, capitalize and title are also available to make strings uppercase, capitalized (all
characters lowercase except the first one) or titlecased (all characters lowercase except the first one
of each word):
8. Twig 128

{# prints "hello world" #}


{{ 'HELLO WORLD' | lower }}

{# prints "HELLO WORLD" #}


{{ 'hello world' | upper }}

{# prints "Hello world" #}


{{ 'HELLO WORLD' | capitalize }}
{{ 'hello world' | capitalize }}

{# prints "Hello World" #}


{{ 'HELLO WORLD' | title }}
{{ 'hello world' | title }}

21. Is it possible to define additional global variables to be available in all Twig templates?
Answers 1 and 3 are correct. The easiest way to add global variables for all Twig templates is using
the twig.globals setting, and accepts primitive values, service container parameters and services.
For more advanced use cases, Twig extensions can define global variables too. It is not possible to
use a listener to add global variables as there is no event generated just before the template is parsed
(kernel.view is only generated when the return value of a controller is not a Response object).
Overriding the render() method of the base controller would also be an option.

22. Which of the following solutions would be better to have a variable called env in all Twig
templates containing the current environment name? (Only 1 answer)
All answers could work, but the easiest and cleanest way is by adding the variable to the
twig.globals setting. Answer 1 would not work for templates that don’t inherit from the base
template. Similarly, answer 3 would force all controllers to inherit from the base controller. Answer
4 is not recommended at all as you would be editing Symfony itself.

23. Given the following Twig template, what would be its output?
Answer 4 is correct. The spaceless tag removes whitespaces between HTML tags, not within tags
or text.

24. In Twig, for loops create a special variable to get some information such as the current
8. Twig 129

iteration or whether the current iteration is the first/last. What is its name?
Answer 1 is correct. The loop array is created for the for loop scope and contains several keys:

• index: Current iteration starting from 1.


• index0: Current iteration starting from 0.
• revindex: Number of iterations from the end of the loop (1 indexed).
• revindex0: Number of iterations from the end of the loop (0 indexed).
• first: It’s true when index is 1 (first iteration).
• last: It’s true when revindex is 1 (last iteration).
• length: Number of elements in the sequence.
• parent: Parent context, which contains variables defined before the for loop or passed to the
template from the controller.

For example, the following template would generate the resulting trace:

{# loop_variable.html.twig #}
{% for letter in ['a', 'b', 'c'] %}
{# ... #}
{% endfor %}

Iteration index index0 revindex revindex0 first last length


1 1 0 3 2 true false 3
2 2 1 2 1 false false 3
3 3 2 1 0 false true 3

25. What error do you get if you render the following Twig template from a controller, if the
route name is book_list?
Answer 2 is correct. The controller() function expects a controller name, not the current route
name. As the route name (book_list) is not in the format Bundle:Controller:Action, it cannot be
parsed.
Answer 1 would be true if you change _route by _controller, as it contains the value of the current
controller in the right format. The following code generates an infinite loop, so when the maximum
level is reached it throws an error:

{# infinite_loop.html.twig #}
{% render(controller(app.request.attributes.get("_controller"))) %}
8. Twig 130

26. Which of the following are good use cases for the cycle function in Twig?
Answer 1 is correct. Zebra stripes on HTML tables is a common use case for the cycle function, as
it can be used to easily apply different styles to even and odd rows:

{# cycle.html.twig #}
{% set rows = [[1, 'one'], [2, 'two']] %}

<table>
{% for row in rows %}
<tr class="{{ cycle(['odd', 'even'], loop.index0) }}">
<td>{{ row[0] }}</td>
<td>{{ row[1] }}</td>
</tr>
{% endfor %}
</table>

The previous template generates the following HTML table:

<table>
<tr class="odd">
<td>1</td>
<td>one</td>
</tr>
<tr class="even">
<td>2</td>
<td>two</td>
</tr>
</table>

27. What is the output of the following Twig template?


Answer 2 is correct. The first filter works with arrays, hashes and strings, and returns the first
element of the input data:
8. Twig 131

{# first_filter.html.twig #}
{{ ['hello', 'bye'] | first }} {# result: 'hello' #}
{{ {one: 'hello', two: 'bye'} | first }} {# result: 'hello' #}
{{ 'hello'| last }} {# result: 'h' #}

The last filter works the same way, but returns the last element of the input data:

{# last_filter.html.twig #}
{{ ['hello', 'bye'] | last }} {# result: 'bye' #}
{{ {one: 'hello', two: 'bye'} | last }} {# result: 'bye' #}
{{ 'hello'| last }} {# result: 'o' #}

28. The goal of the following template is to choose a different layout for AJAX requests. Would
it work?
Answer 4 is correct, the approach is correct. It is possible to make a layout conditional, as well as
making it depend on a variable. In fact, the template name can be any valid expression.

29. In nested loops, how can you access to the loop.index variable of the parent loop?
Answer 3 is correct. In Twig, each loop has its own scope, but the loop variable contains a reference
to the parent scope. For example, the following template prints out 1 1 1 2 2 2 3 3 3:

{# parent_loop.html.twig #}
{% for i in 'a'..'c' %}
{% for j in 'a'..'c' %}
{{ loop.parent.loop.index }}
{% endfor %}
{% endfor %}

30. Given the following Twig extension, what would be the output of {{ hello("bye") }}?
Answer 2 is correct. It’s basically a catch-all function. Twig supports dynamic functions since version
1.5, so if you define a function that includes *, it can be replaced by any string.
Symfony itself makes use of dynamic functions for the render_* function, which is executed when
using render_esi or render_hinclude. This function is defined in the Twig bridge.
8. Twig 132

Similarly, you can define dynamic filters as well. The following example defines the dynamic filter
add_*, which adds two numbers:

// AppBundle/Twig/Extension/MagicExtension.php
class MagicExtension extends \Twig_Extension
{

public function getFilters()


{
return [
new \Twig_SimpleFilter(
'add_*',
[$this, 'getAdd'],
['is_safe' => ['html']]
)
];
}

public function getAdd($number, $input)


{
return $number + $input;
}

public function getName()


{
return 'magic';
}
}

So, if you execute {{ 1 | add_2 }} the output will be 3.

31. What is the output of the following template if not_found.html.twig doesn’t exist and
error.html.twig contains An error occurred?

Answer 3 is correct. The default filter returns the passed value if the input is undefined or empty,
but it doesn’t catch errors.

32. In which of the following scenarios would you use the Twig verbatim tag?
Answer 1 is correct. When Twig finds a verbatim section, its content is not parsed, but treated as
8. Twig 133

raw text. For example, the following template outputs {{ [1, 2, 3] | join(', ') }}:

{# verbatim.html.twig #}
{% verbatim %}
{{ [1, 2, 3] | join(', ') }}
{% endverbatim %}

33. What is the logical name of a Twig template located at app/Resources/views/layout.html.twig?


Answer 4 is correct. When using logical names, they are composed of three fragments:

• Bundle name.
• Directory inside Resources/views.
• Template file name.

In the current example, as the template is not inside any bundle but belongs to the application, the
first fragment is empty. Also, as it is not located in any subdirectory of Resources/views, the second
fragment is empty as well. The following table contains some examples of templates and their logical
names:
Location Logical name
app/Resources/views/layout.html.twig ::layout.html.twig
app/Resources/views/Static/tos.html.twig :Static:tos.html.twig
src/AppBundle/Resources/views/mail.txt.twig AppBundle::mail.txt.twig

Takeaways
• General
– The Twig bridge defines functions and filters that make use of Symfony components,
such as url(), path(), is_granted() or render(). They are not included in the core of
Twig.
– Loops have their own scope and define a special variable called loop, which can be used
to get the index and access to the parent scope.
8. Twig 134

– Named parameters are supported in Twig since 1.12. {{ "now" | date(timezone="Europe/Madrid",


format="H:i") }}, {{ "now" | date("H:i", "Europe/Madrid") }} and {{ "now" |
date("H:i", timezone="Europe/Madrid") }} are equivalent.
– The syntax of Twig templates can be checked with the twig:lint command. It takes
into account all tags, functions and filters defined by bundles.
• Output scaping
– Output escaping is enabled by default in Twig templates and it can be disabled per
instruction with the raw filter.
– It basically prevents XSS attacks.
• Filters and functions
– Dynamic functions and filters allow to define functions/filters with placeholders using
the character *.
– The spaceless tag removes whitespaces between HTML tags, not within tags or text.
– The verbatim tag can be used to render Twig code without parsing it.
• Global variables
– app, self, context and charset are always available in Twig templates. app is added by
Symfony, while the other three are defined by Twig.
– The app global variable provides 6 application specific variables: security, user,
request, session, environment and debug.
– Global variables can be set from a custom extension (overriding the getGlobals()
method) or with the twig.globals option.
• Caching
– Twig templates are compiled down to PHP code and stored in the %cache_dir%/twig
directory.
– Twig templates are recompiled if the templating engine detects that the file has changed
only if the debug mode is enabled.
– Compiled templates contain information for debugging purposes. For example, the
getDebugInfo() method maps lines of the PHP compiled template and the Twig
template.
9. Forms
Exam goals
9.1. Create forms
9.2. Handling forms
9.3. Form types
9.4. Render forms with Twig
9.5. Forms security (CSRF)

Questions
1. Does {{ form_start(form) }} render the enctype attribute?

1. Yes, it always renders the enctype attribute.


2. Yes, but only when the form contains a file field.
3. Yes, but only when the action method is POST or PUT.
4. No, {{ form_enctype(form) }} must be used when the enctype attribute is needed.

2. What was the HTTP method configured in the form given the following HTML?

<form method="post" action="/">


<input type="hidden" name="_method" value="PUT" />
</form>

1. GET
2. POST
3. PUT
4. OPTIONS

3. Which of the following sentences are true about the {{ form_end() }} helper?

1. Renders the form end tag </form>.


2. Renders fields that have not been rendered yet.
3. Renders validation errors
9. Forms 136

4. Renders the hidden field for CSRF protection

4. How does Symfony prevent CSRF (Cross-Site Request Forgery) attacks?

1. Adds a hidden field to forms.


2. Enables output escaping by default in Twig templates.
3. Automatically escapes all input data from superglobal arrays, such as $_GET or $_POST.
4. Disables opening and including remote files with allow_url_fopen and allow_url_include.

5. Which of the following sentences are true about CSRF tokens?

1. They are different for each user, form and request.


2. Setting the intention form option to GET forces the token to be sent in the URL.
3. The secret parameter from parameters.yml is used to generate it.
4. They are printed out with the {{ form_csrf() }} helper.

6. By default, a form is tied to an array of data. How can you work with an object instead?

1. Setting the data_class form option to the class name.


2. Pass an object when creating the form with createFormBuilder or createForm.
3. Setting the framework.form.data option to stdclass.
4. Extending the form class from Symfony\Component\Form\AbstractObjectType instead of
Symfony\Component\Form\AbstractType.

7. Do the following two controllers generate an exception?

// AppBundle/Controller/BookController
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class DefaultController extends Controller


{

public function formAction()


{

$form = $this->createFormBuilder(new Request())


->add('title', 'text')
->getForm();

// ...
}
}
9. Forms 137

// AppBundle/Controller/BookController
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DefaultController extends Controller


{

public function formAction()


{

$form = $this->createFormBuilder([])
->add('title', 'text')
->getForm();

// ...
}
}

1. Yes, both controllers generate an exception


2. Only the first controller generate an exception
3. Only the second controller generate an exception
4. None of the controllers generate an exception

8. How can you get the value of the title field from a form in a controller? Assume that the
underlying data is an array

1. $form->data['title']
2. $form->data->get('title')
3. $form->get('title')->getData()
4. $form->getData()['title']

9. Given the following form, how can you know if the saveAsDraft button was clicked?

public function formAction(Request $request)


{
// ...

$form = $this->createFormBuilder($book)
->add('name', 'text')
->add('save', 'submit', array('label' => 'Save'))
->add('saveAndPublish', 'submit', array('label' => 'Save & Publish'))
->add('saveAsDraft', 'submit', array('label' => 'Save as draft'))
9. Forms 138

->setMethod('POST')
->getForm();

// ...
}

1. $form->get('saveAsDraft')->isClicked()
2. $form->handleRequest($request, 'saveAsDraft')
3. isset($request->request->get('form')['saveAsDraft'])
4. $request->headers->get('_form.saveAsDraft')

10. How are url fields rendered in old browsers that don’t support HTML5?

1. <input type="text" ... />


2. <input type="url" ... />
3. <input type="text" data-type="url" ... />
4. <url ... />

11. What is the HTML code generated for the name field by the following form?

public function formAction(Request $request)


{
// ...

$form = $this->createFormBuilder($book)
->add('name', 'text', [
'required' => true
]);

// ...
}

1. <input type="text" name="form[name]" required="required" />


2. <input type="text" name="form[name]" />

12. What attribute can be used in the <form> tag to disable HTML5 validation?

1. disabled
2. novalidate
3. formnovalidate
4. validation

13. What needs to be done in order to be able to create a form like the following one?
9. Forms 139

public function newAction()


{
$book = new Book();

$form = $this->createForm('book', $book);

// ...
}

1. Add the form class as a service, use the tag form.type and the alias book
2. Add the form class as a service and use the alias form_book
3. Add the form class to the framework.form.types config value
4. None of the above are correct

14. Given the following Twig instruction, what sentences are true?

{# AppBundle/Resources/views/form.html.twig #}
{% form_theme form 'form/theme1.html.twig' 'Form/theme2.html.twig' %}

{# ... #}

1. Only the first theme will be used


2. Only the second theme will be used
3. Both form themes will be considered, in the order they appear in the form_theme instruction
4. An exception will be thrown

Answers
1. Does {{ form_start(form) }} render the enctype attribute?
Answer 2 is correct, {{ form_start(form) }} renders the start tag of the form (<form>), including
the action URL and method. If the form contains a file field, it also renders the enctype attribute
with the value multipart/form-data, regardless of the action method.
The {{ form_enctype() }} helper renders the enctype attribute with the value multipart/form-
data when needed, or just prints nothing otherwise.
9. Forms 140

2. What was the HTTP method configured in the form given the following HTML?
Answer 3 is correct. As some browsers do not support PUT or DELETE requests, Symfony provides a
workaround to be able to use these HTTP methods in all browsers by including a _method parameter
that is used to match routes when the framework.http_method_override option is true. In Symfony
forms, all methods except GET and POST generate this hidden field. It is easy to see how it works by
checking the Twig block used by the {{ form_start() }} helper:
In the example, as $form->setMethod('PUT') was used, Symfony adds the hidden field and sets the
method to POST. You would get the same result with DELETE and PATCH methods, as this is the

{# form_div_layout.html.twig #}
{%- block form_start -%}

{# if method is different than GET or POST, use POST #}


{%- if method in ["GET", "POST"] -%}
{% set form_method = method %}
{%- else -%}
{% set form_method = "POST" %}
{%- endif -%}
<form method="{{ form_method|lower }}">

{# if method is different than GET or POST, add _method hidden field #}


{%- if form_method != method -%}
<input type="hidden" name="_method" value="{{ method }}" />
{%- endif -%}

{%- endblock form_start -%}

3. Which of the following sentences are true about the {{ form_end() }} helper?
Answers 1, 2 and 4 are correct. The {{ form_end() }} helper renders all fields that have not been
rendered yet, including the CSRF hidden field, and the </form> tag.

4. How does Symfony prevent CSRF (Cross-Site Request Forgery) attacks?


Answer 1 is correct. CSRF (Cross-Site Request Forgery) attacks work by making authenticated users
of a website submit data unwittingly. For example, imagine that a bookstore website has the URL to
purchase a book: http://example.com/purchase?id=123. If that URL is not protected against CSRF
attacks, a malicious user could place that link in a forum system that allows HTML, so if the user
9. Forms 141

clicks on it and is authenticated in the bookstore website, the purchase would be made. It can even
be placed inside an image element, so the request would be made without having to click on the
link:

<img src="http://example.com/purchase?id=123" />

The Symfony Form component prevents CSRF attacks by including a hidden field (token) in forms
with a different value for each user. Once submitted, it automatically checks that the token is correct.
As the token is different for each user, an attacker would need to know the token for the target user.

5. Which of the following sentences are true about CSRF tokens?


Only answer 3 is correct. CSRF tokens are generated combining the value of the secret parameter
and the current user, so they are the same for all forms and requests. Using the intention form
option, you can make them different for each form:

use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class BookType extends AbstractType


{
// ...

public function setDefaultOptions(OptionsResolverInterface $resolver)


{
$resolver->setDefaults(array(
// ...
'intention' => 'book_form'
));
}

// ...
}

So, if two forms have different intention keys, will have different CSRF tokens.
The hidden form field is generated by the {{ form_end() }} helper. If you need to generate it
manually, it can be done as any other form field (by default, its name is _token):

{{ form_row(form._token) }}
9. Forms 142

6. By default, a form is tied to an array of data. How can you work with an object instead?
Answers 1 and 2 are correct. There are two ways to tie a form with an object, setting the data_class
form option to the appropiate class name or passing the object when creating the form. The base
controller provides the methods createFormBuilder() and createForm(), which accept the object
as an argument. If you are not using the base controller, the methods create() and createBuilder()
from form.factory provide the same functionality.

7. Do the following two controllers generate an exception?


Answer 2 is correct. When mapping forms to objects, if any of the fields does not exist an
exception is thrown. As the Symfony\Component\HttpFoundation\Request class does not contain
a property called title nor the public methods getTitle(), isTitle(), hasTitle() or __get(), a
NoSuchPropertyException exception is thrown. In the other hand, when working with arrays, fields
can be added without this restriction.

8. How can you get the value of the title field from a form in a controller? Assume that the
underlying data is an array
Answers 3 and 4 are correct. The underlying data of form objects (remember that fields are also
form objects) can be obtained using the getData() method, while the get() method returns the
field itself. Answer 3 is correct as it first gets the title field, and then gets its data. Answer 4 gets
all data at first and then get the value from the array (as we assumed that the form was tied to an
array). There is no data public property in form objects.

9. Given the following form, how can you know if the saveAsDraft button was clicked?
Answers 1 and 3 are correct. While answer 1 is the right way to do it, getting the form.saveAsDraft
POST value would also work in this specific case. If the form name or the method changes, it wouldn’t
work.

10. How are url fields rendered in old browsers that don’t support HTML5?
Answer 2 is correct. The Form component doesn’t render fields differently depending on the browser,
and the right input type for URLs is url. In older browsers that don’t support this input field, they
will be treated exactly as input type="text" fields.
9. Forms 143

11. What is the HTML code generated for the name field by the following form?
Answer 1 is correct. The required option (which would not be needed here as its default value is
already true) adds the HTML5 required attribute to the form field, so HTML5-ready browsers will
check if the field is left blank. Keep in mind that there will not be any validation in the server-side.

12. What attribute can be used in the <form> tag to disable HTML5 validation?
Answer 2 is correct. The novalidate attribute can be applied to a form to disable HTML5 validation
when submitting it. The formnovalidate also exists, but it can be applied only to submit or image
input types.

13. What needs to be done in order to be able to create a form like the following one?
Answer 1 is correct. To be able to create a form like that, you need first to define the form as a
service, then tag it with form.type and set the alias to book:

# AppBundle/Resources/config/services.yml
services:
app.form.type.book:
class: AppBundle\Form\Type\BookType
tags:
- { name: form.type, alias: book }

The FrameworkBundle has a compiler pass to get all defined forms with the form.type tag and
register them as form types.

14. Given the following Twig instruction, what sentences are true?
Answer 3 is correct, both themes will be taken into account. When rendering the form, the theming
engine will look for blocks defined in the first theme, and if they are not found, in the second one.
In case the block is not defined in any of them, it will be used the default theme.
9. Forms 144

Takeaways
• Creating and handling forms
– When mapping forms to objects, if any of the fields does not exist an exception is thrown.
– By default, forms are tied to arrays. They can be tied to objects with the data_class
property or the form class or passing the object when creating the form.
– As some browsers only support GET and POST methods, when using other HTTP methods,
a hidden field is added containing the selected method and the actual form method is
changed to POST, to workaround this limitation.
• Twig
– {{ form_start() }} renders the start tag of the form (with the action URL and method)
and the enctype attribute if the form contains a file.
– {{ form_end() }} renders all fields that have not been rendered yet, including the CSRF
hidden field, and the </form> tag.
– {% form_theme %} can contain more than one theme. When rendering the form, all of
them will be taken into account in the order they are defined, until the specific block is
found in one of the themes.
• HTML5
– Older browsers that don’t support HTML5 form fields like url or number display them
as regular text fields.
– The required option in Symfony forms doesn’t add any extra server-side validation, it
just includes the required HTML5 attribute to get validated in the browser.
– novalidate in the <form> tag or formnovalidate in submit or image input types can be
used to disable HTML5 validation in the browser.
• CSRF
– CSRF (Cross-Site Request Forgery) attacks work by making authenticated users of a
website submit data unwittingly.
– CSRF tokens are different for each user. The intention form option can be used to have
different tokens for each user and form.
10. Validation
Exam goals
10.1. Validate a PHP object
10.2. Native validation rules
10.3. Validation scopes
10.4. Validation groups

Questions
1. The Symfony Validator component is based on…

1. PSR-0
2. JSR 303
3. RFC 2616
4. CVE-2015-2308

2. Which of the following formats are available to define validation rules or constraints?

1. YAML
2. PHP
3. Annotations
4. XML

3. Which of the following elements can contain validation constrains?

1. Classes
2. Public properties
3. Private and protected properties
4. Public getters/issers (for example, getName or isActive)
5. Private and protected getters/issers
6. Any public method

4. What will the $errors variable contain after executing the following code?
10. Validation 146

// Model/Book.php
use Symfony\Component\Validator\Constraints as Assert;

class Book
{
/**
* @Assert\NotBlank()
*/
public $title;
}

// Controller/BookController.php
public function indexAction($slug)
{
$book = new Book();

$validator = $this->get('validator');
$errors = $validator->validate($book);

// ...
}

1. true
2. false
3. A ConstraintViolationList object with no violations
4. A ConstraintViolationList object with 1 violation

5. Assuming that the validate() method detects no violations, what will the Response object
contain?

// Controller/BookController.php
public function indexAction()
{
$book = new Book();

$validator = $this->get('validator');
$errors = $validator->validate($book);

return new Response((string) $errors);


}
10. Validation 147

1. false
2. An empty string
3. Fatal error: Object of class ConstraintViolationList could not be converted to
string
4. Notice: Array to string conversion

6. Which of the following validation constraints do not exist out of the box?

1. NotBlank
2. NotNull
3. Integer
4. Boolean

7. When using the Type(type="callable") constraint, which of the following values would be
considered valid?

1. "hello"
2. "date"
3. "echo"
4. [__CLASS__, __METHOD__]

8. How can you validate an object but only against a subset of the constraints?

1. Using roles
2. Using the method validatePartial() instead of validate(), as it accepts a list of constraints
3. Using validation groups
4. It is not possible to partially validate an object

9. How can you validate an IP without using the Symfony Validator component?

1. check('1.1.1.1', CHECK_VALIDATE_IP)
2. is_ip('1.1.1.1')
3. filter_var('1.1.1.1', FILTER_VALIDATE_IP)
4. preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', '1.1.1.1')

10. Which of the following constraints could be used to validate that a given string does not
contain words from a blacklist?

1. Regex
2. Choice
3. Callback
4. Creating a custom constraint

11. Given the following constraints, how many violations will return the validator if numPages
contains the string one hundred and title is empty?
10. Validation 148

// Model/Book.php
use Symfony\Component\Validator\Constraints as Assert;

/**
* @Assert\GroupSequence({"Book", "Extra"})
*/
class Book
{
/**
* @Assert\NotBlank
*/
public $title;

/**
* @Assert\Type(type="number", groups={"Extra"})
*/
public $numPages;
}

1. 0
2. 1
3. 2
4. None of the above are correct

12. By default, error messages from validation constraints in forms are read from the
validators catalog. How can you configure it to be read from the custom catalog instead?

1. Setting the option framework.validation.translation_domain to custom in the configura-


tion file
2. Adding an event listener for the config.load event to rename the catalog on the fly
3. Adding a redirection in the main validators.{language}.{format} file with the import
option
4. It cannot be done, there is no option for that

Answers
1. The Symfony Validator component is based on…
10. Validation 149

Answer 2 is correct. The Symfony Validator component is based on the JSR303 Bean Validation
specification¹⁶, which is a standard in Java for validation. It defines a model composed of constraints
and validators.
Other answers are incorrect and have been chosen carefully so you can eliminate them easily. PSR-
0 is the Autoloading Standard, created by the PHP Framework Interop Group (FIG), RFC 2616 is a
well-known RFC as it specifies the 1.1 version of HTTP and the acronym CVE stands for Common
Vulnerabilities and Exposures.

2. Which of the following formats are available to define validation rules or constraints?
All answers are correct. Symfony always tries to be very flexible regarding the format of the
configuration files, and validation is not a exception. Annotations can be enabled through the
framework.validation.enable_annotations setting, as they are disabled by default.

3. Which of the following elements can contain validation constrains?


Answers 1, 2, 3 and 4 are correct. Most constraints can be applied to properties and public methods
starting with get or is (in fact, the validation is run against the return value of the method), and
some of them can also be used with classes, such as the Callback constraint.

4. What will the $errors variable contain after executing the following code?
Answer 4 is correct. The method validate of the validator service always returns a ConstraintVi-
olationList object containing a list of violations (empty list if there are no violations, but a
ConstraintViolationList object anyway). In the example, as $book->title is empty, the NotBlank
constraint is not met.
As the ConstraintViolationList class implements Traversable, Countable and ArrayAccess
interfaces, the following code is valid:

// Controller/BookController.php
echo 'Errors:' . PHP_EOL;
foreach ($errors as $error) {
echo $error->getMessage() . PHP_EOL;
}

echo 'Total errors: ' . count($errors) . PHP_EOL;

If the title property is empty, it would print:


¹⁶https://jcp.org/en/jsr/detail?id=303
10. Validation 150

Errors:
This value should not be blank.
Total errors: 1

5. Assuming that the validate() method detects no violations, what will the Response object
contain?
Answer 2 is correct. The ConstraintViolationList class implements the method __toString,
which displays the list of violations and can be useful for debugging:

// ConstraintViolationList.php
namespace Symfony\Component\Validator;

class ConstraintViolationList implements \IteratorAggregate, ConstraintViolation\


ListInterface
{
// ...

public function __toString()


{
$string = '';

foreach ($this->violations as $violation) {


$string .= $violation."\n";
}

return $string;
}

// ...
}

6. Which of the following validation constraints do not exist out of the box?
Answers 3 and 4 are correct. For validating if a given property or the result of a getter/isser is an
integer or boolean, the Type constraint must be used. For example, the following YAML file adds
a constraint to make sure that the property active is boolean:
10. Validation 151

# Resources/config/validation.yml
AppBundle\Model\Book:
properties:
active:
- Type:
type: boolean
message: The "active" property must be true or false.

7. When using the Type(type="callable") constraint, which of the following values would be
considered valid?
Answers 2 and 4 are correct. Internally, the Type constraint makes use of the is_* and ctype_* PHP
functions:

// Type.yml
namespace Symfony\Component\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class TypeValidator extends ConstraintValidator


{

public function validate($value, Constraint $constraint)


{
$type = strtolower($constraint->type);
$type = $type == 'boolean' ? 'bool' : $constraint->type;
$isFunction = 'is_'.$type;
$ctypeFunction = 'ctype_'.$type;

if (function_exists($isFunction) && $isFunction($value)) {


return;
} elseif (function_exists($ctypeFunction) && $ctypeFunction($value)) {
return;
} elseif ($value instanceof $constraint->type) {
return;
}

$this->context->addViolation($constraint->message, [
'{{ value }}' => $this->formatValue($value),
'{{ type }}' => $constraint->type
10. Validation 152

]);
}
}

In the example, is_callable() will be used, which checks if the value can be called as a function.
As function names such as date and [class, method] arrays are considered callables, they would
be valid. In the other hand, echo is not a function but a language constructor, so it returns false

8. How can you validate an object but only against a subset of the constraints?
Answer 3 is correct. Using validation groups, only the constraints included in the selected groups
are used. For example, given the following constraint:

// User.php
/**
* @Assert\NotBlank(groups={"admin"})
*/
public $adminPassword;

It would only return violations if the admin group is specified:

// no violations
$validator->validate($user);

// 1 violation
$validator->validate($user, ['admin']);

9. How can you validate an IP without using the Symfony Validator component?
Answer 3 is correct. The filter_var() function can be used to validate (more specifically, filter) a
variable, by using several filters. One of those filters is FILTER_VALIDATE_IP. Answer 4 would work
in some cases, but it’s not correct, as it would take wrong IPs like 999.999.999.999 as correct.
The FILTER_VALIDATE_IP detects wrong IPs and also works with IPv6:
10. Validation 153

// validate_ip.php
// return 1.1.1.1
filter_var('1.1.1.1', FILTER_VALIDATE_IP);

// return false
filter_var('1.1.1.256', FILTER_VALIDATE_IP));

// return 2001:0db8:0000:0000:0000:ff00:0042:8329
filter_var('2001:0db8:0000:0000:0000:ff00:0042:8329', FILTER_VALIDATE_IP);

10. Which of the following constraints could be used to validate that a given string does not
contain words from a blacklist?
Answers 1, 3 and 4 are correct. This is probably a good use case to create a custom constraint, but
Regex and Callback can be used as well. Choice only checks that the value is included in the array
of valid values.
The following three examples show how you could check that the words “one” and “two” are not in
the title, by using the approaches from valid responses. Please, don’t use this code in production as
it lacks from some needed features (such as marking the word someone as invalid), it’s only goal is
to explain how the different approaches would work:
Regex constraint:

// Model/Book.php
use Symfony\Component\Validator\Constraints as Assert;

class Book
{
/**
* @Assert\Regex(pattern="/one/", match=false)
* @Assert\Regex(pattern="/two/", match=false)
*/
public $title;
}

Callback constraint:
10. Validation 154

// Model/Book.php
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ExecutionContextInterface;

/**
* @Assert\Callback(methods={"isForbiddenWordUsed"})
*/
class Book
{

public $title;

public function isForbiddenWordUsed(ExecutionContextInterface $context)


{
$list = ['one', 'two'];

foreach ($list as $word) {


if (false !== strpos($this->title, $word)) {
$context->addViolation($word . ' is forbidden');
}
}
}
}

Custom constraint:

// Validator/Constraints/ForbiddenWords.php
use Symfony\Component\Validator\Constraint;

/**
* @Annotation
*/
class ForbiddenWords extends Constraint
{
public $message = '%word% is forbidden';
}
10. Validation 155

// Validator/Constraints/ForbiddenWordsValidator.php
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class ForbiddenWordsValidator extends ConstraintValidator


{
public function validate($value, Constraint $constraint)
{
$list = ['one', 'two'];

foreach ($list as $word) {


if (false !== strpos($value, $word)) {
$this->context->addViolation(
$constraint->message,
['%word%' => $value]
);
}
}
}
}

// Model/Book.php
use AppBundle\Validator\Constraints\ForbiddenWords;

class Book
{
/**
* @ForbiddenWords
*/
public $title;
}

11. Given the following constraints, how many violations will return the validator if numPages
contains the string one hundred and title is empty?
Answer 2 is correct. Group sequences are useful when you want to validate some values only if
other values are valid. In the example, the value of numPages will be validated only if title is not
blank.
10. Validation 156

12. By default, error messages from validation constraints in forms are read from the
validators catalog. How can you configure it to be read from the custom catalog instead?

Answer 1 is correct. The catalog for validation constraints translations can be changed setting
the option framework.validation.translation_domain to custom. By default, this option is set
to validators.

Takeaways
• Validator component
– The Symfony Validator component is based on the JSR303 Bean Validation specification.
– Constraints can be defined using XML, YAML, PHP or annotations. Annotations can be
disabled.
– Classes, properties and public methods starting with get or is can contain validation
constraints.
– The method Symfony\Component\Validator\Validator::validate always returns a
ConstraintViolationList object containing a list of violations (empty list if there are
no violations).
– ConstraintViolationList can be printed out for debugging purposes as it implements
the __toString method.
• Validation scopes
– Classes, properties (even private or protected) and public methods starting with get or
is can contain validation constraints.
• Validation groups
– Validation groups allow to validate an object or a value against a subset of constraints.
11. Dependency Injection
Exam goals
11.1. The Service container
11.2. Global configuration parameters
11.3. Symfony2 services
11.4. Register new services
11.5. Tags
11.6. Semantic configuration

Questions
1. What command can be used to list all registered services?

1. container:list
2. container:debug
3. container:export
4. container:extract

2. For efficiency, the DIC (Dependency Injection Container) is dumped as a PHP class in
the cache directoy, with the name {rootDir}{environment}[Debug]ProjectContainer. So, could
you find a file called appDevProjectContainer.php in the /app/cache/dev directory?

1. Yes, by modifying the web/app_dev.php front controller: new AppKernel('dev', false)


2. Yes, by executing a command with --env=dev --no-debug
3. Yes, by setting the framework.container option to “%kernel.root_dir% %kernel.environment%
ProjectContainer”
4. No

3. In Symfony, all registered services must extend from the abstract class Symfony\Component\
DependencyInjection\AbstractService.

1. Yes
2. No
11. Dependency Injection 158

4. Which are the two recommended ways to inject dependencies into services?

1. Construction injection
2. Setter injection
3. Getter injection
4. Property injection

5. If there are two event listeners registered for the same event, which one will be executed
first?

1. The one registered first


2. The one with less dependencies
3. The one with the highest priority
4. The one with the lowest priority

6. How can you inject the exporter.format parameter into a service using XML?

1. <argument type="parameter">exporter.format</argument>
2. <argument id="parameter.exporter.format" />
3. <argument type="service">%exporter.format%</argument>
4. <argument>%exporter.format%</argument>

7. What will be the value of the exporter.format parameter defined in the following way?

<!-- services.xml -->


<parameters>
<parameter key="exporter.format">
json
</parameter>
</parameters>

1. json
2. \n json \n
3. \njson\n
4. Empty value

8. How does the TwigBundle bundle knows that a given service must be registered as a Twig
extension?
11. Dependency Injection 159

1. By convention
2. Parent class
3. Tags
4. Scope

9. By default, registered services are private.

1. True
2. False

10. Which of the following sentences are true about synthetic services?

1. They are injected into the container instead of being created by the container
2. The request and kernel services are synthetic
3. Third-party services cannot be registered as synthetic
4. They cannot be injected into another services

11. Does it make sense to have event listeners with negative priorities?

1. Yes
2. No

12. Which of the following advantages are true when exposing semantic configuration?

1. It’s easier than normal service configuration but less flexible


2. It allows to have a configuration hierarchy
3. It is possible to validate configuration values against an XML Schema file
4. The container:reference command shows the configuration reference

13. Even if it’s not a good practice, but how can you inject the whole container into a service?

1. Injecting the service_container service


2. Implementing the Symfony\Component\DependencyInjection\ContainerAwareInterface in-
terface
3. Configuring the service as synthetic
4. The container cannot be injected in other services due to circular references

14. Which of the following steps are required to define a new tag to do something special with
tagged services?
11. Dependency Injection 160

1. Define the new tag in the DependencyInjection\Tag directory of the bundle


2. Add a compiler pass
3. Create a new event listener for the kernel.container_tag event
4. Tagged services must be abstract

15. To add a compiler pass, is it enough to place it in the DependencyInjection/Compiler di-


rectory of the bundle, and implement the Symfony\Component\DependencyInjection\Compiler\
CompilerPassInterface interface?

1. Yes, compiler passes are loaded by convention


2. No, they must be added as services with the container.compiler_pass tag
3. No, they must be registered in the container with the addCompilerPass() method
4. No, they must be added to the framework.container.compiler list in the configuration file

Answers
1. What command can be used to list all registered services?
Answer 2 is correct. The container:debug command lists registered services (by default only public
services, but you can also view private services with --show-private) and provides information
about a specific service. It can be used to list parameters and tagged services.
For example, to get information about the validator service:

$ php app/console container:debug validator


[container] Information for service validator

Service Id validator
Class Symfony\Component\Validator\Validator
Tags -
Scope container
Public yes
Synthetic no
Required File -

Or to get all registered data collectors (collect data during the request lifecycle to be shown in the
profiler):
11. Dependency Injection 161

$ php app/console container:debug --tag=data_collector


[container] Public services with tag data_collector
Service Id id priority Scope Class Name
data_collector.request request 255 container ...\RequestDataCollector
data_collector.router router 255 container ...\RouterDataCollector

2. For efficiency, the DIC (Dependency Injection Container) is dumped as a PHP class in
the cache directoy, with the name {rootDir}{environment}[Debug]ProjectContainer. So, could
you find a file called appDevProjectContainer.php in the /app/cache/dev directory?
Answers 1 and 2 are correct. You can have 1 dumped container when the debug mode is enabled
and another one when is disabled. As by default, the debug mode is enabled in the dev environment,
you need to disable it in order to get that file. Executing commands with --no-debug will disable
it, forcing the kernel to dump the container if it doesn’t exist yet. For the web/app_dev.php front
controller, as it is hardcoded, it must be changed in the file itself.

3. In Symfony, all registered services must extend from the abstract class Symfony\Component\
DependencyInjection\AbstractService.

Answer 2 is correct. Services are just PHP classes that provide some functionality. The can extend
from other classes or not, and don’t need to extend from any special class to be considered “services”.

4. Which are the two recommended ways to inject dependencies into services?
Answers 1 and 2 are correct. Setter injection is used when you have optional dependencies, while
construction injection can be used for required and optional dependencies. For example, in the
following service, Mailer and Logger are optional and Exporter is required:

// Transformer.php
class Transformer
{
protected $exporter;
protected $mailer;
protected $logger;

public function __construct(


ExporterInterface $exporter,
MailerInterface $mailer = null
) {
11. Dependency Injection 162

// ...
}

public function setLogger(LoggerInterface $logger)


{
// ...
}

Property injection is also an option, as it works similar as the setter injection, but it has a few
disadvantages such as lack of control or being unable to use type hinting, so it is not recommended.

5. If there are two event listeners registered for the same event, which one will be executed
first?
Answer 3 is correct. By default, event listeners have a priority value of 0, but it can be increased
so it’s executed first.
For example, given the following two event listeners, A will be executed first:

<!-- src/AppBundle/Resources/config/services.xml -->


<service id="book.listener.a" class="...">
<tag name="kernel.event_listener"
event="kernel.request"
method="onKernelRequest"
priority="10" />
</service>

<service id="book.listener.b" class="...">


<tag name="kernel.event_listener"
event="kernel.request"
method="onKernelRequest"
priority="5" />
</service>

6. How can you inject the exporter.format parameter into a service using XML?
Answer 4 is correct. Parameters are injected into other services wrapping them between % characters.
11. Dependency Injection 163

7. What will be the value of the exporter.format parameter defined in the following way?
Answer 2 is correct. Parameter values defined in XML files are not trimmed, so you must be very
careful, especially when using parameters to define class names (not recommended anymore) or
constants.

8. How does the TwigBundle bundle knows that a given service must be registered as a Twig
extension?
Answer 3 is correct. When you tag a service, other bundles can get all services with a specific tag, to
handle them in a different way. For example, the TwigBundle bundle takes care of registering Twig
extension, and to do so, looks for services with the tag twig.extension.
To add the twig.extension tag to a service, the tags collection is used:

# app/config/services.yml
services:
foo.twig.extension:
class: AppBundle\Twig\Extension\MetaExtension
tags:
- { name: twig.extension }

Then, the TwigBundle bundle, through a compiler pass, gets all services tagged with the twig.extension
tag and register them as Twig extensions:

// TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;

class TwigEnvironmentPass implements CompilerPassInterface


{
public function process(ContainerBuilder $container)
{
// ...

$definition = $container->getDefinition('twig');
11. Dependency Injection 164

// ...

$serviceIds = $container->findTaggedServiceIds('twig.extension');
foreach ($serviceIds as $id => $attributes) {
$definition->addMethodCall('addExtension', [new Reference($id)]);
}

// ...
}
}

9. By default, registered services are private.


Answer 2 is correct. By default, services are public. That means that you can access to them from
your code. Unlike public services, private services are not meant to be accessed from your code
but injected into other services.

10. Which of the following sentences are true about synthetic services?
Answers 1 and 2 are correct. Synthetic services are services that are injected into the container
instead of being created by the container. The request and kernel services are two examples of
synthetic services, as they are not created by the container itself, but injected.

11. Does it make sense to have event listeners with negative priorities?
Answer 1 is correct. It’s possible to assign negative priorities and can be useful in some scenarios.
For example, the ExceptionListener event subscriber from the HttpKernel, which converts an
exception into a proper Response object, is registered with the value -128 to give the opportunity to
other listeners to create a response:
11. Dependency Injection 165

// HttpKernel/EventListener/ExceptionListener.php
namespace Symfony\Component\HttpKernel\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ExceptionListener implements EventSubscriberInterface


{
// ...

public static function getSubscribedEvents()


{
return [
KernelEvents::EXCEPTION => array('onKernelException', -128)
];
}
}

12. Which of the following advantages are true when exposing semantic configuration?
Answers 2 and 3 are correct. When exposing semantic configuration for a bundle, you have much
more flexibility, but it’s more complicated, so it’s only recommended for bundles that are going to
be shared. Answer 4 is not correct, as the right command is config:dump-reference.

13. Even if it’s not a good practice, but how can you inject the whole container into a service?
Answer 1 is correct. The service_container service can be injected like any other service. As the
question states, it is not a good practice, but can be necessary sometimes.
The Symfony\Component\DependencyInjection\ContainerAwareInterface interface exists, and it
should be implemented by classes that depends on the container, but that doesn’t mean that the
container will be injected automatically just by implementing the interface. The FramworkBundle
bundle injects the container automatically in commands and controllers implementing that interface,
but not in any service. The kernel does the same with bundles.
Controllers:
11. Dependency Injection 166

// FrameworkBundle/Controller/ControllerResolver.php
namespace Symfony\Bundle\FrameworkBundle\Controller;

use Symfony\Component\DependencyInjection\ContainerAwareInterface;

class ControllerResolver
{

protected function createController($controller)


{
// ...

$controller = new $class();


if ($controller instanceof ContainerAwareInterface) {
$controller->setContainer($this->container);
}

// ...
}
}

Commands:

// FrameworkBundle/Console/Application.php
namespace Symfony\Bundle\FrameworkBundle\Console;

use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Application extends BaseApplication


{
// ...

public function doRun(InputInterface $input, OutputInterface $output)


{
// ...

$container = $this->kernel->getContainer();

foreach ($this->all() as $command) {


11. Dependency Injection 167

if ($command instanceof ContainerAwareInterface) {


$command->setContainer($container);
}
}
// ...
}
}

And finally the bundles. There is no check as all bundles extend from Symfony\Component\HttpKernel\Bundle,
which in turn extends from Symfony\Component\DependencyInjection\ContainerAware. This class
implements the Symfony\Component\DependencyInjection\ContainerAwareInterface interface.

// HttpKernel/Kernel.php
namespace Symfony\Component\HttpKernel;

abstract class Kernel implements KernelInterface, TerminableInterface


{
// ...

public function boot()


{
// ...

foreach ($this->getBundles() as $bundle) {


$bundle->setContainer($this->container);
$bundle->boot();
}

// ...
}

// ...
}

14. Which of the following steps are required to define a new tag to do something special with
tagged services?
Only answer 2 is correct. A compiler pass can be used to get all tagged services and do some
actions with them. For example, the TwigBundle bundle defines some compiler passes, one of them
(TwigEnvironmentPass) to register extensions:
11. Dependency Injection 168

// TwigBundle/DependencyInjection/Compiler/TwigEnvironmentPass.php
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;

class TwigEnvironmentPass implements CompilerPassInterface


{
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition('twig')) {
return;
}

$definition = $container->getDefinition('twig');

// ...
foreach ($container->findTaggedServiceIds('twig.extension') as $id => $a\
ttributes) {
$definition->addMethodCall('addExtension', array(new Reference($id))\
);
}

// ...
}
}

In the compiler pass, first it gets all services tagged with the tag twig.extension, and for each of
them, executes the addExtension() method of the twig service.

15. To add a compiler pass, is it enough to place it in the DependencyInjection/Compiler di-


rectory of the bundle, and implement the Symfony\Component\DependencyInjection\Compiler\
CompilerPassInterface interface?

Answer 3 is correct, it is true that compiler passes must extend from the Symfony\Component\DependencyInjection\
interface, but it is not necessary to place them in the DependencyInjection/Compiler directory of
the bundle, as they are registered manually using the addCompilerPass() method, usually from the
bundle class.
11. Dependency Injection 169

Takeaways
• Dependency Injection
– The Dependency Injection pattern is a software design pattern where the dependencies of
a class are injected instead of being created. That is, following the Dependency Inversion
principle.
– In large projects, injecting dependencies manually can be tedious and error prone. A
Dependency Injection Container (DIC) does it for us.
– Types of injection
* Construction injection: dependencies are injected when creating the object. It can
be used for required and optional dependencies.
* Setter injection: dependencies are injected through setters, and they are always
optional.
* Property injection: dependencies are injected directly through public properties.
This type of injection is not recommended as there is no control at all of what is
being injected.
• The Service Container
– Types of services
* Public: by default services are public.
* Private: private services are like public ones, but they cannot be used directly, only
injected into other services.
* Synthetic: Synthetic services are injected directly into the container instead of
being created by the container. kernel and request are two examples of synthetic
services.
• Tags
– Compiler passes can be used to get all services tagged with a specific tag and perform
some actions.
• Semantic configuration
– Semantic configuration is only recommended for bundles that are going to be shared.
– It is more complex but much more flexible.
– Compiler passes are registered in the bundle by adding them into the build method of
the bundle definition class.
12. Security
Exam goals
12.1. Authentication
12.2. Authorization
12.3. Configuration
12.4. Providers
12.5. Firewalls
12.6. Users
12.7. Encoders
12.8. Roles
12.9. Access Control Rules

Questions
1. By default, how does Symfony know that the file app/config/security.yml contains the
security system configuration?

1. By convention, Symfony automatically looks for a file called security.{yml|xml|php} in that


directory
2. It is loaded from the main configuration file
3. It is loaded directly from the AppKernel class
4. The option framework.security.file contains the relative path of the file

2. What is the purpose of the following firewall defined in the security.yml file?

# app/config/security.yml
security:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false

1. Disable the security system for the dev environment.


2. Disable Symfony development tools for the dev environment.
12. Security 171

3. Avoid Symfony development tools being blocked by the security system.


4. Enable the profiler to be used in functional tests.

3. When using HTTP basic, how does the server starts the authentication process?

1. Rendering a login form with the fields _user and _password.


2. Sending the WWW-Authenticate HTTP header with the HTTP 401 Not Authorized status code.
3. Sending the status code HTTP 418 Authentication Required.
4. Redirecting the request to the port 443.

4. Which of the following authentication methods are not included by default in the Symfony
security component?

1. HTTP basic.
2. Form login
3. OAuth.
4. OpenID.

5. Is it possible to secure services using security.context?

1. Yes.
2. Yes, but only when the service does not make use of the Request object.
3. No

6. How can you get the User object of the current user from a controller extending from the
base controller?

1. $this->user
2. $this->get('security.context')->getToken()->getUser()
3. $this->getSecurity()->getUser()
4. $this->getUser()

7. What is the length of the string generated by the password_hash() function when using the
PASSWORD_BCRYPT algorithm?

1. 32
2. 40
3. 42
4. 60

8. Given the following security.yml file, how the password of the user user will be stored in
the database?
12. Security 172

# app/config/security.yml
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext

providers:
in_memory:
memory:
users:
user: { password: 1234, roles: [ 'ROLE_USER' ] }

1. In plain text
2. Hashed with md5
3. Hashed with bcrypt
4. None of the above are correct

9. Which of the following sentences are true when using a password encoder based on
algorithms like md5, sha1 or bcrypt?

1. Passwords are stored in the database encoded


2. You can change the hashing algorithm at any time and authentication should keep working
as long as salts are rehashed
3. Authentication works by comparing both hashed passwords, the one stored in the database
and one provided by the user
4. Given the same password, salt and algorithm, you will get a different hash string in each
execution

10. What PHP function can you use to know if the joaat algorithm is available to encode
passwords?

1. hash()
2. hash_algos()
3. is_hash()
4. hash_joaat()

11. Given the following role hierarchy, does the isGranted() method of the security.context
service return true for both ROLE_ADMIN and ROLE_USER roles, if the user has the ROLE_ADMIN
role?
12. Security 173

# app/config/security.yml
security:
role_hierarchy:
ROLE_ADMIN: ROLE_USER

1. Yes
2. No

12. Would a user without the ROLE_ADMIN role have access to /profile/admin, assuming the
following access_control expressions?

# app/config/security.yml
security:
access_control:
- { path: /admin, roles: ROLE_ADMIN }

1. Yes
2. No

13. Is it recommended to encode passwords in the database when using HTTPS?

1. Yes
2. No, as they are already encrypted
3. No, as there is no way to get the password entered by the user
4. None of the above are correct

14. Do you see any potential problem if a registration form returns the following error? “The
password you entered cannot be longer than 60 characters”

1. Yes, they might be using a weak encryption algorithm


2. Yes, they might be storing the passwords in plain text
3. Yes, they might be using an old version of PHP
4. No

15. Do all logged in users have the IS_AUTHENTICATED_REMEMBERED “role”?

1. No, only those logged in because of a “remember me cookie”


2. No, only those logged in because of login form
3. No, only anonymous users have this
12. Security 174

4. Yes

16. If there are 5 voters to decide whether a user has granted access to an action and the strategy
is afirmative, how many voters must return VoterInterface::ACCESS_GRANTED to grant access?

1. 0
2. 1 or more
3. 3 or more
4. 5

17. Which of the following sentences about ACLs are true?

1. The init:acl command generates ACL tables in the database


2. They are simpler to use than voters
3. The order in which entries are checked is important
4. ACEs are checked explicitly in the controller

Answers
1. By default, how does Symfony know that the file app/config/security.yml contains the
security system configuration?
Answer 2 is correct. By default, the main configuration file (app/config/config.yml) imports the
file security.yml. This is just a way to separate all the configuration related to security in a different
file, but it could be added directly to the file config.yml.
Other answers are not correct. It is true that you can choose what configuration to load by overriding
the method registerContainerConfiguration in the class AppKernel, but by default, only the
main configuration file is loaded and other files are imported from it, such as parameters.yml and
security.yml.

2. What is the purpose of the following firewall defined in the security.yml file?
Answer 3 is correct. The dev firewall makes sure that Symfony development tools are not blocked by
the security system. By default, the dev firewall is defined as the first one, so URLs like /_profiler
(profiler) or /_wdt (toolbar) will be intercepted and the security system will be disabled for them.
Answer 4 is totally wrong, as functional tests don’t access to the profiler via URL. Regarding answers
12. Security 175

1 and 2, this has nothing to do with environments. The pattern option is compared against URLs,
not routes or environments.

3. When using HTTP basic, how does the server starts the authentication process?
Answer 2 is correct. When the server wants the user agent (usually a browser) to authenticate,
it sends the WWW-Authenticate HTTP header in a response with the HTTP 401 Not Authorized
status code. The user agent then sends a new request with an Authorization header containing
the username and password encoded with a variant of Base64. Therefore, it is typically used over
HTTPS as credentials are not encrypted.

4. Which of the following authentication methods are not included by default in the Symfony
security component?
Answers 3 and 4 are correct. The Symfony component ships with some authentication methods such
as HTTP basic, form login or x509, but OAuth and OpenID are not available out of the bow.

5. Is it possible to secure services using security.context?


Answer 1 is correct. You can secure any service by injecting the security.context service and using
it the same way as within controllers. For example, the following service to send emails checks that
the current user contains the role ROLE_ADMIN:

// Mailer.php
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class Mailer
{
protected $securityContext;

public function __construct(SecurityContextInterface $securityContext)


{
$this->securityContext = $securityContext;
}

public function sendNewsletter()


{
if (false === $this->securityContext->isGranted('ROLE_ADMIN')) {
12. Security 176

throw new AccessDeniedException();


}

// ...
}

6. How can you get the User object of the current user from a controller extending from the
base controller?
Answers 2 and 4 are correct. The base controller provides the getUser() method, which returns the
User object or null.

7. What is the length of the string generated by the password_hash() function when using the
PASSWORD_BCRYPT algorithm?
Answer 4 is correct. The result will always be a 60 character string. The following example shows
how to encode a password using the BCrypt algorithm:

// bcrypt_hash.php
$encoded = password_hash('123456', PASSWORD_BCRYPT);

// 60
var_dump(strlen($encoded));

// hashed password
var_dump($encoded);

Keep in mind that if no salt is provided, the function generates a cryptographically safe salt
randomly, so two consecutive calls will get a different encoded password:

// bcrypt_hash.php
// $2y$10$Un0YdQQM4ipn4PBsr0ztpuHOT7pSajiYcJDKkp6fXn4h/Rhu9w2fe
var_dump(password_hash('123456', PASSWORD_BCRYPT));

// $2y$10$lpaDRBu8wZPTdkWduo06oO/wPE8tv9wCMdXRm.adyGMa9Hs4xXSRe
var_dump(password_hash('123456', PASSWORD_BCRYPT));

The BCryptPasswordEncoder, which ships with the Security component, makes use of this function
too to encode passwords:
12. Security 177

// Security/Core/Encoder/BCryptPasswordEncoder.php
class BCryptPasswordEncoder extends BasePasswordEncoder
{
// ...

public function encodePassword($raw, $salt)


{
// ...

$options = array('cost' => $this->cost);

if ($salt) {
$options['salt'] = $salt;
}

return password_hash($raw, PASSWORD_BCRYPT, $options);


}

// ...
}

8. Given the following security.yml file, how the password of the user user will be stored in
the database?
Answer 4 is correct. Users loaded using the in_memory provider are not stored in the database, but
in memory. They are simply stored in an array:

// Security/Core/User/InMemoryUserProvider.php
namespace Symfony\Component\Security\Core\User;

use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class InMemoryUserProvider implements UserProviderInterface


{
private $users;

// ...

public function createUser(UserInterface $user)


{
12. Security 178

// ...

$this->users[strtolower($user->getUsername())] = $user;
}

// ...
}

9. Which of the following sentences are true when using a password encoder based on
algorithms like md5, sha1 or bcrypt?
Answers 1 and 3 are correct. The authentication mechanism when using encoded passwords works
the following way:

1. A new user is created in the database and the password is encoded using the chosen algorithm.
2. The user wants to log in, so it enters his/her credentials.
3. The provided password is encoded using the same algorithm.
4. Both encoded passwords are compared, and if they are the same, authentication succeeds.

Even if you use the plaintext encoder, steps are exactly the same, but the encoder just returns the
password in clear.
Answer 2 is not correct, as if you change the algorithm and the user password has been encoded
with a different algorithm, they won’t match. So, if the algorithm is changed, all passwords need to
be rehashed with the new algorithm. Answer 4 is also wrong because given the same input, one-way
hash functions must return the same string, otherwise authentication would be impossible.

10. What PHP function can you use to know if the joaat algorithm is available to encode
passwords?
Answer 2 is correct. The hash_algos() function returns an array with all supported hashing
algorithms. Any supported algorithm, as well as a few others like bcrypt, can be used as encoders
in Symfony:

# app/config/security.yml
security:
encoders:
AppBundle\Entity\User: joaat

Support for the joaat algorithm was introduced in PHP 5.4.0, so you should get it in the list since
12. Security 179

that version. The name comes from Jenkins’s one-at-a-time, which is one of the functions included
in the Jenkins hash function¹⁷ set.

11. Given the following role hierarchy, does the isGranted() method of the security.context
service return true for both ROLE_ADMIN and ROLE_USER roles, if the user has the ROLE_ADMIN
role?
Answer 1 is correct. It returns true for both roles, as the hierarchy defines that a user with the
ROLE_ADMIN role also contains ROLE_USER.

12. Would a user without the ROLE_ADMIN role have access to /profile/admin, assuming the
following access_control expressions?
Answer 2 is correct. As expressions used in the path field are treated as regular expressions, /admin
matches any path containing /admin. To match only paths that start by /admin, the ˆ character
should be used:

// access_control_regexp.php
// 1
var_dump(preg_match('/\/admin/', '/profile/admin'));

// 0
var_dump(preg_match('/^\/admin/', '/profile/admin'));

Under the hood, the access_control expressions are compiled down and stored in cache:

// app/cache/dev/appDevDebugProjectContainer.php
use Symfony\Component\DependencyInjection\Container;

class appDevDebugProjectContainer extends Container


{

// ...

protected function getSecurity_Firewall_Map_Context_SecuredAreaService()


{
// ...

¹⁷http://en.wikipedia.org/wiki/Jenkins_hash_function
12. Security 180

$g = new \Symfony\Component\HttpFoundation\RequestMatcher('/admin/');

$h = new \Symfony\Component\Security\Http\AccessMap();
$h->add($g, array(0 => 'ROLE_ADMIN'), NULL);

// ...
}

// ...
}

As you can see, the Symfony\Component\Security\Http\AccessMap object maps Symfony\Component\HttpFoundati


objects with their required roles, so once a Request arrives, it can be matched using the Request-
Matcher and then get the required roles with the AccessMap. The following script shows a simple
example:

// security.php
use Symfony\Component\HttpFoundation\RequestMatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\AccessMap;

$request = Request::create('/profile/admin');

$matcher = new RequestMatcher('/admin');

// true
var_dump($matcher->matches($request));

$map = new AccessMap();


$map->add($matcher, ['ROLE_ADMIN']);

// [0] =>
// 0 => [ ROLE_ADMIN ]
// 1 => NULL
var_dump($map->getPatterns($request));

13. Is it recommended to encode passwords in the database when using HTTPS?


Answer 1 is correct. When using HTTPS, requests and responses are encrypted in the transmitter
and decrypted in the receiver, but this process is transparent for your application. For example, in
12. Security 181

a login form over HTTPS, credentials entered by the user will be encrypted to travel through the
network, but when you get them from Symfony, they will be in clear as the webserver (or a third-
party software like OpenSSL) has already decrypted them.

14. Do you see any potential problem if a registration form returns the following error? “The
password you entered cannot be longer than 60 characters”
Answer 2 is correct. It is highly recommended to store passwords using a strong hashing algorithm
like bcrypt, and one of the main features of hash algorithms is that regardless of the length of the
input string, they always return a fixed-length hash. For example, md5 always returns a 32-character
hexadecimal number, no matter how big or small the input string is. That means that if you are
using a hashing algorithm, you don’t have to worry about the length of the clear text password.
This is not always true for huge passwords, as there can be other security issues due to the time
to encode it. The time it takes to compute the hash increases significantly with the length of the
password. That’s why Symfony recommends to validate that the password is 4096 characters or
fewer.

15. Do all logged in users have the IS_AUTHENTICATED_REMEMBERED “role”?


Answer 4 is correct. All logged in users have this “role” (technically not a role, but acts like one),
even if they were not logged in because of a “remember me cookie”. IS_AUTHENTICATED_FULLY is
very similar to IS_AUTHENTICATED_REMEMBERED, but definitely stronger as users that were logged
in because of “remember me cookie” won’t have it. So, even though it may seem illogical, IS_-
AUTHENTICATED_REMEMBERED can be used to check if a user is logged in taking into account as well
all those logged using the “remember me “ cookie.

16. If there are 5 voters to decide whether a user has granted access to an action and the strategy
is afirmative, how many voters must return VoterInterface::ACCESS_GRANTED to grant access?
Answer 2 is correct. When using the affirmative strategy (default one), access is granted as soon
as one of the voters return VoterInterface::ACCESS_GRANTED.
There are other two strategies: consensus and unanimous. When consensus is used, access is granted
if there are more voters granting access than denying it. With unanimous, all voters must grant
access.

17. Which of the following sentences about ACLs are true?


Answers 1 and 3 are correct. The init:acl command generates the database structure needed to
12. Security 182

store ACEs (Access Control Entry), and the order is definitely important. It is recommended to place
more specific entries at the beginning.
Answer 2 is not correct as voters are usually easier to use. Answer 4 is also not valid as they are not
checked explicitly, but through the isGranted() method of the security context.

Takeaways
• Authentication
– The authentication system accepts the user credentials and checks that the user is who
claims to be.
• Authorization
– The authorization system decides whether the authenticated user has access to a given
resource, based on roles, ACL or any other information.
– The access_control configuration setting maps request patterns (path, host, IP or
method) to required roles.
• Password encoding
– By default, algorithms returned by hash_algos() are supported, plus additional encoders
like bcrypt.
– A password entered by a user is encoded and then compared against all the encoded
password in the users database.
– The password_hash() function creates a new password hash using a strong one-way
hashing algorithm. The PASSWORD_DEFAULT contains the strongest supported hashing
algorithm, bcrypt as of PHP 5.5 and 5.6.
– Passwords can be dynamically encoded using the security.encoder_factory service.
• Roles
– Each user contain a set of roles. While it can be empty, a common convention is to add
the ROLE_USER role to every user.
– To be handled by Symfony, they must start by ROLE_.
– With role hierarchy, users with a given role will also have the roles of its ancestors.
13. HTTP Cache
Exam goals
13.1. Cache types (browser, proxies and reverse proxies)
13.2. Expiration (Expires, Cache-control)
13.3. Validation (ETag, Last-Modified)
13.4. Client cache
13.5. Server cache
13.6. Edge Side Includes

Questions
1. What technology can be used to cache page fragments with different expiration times?

1. WebGL
2. ESI
3. HATEOAS
4. mod_cache Apache module

2. How browsers handle the following XML tags?

<esi:include src="http://cache.example.com/resources/1" />

1. For each <esi:include> tag, the browser sends a request to the server to retrieve the fragment
2. When the server sends the HTML file, it includes the fragments in X-ESI-Fragment headers,
so the browser can replace each tag for its contents
3. The browser sends an XMLHttpRequest request for each fragment, and sets a timer to update
them at the given intervals
4. Browsers don’t handle ESI tags

3. Which of the following ESI tags are implemented in Symfony?

1. attempt
2. except
13. HTTP Cache 184

3. include
4. inline

4. What are the benefits of HTTP caching?

1. Reduce bandwidth usage


2. Reduce disk space on the server
3. Reduce the size of the HTTP responses
4. Reduce the CPU and memory usage on the server

5. When not using a real reverse proxy cache like Varnish, how do you enable the one written
in PHP that ships with Symfony?

1. Executing the command php app/cache server:run --enable-esi


2. Using the AppCache class in the front controller
3. It is enabled by default
4. Symfony doesn’t provide any built-in reverse proxy cache

6. If your ISP has an invisible reverse proxy between your home network and the Internet,
should cache HTTP responses with Cache-Control: private?

1. Yes
2. No

7. If framework.esi is set to true, what is the value of the Cache-Control header in the HTTP
response generated by the following controller?

// AppBundle/Controllers/BookController.php
use Symfony\Component\HttpFoundation\Response;

class BookController
{
public function indexAction()
{
return new Response('hello');
}
}

1. no-cache
2. private, max-age=0, must-revalidate
13. HTTP Cache 185

3. max-age=3600, must-revalidate
4. The is no Cache-Control header in that response

8. What two caching models define the HTTP specification?

1. Expiration model
2. Validation model
3. Confirmation model
4. Invalidation model

9. In terms of number of requests, is the Expires header more efficient than the ETag one?

1. Yes
2. No

10. By default, when using the built-in Symfony reverse proxy cache…

1. the _method parameter is ignored


2. the X-Symfony-Cache HTTP header is added to the response when debug is true
3. fragments are stored in a sqlite database
4. communication between the Symfony app and the proxy is done asynchronously

11. One of the following HTTP headers is not defined in the HTTP specification, which one is
it?

1. Cache-Control
2. Expires
3. Last-Modified
4. Last-Invalidated

12. Given the following controller, how many requests will the browser make if the URL is
entered twice?
13. HTTP Cache 186

// AppBundle/Controllers/TestController.php
use Symfony\Component\HttpFoundation\Response;

class TestController
{
public function testAction()
{
$response = new Response('hello');
$response->setSharedMaxAge(600);

return $response;
}
}

1. 0
2. 1
3. 2

13. What is the value of the Expires HTTP response header if current time is Sun, 24 May 2015
13:36:29 GMT?

// AppBundle/Controllers/TestController.php
use Symfony\Component\HttpFoundation\Response;

class TestController
{
public function testAction()
{
$date = new \DateTime('now', new \DateTimeZone('UTC'));
$date->add(new \DateInterval('P1D'));

$response = new Response('test');


$response->setExpires($date);

return $response;
}
}

1. Mon, 25 May 2015 13:36:29 UTC


2. Mon, 25 May 2015 13:36:29 GMT
3. Thu, 01 Jan 1970 00:00:00 +0000
13. HTTP Cache 187

4. Sun, 24 May 2015 13:36:29 UTC

14. What is true when using the ETag header for caching?

1. The client must send the If-None-Match header with the ETag of the cached resource
2. Generated ETag tokens should be random
3. Clients generate ETag tokens based on the body and headers of the HTTP response
4. By default, the Response::setEtag() method uses the md5() function to generate ETag tokens

15. What is the HTTP status code returned by the following controller?

// AppBundle/Controllers/TestController.php
use Symfony\Component\HttpFoundation\Response;

class TestController
{
public function testAction()
{
$response = new Response('test');
$response->setNotModified();

return $response;
}
}

1. 302
2. 304
3. 415
4. 500

16. Which of the following HTTP cache headers are affected if the webserver’s clock is not on
time?

1. Expires
2. Cache-Control
3. ETag
4. Last-Modified

17. Are HTTPS pages cached by shared proxies?


13. HTTP Cache 188

1. Yes, they are handled like HTTP pages, so as long as the correct headers are set, caching will
work exactly the same
2. Yes, but only if ssl-cache is present in the Cache-Control
3. Yes, but only if the server has a valid SSL certificate
4. No, never

18. Why caching pages with a CSRF token is problematic?

1. Because CSRF tokens are different for each user


2. Because forms with CSRF tokens add by default the Cache: false header
3. Because CSRF validation doesn’t work with ESI tags
4. None of the above are correct

Answers
1. What technology can be used to cache page fragments with different expiration times?
Answer 2 is correct. Edge Side Includes is a markup language that allows to define fragments inside
your HTML code with different caching strategies. So it is possible to define different expiration
times for each fragment. For example, if a web page is cached for 1 hour, but contains two sections
that change more often, specific expiration times for those sections can be defined:

Figure 5. Different expiration times for each fragment


13. HTTP Cache 189

2. How browsers handle the following XML tags?


Answer 4 is correct. Browsers don’t understand ESI tags, as they are meant to be processed only by
reverse proxy cache software. Between the browser and the server must be a third element processing
these kind of tags, asking to the server if a fragment is not fresh anymore and sending to the browser
the complete HTML response.
The following diagrams show how ESI + HTTP caching works:

Figure 6. Two ESI fragments, none cached yet


13. HTTP Cache 190

Figure 7. Two ESI fragments, both cached and fresh


13. HTTP Cache 191

Figure 8. Two ESI fragments, one fresh and one stale

3. Which of the following ESI tags are implemented in Symfony?


Answer 3 is correct. Only the include tag is implemented in Symfony, as it is the only useful one
outside of Akamai context.

4. What are the benefits of HTTP caching?


Answers 1 and 4 are correct. HTTP caching can reduce bandwidth, CPU and memory usage, as there
will be less requests and/or some requests will require less processing. In combination with HTTP
compression, it can reduce the size of HTTP responses as well, but caching doesn’t reduce it per se.

5. When not using a real reverse proxy cache like Varnish, how do you enable the one written
in PHP that ships with Symfony?
Answer 2 is correct. The AppCache class (caching kernel) is the implementation of the reverse proxy,
and wraps the default kernel (AppKernel):
13. HTTP Cache 192

// web/app.php
//...

require_once __DIR__.'/../app/AppKernel.php';
require_once __DIR__.'/../app/AppCache.php';

$kernel = new AppKernel('prod', false);


$kernel->loadClassCache();

$kernel = new AppCache($kernel);

// ...

6. If your ISP has an invisible reverse proxy between your home network and the Internet,
should cache HTTP responses with Cache-Control: private?
Answer 2 is correct. By using private inside a Cache-Control header, shared cache servers won’t
cache the response as it is intended for a single user.

7. If framework.esi is set to true, what is the value of the Cache-Control header in the HTTP
response generated by the following controller?
Answer 1 is correct. Symfony automatically sets conservative Cache-Control values, based on other
caching headers:

• No cache header is defined (Cache-Control, Expires, ETag or Last-Modified): Cache-


Control: no-cache
• Other cache headers are used and Cache-Control is empty: private, must-revalidate
• Cache-Control is set but no public or private directives have been set: adds the private
directive automatically (except when s-maxage is set).

8. What two caching models define the HTTP specification?


Answers 1 and 2 are correct. The HTTP specification (RFC 2616) defines two caching models:

• Expiration model: specify how long a response should be considered fresh.


• Validation model: in each request, the client asks if the cached response is still fresh.
13. HTTP Cache 193

9. In terms of number of requests, is the Expires header more efficient than the ETag one?
Answer 1 is correct. With the Expires header, the HTTP response can be cached up to the
returned date/time, and the client won’t submit any request to the server while the resource is
fresh (expiration model). When using the ETag header, the client asks to the server every time if the
cached response is still fresh, so there are no savings in the number of requests.

10. By default, when using the built-in Symfony reverse proxy cache…
Answers 1 and 2 are correct. When using the built-in Symfony reverse proxy, the _method parameter
is ignored. To take into account, the Request::enableHttpMethodParameterOverride() must be
executed before creating the Request object from global variables:

// web/app.php
// ...
$kernel = new AppCache($kernel);

Request::enableHttpMethodParameterOverride();
$request = Request::createFromGlobals();
// ...

This method just sets the $httpMethodParameterOverride static variable to true, which is later
considered when getting the intended method.

11. One of the following HTTP headers is not defined in the HTTP specification, which one is
it?
Answer 4 is correct. The Last-Invalidated header doesn’t exist and it shouldn’t make sense.

12. Given the following controller, how many requests will the browser make if the URL is
entered twice?
Answer 3 is correct. The Request::setSharedMaxAge() sets the value of the directive s-maxage,
included in the Cache-Control header. Unline maxage, s-maxage is only taken into account by shared
caches, and as the browser cache is not a shared cache, it just ignores it.
13. HTTP Cache 194

13. What is the value of the Expires HTTP response header if current time is Sun, 24 May 2015
13:36:29 GMT?

Answer 2 is correct. The HTTP specification requires that the value of the Expires header is
formatted following the RFC 1123 format. The setExpires() method automatically formats the
date into the required format:

// HttpFoundation/Response.php
namespace Symfony\Component\HttpFoundation;

class Response
{

// ...

public function setExpires(\DateTime $date = null)


{
// ...

$date = clone $date;


$date->setTimezone(new \DateTimeZone('UTC'));
$this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT');

return $this;
}

// ...
}

As you can see, the output format must be always in the GMT timezone, and answer 2 is the only
answer in that timezone.

14. What is true when using the ETag header for caching?
Only answer 1 is correct. For each cached resource, the client asks to the server if it is still fresh
by sending the If-None-Match header, and if it’s still fresh, it returns an HTTP 304 Not Modified
response. Other answers are not correct: ETag tokens cannot be random as they would change in
each request and there wouldn’t be any cathing, clients don’t generate them, servers do, and the
Response::setEtag() doesn’t generate any tag, it’s a task that must be done outside.
13. HTTP Cache 195

15. What is the HTTP status code returned by the following controller?
Answer 2 is correct: 304 Not Modified. When using the validation caching model, this response
must be sent if the resource is the same as the cached one, based on ETag or modification time.

16. Which of the following HTTP cache headers are affected if the webserver’s clock is not on
time?
Answers 1 and 4 are correct. As Expires and Last-Modified work with dates, the clocks on the web
server and client must be synchronised to work as expected. Cache-Control header does not work
directly with dates, but there could also be problems if you use the current date/time to generate the
number of seconds that a resource is considered fresh.

Network Time Protocol


One way to make sure that your webserver clock is accurate is by using the Network Time
Protocol (NTP), so the clock is synchronised frequently.

17. Are HTTPS pages cached by shared proxies?


Answer 4 is correct. Shared proxies don’t cache HTTPS responses.

18. Why caching pages with a CSRF token is problematic?


Answer 1 is correct. As CSRF tokens are different for each user, if a reverse proxy caches pages
containing a form with a token, it will cache the token of the first user, so subsequent validations
will fail.

Takeaways
• Caching models
– Expiration model
* Responses include a header letting the browser know for how much time the
response will be considered fresh.
13. HTTP Cache 196

* Cache-Control and Expires headers are used.


* More efficient. As long as the response is considered fresh, no new requests are
made for the same resource.
* Less flexible. If the resource changes but it’s still cached and considered fresh, there
is no way to get the new version of the resource unless the URL changes.
– Validation model
* Responses also include a header that is used in consequent requests to determine if
the cached response is still fresh.
* ETag/If-None-Match and If-Modified-Since/Last-Modified headers are used.
* Less efficient. The number of requests is the same as when not using HTTP caching.
Anyway, if the resource is still fresh it saves bandwidth because an empty response
is sent in that case, and possibly CPU cycles on the server.
* More flexible. If the resource changes, the server will return the new response.
* Etags can be expensive to calculate. It is recommended to have a fast way to generate
and compare etags.
– Both models can be combined to have the best of both worlds.
• ESI
– With ESI, each fragment can have different caching mechanisms and expiration times.
– Symfony only implements the include ESI tag.
– Browsers cannot understand ESI tags, they must be processed first by the reverse proxy
to replace them by the real contents.

Further reading
• ESI Language Specification 1.0. http://www.w3.org/TR/esi-lang
• Caching Tutorial for Web Authors and Webmasters, by Mark Nottingham. https://www.mnot.net/cache_-
docs/
14. The command line
Exam goals
14.1. Symfony2 commands
14.2. Custom commands
14.3. Configuration
14.4. Options and arguments
14.5. Read the input and write the output

Questions
1. What is the Symfony command to create a new bundle?

1. generate:bundle
2. create:bundle
3. bundle:new
4. bundle:generate

2. The Symfony console tool does not support running commands in the test environment

1. True
2. False

3. Given the following Twig template, what is the result of executing cat test.txt.twig | php
app/console twig:lint?

{# test.txt.twig #}
Hello {{ name }}

1. Error: twig:lint is not a valid command


2. Error: no input argument
3. Error: the variable name is not defined
4. OK
14. The command line 198

4. What is the format of the app/console file?

1. PHAR file
2. Binary file
3. Plain PHP script
4. Self-executable compressed file

5. What is the default environment used in Symfony commands?

1. dev
2. test
3. prod
4. cli

6. How can you set prod as the default environment for all Symfony commands?

1. Editing the app/console file and passing prod when creating the kernel
2. Setting the environment variable SYMFONY_ENV to prod
3. Setting environments.default to prod in the parameters.yml file
4. It’s not possible, --env must be used in all commands

7. When executing a Symfony command, how can you increase the verbosity to its maximum
level?

1. -vvv
2. --debug
3. --verbosity=debug
4. --quiet=false

8. How can you run a Symfony application using the PHP built-in webserver at local-
host:8080?

1. php -S localhost:8080 -t web


2. php app/console server:run localhost:8080
3. php -d "webserver.enable=1" -d "webserver.host=localhost:8080"
4. Symfony applications cannot run in the built-in webserver as it doesn’t support .htaccess
files

9. Given the following execute() method of a custom command, what changes must be done
so it doesn’t output anything when --quiet or -q is used?
14. The command line 199

// AppBundle/Command/HelloCommand.php
protected function execute(
InputInterface $input,
OutputInterface $output
) {
$output->writeln('hello');
}

1. Don’t execute the writeln() method if $input->getArgument('quiet') returns true


2. Don’t execute the writeln() method if $input->getOption('quiet') returns true
3. Don’t execute the writeln() method if $output->getVerbosity() returns OutputInter-
face::VERBOSITY_QUIET
4. Nothing

10. What needs to be done to register a custom command so it can be executed and listed when
you execute php app/console list?

1. Define command as services


2. Place it in the Command directory of the bundle and suffix the PHP class with Command
3. Add it to the registerCommands() method in the AppKernel class
4. Add it to the application registered in the app/console file

11. What type of argument would you use to accept more than one input parameter? For
example, php app/console hello Raul John Maria

1. InputArgument::OPTIONAL
2. InputArgument::MULTIPLE
3. InputArgument::ARRAY
4. InputArgument::NONE

12. By default, does the command cache:clear warms up the cache as well as clearing it?

1. Yes
2. No

Answers
1. What is the Symfony command to create a new bundle?
Only answer 1 is correct. Symfony uses the generate namespace for several code generators, such
as generate:controller or generate:bundle.
14. The command line 200

2. The Symfony console tool does not support running commands in the test environment
The afirmation is false, so answer 2 is correct. The Symfony console tool (app/console) can
run in any environment, as long as it is “defined”. By default, the AppKernel class located at
app/AppKernel.php, tries to load the file app/config/config_{env}.yml, so it that file does not
exist the environment can be considered as “not defined”.

$ php app/console container:debug --env=notfound

[InvalidArgumentException]
The file "app/config/config_notfound.yml" does not exist.

3. Given the following Twig template, what is the result of executing cat test.txt.twig | php
app/console twig:lint?

Answer 4 is correct. The twig:lint command checks the syntax of Twig files, and it accepts a
file name, a directory path, a bundle name or input from stdin, like in this example. As the file
1.txt.twig has no errors, the command returns OK with 0 as return code.

4. What is the format of the app/console file?


Answer 3 is correct. The app/console file is just a plain PHP script that bootstrap the kernel, creates
the console application and sends the input arguments to be processed. It contains a shebang pointing
to the PHP binary (#!/usr/bin/env php) so the Linux shell knows how it can be executed.

Shebang
Instead of using the full path of the PHP binary, the app/console file uses /usr/bin/env
php. This makes it more portable, as the location of the PHP binary is not standard. The env
utility finds the first binary called php in the directories specified by the $PATH environment
variable.

5. What is the default environment used in Symfony commands?


Answer 1 is correct. If the SYMFONY_ENV environment variable or the --env (or -e) option are not
present, dev is used by default. If you look at the app/console file, the value of --env or -e is read,
14. The command line 201

and if it’s not present, it checks whether SYMFONY_ENV exists or not:

// app/console
// ...

use Symfony\Component\Console\Input\ArgvInput;

$input = new ArgvInput();


$env = $input->getParameterOption(
['--env', '-e'],
getenv('SYMFONY_ENV') ?: 'dev'
);

// ...

6. How can you set prod as the default environment for all Symfony commands?
Anwsers 1 and 2 are both correct. When the --env option is not present, the app/console scripts
checks if the environment variable SYMFONY_ENV exists, and uses that value. Otherwise, dev is used
as the default environment.

7. When executing a Symfony command, how can you increase the verbosity to its maximum
level?
Answer 1 is correct. If -vvv is present, OutputInterface::VERBOSITY_DEBUG is used as the verbosity
level:

// Console/Application.php
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Application
{

// ...

protected function configureIO(InputInterface $input, OutputInterface $outpu\


t)
{
14. The command line 202

// ...

if (true === $input->hasParameterOption(array('--quiet', '-q'))) {


$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
} else {
if ($input->hasParassmeterOption('-vvv') || $input->hasParameterOpti\
on('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
$output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
} elseif ($input->hasParameterOption('-vv') || $input->hasParameterO\
ption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
} elseif ($input->hasParameterOption('-v') || $input->hasParameterOp\
tion('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getPar\
ameterOption('--verbose')) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
}
}
}

// ...
}

Even if it checks whether --verbose=3 (or any other value) is present, it’s impossible to get it as the
mode of the input option is InputOption::VALUE_NONE.

8. How can you run a Symfony application using the PHP built-in webserver at local-
host:8080?

Answers 1 and 2 are correct. While it’s true that the built-in webserver doesn’t support .htaccess
rewrite rules, they are not always necessary and there are ways to workaround this limitation.
When using the first example, you must always include the front controller (app.php or app_-
dev.php):
14. The command line 203

$ php -S localhost:8080 -t web

PHP 5.5.19 Development Server started at Fri Jun 5 15:48:45 2015


Listening on http://localhost:8080
Document root is project/web

[Fri Jun 5 15:48:55 2015] ::1:51828 [200]: /app_dev.php/example


[Fri Jun 5 15:48:58 2015] ::1:51831 [404]: /example - No such file or directory

But if you add a PHP script as an argument, it gets executed for each request, acting like a routing
script.

$ php -S localhost:8080 -t web router.php

For example, if router.php contains echo "hello";, hello will be the response to any request, even
if the route is not defined.
Symfony provides router files that can be used with the built-in webserver. And that is the difference
between answer 1 and 2. The server:run command adds the routing file so it is not necessary
to include the front controller in every request. Router files are located in the Resources/config
directory of the FrameworkBundle bundle.

9. Given the following execute() method of a custom command, what changes must be done
so it doesn’t output anything when --quiet or -q is used?
Answer 4 is correct. While you could use the method described in the first answer, there is no need
to do it, as the $output object already takes care of it:

// Console/Output/Output.php
namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Formatter\OutputFormatter;

abstract class Output implements OutputInterface


{

// ...

public function writeln($messages, $type = self::OUTPUT_NORMAL)


{
14. The command line 204

$this->write($messages, true, $type);


}

public function write(


$messages,
$newline = false,
$type = self::OUTPUT_NORMAL
) {
if (self::VERBOSITY_QUIET === $this->verbosity) {
return;
}

// ...
}

// ...
}

As you can see, calls to write() and writeln() go always to write(), which checks if the verbosity
level is OutputInterface::VERBOSITY_QUIET. In that case, it exits and doesn’t output anything at
all.

10. What needs to be done to register a custom command so it can be executed and listed when
you execute php app/console list?
Answer 2 is correct. Commands are loaded by convention, as Symfony automatically looks for PHP
classes with the Command suffix in the Command directory of the registered bundles.
The console application, defined in the FrameworkBundle bundle, looks for commands in all
registered bundles executing the registerCommands() method of each bundle. This method is
defined in the Symfony\Component\HttpKernel\Bundle\Bundle abstract class, so it’s inherited by
all bundles:

// HttpKernel/Bundle/Bundle.php
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\Console\Application;

abstract class Bundle extends ContainerAware implements BundleInterface


{

public function registerCommands(Application $application)


{
14. The command line 205

// check if the Command directory exists


if (!is_dir($dir = $this->getPath().'/Command')) {
return;
}

// look for *Command.php files


$finder = new Finder();
$finder->files()->name('*Command.php')->in($dir);

foreach ($finder as $file) {


// check if the class exists and register command
// ...
}
}
}

11. What type of argument would you use to accept more than one input parameter? For
example, php app/console hello Raul John Maria
Answer 3 is correct. You can get the 3 input parameters as an array using InputArgument::ARRAY.

12. By default, does the command cache:clear warms up the cache as well as clearing it?
Answer 1 is correct. The cache:clear command clears the cache and warms it up, unless --no-
warmup is used. To manually warm up the cache, the command cache:warmup can be used.

Takeaways
• General
– By default, commands are executed in the dev environment. It can be changed with the
--env (or -e) option or setting the SYMFONY_ENV environment variable.
– Verbosity can be increased with -v, -vv and -vvv
• Symfony commands
– list: lists all defined commands in the project. They are “discovered” because they use
a naming convention.
14. The command line 206

– twig:lint: checks the syntax of a Twig template


– cache:clear: clears and warms up the cache
– server:run: starts the PHP built-in webserver for the current project
• Arguments and options
– Arguments are the strings after the command name. They are ordered and can be
required or optional.
– Options are specified using two dashes -- (or one dash - if you declare an alias). They
are not ordered and are always optional.
15. Automated Tests
Exam goals
15.1. Unit tests with PHPUnit
15.2. Functional tests
15.3. The Client object
15.4. The Profile object
15.5. Access framework objects
15.6. Configure the client
15.7. Introspect the request and response

Questions
1. What command has to be executed to run all PHPUnit tests included in the Tests directory
of each bundle? (current directory is the main Symfony directory)

1. phpunit -c run
2. phpunit -c app
3. phpunit -c Tests
4. phpunit -c web
5. php app/console tests:run

2. Given the following PHPUnit configuration file, will tests located in the src/AppBundle/Tests
directory be run?

<!-- app/phpunit.xml.dist -->


<phpunit>
<testsuites>
<testsuite name="Test Suite">
<directory>../src/*/*Bundle/Tests</directory>
<directory>../src/*/Bundle/*Bundle/Tests</directory>
</testsuite>
</testsuites>

</phpunit>
15. Automated Tests 208

1. Yes
2. No

3. PHPUnit does not support functional tests.

1. True
2. False

4. How does PHPUnit know how to autoload classes of your Symfony project?

1. By convention, it automatically loads the file vendor/autoload.php


2. By executing composer dumpautoload internally
3. By loading the app/bootstrap.php.cache file, which requires the Composer autoload file
4. By loading the container located at app/cache

5. Is it recommended to store the app/phpunit.xml file in your code repository?

1. Yes
2. No

6. In functional tests extending from the WebTestCase class, how can you create a client to
simulate HTTP requests to the Symfony application?

1. $client = static::createClient();
2. $client = static::createCrawler();
3. $client = $this->container->get('test_client');
4. $client = $this->get('test_client');

7. Given the following HTML code, which of the these expressions would return true when
using the DomCrawler component?
15. Automated Tests 209

<html>
<body>
<h1>Book list</h1>

<h2>Books:</h2>

<ul>
<li>Book 1</li>
<li>Book 2</li>
<li>Book 3</li>
</ul>
</body>
</html>

1. $crawler->filter('html:contains("Book list")')->count() > 0


2. $crawler->filter('h1')->count() > 0
3. $crawler->filter('h1')->text() == 'Book list'
4. $crawler->filter('html > body')->html() == 'Book list'

8. When working with the test client, what is the name of the environment where controllers
are executed?

1. A combination of the current environment and “+test”. For example, “dev+test”


2. By default is “test”
3. The “test” environment is a virtual one, the test client simulates a request from normal
environments like “dev” or “prod”
4. None of the above are correct

9. Is it recommended to use the router service to generate URLs for functional tests?

1. Yes
2. No

10. Given the following functional test, will always fail if you add sleep(900) to the controller
that handles the / URL path assuming that there are no errors in the controller?
15. Automated Tests 210

// AppBundle/Controllers/BookControllerTest.php
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class BookControllerTest extends WebTestCase


{
public function testIndex()
{
$client = static::createClient();

$client->enableProfiler();
$client->request('GET', '/');

$profiler = $client->getProfile();

$this->assertLessThan(
1000,
$profiler->getCollector('time')->getDuration()
);
}
}

1. Yes
2. No, but probably most of the times
3. No, it should pass as long as the controller returns any response
4. None of the above are correct

11. Which of the following configuration settings don’t make sense in the test environment?

1. web_profiler.intercept_redirects = true
2. swiftmailer.disable_delivery = true
3. framework.profiler.collect = false
4. framework.test = "test_prod"

Answers
1. What command has to be executed to run all PHPUnit tests included in the Tests directory
of each bundle? (current directory is the main Symfony directory)
Answer 2 is correct. By default, Symfony provides a PHPUnit configuration file, which is located at
app/phpunit.xml.dist. In addition to setting some options like enabling colors or the path of the
bootstrap.php.cache file, it defines what directories must be used to look for tests. By default, tests
located inside the Tests directory of any bundle in the src directory are used:
15. Automated Tests 211

<!-- app/phpunit.xml.dist -->


<phpunit>
<testsuites>
<testsuite name="Project Test Suite">
<directory>../src/*/*Bundle/Tests</directory>
<directory>../src/*/Bundle/*Bundle/Tests</directory>
</testsuite>
</testsuites>

<!-- ... -->


</phpunit>

2. Given the following PHPUnit configuration file, will tests located in the src/AppBundle/Tests
directory be run?
Answer 2 is correct. You would need to add a new directory pattern such as ../src/App-
Bundle/Tests or ../src/*/Tests.

3. PHPUnit does not support functional tests.


Answer 2 is correct, you can create functional tests and run them using PHPUnit. Sometimes can
be difficult to differentiate between a unit test and a functional, but in short, functional tests check
a particular feature without paying attention to intermediate results. In the other hand, unit tests
check the smallest unit of functionality, for example, a class or a function. With PHPUnit, both unit
and functional tests can be created and executed.

4. How does PHPUnit know how to autoload classes of your Symfony project?
Answer 3 is correct. The PHPUnit configuration file provided by Symfony (at app/phpunit.xml.dist),
makes use of the bootstrap option to execute code before the tests. As the file app/boot-
strap.php.cache loads the autoload file from vendor/autoload.php, the autoload mechanism is
available for PHPUnit tests.

5. Is it recommended to store the app/phpunit.xml file in your code repository?


Answer 2 is correct. PHPUnit first checks whether the phpunit.xml file exists, and only if it doesn’t,
it looks for the phpunit.xml.dist file. So, you can use the phpunit.xml file only for your local
machine (ignoring the file in the code repository) and the phpunit.xml.dist file for project settings.
15. Automated Tests 212

6. In functional tests extending from the WebTestCase class, how can you create a client to
simulate HTTP requests to the Symfony application?
Answer 1 is correct. The WebTestCase class defines the static method createClient, which boot-
straps the kernel of the application and returns an instance of Symfony\Bundle\FrameworkBundle\Client
from the container (service test.client).

7. Given the following HTML code, which of the these expressions would return true when
using the DomCrawler component?
Answers 1, 2 and 3 are correct. In the first one, it finds all elements that contain the string “Book list”
in the whole document, so it will return one element. In the second one, it gets all <h1> elements
and count them, and as there is one element, the expression is true. In the third one, it gets again all
<h1> elements and returns the node value of the first node of the list. The last one is not true, as it
should be “html > body > h1”

8. When working with the test client, what is the name of the environment where controllers
are executed?
Answer 2 is correct. The test environment is like any other environment, but it doesn’t have a
front controller (you could create one). For example, the following command runs in the “test”
environment, otherwise the test.client service wouldn’t be available:

$ php app/console container:debug test.client --env=test


[container] Information for service test.client

Service Id test.client
Class Symfony\Bundle\FrameworkBundle\Client
Tags -
Scope prototype
Public yes
Synthetic no
Required File -

9. Is it recommended to use the router service to generate URLs for functional tests?
Answer 2 is correct, it is not recommended. If URLs are generated using the router service, you
15. Automated Tests 213

won’t be able to detect changes in the URL that can have a negative impact for the users.

10. Given the following functional test, will always fail if you add sleep(900) to the controller
that handles the / URL path assuming that there are no errors in the controller?
Answer 1 is correct. The test checks if the time spent to generate the response is higher than 1
second (1000 ms). As the sleep() function accepts a number seconds, the controller will take at
least 15 minutes to generate the response.
The profiler object can be really useful for functional tests. In addition to the time data collector,
it provides a some others (you can add one yourself too):

Data collector Class


config Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector
event Symfony\Component\HttpKernel\DataCollector\EventDataCollector
exception Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector
logger Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector
memory Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector
request Symfony\Component\HttpKernel\DataCollector\RequestDataCollector
router Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector
time Symfony\Component\HttpKernel\DataCollector\TimeDataCollector
security Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector
swiftmailer Symfony\Bundle\SwiftmailerBundle\DataCollector\MessageDataCollector
db Doctrine\Bundle\DoctrineBundle\DataCollector\DoctrineDataCollector

Some examples of data provided by data collectors through the profiler:

$profiler = $client->getProfile();

$collectors = $profiler->getCollectors();

// time in seconds to generate the response


$collectors['time']->getDuration();

// memory used
$collectors['memory']->getMemory();

// number of database queries


$collectors['db']->getQueryCount();

// symfony version
$collectors['config']->getSymfonyVersion();
15. Automated Tests 214

// checks whether an exception has been thrown


$collectors['exception']->hasException();

11. Which of the following configuration settings don’t make sense in the test environment?
Answer 1 is correct, it’s the only configuration setting that doesn’t make sense at all as the web_-
profiler.intercept_redirects is meant to be used in the browser. Answer 2 disables sending
emails while you are running tests (you can get them later from the swiftmailer data collector).
Answer 3 disables the profiler for all tests to get a better performance (it can be enabled per-request).
And finally answer 4 sets test_prod as the test environment, for example to run tests with different
configuration settings.

Takeaways
• PHPUnit
– Symfony provides a default PHPUnit configuration file at app/phpunit.xml.dist.
– Autoload in PHPUnit tests is enabled by including the file app/bootstrap.php.cache in
the PHPUnit configuration file.
– PHPUnit looks for the app/phpunit.xml file and if it doesn’t exist, checks for the
app/phpunit.xml.dist file.
– When tests are located in unconventional directories, additional patterns must be added
in the PHPUnit configuration file so it can find them.
• Test client
– The test client simulates an HTTP client and sends requests to the Symfony application.
– By default, it runs in the test environment, but can be configured to run in a different
environment.
• Crawler
– The crawler provides methods to work with HTML/XML documents, selecting nodes,
finding text of clicking on buttons.
• Profiler
– The profiler is available for functional tests to get information like number of database
queries, memory usage, time spent or emails sent.
15. Automated Tests 215

Further reading
• PHPUnit documentation. https://phpunit.de/documentation.html
16. Miscellaneous
Exam goals
16.1. Error handling
16.2. Debug the code

Questions
1. By default, what is the bundle that converts exceptions into error pages?

1. FrameworkBundle
2. SecurityBundle
3. TwigBundle
4. DebugBundle

2. Which of the following are valid reasons to replace $kernel->loadClassCache() by $kernel-


>loadClassCache('classes', '.php.cache') in the web/app_dev.php file?

1. Performance
2. Ease of debugging
3. Security
4. Workaround for some IDEs

3. Is it possible to disable the bootstrap.php.cache file to make debugging easier?

1. Yes
2. No

4. Which of the following tools can be used to debug step-by-step your PHP code?

1. var_dump()
2. Xdebug
3. Syslog
4. Symfony’s web profiler
16. Miscellaneous 217

5. What event is thrown when an unhandled exception occurs?

1. kernel.exception
2. kernel.terminate
3. kernel.error
4. No event is thrown when an exception occurs

6. Given the following service definition and controller, how many times will be executed the
method ControllerListener::onKernelController when accessing to /books/test?

<!-- AppBundle/Resources/config/services.xml -->


<service
id="app.controller.listener"
class="AppBundle\EventListener\ControllerListener">
<tag name="kernel.event_listener"
event="kernel.controller"
method="onKernelController" />
</service>

// AppBundle/Controllers/BookController.php
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class BookController
{
/**
* @Route("/books/{slug}", name="book_detail")
*/
public function detailAction($slug)
{
return $this->createNotFoundException('Book not found');
}
}

1. 0
2. 1
3. 2
4. Infinite, up to the recursion or memory/time limits

7. What Symfony component can be used to measure the execution time of certain parts of
your code?
16. Miscellaneous 218

1. Timer
2. Debug
3. Filesystem
4. Stopwatch

8. In PHP, must all exceptions be derived from the base Exception class to be thrown?

1. Yes
2. No, “normal” objects can be thrown as well
3. No, they must implement the ExceptionInterface interface
4. No, they can also extend from SplException

9. What is the output of the following script in PHP 5.6+?

// exceptions.php
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

try {
throw new NotFoundHttpException();
} catch (\Exception $e) {
echo 1;
} catch (NotFoundHttpException $e) {
echo 2;
} catch (\Exception $e) {
echo 3;
} finally {
echo 4;
}

echo 5;

1. 245
2. 145
3. 12345
4. 25
16. Miscellaneous 219

Answers
1. By default, what is the bundle that converts exceptions into error pages?
Answer 3 is correct. Whenever an exception occurs, the TwigBundle:Exception:show controller is
executed, which converts an exception into an error page if debug mode is disabled or an “exception
detected” page when is enabled.
Roughly speaking, the showAction() method of the ExceptionController controller just renders a
template, whose location depends on the debug value:

// TwigBundle/Controller/ExceptionController.php
public function showAction(
Request $request,
FlattenException $exception,
DebugLoggerInterface $logger = null
) {
// ...

$code = $exception->getStatusCode();

return new Response($this->twig->render(


$this->findTemplate(
$request,
$request->getRequestFormat(),
$code,
$this->debug),
[
'status_code' => $code,
'exception' => $exception,
// ...
]
));
}

Templates are located in the directory Resources/views/Exception of the TwigBundle bundle. There
are different templates for exceptions and errors.

2. Which of the following are valid reasons to replace $kernel->loadClassCache() by $kernel-


>loadClassCache('classes', '.php.cache') in the web/app_dev.php file?
Answer 4 is correct. That change makes the PHP containing a copy of some Symfony classes to be
16. Miscellaneous 220

named classes.php.cache instead of classes.php. Some IDEs don’t like having the same classes in
different files, so this way you can configure it to ignore any *.php.cache file. Performance, security
and ease of debugging is exactly the same after this change.

3. Is it possible to disable the bootstrap.php.cache file to make debugging easier?


Answer 1 is correct. The bootstrap.php.cache can be disabled so you won’t end up in that file in
your debugging session. To accomplish this, the following changes must be done:

// app/app_dev.php
// ...

$loader = require_once __DIR__.'/../app/bootstrap.php.cache';


$loader = require_once __DIR__.'/../app/autoload.php';
require_once __DIR__.'/../app/AppKernel.php';

$kernel = new AppKernel('dev', true);


$kernel->loadClassCache();

// ...

4. Which of the following tools can be used to debug step-by-step your PHP code?
Only answer 2 is correct. Xdebug¹⁸ allows to run step-by-step your program to see how it changes.
The var_dump() function and the web profiler can be used to check the value of a variable or detect
errors, but they don’t allow to start/stop the execution of your code.

5. What event is thrown when an unhandled exception occurs?


Answer 1 is correct. When an unhandled exception is thrown, a new kernel.exception event is
dispatched so listeners can handle the exception and show the error in an appropiate way. For
example, the following controller makes the kernel to dispatch the event:

¹⁸http://xdebug.org/
16. Miscellaneous 221

// AppBundle/Controller/BookController.php
class BookController
{
public function imageAction()
{
throw new \Exception('Unknown error');
}
}

6. Given the following service definition and controller, how many times will be executed the
method ControllerListener::onKernelController when accessing to /books/test?
Answer 3 is correct. As the kernel.controller is dispatched when the controller is resolved, it will
be generated twice. First, when the controller for the /books/test route is resolved, and then when
the controller for the exception or error page is resolved.

7. What Symfony component can be used to measure the execution time of certain parts of
your code?
Answer 4 is correct. The Stopwatch provides some useful methods to measure the execution time of
pieces of PHP code. It allows tagging events, measure events in periods and even get the maximum
memory usage.
For example, if you want to measure the execution time of the following function to calculate the
nth number in the Fibonacci sequence¹⁹:

// fibonacci.php
function fibonacci($n)
{
if ($n <= 2) {
return 1;
} else {
return fibonacci($n - 1) + fibonacci($n - 2);
}
}

This simple benchmark shows the execution time of the function depending on the number, from
1 to 35. As the computational complexity of the function is O(2ˆnˆ), the execution time grows
exponentially in each iteration:
¹⁹http://en.wikipedia.org/wiki/Fibonacci_number
16. Miscellaneous 222

// fibonacci_benchmark.php
use Symfony\Component\Stopwatch\Stopwatch;

$stopwatch = new Stopwatch();


for ($i=1; $i<35; $i++) {
$eventName = sprintf('Fibonacci %d', $i);

// measure
$event = $stopwatch->start($eventName, 'fibonacci');
fibonacci($i);
$event->stop();

printf("- %s: %dms\n", $eventName, $event->getDuration());


}

As the computational complexity of the function is O(2ˆnˆ), you can see that the execution time
grows exponentially in each iteration:

- Fibonacci 1: 0ms
- Fibonacci 2: 0ms
...
- Fibonacci 15: 1ms
...
- Fibonacci 20: 16ms
...
- Fibonacci 25: 180ms
...
- Fibonacci 33: 8874ms
- Fibonacci 34: 15672ms
- Fibonacci 35: 24304ms

8. In PHP, must all exceptions be derived from the base Exception class to be thrown?
Answer 1 is correct. All exceptions must be derived from the base Exception class , otherwise you
get the following error:

Exceptions must be valid objects derived from the Exception base class

There is no any interface to implement exceptions and SplException doesn’t exist. The SPL provides
a set of standard exceptions such as BadFunctionCallException or RuntimeException, but all of
them derive from the base exception class.
16. Miscellaneous 223

Throwable interface in PHP 7


PHP 7 will include a new interface that all exceptions must implement, the Throwable
interface. Only objects that implement this interface will be able to thrown. The interface
will define the following methods: getMessage(), getCode(), getFile(), getLine(),
getTrace(), getTraceAsString() and __toString().

9. What is the output of the following script in PHP 5.6+?


Answer 2 is correct. catch blocks are evaluated in order, and at most, only one is executed (none
if there are no catch blocks for the given exception). In the example, as the first one catches all
exceptions, the rest will be ignored, even if they are repeated. The finally block is executed always,
regardless of whether an exception has been thrown, and before normal execution resumes. For
example, the following code prints 134, and the finally block is executed even though there are no
exceptions:

// finally.php
try {
echo 1;
} catch (\Exception $e) {
echo 2;
} finally {
echo 3;
}

echo 4;

Takeaways
• Error handling
– The TwigBundle bundle takes care of rendering a proper exception/error page when an
exception occurs.
– All exceptions must derive from the internal Exception class. In PHP 7, any class
implementing the new Throwable interface can be thrown.
16. Miscellaneous 224

– In a try {...} catch {...} finally {...} structure, the finally block is executed
always regardless of an exception is thrown or not. finally was introduced in PHP 5.6.
• Debugging
– router:debug displays all current routes and provides more information about specific
ones.
– Xdebug can be used to execute code step-by-step.
– The Stopwatch component provides methods to measure execution time of pieces of PHP
code.

Further reading
• Xdebug documentation. http://xdebug.org/docs/
Extra materials
Hands-on exercises
Hands-on exercises are meant to help you understand how Symfony works under the hood and
how all the different pieces are glued together. In my opinion, this knowledge is important to fully
understand some concepts.

Best practices
While doing the exercises, please forget about best practices and focus on how can you
solve the given problem. Even if you have to use extreme approaches like using eval(),
they will give you a better understanding on Symfony internals.
Hands-on exercises 227

1. Custom autoloading
Required time: 1-2 hours
Difficulty: Easy

Autoloading in PHP allows you to dynamically load the PHP files which contain the classes or
interfaces you need, so you don’t have to write a huge list of include or require instructions. The
autoloading mechanism lets you define a function that will be automatically called in case you are
trying to use a class/interface which hasn’t been defined yet.
Autoloading is heavily used in Symfony, so even though Composer already provides an autoloading
system and you probably won’t have to deal with it, it’s important to understand how it works
internally. In this first exercise you will have to write a simple autoloading mechanism from
scratch, based on rules and a classmap. Figure 9 shows the basic idea behind the Composer autoload
mechanism, using two mechanisms: classmaps (big array mapping FQN to PHP files) and rules
(PSR-0/PSR-4).

Figure 9. Autoloading

The goal of the exercise is understand how this mechanism works by creating a simple autoloading
system.
Hands-on exercises 228

1. Create a project with the following directory structure and PHP classes:
• /index.php
• /src
• /src/BookProject
• /src/BookProject/Controller
• /src/BookProject/Controller/BookController.php
• /src/BookProject/Model
• /src/BookProject/Model/Author.php
• /src/BookProject/Model/Book.php
2. Controller\BookController must contain a method with the following signature: public
function detailAction(Book $book), which just return the public property title of the
passed Book object.
3. Model\Book must have a public property called title, and the constructor must accept the
title as well.
4. Model\Author can be an empty class.
5. Copy the following code into the index.php file:

include __DIR__ . '/autoload.php';

use BookProject\Controller\BookController;
use BookProject\Model as ProjectModel;

$bookController = new BookController();

$book = new ProjectModel\Book('Test book');

echo $bookController->detailAction($book);

If you comment the first line and execute the script (or create an empty autoload.php file),
you will get the following errors:

$ php index.php
PHP Fatal error: Class 'BookProject\Controller\BookController' not found in
index.php on line 8

The error is telling you that the PHP interpreter has not loaded any file containing a class
named BookController in the namespace BookProject\Controller. One way to fix it would
be to include the file directly:

include __DIR__ . '/src/BookProject/Controller/BookController.php';

But, what if you have hundreds of files? You would need to have hundreds of include
instructions. Including files “on demand” is a better solution.
Hands-on exercises 229

6. Then, create the autoload.php file and register a function to be called when the interpreter
can’t find a class/interface using spl_autoload_register(). This function must convert fully
qualified names into file paths, and if the file exists, load it. Play with it and answer the
following questions:
1. How many times is the autoload function called? Does it change if you create two Book
objects?
2. What values do you get as input parameters of the autoload function? Do aliases affect?
7. In the autoload.php file, register a second function that will act as a classmap, which should
be called first. This second function will contain an array mapping fully qualified names into
file paths, but only for BookProject\Model\Book:

//...
$map = [
'BookProject\Model\Book' => 'src/BookProject/Model/Book.php'
];
//...

So, this second function will only autoload this class, while the one defined previously will be
in charge of the rest of the classes.

Solution
Once created the boilerplate code to make the exercise, you must use the spl_autoload_register()
function, which creates a queue of autoload functions and runs through each of them in the order
they are defined. There are only two rules to convert a FQN into a PHP class file:

1. Replace \ by / (or DIRECTORY_SEPARATOR to make it more portable).


2. Place it between src/ and .php.

$path = __DIR__ . '/src/' . str_replace('\\', DIRECTORY_SEPARATOR, $fqn) . '.php'

Then, if the calculated file exists, it must be loaded. The full autoloading function should look like
this:
Hands-on exercises 230

spl_autoload_register(function($fqn) {
$filename = str_replace('\\', DIRECTORY_SEPARATOR, $fqn) . '.php';
$path = __DIR__ . '/src/' . $filename;
if (file_exists($path)) {
require_once $path;

return true;
}

return false;
});

The autoload function gets called twice, with the given values:

1. BookProject\Controller\BookController
2. BookProject\Model\Book

If you create 2 or more BookProject\Model\Book objects, it does not change, as the second time
the class is already loaded. Also, aliases are resolved before calling the autoloading function, as
otherwise would be impossible to do from the function without more context.
Finally, to create the second autoloading function, it must be defined first, as that is the order that
spl_autoload_register() follows:

spl_autoload_register(function($fqn) {
$map = [
'BookProject\\Model\\Book' => 'src/BookProject/Model/Book.php'
];

if (array_key_exists($fqn, $map)) {
$path = $map[$fqn];
if (file_exists($path)) {
require_once $path;

return true;
}
}

return false;
});

spl_autoload_register(function($fqn) {
// rules-based autoload...
});
Hands-on exercises 231

This way, both functions are called to load BookProject\Controller\BookController, but only the
first one for BookProject\Model\Book.

Extra work
Check out how the Composer autoloading system works and what are the differences when
you run composer dump-autoload --optimize.
Hands-on exercises 232

2. Overriding default locations


Required time: 1-2 hours
Difficulty: Easy

In this exercise, you are going to learn how Symfony loads all the different configuration files, by
overriding their default locations. You will also see how the directory structure of Symfony can be
easily overriden.

1. Install Symfony 2.3 LTS:

$ symfony new exercise2 2.3

2. Change the names of the following files in the app/config directory:


• config.yml to main_config.yml
• config_dev.yml to env_config_dev.yml
• config_prod.yml to env_config_prod.yml
• config_test.yml to env_config_test.yml
• parameters.yml to params.yml
• routing.yml to routes.yml
• routing_dev.yml to routes_dev.yml
• security.yml to auth.yml
3. Make all the needed changes so Symfony works as expected.
4. Once done, override the default directory structure:
• Cache files must be stored in var/cache, and each environment must have its own
subdirectory with the name env_{environment} (for example, var/cache/env_dev for
the dev environment).
• Log files must be stored in var/logs.

Solution
As you can see in figure 10, when creating the kernel, Symfony loads the main configuration
file for the given environment, and the rest of configuration files are imported from it (such as
security.yml) or loaded through configuration options (like routing_dev.yml).
Hands-on exercises 233

Figure 10. Loading configuration files

This main configuration file is loaded from the registerContainerConfiguration() method of the
AppKernel class. By default, app/config/config_{environment}.yml is used. So, as you changed
the name of the main configuration files, that method must be changed accordingly:

// app/AppKernel.php
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(
$this->getRootDir().'/config/env_config_'.$this->getEnvironment().'.yml'
);
}

Then, to import the base configuration file from env_config_*.yml:

# app/config/env_config_{env}.yml
imports:
- { resource: main_config.yml }

From the base configuration file are loaded other files that are not dependant on the environment,
such as the parameters and security configuration. They must be changed accordingly as well:
Hands-on exercises 234

# app/config/main_config.yml
imports:
- { resource: params.yml }
- { resource: auth.yml }

Then, routes are loaded by the FrameworkBundle bundle, which reads the value of the frame-
work.router.resource setting. For example, for the dev environment, app/config/config/routes_-
dev.yml will be loaded.

# app/config/env_config_dev.yml
framework:
router:
resource: "%kernel.root_dir%/config/routes_dev.yml"

class Router extends BaseRouter implements WarmableInterface


{

public function __construct(


ContainerInterface $container,
$resource, array $options = [],
RequestContext $context = null
) {
// ...

$this->resource = $resource;

// ...
}

public function getRouteCollection()


{
$this->collection = $this->container->get('routing.loader')
->load($this->resource, $this->options['resource_type']);
$this->resolveParameters($this->collection);

// ...
}
}

Finally, to change the default location of cache and logs directories, the app/AppKernel.php must
be modified again overriding the methods getCacheDir() and getLogDir().
Hands-on exercises 235

// app/AppKernel.php

class AppKernel extends Kernel


{
// ...

public function getCacheDir()


{
return $this->rootDir.'/var/cache/env_'.$this->environment;
}

public function getLogDir()


{
return $this->rootDir.'/var/logs';
}
}
Hands-on exercises 236

3. Twig extensions playground


Required time: 2-4 hours
Difficulty: Medium

Twig extensions allows you to add extra functionality to Twig, such as functions, filters, tags or
global variables. In this exercise, you are going to create a Twig extension that will include some
global variables and custom functions and filters.

1. Install Symfony 2.3 LTS:

$ symfony new exercise3 2.3

2. Create a new Twig extension called PlaygroundExtension in src/AppBundle/Twig/Exten-


sion/PlaygroundExtension.
3. Register and tag the newly created Twig extension.
4. Add a global variable called random, which will contain a random value. The following
template should print out a random value:

{{ random }}

5. If you use the random variable multiple times, you will get always the same value. This is the
expected behaviour in global variables, but try to go beyond and get a different value every
time you the random variable. The following template should return two different random
values:

{{ random }} - {{ random }}

Suggestion: try to use anonymous functions, array-like objects like SplFixedArray, interfaces
for array-like functionality like ArrayAccess and magic methods. They don’t necessarily have
to work, but you should get at least one way to solve the problem.
6. Implement the custom function sleep($seconds) to delay the execution of the template
$seconds seconds. If you execute the following template, it should be a difference of 3 seconds
between the two dates:

{{ "now"|date('r') }}
{{ sleep(3) }}
{{ "now"|date('r') }}
Hands-on exercises 237

7. Implement the custom function kernel_cache_dir(), which should return a string containing
the cache directory of the kernel. You must inject the kernel service in order to get that
information.
8. Similarly, you must provide all the functions listed in the following table, but you can only
create one Twig function. You must try to do it as generic as possible, so for example if the
kernel provides the method getUploadsDir(), the Twig function kernel_uploads_dir works
with no further changes. Hint: dynamic functions.

Twig function Kernel method


kernel_cache_dir getCacheDir()
kernel_log_dir getLogDir()
kernel_charset getCharset()
kernel_environment getEnvironment()
kernel_name getName()
kernel_start_time getStartTime()

Solution
Creating a new Twig extension and adding a global variable called random is straightforward. Just
create the extension class, register it as a service with the tag twig.extension and return an array
with global variables in the getGlobals() method:

// src/AppBundle/Twig/Extension/PlaygroundExtension.php
class PlaygroundExtension extends \Twig_Extension
{

public function getGlobals()


{
return [
'random' => rand()
];
}

public function getName()


{
return 'playground';
}
}

Global variables are expected not to change, but one of the goals of this exercise is to force you to
dive deeper into how the Twig extensions system work. So, how
Hands-on exercises 238

public function getGlobals()


{
return [
'random' => function() {
return rand();
}
];
}

But this generates a fatal error: Object of class Closure could not be converted to string.
Anonymous functions return a Closure instance, and as it doesn’t implement the __toString()
magic method, cannot be converted into a string representation. Anyway, this error should give you
a hint on how to accomplish it, but we’ll get back later.
The next attempt would be to return an anonymous function that returns the globals array, expecting
to be executed every time a global variable is used:

public function getGlobals()


{
return function() {
return [
'rand' => rand()
];
};
}

But this doesn’t work either: "getGlobals()" must return an array of globals. If you look the
function that initializes globals, it checks that the returned value is an array with is_array():

class Twig_Environment
{

// ...

protected function initGlobals()


{
$globals = array();
foreach ($this->extensions as $extension) {
$extGlob = $extension->getGlobals();
if (!is_array($extGlob)) {
throw new UnexpectedValueException(
sprintf(
Hands-on exercises 239

'"%s::getGlobals()" must return an array of globals.',


get_class($extension)
)
);
}

$globals[] = $extGlob;
}

$globals[] = $this->staging->getGlobals();

return call_user_func_array('array_merge', $globals);


}
}

Creating a class that implements the ArrayAccess interface could be another approach, so getGlob-
als() would return an instance of the class:

class RandomValue implements \ArrayAccess


{

public function offsetExists($offset)


{
return ('random' === $offset);
}

public function offsetGet($offset)


{
return rand();
}

public function offsetSet($offset, $value) {}

public function offsetUnset($offset) {}

If you instantiate the class and access to the random index twice, you will get two different values:
Hands-on exercises 240

$random = new RandomValueArray();

var_dump($random['random'], $random['random']);

But this doesn’t work with Twig globals. If you recall, is_array() was used to check the returned
value of getGlobals(). And is_array() only returns true for real arrays. Objects implementing
ArrayAccess or SplFixedArray objects are not considered arrays by the is_array() function.

$random = new RandomValueArray();


$fixedArray = new \SpfFixedArray(10);

var_dump(is_array($random), is_array($fixedArray)); // false, false

So, is there anything you can do to get different values every time the random variable is used? Yes.
Check the following class and the getGlobals() method:

class RandomValue
{
public function __toString()
{
return (string) rand();
}
}

class PlaygroundExtension
{
public function getGlobals()
{
return [
'random' => new RandomValue()
];
}
}

As the RandomValue class implements the __toString() magic method, every time you use the
random global variable, Twig tries to use the string representation of the RandomValue object, so you
will get a different value each time. Obviously, don’t do this in your projects and use functions and
filters instead!
Creating the sleep() function is trivial:
Hands-on exercises 241

// src/AppBundle/Twig/Extension/PlaygroundExtension.php

class PlaygroundExtension extends \Twig_Extension


{

public function getFunctions()


{
return [
new \Twig_SimpleFunction('sleep', [$this, 'getSleep'])
];
}

public function getSleep($seconds)


{
sleep($seconds);
}
}

Extra work
If you want to have an even more difficult problem, try to define the global variable now,
which should contain a DateTime object with the current time. By using the recently created
sleep() function you can test if now is returning the current date/time.

For the kernel_cache_dir() function you need to inject the kernel service and then get the cache
directory from the getCacheDir() method:

// src/AppBundle/Twig/Extension/PlaygroundExtension.php

class PlaygroundExtension extends \Twig_Extension


{
protected $kernel;

public function __construct(KernelInterface $kernel)


{
$this->kernel = $kernel;
}

public function getFunctions()


{
return [
new \Twig_SimpleFunction(
Hands-on exercises 242

'kernel_cache_dir',
[$this, 'getKernelCacheDir'],
['is_safe' => ['html']]
)
];
}

public function getKernelCacheDir()


{
return $this->kernel->getCacheDir();
}
}

Finally, for dynamic functions, you just need to define the function including the * character, which
will be replaced by anything. In the method that handles the dynamic function, you must convert
the lowered snake_case name of the function (the dynamic part) to CamelCase, and then try to
execute it:

// src/AppBundle/Twig/Extension/PlaygroundExtension.php

class PlaygroundExtension extends \Twig_Extension


{
protected $kernel;

public function __construct(


\Twig_LoaderInterface $loader,
KernelInterface $kernel
) {
$this->kernel = $kernel;
}

public function getFunctions()


{
return [
new \Twig_SimpleFunction(
'kernel_*',
[$this, 'getKernel'],
['is_safe' => ['html']]
)
];
}
Hands-on exercises 243

public function getKernel($value)


{
// snake case to camel case
$snakeCaseParts = explode('_', $value);
$camelCase = implode('', array_filter($snakeCaseParts, function($part) {
return ucfirst($part);
}));
$methodName = 'get' . $camelCase;

try {
return $this->kernel->$methodName();
} catch (\Exception $e) {
$message = 'The function "kernel_' . $value . '" does not exist';
throw new \Twig_Error_Syntax($message);
}
}
}

You could have used Reflection or the method_exists() function to check the existence of the
function, but for the purposes of the exercise this is enough.
Hands-on exercises 244

4. The playful bundle


Required time: 2-3 hours
Difficulty: Hard

In this exercise, you will create a new bundle

1. Install Symfony 2.3 LTS:


$ symfony new exercise3 2.3
2. Create the bundle Certification/PlayfulBundle.
3. Create the controller Certification/PlayfulBundle/Controller/PlayController and the
method playAction(), which must return a Response object with dummy content, for
example:
return new Response('hello');
4. Create the file routing.yml in the Resources/config directory of the bundle. Create a route
that points to CertificationPlayfulBundle:Play:play. Load this file from the main routing
file.
5. Now the fun starts… The short name of the bundle is CertificationPlayfulBundle, but you
must find a way to change it to PlayBundle without changing the bundle structure nor
the bundle class name. Keep in mind that the references to CertificationPlayfulBundle
in route definitions must be updated. Once you find a way to do it, check the parameter
kernel.bundles of the compiled container to see how this change affects to that value.
6. Now, the the bundle name must change in each execution, using the PHP rand() function.
The bundle name should use the following pattern:
$name = 'Play' . rand(1, 99) . 'Bundle';

Keep in mind that this change is going to affect how the routing.yml file of the bundle is
loaded from the main routing file. You will also have to find a way to load that file taking into
account that you don’t know what is the name of the bundle.
As a suggestion, check all the different routing loaders (container:debug --tag=routing.loader
--show-private) to see if you could use any of them. Check out how to create custom routing
loaders as well.
7. Make sure that the route defined in the bundle is shown when executing the command
router:debug.
8. Create a new custom command in Command/NameCommand.php. When play:name is executed,
it should print out the name of the bundle (it must be different in each execution).
9. Rename the Command directory to CommandLine and NameCommand.php to NameCommandLine.php.
Does the command still works? If not, what changes could you make to make it work?
Hands-on exercises 245

Solution
The first four steps of this exercise are trivial and you should not have any problem to complete
them. In the fifth step, you have to change the short name of the bundle without changing
its structure or the bundle class. As you may know, the bundles’ main class extend from Sym-
fony\Component\HttpKernel\Bundle\Bundle. This class contains the getName() method, which is
used by the kernel to determine the name of the bundle:

namespace Symfony\Component\HttpKernel\Bundle;

abstract class Bundle extends ContainerAware implements BundleInterface


{
protected $name;

// ...

final public function getName()


{
if (null !== $this->name) {
return $this->name;
}

$name = get_class($this);
$pos = strrpos($name, '\\');

return $this->name = false === $pos ? $name : substr($name, $pos + 1);


}

// ...
}

By default, this method gets the FQN of the current class and removes the last part of it. For
example, if the bundle class is Certification\PlayfulBundle\PlayfulBundle, it returns Certi-
fication\PlayfulBundle\PlayfulBundle. As you can see, this method is final, so it can’t be
overwritten in child classes. So, how can you change the name of the bundle? Overwriting the
name property before it gets initialized the first time getName() is executed:
Hands-on exercises 246

namespace Certification\PlayfulBundle\PlayfulBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class PlayfulBundle extends Bundle


{
public function __construct()
{
$this->name = 'PlayBundle';
}
}

If you change all occurrences of CertificationPlayfulBundle to PlayBundle, it should work, and


the compiled container should contain the name of the bundle in the kernel.bundles parameter:

// app/cache/dev/appDevDebugProjectContainer.php

use Symfony\Component\DependencyInjection\Container;

class appDevDebugProjectContainer extends Container


{
protected function getDefaultParameters()
{
return array(
'kernel.root_dir' => $this->targetDirs[2],
'kernel.environment' => 'dev',
'kernel.debug' => true,
'kernel.name' => 'app',
'kernel.cache_dir' => __DIR__,
'kernel.logs_dir' => ($this->targetDirs[2].'/logs'),
'kernel.bundles' => array(
// ...
'PlayBundle' => 'Certification\\PlayfulBundle\\PlayfulBundle',
// ...
);
// ...
}
}

The next task is to make the bundle name different in each request by adding a random value:
Hands-on exercises 247

namespace Certification\PlayfulBundle\PlayfulBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class PlayfulBundle extends Bundle


{
public function __construct()
{
$this->name = 'Play' . rand(1,99) . 'Bundle';
}
}

After doing this, you have no way to know what’s the name of the bundle to load the routing file,
as @PlayBundle no longer works. While there can be different ways to solve the problem, probably
creating a custom routing loader is the most elegant way to do it. With a custom routing loader you
no longer depend on the bundle name, as the loader takes care of loading the routes:

// src/Certification/PlayfulBundle/Routing/RoutingLoader.php

namespace Certification\PlayfulBundle\Routing;

use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\Loader\YamlFileLoader;

class RoutingLoader extends Loader


{
protected $loaded = false;

protected $loader;

public function __construct(YamlFileLoader $loader)


{
$this->loader = $loader;
}

public function load($resource, $type = null)


{
$this->loaded = true;

$reflection = new \ReflectionClass(\AppBundle\AppBundle::class);


$baseDir = dirname($reflection->getFileName());
Hands-on exercises 248

return $this->loader->load($baseDir . '/Resources/config/routing.yml');


}

public function supports($resource, $type = null)


{
return 'special' === $type;
}
}

Basically, the load() method returns a RouteCollection, which is returned when the rout-
ing.loader.yml service parses the previously created Resources/config/routing.yml file.

Then, it must be registered as a service (notice that is tagged as routing.loader:

<!-- src/Certification/PlayfulBundle/Resources/config/services.xml -->

<service
id="certification.playful.routing_loader"
class="Certification\PlayfulBundle\Routing\RoutingLoader">
<argument type="service" id="routing.loader.yml" />
<tag name="routing.loader" />
</service>

And finally is used in the main routing file to load the bundle routes:

# app/config/routing.yml

app:
resource: .
type: special

Step 8 has only the difficulty of finding the bundle name. One way to do it is to get the kernel
service from the container, get the list of bundles and check which one has the same namespace as
the bundle class:
Hands-on exercises 249

// src/Certification/PlayfulBundle/Command/NameCommand.php

namespace Certification\PlayfulBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class NameCommand extends ContainerAwareCommand


{
protected function configure()
{
$this->setName('play:name');
}

protected function execute(InputInterface $input, OutputInterface $output)


{
$kernel = $this->getContainer()->get('kernel');

foreach ($kernel->getBundles() as $bundle) {


if ($bundle->getNamespace() === 'AppBundle') {
$output->writeln($bundle->getName());
}
}
}
}

Finally, in the step 9 you must change the way commands are registered in the console application.
Similarly as you did in the step 5, the Symfony\Component\HttpKernel\Bundle\Bundle abstract class
implements the registerCommands() method, that looks for commands that follow the standard
convention. As you are using now a different convention, this method must be overwritten:

namespace Certification\PlayfulBundle\PlayfulBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\Console\Application;

class PlayfulBundle extends Bundle


{
// ...

public function registerCommands(Application $application)


Hands-on exercises 250

{
// check if the directory exists
if (!is_dir($dir = $this->getPath().'/CommandLine')) {
return;
}

// find possible commands


$finder = new Finder();
$finder->files()->name('*CommandLine.php')->in($dir);

foreach ($finder as $file) {


// check with ReflectionClass if the class is:
// - subclass of Symfony\Component\Console\Command\Command
// - not abstract
// - constructor parameters > 0

// ...
}
}
}
Hands-on exercises 251

5. Controlling the framework with HTTP headers


Required time: 3-5 hours
Difficulty: Hard

In this last exercise you are going to work with kernel events to change the behaviour of the
framework. By using HTTP headers, a user will be able to set/unset a maintenance mode to show a
special page, simulate AJAX requests and even set the environment or the controller to be executed.
This is probably a terrible idea from a security point of view, but it’s fun and it’s definitely a great
way to understand the Symfony internals.
The following table contains the HTTP headers that will be available (all headers are optional):

Header Values Default Description


X-Sf-Maintenance 0 or 1 0 Set/unset maintenance mode
X-Sf-Ajax 0 or 1 0 Simulate AJAX request
X-Sf-View-Date format null Format for DateTime responses
X-Sf-Controller callable null Override controller
X-Sf-Exception-Code code null Override exception status code
X-Sf-Env environment null Override environment

Some comments about the different headers:

• X-Sf-Maintenance will be 0 or 1. If it’s set to 1, it will show a static maintenance page with
the message Under maintenance. To save resources, the response should be generated as soon
as possible.
• X-Sf-Ajax, if present and set to 1, will simulate an AJAX request, so if you call $request-
>isXmlHttpRequest() from a controller, it must return true.
• X-Sf-View-Date will only be used if a controller returns a DateTime object, instead of a proper
response. For example, if the value is r, and the controller returns a DateTime object, it will
return a RFC 2822 formatted date.
• X-Sf-Controller will override the resolved controller, so no matter what URL or HTTP
method is submitted, it will always execute the provided controller. The value must be in
any of the valid formats, like the ones you would use in the routing.yml file.
• X-Sf-Exception-Code will only be considered when an exception is thrown, setting the HTTP
status code to the one provided, but still showing the exception page.
• X-Sf-Env will execute the application in a different environment. Think twice before starting
with this part and keep in mind that kernel listeners are already being executed in an
environment.
Hands-on exercises 252

Then, all responses must include the following information:

Header Values Description


X-Sf-Ajax 0 or 1 1 in AJAX requests
X-Sf-Controller Executed controller
X-Sf-Env Current environment
X-Sf-Method Current HTTP method
X-Sf-Route Resolved route
X-Sf-Version MAJOR.MINOR.PATCH Symfony version

So, the steps to do the exercise:

1. Install Symfony 2.3 LTS:

$ symfony new exercise5 2.3

2. Create the bundle Certification/HttpControlBundle.


3. Create and register one event listener for each functionality. For the X-Sf-Env request header
you may need some extra stuff. Feel free to make any change.
4. To test it, you can use curl with the -H option to set headers or tools like POSTMAN²⁰.

Solution
This is probably the hardest exercise, as you have to investigate how things work, but once you
understand it the solution is quite straightforward.

X-Sf-Maintenance header

If the X-Sf-Maintenance header is present and set to 1, you must return a response with the message
Under maintenance. One way to do it with event listeners is to listen to the kernel.request event,
check if the header is set and then just return a Response object:

namespace Certification\HttpControlBundle\EventListener;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class MaintenanceListener
{
public function onKernelRequest(GetResponseEvent $event)

²⁰https://www.getpostman.com/
Hands-on exercises 253

{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}

if (1 == $event->getRequest()->headers->get('X-Sf-Maintenance', false)) {
$event->setResponse(new Response('Under maintenance'));
}
}
}

To register the listener:

<service
id="certification.http_control.listener.maintenance"
class="Certification\HttpControlBundle\EventListener\MaintenanceListener">
<tag name="kernel.event_listener"
event="kernel.request"
method="onKernelRequest" />
</service>

You can set a higher priority (with the priority attribute in the tag tag) so it is executed before that
any other kernel.request listener.
There is a much faster way to accomplish it so Symfony isn’t used at all by changing the front
controller to detect the header:

// app.php

if (isset($_SERVER['HTTP_X_SF_MAINTENANCE']) &&
'1' === $_SERVER['HTTP_X_SF_MAINTENANCE']) {
echo 'Under maintenance';
exit;
}

Anyway, the kernel.request listener is the best way as you will probably use Twig to render the
maintenance page, get information from the database, log some messages, etc.

X-Sf-Ajax header

When the X-Sf-Ajax is set to 1, you must change something to make Request::isXmlHttpRequest()
return true. As this method checks that the X-Requested-With header contains the value XML-
HttpRequest, you just need to overwrite that header from a kernel.request listener:
Hands-on exercises 254

namespace Certification\HttpControlBundle\EventListener;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class AjaxSimulatorListener
{
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}

$request = $event->getRequest();
if (1 == $request->headers->get('X-Sf-Ajax', false)) {
$request->headers->set('X-Requested-With', 'XMLHttpRequest');
}
}
}

To register the listener:

<service
id="certification.http_control.listener.ajax_simulator"
class="Certification\HttpControlBundle\EventListener\AjaxSimulatorListener">
<tag name="kernel.event_listener"
event="kernel.request"
method="onKernelRequest" />
</service>

X-Sf-View-Date header

If a controller returns something different than a Response object, the kernel.view event is
dispatched so listeners can convert that value into a Response object. This listener will do it for
DateTime objects using the format provided:
Hands-on exercises 255

namespace Certification\HttpControlBundle\EventListener;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class DateViewListener
{
public function onKernelView(GetResponseForControllerResultEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}

$format = $event->getRequest()->headers->get('X-Sf-View-Date', false);


if (false !== $format) {
$controllerResponse = $event->getControllerResult();

if ($controllerResponse instanceof \DateTime) {


$response = new Response($controllerResponse->format($format));
$event->setResponse($response);
}
}
}
}

To register the listener:

<service
id="certification.http_control.listener.date_view"
class="Certification\HttpControlBundle\EventListener\DateViewListener">
<tag name="kernel.event_listener"
event="kernel.view"
method="onKernelView" />
</service>

X-Sf-Controller header

You may be tempted to use the kernel.controller event to override whatever controller that has
been resolved. While this would work in most cases, it would fail if the route doesn’t exist, as
the kernel.controller event would never be dispatched. The proposed solution makes use of the
kernel.request event again, setting the _controller value of the Request::attributes parameter
Hands-on exercises 256

bag with the provided controller. This works because the RouterListener from the HttpKernel
checks if _controller has been already set, and in that case, it doesn’t try to get the controller from
the request.

namespace Certification\HttpControlBundle\EventListener;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class ControllerListener
{
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}

$request = $event->getRequest();
$controllerValue = $request->headers->get('X-Sf-Controller', false);
if (false === $controllerValue) {
return;
}

$request->attributes->set('_controller', $controllerValue, true);


}
}

For this solution to work, it must be executed before the RouterListener listener, which has a
priority of 32. So, the listener must have a higher priority:

<service
id="certification.http_control.listener.controller"
class="Certification\HttpControlBundle\EventListener\ControllerListener">
<tag name="kernel.event_listener"
event="kernel.request"
method="onKernelRequest"
priority="33" />
</service>

X-Sf-Exception-Code header

The HttpKernel component provides a few exceptions that extend from Symfony\Component\HttpKernel\Exception
For example, the NotFoundHttpException just sets the status code to 404. You can use a similar
Hands-on exercises 257

approach to override the status code.


Once a kernel.exception event is dispatched, the listener replaces the current exception by a new
HttpException exception, with the overriden status code, and the old exception is set as previous
exception:

namespace Certification\HttpControlBundle\EventListener;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class ExceptionCodeListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{

if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {


return;
}

$request = $event->getRequest();
$statusCode = $request->headers->get('X-Sf-Exception-Code', false);
if (false === $statusCode) {
return;
}

$exception = new HttpException(


$statusCode,
'Symfony error',
$event->getException()
);

$event->setException($exception);
}
}

And to register the listener, it must have a higher priority than the ExceptionListener listener from
the HttpKernel component, but as its priority is -128, the default value of 0 is enough in this case:
Hands-on exercises 258

<service
id="certification.http_control.listener.exception_code"
class="Certification\HttpControlBundle\EventListener\ExceptionCodeListener">
<tag name="kernel.event_listener"
event="kernel.exception"
method="onKernelException" />
</service>

X-Sf-Env header

As you know, the environment cannot be changed in runtime once the kernel has been initialized.
One solution would be to create a “meta” front controller. This new front controller would check
the value of the X-Sf-Env header and create the AppKernel with the chosen environment, or just
require the front controller for the environment:

// meta_front_controller.php

use Symfony\Component\HttpFoundation\Request;

$loader = require_once __DIR__.'/../app/bootstrap.php.cache';

$request = Request::createFromGlobals();

$env = $request->headers->get('X-Sf-Env', 'dev');


if ('prod' === $env) {
require __DIR__ . '/app.php';
} else {
require __DIR__ . '/app_' . $env . '.php';
}

Extra work
Try to use the Validator component to validate input data in the event listeners.

Response header

Finally, for all the information that must be included in every response, the kernel.response event
is used. The controller, route, HTTP method and whether or not is an AJAX request can be obtained
from the Request object, while the environment parameter can be injected and the Symfony version
is in the Symfony\Component\HttpKernel\Kernel::VERSION constant:
Hands-on exercises 259

namespace Certification\HttpControlBundle\EventListener;

use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class ResponseInfoListener
{

protected $environment;

public function __construct($environment)


{
$this->environment = $environment;
}

public function onKernelResponse(FilterResponseEvent $event)


{

if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {


return;
}

$response = $event->getResponse();
$request = $event->getRequest();

$response->headers->add([
'X-Sf-Env' => $this->environment,
'X-Sf-Controller' => $request->attributes->get('_controller'),
'X-Sf-Route' => $request->attributes->get('_route'),
'X-Sf-Ajax' => $request->isXmlHttpRequest() ? 1 : 0,
'X-Sf-Version' => \Symfony\Component\HttpKernel\Kernel::VERSION,
'X-Sf-Method' => $request->getMethod()
]);

$event->setResponse($response);
}
}

And to register the listener:


Hands-on exercises 260

<service
id="certification.http_control.listener.response_info"
class="Certification\HttpControlBundle\EventListener\ResponseInfoListener">
<argument>%kernel.environment%</argument>
<tag name="kernel.event_listener"
event="kernel.response"
method="onKernelResponse" />
</service>
Appendixes
Answer sheet
1. PHP

Question Correct answers


1 2, 3, 4
2 3, 4
3 2
4 4
5 1
6 4
7 1
8 2
9 1
10 3
11 4
12 3
13 4
14 1, 4
15 1
16 2
17 1, 2, 3, 4
18 1, 2, 3
19 4
20 3
21 3
22 1
23 2
24 4
25 1
26 2, 3
27 2
28 1
29 2
30 2
31 3
32 1
33 1
34 2
Answer sheet 263

2. HTTP

Question Correct answers


1 3
2 1, 4
3 2
4 4
5 3
6 2
7 4
8 2
9 2
10 4
11 1, 2
12 4
13 1
14 1

3. Architecture

Question Correct answers


1 3
2 1
3 1
4 1, 2, 3
5 1, 2, 3, 4
6 1, 2
7 1
8 4
9 4
10 3
11 3
12 2, 4
13 1, 2, 3, 4
14 1, 4
15 1
16 2
17 4
18 4
19 2
20 2
21 4
Answer sheet 264

4. Standardization

Question Correct answers


1 1
2 1
3 3
4 1
5 1, 2
6 2
7 1, 2
8 4
9 3
10 2, 3
11 2
12 1, 2
13 3
14 4
15 1

5. The Bundles

Question Correct answers


1 3, 5
2 3
3 2
4 4
5 1
6 3
7 2
8 3
9 2
10 3
11 4

6. The Controllers
Answer sheet 265

Question Correct answers


1 1
2 4
3 1, 4, 5
4 2
5 2, 4
6 2
7 4
8 2
9 1, 2
10 4
11 3, 4
12 2, 3
13 4
14 1, 2, 3
15 3
16 2
17 4
18 1
19 1
20 4
21 2
22 3

7. Routing

Question Correct answers


1 1
2 4
3 4
4 1
5 2
6 4
7 1
8 1
9 3
10 4
11 1
12 2
13 3
14 3
15 2
16 3
17 2, 3, 4
Answer sheet 266

Question Correct answers

8. Twig

Question Correct answers


1 1, 2, 4
2 2
3 2
4 1
5 2, 4
6 2
7 3
8 3
9 4
10 3
11 4
12 1, 2, 3, 4
13 1
14 1, 2, 3, 4
15 3
16 1, 2
17 3
18 4
19 2
20 1, 2, 4
21 1, 3
22 2
23 4
24 1
25 2
26 1
27 2
28 4
29 3
30 2
31 3
32 1
33 4

9. Forms
Answer sheet 267

Question Correct answers


1 2
2 3
3 1, 2, 4
4 1
5 3
6 1, 2
7 3
8 3, 4
9 1, 3
10 2
11 1
12 2
13 1
14 3

10. Validation

Question Correct answers


1 2
2 1, 2, 3, 4
3 1, 2, 3, 4
4 4
5 2
6 3, 4
7 2, 4
8 3
9 3
10 1, 3, 4
11 2
12 1

11. Dependency Injection


Answer sheet 268

Question Correct answers


1 2
2 1, 2
3 2
4 1, 2
5 3
6 4
7 2
8 3
9 2
10 1, 2
11 1
12 2, 3
13 1
14 2
15 3

12. Security

Question Correct answers


1 2
2 3
3 2
4 3, 4
5 1
6 2, 4
7 4
8 4
9 1, 3
10 2
11 1
12 2
13 1
14 2
15 4
16 2
17 1, 3

13. HTTP Cache


Answer sheet 269

Question Correct answers


1 2
2 4
3 3
4 1, 4
5 2
6 2
7 1
8 1, 2
9 1
10 1, 2
11 4
12 3
13 2
14 1
15 2
16 1, 4
17 4
18 1

14. The command line

Question Correct answers


1 1
2 2
3 4
4 3
5 1
6 1, 2
7 1
8 1, 2
9 4
10 2
11 3
12 1

15. Automated Tests


Answer sheet 270

Question Correct answers


1 2
2 2
3 2
4 3
5 2
6 1
7 1, 2, 3
8 2
9 2
10 1
11 1

16. Miscellaneous

Question Correct answers


1 3
2 3, 4
3 1
4 2
5 1
6 3
7 4
8 1
9 2
Index
PHP functions
__halt_compiler: 1.26, 1.29
__invoke: 7.16
__sleep: 1.29
__toString: 10.5
array_merge: 7.10
array_replace: 7.6
call_user_func_array: 7.1, 7.16
create_function: 1.17
date: 8.6
dirname: 5.7
echo: 1.21
eval: 1.12
exec: 3.14
file_put_contents: 3.14
filter_var: 10.9
func_get_args: 1.2
get_class: 1.34
getenv: 14.5
goto: 7.10
hash: 12.9, 12.10
hash_algos: 12.10
htmlspecialchars: 8.5
implode: 8.30
in_array: 7.5, 7.10
include: 1.25
include_once: 1.25
is_callable: 7.16, 10.7
Index 272

levenshtein: 1.28
md5: 12.9, 13.14
mkdir: 3.14
passthru: 3.14
password_hash: 12.7
php_check_syntax: 1.12
phpinfo: 1.1
phpversion: 1.1
preg_match: 7.5, 7.6, 12.12
print: 1.21
printf: 1.21
proc_open: 3.18
rand: 1.17
require: 1.25
require_once: 1.25
sha1: 12.9
shell_exec: 3.14
sleep: 15.10
spl_autoload_register: 1.29
strlen: 1.33
strpos: 7.5, 7.6
strtolower: 1.4, 7.16, 8.20
strtotime: 8.6
strtoupper: 8.20
ucfirst: 8.20
ucwords: 8.20
unlink: 1.25

PHP constants
FILTER_VALIDATE_IP: 10.9
PASSWORD_BCRYPT: 12.7
PHP_EOL: 1.33
PHP_EXTRA_VERSION: 1.1
Index 273

PHP_MAJOR_VERSION: 1.1
PHP_MINOR_VERSION: 1.1
PHP_RELEASE_VERSION: 1.1
PHP_VERSION_ID: 1.1
__CLASS__: 10.7
__FILE__: 1.25
__METHOD__: 10.7
__NAMESPACE__: 1.20, 1.31

PHP built-in classes and interfaces


ArrayAccess: 10.4
BadFunctionCallException: 16.8
Closure: 1.34
Countable: 1.24, 10.4
DateInterval: 13.13
DateTime: 13.13
Exception: 1.33, 16.8, 16.9
Iterator: 1.24, 3.14
IteratorAggregate: 3.14
RuntimeException: 16.8
SplFixedArray: 1.17
SplMinHeap: 1.32
Traversable: 3.14, 10.4

PHP configuration settings


allow_url_fopen: 9.4
allow_url_include: 9.4
apc.stat: 8.4
disable_functions: 1.4
phar.require_hash: 1.26

Symfony components
Config: 12.1
Console: 1.28, 5.11, 8.8, 14.1, 14.2, 14.3, 14.5, 14.7, 14.8, 14.9, 14.10, 14.11, 14.12
Debug: 16.7
Index 274

DependencyInjection: 1.28, 9.13, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9, 11.11, 11.14,
11.15, 12.5
DomCrawler: 15.7
EventDispatcher: 3.17, 11.5, 11.11
Filesystem: 3.14, 16.7
Finder: 3.16, 5.11
Form: 8.7, 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9, 9.10, 9.11, 9.12, 9.13, 9.14, 10.12, 12.14
HttpFoundation: 1.11, 1.27, 2.4, 2.10, 3.1, 6.1, 6.20, 6.21, 8.13, 9.7, 9.9, 12.12, 13.7, 13.10, 13.13,
13.14, 13.15, 15.8
HttpKernel: 2.14, 3.2, 3.21, 5.7, 5.8, 5.10, 5.11, 8.7, 11.12, 11.13, 13.5, 13.10, 15.10, 16.2, 16.3, 16.5,
16.6
OptionsResolver: 3.16
Process: 3.15, 3.18, 3.19
Routing: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 7.10, 7.11, 7.12, 7.13, 7.14, 7.15, 7.16, 7.17, 8.7, 8.13,
9.2, 15.9
Security: 8.7, 12.1, 12.2, 12.3, 12.4, 12.5, 12.6, 12.7, 12.8, 12.9, 12.10, 12.11, 12.12, 12.13, 12.14,
12.15, 12.16, 12.17
Stopwatch: 3.16, 16.7
Translation: 3.20, 6.21, 6.22, 10.12
Validator: 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9, 10.10, 10.11, 10.12
Yaml: 7.6

Symfony classes and interfaces


AppCache: 13.5, 13.10
AppKernel: 5.3, 13.5, 13.10
Symfony\Bundle\FrameworkBundle\Console\Application: 11.13
Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver: 11.13
Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector: 15.10
Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass: 9.13
Symfony\Bundle\FrameworkBundle\Test\WebTestCase: 15.6
Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector: 15.10
Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigEnvironmentPass: 11.14
Symfony\Component\Console\Input\Input: 14.9
Symfony\Component\Console\Input\InputInterface: 14.9
Index 275

Symfony\Component\Console\Output\Output: 14.9
Symfony\Component\Console\Output\OutputInterface: 14.9
Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface: 11.15
Symfony\Component\DependencyInjection\ContainerAwareInterface: 11.13
Symfony\Component\HttpFoundation\BinaryFileResponse: 6.1
Symfony\Component\HttpFoundation\Request: 1.11, 2.4, 2.10, 6.20
Symfony\Component\HttpFoundation\RequestMatcher: 12.12
Symfony\Component\HttpFoundation\Response: 1.27, 2.10, 6.1, 13.13, 13.14, 13.15
Symfony\Component\HttpKernel\Bundle\Bundle: 5.7, 5.10, 5.11, 11.15, 14.10
Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector: 15.10
Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface: 15.10
Symfony\Component\HttpKernel\DataCollector\EventDataCollector: 15.10
Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector: 15.10
Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector: 15.10
Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector: 15.10
Symfony\Component\HttpKernel\DataCollector\TimeDataCollector: 15.10
Symfony\Component\HttpKernel\EventListener\ExceptionListener: 11.11
Symfony\Component\HttpKernel\Event\GetResponseEvent: 3.21
Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent: 3.21
Symfony\Component\HttpKernel\Exception\NotFoundHttpException: 16.9
Symfony\Component\HttpKernel\Kernel: 5.7, 5.8, 11.13
Symfony\Component\Process\PhpProcess: 3.19
Symfony\Component\Process\Process: 3.18, 3.19
Symfony\Component\Routing\RequestContext: 7.10
Symfony\Component\Security\Core\Authorization\Voter\VoterInterface: 12.16
Symfony\Component\Security\Core\User\InMemoryUserProvider: 12.8
Symfony\Component\Security\Http\AccessMap: 12.12
Symfony\Component\Translation\MessageCatalogue: 3.20
Symfony\Component\Validator\ConstraintViolationList: 10.4, 10.5

Symfony bundles
FrameworkBundle: 5.8, 11.13, 14.8, 14.12, 15.6, 15.10, 16.1
SecurityBundle: 15.10, 16.1
Index 276

TwigBundle: 8.8, 8.9, 11.8, 11.14, 16.1, 16.6


WebProfilerBundle: 5.8, 16.4

Symfony bridges
Doctrine: 3.3
PsrHttpMessage: 4.15
Swiftmailer: 3.3
Twig: 3.3, 8.7

Symfony commands
cache:clear: 14.12
cache:warmup: 8.9, 14.12
container:debug: 4.7, 11.1
generate:bundle: 14.1
generate:controller: 14.1
init:acl: 12.17
router:debug: 7.9, 7.15
router:dump-apache: 7.15
server:run: 14.8
twig:lint: 8.8, 14.3

Twig functions and filters


capitalize: 8.20
controller: 8.7
cycle: 8.26
date: 8.6
default: 8.31
extends: 8.28
first: 8.27
form_end: 9.3
form_start: 9.1
is_granted: 8.7
last: 8.27
lower: 8.20
path: 8.1, 8.7
Index 277

raw: 8.5
render: 8.7
set: 8.11, 8.12, 8.28
spaceless: 8.23
title: 8.20
trans: 8.7
transchoice: 8.7
upper: 8.20
url: 8.1, 8.7
verbatim: 8.32
List of figures
1. Figure 1. PHAR files
2. Figure 2. HTTP + GZIP
3. Figure 3. Kernel events
4. Figure 4. Mediator pattern
5. Figure 5. Different expiration times for each fragment
6. Figure 6. Two ESI fragments, none cached yet
7. Figure 7. Two ESI fragments, both cached and fresh
8. Figure 8. Two ESI fragments, one fresh and one stale
9. Figure 9. Autoloading
10. Figure 10. Loading configuration files

Você também pode gostar