Professional Documents
Culture Documents
Raúl Fraile
This book is for sale at http://leanpub.com/symfony-selfstudy
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.
Special thanks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
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
Appendixes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
Answer sheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
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.
• 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.
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
1. Yes
2. No
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
1. Yes
2. No
1. Yes
2. Only classes can override constants
3. Only interfaces can override constants
4. No, constants can never be overriden
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
// example.php
use Symfony\Component\HttpFoundation\Request;
12. How can you check the syntax of a PHP script without executing it?
// script.php
class Book {
var $title = 'Title';
}
14. Which of the following sentences are true about abstract classes in PHP?
// abstract_method.php
class Book {
abstract function getTitle();
}
1. Yes
2. No
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'])
1. Classes
2. Interfaces
3. Functions
4. Variables
// 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
// script.php
namespace Bundle\Entity;
echo __NAMESPACE__;
1. PHP 7
1. Entity
2. Bundle
3. Bundle\Entity
4. Fatal error, __NAMESPACE__ is not defined
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;
25. Given the file test.php, what happens if you run php test.php?
// test.php
unlink(__FILE__);
echo 'hello';
26. Which of the following sentences are true about PHAR files?
1. new Response()
2. new HttpFoundation\Response()
3. new HttpFoundation::Response()
4. It is not possible
1. spl_autoload_register() - PSR-4
2. closure - abstract class
3. phar - __halt_compiler()
4. __sleep() - serialization
error_reporting(E_ALL ^ E_NOTICE);
$value = 1;
$get = function() {
echo $value;
};
echo 2;
$get();
1. 1
2. 2
3. 12
4. 21
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 {}
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
$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');
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.
// function_names_as_class_names.php
class strpos {}
$strpos = new strpos();
namespace functions and classes with the same name, as shown in the following script:
// script.php
namespace Test;
function test() {}
class test {}
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 {}
// final_method.php
class Book {
final public function getTitle() {}
}
// interface_multiple_inheritance.php
interface BookInterface
{
public function getTitle();
}
interface CategoryInterface
{
public function getCategory();
}
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.
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
// abstract_method.php
abstract class Book {
abstract function getTitle();
}
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:
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
// variable_namespace.php
namespace Foo {
$number = 1;
}
namespace Bar {
echo $number;
}
// 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
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.
// use_alias.php
use Symfony\Component\HttpFoundation as Http;
// levenshtein.php
$commands = [
'start',
'stop',
'reload'
];
// $input = 'statr';
// ...
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
[InvalidArgumentException]
Command "cache:clean" is not defined.
Did you mean this?
cache:clear
$value = 1;
$get = function() use ($value) {
echo $value;
};
echo 2;
$get();
// 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.
// namespace_fallback.php
namespace One;
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.
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. Performance
2. Debugging
3. Security
4. Caching
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
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?
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?
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
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.
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:
// 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.
provides, and is not cacheable. For example, if you send the following HTTP request:
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:
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();
// ...
}
}
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
{
// ...
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
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:
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;
// ...
The HttpKernel component provides several exceptions for common HTTP status codes (the
following exception classes must be prefixed with Symfony\Component\HttpKernel\Exception):
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
Questions
1. Which of the following classes do not belong to the HttpFoundation component?
1. Request
2. Response
3. Firewall
4. Session
1. Yes
2. No
1. app/console
2. web/app.php
3. web/app_dev.php
4. Symfony\Bundle\FrameworkBundle\Controller
1. Form
2. HttpKernel
3. Security
4. DependencyInjection
6. Which of the following statements are true when the debug mode is enabled?
1. True
2. False
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()?
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
// php_process.php
use Symfony\Component\Process\PhpProcess;
echo $process->getOutput();
1. Hello world
3. Architecture 42
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.
// AppKernel.php
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// ...
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.
// 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.
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
{
// 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.response
$this->dispatcher->dispatch(KernelEvents::RESPONSE, $event);
return $event->getResponse();
}
// kernel.exception
$this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event);
// ...
}
// 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()) {
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();
}
}
$ 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:
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();
}
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
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.
// 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:
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.
// PhpProcess.php
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\RuntimeException;
// ...
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;
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;
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;
$this->stopPropagation();
}
}
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
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?
// 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;
}
}
10. Why is it not recommended to use the @Template annotation to configure the template that
must be used by the controller?
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?
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
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.
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:
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
// 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.
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
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
// BookBundle/Controller/BookController.php
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response;
use Zend\Diactoros\Stream;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
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
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
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;
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
// app/AppKernel.php
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Bundle;
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?
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();
// ...
}
}
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.
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;
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;
return $this->bundleMap[$name];
}
// ...
}
5. The Bundles 76
// HttpKernel/Kernel.php
namespace Symfony\Component\HttpKernel\Bundle;
use Symfony\Component\DependencyInjection\ContainerAware;
return dirname($this->reflected->getFileName());
}
// ...
}
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;
// ...
$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
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
// 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
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
// AppBundle/Controllers/BookController.php
class BookController
{
public function helloAction()
{
return 'hello';
}
}
// AppBundle/Controllers/BookController.php
class BookController
{
public function helloAction()
{
return $this->container->get('templating')
->renderResponse('AppBundle::hello.html.twig');
}
}
11. Which of the following sentences are not true about defining controllers as services?
12. How can you generate a 404 response from a controller that extends from the base
controller?
6. The Controllers 83
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?
// 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');
/**
* @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();
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
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;
$response->headers->set('Content-type', 'image/x-icon');
$response->headers->set('Content-length', filesize($filename));
$response->setContent(readfile($filename));
return $response;
}
}
// AppBundle/Controller/BookController.php
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
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.
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.
// Symfony/Bundle/FrameworkBundle/Controller/Controller.php
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\DependencyInjection\ContainerAware;
// ...
// ...
}
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.
# AppBundle/Resources/config/routing.yml
about_us_static:
path: /about_us
defaults:
_controller: FrameworkBundle:Template:template
template: AppBundle:static:about_us.html.twig
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];
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');
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;
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?
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
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?
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.
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;
// ...
}
}
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?
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.
# 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' }
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
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.
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:
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;
// /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;
12. Given the following route definition, what URL would you get if you use the UrlGenera-
7. Routing 110
torInterface::NETWORK_PATH constant?
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.
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:
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.
// 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. Yes
2. No
3. Only in the dev environment
4. Only when twig.debug is set to true
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
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
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
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?
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
// 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 }}
{# index.html.twig #}
{{ data }}
1. strtolower
2. lowercase
3. lower
4. lowerize
21. Is it possible to define additional global variables to be available in all Twig templates?
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 %}
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"))) %}
26. Which of the following are good use cases for the cycle function in Twig?
{# 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") %}
{# ... #}
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
{
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. 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.
{# 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' }}
// 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 = [];
}
// line 2
echo "";
// line 3
echo "hello world";
}
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.
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:
<a href="http://example.com"></a>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") }}
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.
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.
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.
set in loops
In Twig, loops are scoped, so any variable declared inside a loop won’t be available outside.
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:
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.
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:
For example, the following template would generate the resulting trace:
{# loop_variable.html.twig #}
{% for letter in ['a', 'b', 'c'] %}
{# ... #}
{% endfor %}
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>
<table>
<tr class="odd">
<td>1</td>
<td>one</td>
</tr>
<tr class="even">
<td>2</td>
<td>two</td>
</tr>
</table>
{# 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
{
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 %}
• 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
Questions
1. Does {{ form_start(form) }} render the enctype attribute?
2. What was the HTTP method configured in the form given the following HTML?
1. GET
2. POST
3. PUT
4. OPTIONS
3. Which of the following sentences are true about the {{ form_end() }} helper?
6. By default, a form is tied to an array of data. How can you work with an object instead?
// AppBundle/Controller/BookController
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
// ...
}
}
9. Forms 137
// AppBundle/Controller/BookController
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
$form = $this->createFormBuilder([])
->add('title', 'text')
->getForm();
// ...
}
}
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?
$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?
11. What is the HTML code generated for the name field by the following form?
$form = $this->createFormBuilder($book)
->add('name', 'text', [
'required' => true
]);
// ...
}
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
// ...
}
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' %}
{# ... #}
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 -%}
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.
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:
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.
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
// ...
}
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.
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
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);
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?
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.
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;
}
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;
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;
$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;
// 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;
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;
// 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?
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?
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?
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
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?
13. Even if it’s not a good practice, but how can you inject the whole container into a service?
14. Which of the following steps are required to define a new tag to do something special with
tagged services?
11. Dependency Injection 160
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:
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
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;
// ...
}
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:
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;
$definition = $container->getDefinition('twig');
11. Dependency Injection 164
// ...
$serviceIds = $container->findTaggedServiceIds('twig.extension');
foreach ($serviceIds as $id => $attributes) {
$definition->addMethodCall('addExtension', [new Reference($id)]);
}
// ...
}
}
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;
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
{
// ...
}
}
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;
$container = $this->kernel->getContainer();
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;
// ...
}
// ...
}
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;
$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.
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?
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
3. When using HTTP basic, how does the server starts the authentication process?
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.
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?
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
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”
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
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.
// Mailer.php
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class Mailer
{
protected $securityContext;
// ...
}
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
{
// ...
if ($salt) {
$options['salt'] = $salt;
}
// ...
}
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;
// ...
// ...
$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;
// ...
¹⁷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);
// ...
}
// ...
}
// security.php
use Symfony\Component\HttpFoundation\RequestMatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\AccessMap;
$request = Request::create('/profile/admin');
// true
var_dump($matcher->matches($request));
// [0] =>
// 0 => [ ROLE_ADMIN ]
// 1 => NULL
var_dump($map->getPatterns($request));
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.
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.
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
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
1. attempt
2. except
13. HTTP Cache 184
3. include
4. inline
5. When not using a real reverse proxy cache like Varnish, how do you enable the one written
in PHP that ships with Symfony?
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
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…
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'));
return $response;
}
}
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
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
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:
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';
// ...
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:
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
{
// ...
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.
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
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. PHAR file
2. Binary file
3. Plain PHP script
4. Self-executable compressed file
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?
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');
}
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?
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”.
[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.
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.
// app/console
// ...
use Symfony\Component\Console\Input\ArgvInput;
// ...
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
{
// ...
// ...
// ...
}
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
But if you add a PHP script as an argument, it gets executed for each request, acting like a routing
script.
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;
// ...
// ...
}
// ...
}
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;
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
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?
</phpunit>
15. Automated Tests 208
1. Yes
2. No
1. True
2. False
4. How does PHPUnit know how to autoload classes of your Symfony project?
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>
8. When working with the test client, what is the name of the environment where controllers
are executed?
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;
$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
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.
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.
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:
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):
$profiler = $client->getProfile();
$collectors = $profiler->getCollectors();
// memory used
$collectors['memory']->getMemory();
// symfony version
$collectors['config']->getSymfonyVersion();
15. Automated Tests 214
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
1. Performance
2. Ease of debugging
3. Security
4. Workaround for some IDEs
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
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/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
// 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();
Templates are located in the directory Resources/views/Exception of the TwigBundle bundle. There
are different templates for exceptions and errors.
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.
// app/app_dev.php
// ...
// ...
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.
¹⁸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;
// measure
$event = $stopwatch->start($eventName, 'fibonacci');
fibonacci($i);
$event->stop();
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
// 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:
use BookProject\Controller\BookController;
use BookProject\Model as ProjectModel;
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:
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:
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
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.
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
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'
);
}
# 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"
$this->resource = $resource;
// ...
}
// ...
}
}
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
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.
{{ 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.
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
{
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
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:
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
{
// ...
$globals[] = $extGlob;
}
$globals[] = $this->staging->getGlobals();
Creating a class that implements the ArrayAccess interface could be another approach, so getGlob-
als() would return an instance of the class:
If you instantiate the class and access to the random index twice, you will get two different values:
Hands-on exercises 240
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.
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
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
'kernel_cache_dir',
[$this, 'getKernelCacheDir'],
['is_safe' => ['html']]
)
];
}
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
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
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;
// ...
$name = get_class($this);
$pos = strrpos($name, '\\');
// ...
}
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;
// app/cache/dev/appDevDebugProjectContainer.php
use Symfony\Component\DependencyInjection\Container;
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;
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;
protected $loader;
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.
<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;
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;
{
// check if the directory exists
if (!is_dir($dir = $this->getPath().'/CommandLine')) {
return;
}
// ...
}
}
}
Hands-on exercises 251
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):
• 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
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'));
}
}
}
<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');
}
}
}
<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;
}
<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;
}
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
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)
{
$request = $event->getRequest();
$statusCode = $request->headers->get('X-Sf-Exception-Code', false);
if (false === $statusCode) {
return;
}
$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;
$request = Request::createFromGlobals();
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;
$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);
}
}
<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
2. HTTP
3. Architecture
4. Standardization
5. The Bundles
6. The Controllers
Answer sheet 265
7. Routing
8. Twig
9. Forms
Answer sheet 267
10. Validation
12. Security
16. Miscellaneous
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
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\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
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
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