Sunteți pe pagina 1din 159

Easy development with Symfony

Learn how to develop with Symfony and other tools that


will help and will make your work easier in a big way as
developer

Jon Torrado
This book is for sale at http://leanpub.com/learnsymfony2

This version was published on 2017-05-16

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

© 2015 - 2017 Jon Torrado


Tweet This Book!
Please help Jon Torrado by spreading the word about this book on Twitter!
The suggested hashtag for this book is #learnsymfony2.
Find out what other people are saying about the book by clicking on this link to search for this
hashtag on Twitter:
https://twitter.com/search?q=#learnsymfony2
Also By Jon Torrado
Desarrollo fácil con Symfony
To all those who provided me any kind of knowledgement in my life, to all those people who
changed the way of my professional career and those who made me the person I am right now, both
in the personal and the professional way. In this book I wrote a piece of everyone of you. Thank you.
Contents

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Why I’m writing this book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
What you are going to learn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Whom is this book for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
How the book is divided . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

Chapter 1: installation and configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4


Symfony installer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Composer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
MySQL Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Writing Permissions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
The Web Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Development environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Tip 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

Chapter 2: third party bundles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10


Looking for our bundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Installing a new bundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Tip 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

Chapter 3: Admin Bundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13


Installing the Admin Bundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Creating our Admin Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Tip 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Other possibilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

Chapter 4: User Bundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21


Installing the User Bundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Creating the user structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Creating our admin user . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Tip 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
CONTENTS

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

Chapter 5: MopaBootstrap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Installing MopaBootstrapBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Preparing the base template . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Tip 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

Chapter 6: Gulp (1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39


Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Creating our first SCSS file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Minifying our CSS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Autoprefixer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Gulp watch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Default task . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Tip 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

Chapter 7: Assetic for JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48


Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Minifying our JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Recommendations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Tip 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

Chapter 8: full example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54


Application flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Creating the route . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Creating the action method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Our own home . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
The image carousel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
The claims . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Contact form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Sending an email . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Tip 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

Chapter 9: Doctrine Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75


Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Timestampable filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Slugabble filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Softdeleteable filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Other filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
CONTENTS

Tip 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

Chapter 10: LiipImagineBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84


Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Using with Twig . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Using with PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Tip 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

Chapter 11: ther bundles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92


EWZRecaptchaBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
AcceleratorCacheBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
FOSRestBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
NelmioApiDocBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
DoctrineFixturesBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
DoctrineMigrationsBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
FOSJSRoutingBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
HWIOAuthBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
KnpSnappyBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
JMSTranslationBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Tip 11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

Chapter 12: Gulp (2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98


Sass Lint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
JSCS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
PHP CS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
PHP CBF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Executing PHP commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Tip 12 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

Chapter 13: additional Symfony configuration . . . . . . . . . . . . . . . . . . . . . . . . . 110


ParamConverter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Template without controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Redirecting without controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Dump autoload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Hide logs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Send 500 errors to an email . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
PhpStorm configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
Moving the sessions folder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Tip 13 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
CONTENTS

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

Chapter 14: modifying SonataUserBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . 119


Modifying FOSUserBundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Overriding the templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Overriding the controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Modifying the login and the sign in . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Modifying the sign in (2) and the emails . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Other modifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
Tip 14 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

Chapter 15: deployment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126


Magallanes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Ansible . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Capifony . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Capistrano . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Deploying with capistrano-symfony . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
Capistrano-symfony: final result . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Tip 15 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134

Chapter 16: testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135


What is testing? Do I need it? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
TDD vs BDD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
Testing tools in PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Continuous integration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
Tip 16 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

Epilogue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Bower . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Gassetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Gulp with JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
PostCSS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Symfony made developments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
DDD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Did you like the book? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Thanks to readers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Introduction
Since I started my professional career, almost everything I’ve done was related to the website
world. When I finished my university studies, my knowledgement about web development was very
limited. I just knew a few HTML elements and something a bit more sophisticated called Struts, a
MVC Java framework. I had to learn in a record time in order to live up the environment I was
entering, and that was possible because of the huge documentation and help that everyone can find
through Internet.
I started using a bunch of CMS for different clients, such as Moodle or Drupal, to finally involve
myself in the custom development. Everything was handmade! I had to maintain a website with no
frameworks and almost no libraries at all, everything done from scratch. Thank God I had to do this
kind of stuff! This was the project that totally changed my professional career because I could get
myself inmersed into the deepness of all kind of libraries and spaguetti codes that belonged to really
interesting people.
After serveral unexpected changes, I started my new journey in the Symfony world. Thanks to its
excelent documentation, which I strongly recommend reading more than once a year, and to third
party bundles that are maintained by a really active community, I was developing websites in a very
short term that where incredibly powerful and with a ridiculous effort comparing to what I had been
developing until that moment.
I’ve been developing websites with Symfony for some years, so I consider this the exact moment
to write the book that you are reading right now. Here I’m going to collect what I consider really
important in order to start developing a Symfony project, and I will try to make you able to use
technologies which will make your work easier in a really big way, a way that you would never have
imagined (at least I hadn’t imagined it). Don’t you think that I’m writing the definitive book that
will dominate the world, because nowadays I’m still learning from a sector that changes every single
day and where it’s almost impossible to follow the frenetic changing rhythm where it belongs. But…
I will try to be at the height of the situation. I just want you to do something right before reading
this book: read the Symfony documentation. This documentation is simply great and translated to
a lot of languages, so I won’t repeat any single part of it just because there you can find it perfectly
explained. You don’t have to be a Symfony professional tho, but you have to know what are routes,
controllers, templates and the Symfony console at least (in summary, the first 8 chapters of the
documentation). Are you ready to start? So do I, let’s start!

Why I’m writing this book


One of the most important pieces of my life is the “teaching part”. The possibility to expand your
knowledgement to other people and make this area increase with the work of everybody is something
Introduction 2

that I consider vital with any topic, not just this one. I believe that in the actual society, or at least
in the country where I live, we are lack of excelent teachers that try to improve themselves every
day because, making the best of them, they will make their studendts give the best of them too.
I want to connect what I just told you with two very interesting Japanese statements. The first one
(although it’s an urban leyend, let me be happy telling it to you) is that, in their culture, teachers
are the only ones that are allowed to stay without making a reverence to the emperor, because they
consider that without teachers, there will be no emperor.
On the other side there is an idea that since I read it, it has applied a substancial change to my life:
Kaizen. This Japanese idea consists of the improvement process that we should apply every single
day.
What I’m going to write in this book is what I learnt freely during several weeks, even years, and
I’ve decided to share it with you so you don’t have to spend so much time in learning it by yourself.
Moreover, I consider that labeling this book with a price for something I learnt without speding a
single coin and that nowadays makes me eat every month is totally absurd. Because of this, I want
to share the book completely free, but if you like what you have read right after finishing it and
you want to thank me the work I’ve done, you can buy it for the price you consider knowing that
you are contributing for a good cause, because I will donate the 50% of the earnings of this book to
the watchi¹ crowdfunding platform which funds healthcare for people around the world. You learn,
you enjoy and you help a good cause.

What you are going to learn


During this book we will make some installations and configurations in a practical way in order to
build a base project that will help you in the future for every project you develop. Some of the stuff
that we will use here may not be necessary for some of these projects; others may need different
things and you may need to discover them by yourself. I will try to cover the majority of the elements
that are used nowadays in Symfony and in the web delopment too, elements that may be inserted
into our base project.
You won’t learn how to use Symfony from the very beginning. I consider that the official Symfony
documentation is awesome for this commitment. But after reading the basic part of it, consisting of
the first 8 chapters of the documentation, my aim is that you know how to use Symfony in a more
professional way and final product oriented.
Is there something missing in the book? Nevermind, you have my Twitter account to contact me and
tell me what you think this book should have, or something that should be added. I will recollect
every message and add them as soon as possible. Also, I will try to keep the book maintained so it
can be used as an extra documentation for your projects.
¹https://leanpub.com/causes/watsi
Introduction 3

Whom is this book for


This book is for every single person that is interested in learning how to develop with Symfony, no
matter if he or she knows how to use this framework or not. Even the people that did never develop
with PHP, o maybe people that did never develop at all. Logically, depending on the group of people
you belong, it may be more difficult for you to get some of the statements that I will explain during
the book, but this is not saying that you are out of the target at all.
Regarding the level of the reader, I will risk myself saying that this book is for beginner -
intermediate developers. If you have been developing with Symfony and you are a fan of the web
development, it’s possible that this book doesn’t discover anything new to you. However, it may be
possible that the chapters dedicated to external techologies surprise you with a new tool, but this
could be just a coincidence.
You should feel like reading the book and a bit of willpower, it’s just what you need in order to enter
this world, come on!

How the book is divided


Every chapter will contain a very different explained topic in the way I work. Moreover, every single
chapter will have a trick section that will reveal something special that may help you to be more
efficient in what you do, or it may help you to make things better by showing some shortcuts and
tricks.
We will put the cart before the horse and, after installing the framework we will fill it with a bunch
of third party bundles right before writing a line of code. These bundles will allow us to have a pre-
built base for our projects. It’s possible that some of them are useless, but even though this happens,
it’s very interesting that you spend some time to know how they are built behind the scenes. This
is something that I leave in your hands because as a developer you should be lively and look over
the “it works and that’s all”.
Time to start working. We will begin with the installation and its best practices.
Chapter 1: installation and
configuration
I know that this part is very well covered in the official Symfony documentation², but there are
certain concepts that are divided in various chapters. Some of them can even be done in several
ways. And the question is, which one is the best? This answer is going to be repeated a lot of times
in this book: there isn’t a better or a worse way, you will have to choose the one that fits better to
you.
Of course, there are some best practices that developers usually try to follow, and with that best
practices, I would like to share with you my own best practices during this book. Before writing
down the steps I usually follow, you should know that I work under Linux and/or OSX. Some of the
commands you will read here won’t work with Windows and you will have to adapt them a little
bit. But don’t panic because I have some good news for you: the official documentation covers the
Windows part.
The goal of this chapter is make you learn how to install a Symfony project, preparing your
operative system for future installations of the framework making them a lot easier. Let’s start!

Symfony installer
There’s been a while since Symfony created its own installer in a .phar³ file. Before having this
installer, the developers had to download a compressed file from the official Symfony website and
uncompress it in the folder where he/she was going to develop; after that, they added the project
to Packagist⁴ and the dependency manager Composer⁵ was used as an installer. Nowadays, the best
thing you can do is keeping the Symfony installer as a binary of your operative system so you can
execute it as any other command. In order to achieve this, type the next commands or the ones that
are in the documentation if you are a not a *NIX user:

1 sudo mkdir /usr/local/bin


2 sudo curl -LsS http://symfony.com/installer -o /usr/local/bin/symfony
3 sudo chmod a+x /usr/local/bin/symfony

Once this is done, you will be able to execute the symfony command from the path you want. As an
advice, I must tell you that you should keep the installer updated executing the following command:
²http://symfony.com/doc/current/setup.html
³http://php.net/manual/es/phar.using.intro.php
⁴https://packagist.org/
⁵https://getcomposer.org/
Chapter 1: installation and configuration 5

1 sudo symfony self-update

Awesome! Now that you own the installer in a global way, you can create your first Symfony
project. So, execute the next command:

1 symfony new aupa_bilbao

The previous command will create the folder aupa_bilbao with every single dependency (needed
to start working without losing a minute, everything automagically. If you see, at the end of the
command there is a 2.8. This is telling the Symfony installer to download the latest 2.8 Symfony
Standard. If you delete that version number, you will get the latest non-dev Symfony Standard. We
must use the 2.8 version because the pre-built admin does not work (yet) with Symfony 3. But almost
everything of the book works well with Symfony 3. If you’ve read the Symfony documentation or
if you have used Composer previously, you should know that the vendor folder, placed in the root
of the project, contains all the needed dependencies for your project. These depenencies are defined
in the composer.json file which is also placed in the root of the project. We will be using this file
a lot of times during the book. However, there is a very similar file named composer.lock which
shows the blocked versions of the installed dependencies. This versions will be the ones installed
when composer install command is executed.
At this point, we already talked several times about Composer, but, what is Composer?

Composer
Composer is the excelence tool for the PHP dependency management that will help us to download
and insert “packages” or libraries done by others in our project and then use them in a very simple
way. If you have already used “npm” from Node or “bundler” from Ruby, this is very similar; if it’s
not the case and you’ve never used them, don’t worry about it because you will have the oportunity
to use them in this book.
Same as with the Symfony installer, the best way to use the Composer binary is as an operative
system command and, with this done, execute it wherever you want. To achieve the goal, execute
the following commands:

1 curl -sS https://getcomposer.org/installer | php


2 sudo mv composer.phar /usr/local/bin/composer

Talking about the Symfony project, thanks to his own installer, we already have the “base packages”
downloaded within the vendor folder and the composer.json and composer.lock files well defined.
But if the project you are working in a certain moment is a downloaded project or you are
deploying a project to a server, you should execute the composer install command at the root
of it. This command will install the concrete versions specified in the composer.lock file and,
Chapter 1: installation and configuration 6

moreover, they should be the exact versions that were already tested and which are lack of bugs
and incompatibilities.
However, if you launch the composer update command, the process will ignore the blocking file
and it will update to the latest possible version defined within the composer.json file, updating the
blocking file when it finishes the download process.
Hey! Do not forget to update your dependencies, every week there are lots of bug fixes fixed or
some new features are added which will definitely improve the value of your project. Of course,
keep your Composer updated too with the following command:

1 sudo composer self-update

MySQL Database
I still find projects with a latin1 database codification, nowadays! I suggest you to avoid this
codification and use an UTF-8 one for every project you develop. With Symfony, you won’t need
to manually create the database because the console will create it for you, but in order to use the
correct codification by default, you should do one of these two possibilities. First one: you need the
doctrine-bundle the be least the 1.6 version (composer.json):

1 "doctrine/doctrine-bundle": "^1.6",

And then tell the Symfony framework to use the correct codification. Edit app/config/config.yml:

1 # Doctrine Configuration
2 doctrine:
3 dbal:
4 #...
5 default_table_options:
6 charset: utf8mb4
7 collate: utf8mb4_unicode_ci
8 engine: InnoDB

The other possible option is to edit the my.cnf. This configuration file contains all the parameters
for the MySQL database, and you should add the following two lines to the [mysqld] section:

1 [mysqld]
2 collation-server = utf8mb4_unicode_ci
3 character-set-server = utf8mb4

With this done, you just need to tell Symfony what are your database connection parameters. Go to
the app/config/parameters.yml file, which was automatically created with the installer, and edit
the parameters that you can find there with the ones that fits your machine. Once modified, execute
the next command in order to create the database:
Chapter 1: installation and configuration 7

1 bin/console doctrine:database:create

If you use Windows, you must place php just before the command.

Good work! You already have your database created to start working. You just need to know one
more thing: Symfony uses a series of folders that need to have writing permissions. Those directories
will be used by your web server and your console user, so both of them should have permission to
write within those folders.

Writing Permissions
This is a common mistake that I usually find in a lot of local deployments. A lot of people forget to
give write permission to the var folder, where the cache folder resides, for example.
In this link⁶ you can find how can give permissions to this folder, and if you are using an Ubuntu
based Linux operative system, the following commands are OK for you:

1 HTTPDUSER=`ps axo user,comm | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]gin\


2 x' | grep -v root | head -1 | cut -d\ -f1`
3 sudo setfacl -R -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX var
4 sudo setfacl -dR -m u:"$HTTPDUSER":rwX -m u:`whoami`:rwX var

The first line stores in the variable HTTPDUSER the user who is executing our web server, because
depending on the operative system and the server, this user varies. The other two lines will take
care of giving that user and the console user (the user who will execute terminal commands) the
right permission to write in the var folder.
Given the pertinent permissions, you are about to finish the installation.

The Web Server


You have the possibility to work with Apache in a native way by creating a VirtualHost in your
computer. It’s not the aim of this book to show you how this is done because there exists a much
simpler way that, even it’s not so powerful, it fits our didactical needs. If you want to know how to
achieve the very first option, visit this link⁷.
At this moment, and supposing you already have PHP installed in your computer, the only thing
you have to do is execute this command in the root of your folder:

⁶http://symfony.com/doc/current/setup/file_permissions.html
⁷http://symfony.com/doc/current/setup/web_server_configuration.html
Chapter 1: installation and configuration 8

1 bin/console server:run

If you read the terminal output, Symfony is telling you that you can see your project navigating to
http://127.0.0.1:8000/. Regrettably, there is (almost) nothing to see at all, but we will solve this very
soon.

Development environment
If you are going to develop with Symfony, I suggest you to install a development environment.
Nowadays, the IDE that works like a charm for Symfony is PhpStorm⁸. There are some others like
NetBeans or Aptana, or even editors like Sublime or Brackets that work perfectly. Try them all
and choose the one that fits better your way of working. Personally, the one that I suggest you is
PhpStorm, which will be present in a future “tip”.

Tip 1
In every single chapter I will give you an advice or a trick that will help you a lot with without any
doubts. It depends on you whether to use this tips or not (use them!!). From now on, I will use the
Symfony console quite a lot. As you could see, in order to execute the different commands you
should type bin/console every single time, and if you want to change the execution environment
from dev to prod, which is the default environment, you must add --env=prod at the end of every
command you execute.
With the following tip you will give a rest to your keyboard and, of course, to your hands:

1 alias dev = php bin/console --env=dev


2 alias prod = php bin/console --env=prod

Those alias are not maintained after rebooting your machine, so search for the solution to keep
those alias forever in your user (I should make you work a little bit, shouldn’t I?)
In addition to the alias, there is no need to type the full command: it’s only needed the
discriminatory part of it. What are you talking about? You will see this much brighter with an
example, so look to the following command:

1 dev s:r

The previous line will execute php bin/console --env=dev server:run. Why? Because there are
just two commands that start with “s”: server and swiftmailer. But, inside them, there are just one
that starts with “r”, which is server:run.
Congratulations! You are more efficient than a lot of Symfony developers, who usually don’t know
this little trick :)
⁸https://www.jetbrains.com/phpstorm/
Chapter 1: installation and configuration 9

Summary
In this chapter we have learnt the correct way to deploy a new Symfony project in your computer to
start working with it. Thanks to the global installation of its installer and the Composer dependency
manager, the following new installations will be done in question of minutes, even seconds!
Chapter 2: third party bundles
One of the biggest things that Symfony has and it does share with a lot of open source applications
is that the community apports elements which enrich the framework. There exist several ways of
collaborating with Symfony, but the creation and mainteinance of bundles that give extra features
to the ones that come out of the box is a key point that makes Symfony so used worldwide. During
this chapter, we will learn the basis to install third party bundles, even though every single one
of them usually have different configuration between each other. Let’s start!

Looking for our bundle


Before starting to develop a new feature in your project, you should know this concept: DRY. These
acronym means Don’t Repeat Yourself, so “don’t reinvent the wheel”. A lot of the developments that
you are going to code for your new website may have been done by other people: user management,
image management, database connection and modeling, even NoSQL databases… why do you need
to re-do what others have done and maintain for you?
Is for this reason that, if you know what you need to add into your code is generic enought that other
people may have done it before, I recommend you to visit knpbundles.com⁹ and search here for what
you need. Of course, you can also look in Google or Packagist “what-I-need symfony bundle”, but in
KnpBundles you can navigate between different bundles that are used by other people, filter them
with different criteria and, thanks to this, you may learn and/or use something that was not planned
but that is really interesting and fits perfectly, don’t you think so?
It seems that I have already convinced you by the way, and I’m earning a little piece of your developer
heart, so let’s see how to install any bundle into your project.

Installing a new bundle


You already found what you need and now it’s time to start using it in your project. During this
book, we will install several third party bundles to end with a solid and powerful base project, but
before installing anything, I think that it’s convenient to write about the generic steps you need to
take in order to insert any bundle that you consider useful.
First of all, you need to update the dependency description file composer.json so, when the update
command is launched, it will download the new dependencies into the vendor folder. You can find
in the bundle documentation or in Packagist what you need to insert in this file. Be careful with the
⁹http://knpbundles.com/
Chapter 2: third party bundles 11

versions, both with dependencies and Symfony, not all bundles support all Symfony versions and
vice versa. It’s possible that sometimes you need to investigate a little bit in the documentation to
select the right version of the bundle that fits your needs.
After updating the file, you should launch the update Composer command in order to install the
new dependencies:

1 composer update

The next step is the bundle configuration. As with the composer version, the majority of the bundles
have an specific configuration that you should add into the app/config/config.yml file. The
concrete configuration should be given by the bundle creator, and it will allow you to parametrize
your new dependency so it fits and works like a charm in your project, just in the way you want.
Careful: there are also some bundles that require even more modifications in other framework files
like the routing system, the services or the security related configuration files. Actually, every file is
a configuration file in which you should paste a series of lines that you had previously copied from
a README file or from the documentation.
When this is done, the last step is the bundle activation. It seems like a stupid step but, when you
work everyday with this stuff, this is the kind of action that you forget to do. In order to enable
the new dependencies, you should edit the app/AppKernel.php file and add the instatiation lines
needed that should also be given by the author in the documentation. If you can’t find those lines
for whatever reason, you can go to the vendor folder and look for the main bundle file to find the
specific class name to instantiate.
Everything done! “Tatoo yourself” these steps in your brain source code, even though you can look
whenever you want this chapter to remember everything. These steps are very easy and they will
open you a lot of doors with possibilities for the Symfony environment, don’t you believe this? No
problem, in the next chapters, without coding a single line, you will have a Symfony with everything
you’ve ever wished.

Tip 2
I don’t know if this must be called a tip or a suggestion, but no matter what, it’s something that you
should have in mind everyday as a developer. You have seen in this chapter that knpbundles.com
is a good reference website for third party Symfony bundles. Don’t block yourself searching here
what you need and when you need. You should visit this website with assiduity in order to know
what is happening and what others are using. Be curious and keep yourself updated knowing the
biggest part of the community stuff. You will earn a competitive advantage over all the other people
that are isolated from the community or that are in their comfort zone reusing what they have been
doing for years. In the world that you have decided to enroll, a year means lots of changes and
modifications. You must be in this “ship of changes”, or you will be left in the harbour without even
a “good bye”.
Chapter 2: third party bundles 12

Summary
In this chapter we have learnt the basis to install almost any third party bundle. We have established
some generic steps that will allow you to install and configure the majority of the community
developments. In the next chapter, we will start installing one of the most powerful and used bundles
in the Symfony world: the Admin Bundle from Sonata Project.
Chapter 3: Admin Bundle
We’ve seen in the previous chapter that there exists an active community that apports a lot of
interesting elements to the Symfony environment. Some of them work much harder than others,
and the Sonata project is with no doubts one of the great ones to follow. In their bundle stack¹⁰
you will be able to find out several awesome solutions. In the previous link you can read information
about the bundles that they are working actively at the moment so you can fill your neurons with
interesting stuff. Moreover, during this book, we will try some of these bundles, so you will learn
how to install them, how to configure them and, of course, how to use them. The rest of the bundles
are your homework. Let’s go!

Installing the Admin Bundle


Installing the Admin Bundle is pretty easy nowadays because Sonata has generated a parent
development called core bundle that makes the other installations much easier. For this didactical
example, we will suppose that we will be using Doctrine ORM with a MySQL database, but you
should know that this can be used with a MongoDB database that everybody is talking about during
the last months or even years.
The first thing we are going to do is edit the composer.json file and update our dependencies. In
order to achieve that, we can do it manually or we can execute the following command from the
root of our project:

1 composer require sonata-project/admin-bundle ^3.3

Be careful with the version and check the Sonata Admin GitHub repository for the correct version
that you need, always using the latest stable branch.
As an advice, I will tell you to keep attention to the terminal each time you intall a new dependency,
because you can read lines such as:

1 name1/bundle1 suggests installing name2/bundle2

You should at least read the suggested bundle documentation because it may be very interesting for
your project due to its strict relation with something you have just installed.
The next step is installing the bundle that allows the admin to manage the MySQL database:
¹⁰https://sonata-project.org/bundles/
Chapter 3: Admin Bundle 14

1 composer require sonata-project/doctrine-orm-admin-bundle ^3.1

After that, you need to enable the bundle and its dependencies. In the previous chapter we did this in
the last step. Why? Just because if you enable the bundle without having finished the configuration,
a lot of errors will come up (if the bundle needs a configuration, indeed), so if you leave the
configuration in any of the intermediate steps and you want to keep on doing what you were doing
right before the installation process, you will have to deactivate the bundle again and finish the
installation in another moment. We are going to finish it right now so the order doesn’t really
matter, but keep in mind that you can do this step at the end. To enable the required dependencies,
edit the app/AppKernel.php file adding these lines just before the AppBundle:

1 new Knp\Bundle\MenuBundle\KnpMenuBundle(),
2 new Sonata\CoreBundle\SonataCoreBundle(),
3 new Sonata\BlockBundle\SonataBlockBundle(),
4 new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
5 new Sonata\AdminBundle\SonataAdminBundle(),

For the next step, we have to configure the different bundles that we have just enabled. Everything
you need is in the official documentation, and as a teacher said to me: “a good developer knows how
to copy and paste effortless”. Edit the app/config/config.yml file and add the next configuration
lines:

1 sonata_block:
2 default_contexts: [cms]
3 blocks:
4 sonata.block.service.text:
5 sonata.admin.block.admin_list:
6 contexts: [admin]
7 sonata.admin.block.search_result:
8 contexts: [admin]

Furthermore, we need to activate the translator service. To achieve this, in the same config.yml
file, you should delete the preceeding “#” to the translator line at the top of the file. Did you find
it? Cool, let’s continue.
At the moment, we will do the first “fireproof”: copy the assets to the public folder and clear the
cache. If any of the following commands gives you an error, I have to tell you that you have done
something wrong in any of the previous steps:
Chapter 3: Admin Bundle 15

1 bin/console assets:install web --symlink


2 bin/console cache:clear

To understand those lines you should have read the Symfony documentation (what? You haven’t
read it yet?!?). For the latest ones: the first line of the previous code copies the assets (resources such
as JavaScript files, CSS, images, …) from the non public folders (src/..., vendor/, etc.) to the public
web folder, in this case, creating a symbolic link (the best option when we are in the development
stage, don’t do this at production); the second line simply clears the cache in order to regenerate the
necessary elements for a correct rendering.
Amazing! We already have the admin installed but… it’s not funcional yet. The next part of
the configuration will allow us to access the admin from our browser. So, we need to edit the
app/config/routing.yml file and add the routes that already come with the vendor and that will
make the development reachable from a browser. Add the next configuration lines at the top of the
file:

1 admin:
2 resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
3 prefix: /admin
4
5 _sonata_admin:
6 resource: .
7 type: sonata_admin
8 prefix: /admin

Do you know that you already have an admin working? If you have the server up and running, just
navigate to http://127.0.0.1:8000/admin and you will see your empty and deserted administration
panel. I guess you have some questions right now:

• How do I insert elements to this administration panel?


• I don’t even have a user and I’m able to see it, where is the security?
• It’s ugly! How do I modify the styling?

Keep calm young padawan, every single part will come at its moment, and right now it’s just the
moment to answer the very first point of the previous list, because in the next chapter we will
install another bundle that uses this admin. With it, we will have these features out of the box
automagically:

• User management: sign in, email activation, password recovery, login, …


• User administration: from the admin panel, role edition, …
• Admin security: answering one of the previous questions.
Chapter 3: Admin Bundle 16

• Etc.

The best of all of this is that the user bundle installation is just 10 minutes long, and the power that
it gives you is awesome. You will never take care again of coding the user management anymore for
any of your websites.

Creating our Admin Class


It’s worthless to have and admin if you can’t insert our own elements to it. In this example, we are
going to “develop” a CRUD (Create, Read, Update, Delete) for an entity adding a filtering and an
order system.
To start, you must create the Product.php class as it is written in the Doctrine chapter in the official
Symfony documentation¹¹:

1 <?php
2 // src/AppBundle/Entity/Product.php
3
4 namespace AppBundle\Entity;
5
6 use Doctrine\ORM\Mapping as ORM;
7
8 /**
9 * @ORM\Entity
10 * @ORM\Table(name="product")
11 */
12 class Product
13 {
14 /**
15 * @ORM\Column(type="integer")
16 * @ORM\Id
17 * @ORM\GeneratedValue(strategy="AUTO")
18 */
19 protected $id;
20
21 /**
22 * @ORM\Column(type="string", length=100)
23 */
24 protected $name;
25
¹¹http://symfony.com/doc/current/doctrine.html#add-mapping-information
Chapter 3: Admin Bundle 17

26 /**
27 * @ORM\Column(type="decimal", scale=2)
28 */
29 protected $price;
30
31 /**
32 * @ORM\Column(type="text")
33 */
34 protected $description;
35 }

Remember to generate the getters and the setters and update your database right after creating or
modifying any of your entities:

1 bin/console doctrine:generate:entities AppBundle


2 bin/console doctrine:schema:update --force

The next thing you have to do is declare the admin class that will be the CRUD for this entity. If
you come from previous versions of Symfony, you will know that the dependency injection of every
bundle loaded a file called services.yml. Personally, I like to edit that dependency injection to load an
admin.yml file which also loads services, but it allows us to keep the code well “ordered”: everything
in its propper place. In this chapter’s tip, I will share with you how to achieve this goal, but for this
example, you will have to edit the app/config/services.yml file to load our admin class from there:

1 services:
2 sonata.admin.product:
3 class: AppBundle\Admin\ProductAdmin
4 tags:
5 - { name: sonata.admin, manager_type: orm, group: "Content", label: \
6 "Product" }
7 arguments: [~, AppBundle\Entity\Product, ~]

Excelent. As you can read from the previous code, we need to create a ProductAdmin class that is a
copy & paste from the documentation one, modifying just the entity fields:
Chapter 3: Admin Bundle 18

1 <?php
2 // src/AppBundle/Admin/ProductAdmin.php
3
4 namespace AppBundle\Admin;
5
6 use Sonata\AdminBundle\Admin\AbstractAdmin;
7 use Sonata\AdminBundle\Datagrid\ListMapper;
8 use Sonata\AdminBundle\Datagrid\DatagridMapper;
9 use Sonata\AdminBundle\Form\FormMapper;
10
11 class ProductAdmin extends AbstractAdmin
12 {
13 // Fields to be shown on create/edit forms
14 protected function configureFormFields(FormMapper $formMapper)
15 {
16 $formMapper
17 ->add('name')
18 ->add('price')
19 ->add('description')
20 ;
21 }
22
23 // Fields to be shown on filter forms
24 protected function configureDatagridFilters(DatagridMapper $datagridMapper)
25 {
26 $datagridMapper
27 ->add('name')
28 ->add('price')
29 ;
30 }
31
32 // Fields to be shown on lists
33 protected function configureListFields(ListMapper $listMapper)
34 {
35 $listMapper
36 ->addIdentifier('id')
37 ->add('name')
38 ->add('price')
39 ->add('description')
40 ->add('_action', 'actions', array(
41 'actions' => array(
42 'edit' => array()
Chapter 3: Admin Bundle 19

43 )))
44 ;
45 }
46 }

We made it! If you visit http://127.0.0.1:8000/admin again, you will be able to see the product section.
Try to create, edit, filter and the rest of the available actions that it gives you for free.
From now on, adding more stuff to your administration panel is a “piece of cake”: create an admin
class and enable it in the services.yml file, knowing that the PHP class and the service declaration
are practically an exact copy & paste from every other admin that you have previously created.
In this example, I added you some modifications that are not in the official documentation, like
the edit button in the list. With this admin, you also will be able to add your own buttons that
are associated to your own controllers, so use the admin for everything that fits to you, and adapt
the admin so it fits the rest of the actions that doesn’t come out of the box. How do you do this?
Documentation and trials, come on! By now, here you can read the Sonata Admin Bundle base
configuration¹². Try to change the title, the logo and other elements that you need.

Tip 3
It’s possible that you like to have the admin part completely centralized in your bundle and you don’t
want to depend on the services file that it’s in the app folder. This is achieved with the dependency
injection in a very simply way. If you create a new bundle, this class is already created by default.
You can try this with the following command:

1 bin/console generate:bundle

But without any doubts, the best way to learn is trying it yourself (and breaking everything). To do
so, here you have the necessary documentation in this link¹³. Remember to modify the part of XML
with YAML:

1 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Res\


2 ources/config'));
3 $loader->load('services.yml');
4 $loader->load('admin.yml');

Note: the previous link could change its content because the bundle documentation is
in a re-estructuration process.
¹²https://sonata-project.org/bundles/admin/master/doc/reference/configuration.html
¹³http://symfony.com/doc/current/bundles/extension.html
Chapter 3: Admin Bundle 20

Other possibilities
There are other possibilities to create administration panels effortless in Symfony. Javier Eguiluz¹⁴
does have one of them, nowadays with more GitHub stars than the recently shown Sonata
admin. You should, at least, try it and see if you like it and if it fits better with your projects:
https://github.com/javiereguiluz/EasyAdminBundle¹⁵.

Summary
In this chapter we have learnt to install the Admin bundle from the Sonata project and we have
created our first admin class applied to the admin panel. During the next chapter we will install the
user management system based on this administration development that will give us the complete
control over them, and it will also secure our recently installed admin.
¹⁴https://github.com/javiereguiluz
¹⁵https://github.com/javiereguiluz/EasyAdminBundle
Chapter 4: User Bundle
In this chapter we will extend the admin we’ve just created in the previous chapter and, in
addition, we will give some vitamins to our base project. With the Sonata User Bundle we will
have a complete user management thanks to the incorporation of a well known bundle called
FOSUserBundle. Sonata simply gives you a layer over this bundle that improves the integration
between its admin adding some other extra features. As we did in the previous chapter, the steps
we are going to follow are: installation, configuration and then learn how to use it. Let’s start!

Installing the User Bundle


In the bundle documentation¹⁶ you can read that before installing this bundle we must previously
install Sonata Admin and Sonata Easy Extends bundles. The first one is already installed; the second
one is installed with Composer with the following command:

1 composer require sonata-project/easy-extends-bundle

We already have the requirements correctly installed, so we can start installing Sonata User Bundle.
In order to make it work under Symfony 3, you must add the following lines:

1 "sonata-project/user-bundle": "4.x-dev"

NOTE: as soon as it becomes stable, I will edit the book with the final versions.
As you’ve read in the chapter introduction, this bundle is no more than a layer over FOSUserBundle.
It uses FOSUserBundle version 1 stable release. There is an active branch working with the
FOSUserBundle version 2, but nowadays is not stable yet, so that branch won’t be released until
FOSUsedrBundle 2 gets tagged (please, check it!). What you need right now is to enable both bundles,
as always, editing the app/AppKernel.php file:

1 new Sonata\EasyExtendsBundle\SonataEasyExtendsBundle(),
2 new FOS\UserBundle\FOSUserBundle(),
3 new Sonata\UserBundle\SonataUserBundle(),

The following configuration steps are usually the ones that make people fail at installing the bundle,
so please be aware. The first step is adding the following lines to the app/config/config.yml
configuration file:
¹⁶https://sonata-project.org/bundles/user/master/doc/reference/installation.html
Chapter 4: User Bundle 22

1 fos_user:
2 db_driver: orm
3 firewall_name: main
4 user_class: Sonata\UserBundle\Entity\BaseUser
5 group:
6 group_class: Sonata\UserBundle\Entity\BaseGroup
7 group_manager: sonata.user.orm.group_manager
8 service:
9 user_manager: sonata.user.orm.user_manager

In addition, in the dbal configuration located in the doctrine configuration section, you must add
the last two lines of the following configuration block. Be careful with the indentation because it’s
a YAML file:

1 doctrine:
2 #...
3 dbal:
4 types:
5 json: Sonata\Doctrine\Types\JsonType

Be sure to have the auto_mapping parameter activated in the orm piece of configuration, in the
same part as the dbal section.
Lastly, modify the sonata_block part of the configuration in order to some more lines. This is the
final result:

1 sonata_block:
2 default_contexts: [cms]
3 blocks:
4 sonata.user.block.menu:
5 sonata.user.block.account:
6 sonata.block.service.text:
7 sonata.admin.block.admin_list:
8 contexts: [admin]
9 sonata.admin.block.search_result:
10 contexts: [admin]

Amazing! Now we can load the new routes that will allow us to sign in, request a new password
or see our platform profile. We will also add some routes that belong to the security part of the
bundle between Sonata User and Sonata Admin (and without coding a line!). You just have to add
the following lines to the app/config/routing.yml file:
Chapter 4: User Bundle 23

1 # Sonata User
2 sonata_user_admin_security:
3 resource: '@SonataUserBundle/Resources/config/routing/admin_security.xml'
4 prefix: /admin
5
6 sonata_user_admin_resetting:
7 resource: '@SonataUserBundle/Resources/config/routing/admin_resetting.xml'
8 prefix: /admin/resetting
9
10 # FOS User
11 fos_user:
12 resource: '@FOSUserBundle/Resources/config/routing/all.xml'
13
14 fos_user_group:
15 resource: '@FOSUserBundle/Resources/config/routing/group.xml'
16 prefix: /group

Finally (for now, do not cry victory), you have to configure the security part of your website. The
file that owns this configuration part of the platform is app/config/security.yml. You can delete
the whole content of the file and add the code that I share with you here very nicely:

1 security:
2 encoders:
3 FOS\UserBundle\Model\UserInterface: bcrypt
4
5 role_hierarchy:
6 ROLE_ADMIN: [ROLE_USER, ROLE_SONATA_ADMIN]
7 ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
8 SONATA:
9 - ROLE_SONATA_PAGE_ADMIN_PAGE_EDIT
10
11 providers:
12 fos_userbundle:
13 id: fos_user.user_manager
14
15 firewalls:
16 # Disabling the security for the web debug toolbar, the profiler and Ass\
17 etic.
18 dev:
19 pattern: ^/(_(profiler|wdt)|css|images|js)/
20 security: false
21
Chapter 4: User Bundle 24

22 # -> custom firewall for the admin area of the URL


23 admin:
24 pattern: /admin(.*)
25 context: user
26 form_login:
27 provider: fos_userbundle
28 login_path: /admin/login
29 use_forward: false
30 check_path: /admin/login_check
31 failure_path: null
32 logout:
33 path: /admin/logout
34 anonymous: true
35
36 # -> end custom configuration
37
38 # default login area for standard users
39
40 # This firewall is used to handle the public login area
41 # This part is handled by the FOS User Bundle
42 main:
43 pattern: .*
44 context: user
45 form_login:
46 provider: fos_userbundle
47 login_path: /login
48 use_forward: false
49 check_path: /login_check
50 failure_path: null
51 logout: true
52 anonymous: true
53
54 access_control:
55 # URL of FOSUserBundle which need to be available to anonymous users
56 - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
57 - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
58 - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
59
60 # Admin login page needs to be access without credential
61 - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
62 - { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }
63 - { path: ^/admin/login_check$, role: IS_AUTHENTICATED_ANONYMOUSLY }
Chapter 4: User Bundle 25

64
65 # Secured part of the site
66 # This config requires being logged for the whole site and having the ad\
67 min role for the admin part.
68 # Change these rules to adapt them to your needs
69 - { path: ^/admin/, role: [ROLE_ADMIN, ROLE_SONATA_ADMIN] }
70 - { path: ^/.*, role: IS_AUTHENTICATED_ANONYMOUSLY }

I recommend you to read and understand it because it’s not complicated at all, and remember to
contrast everything that you don’t understand with the official Symfony documentation, in which
you will find answers for sure. But now, it’s time to see some magic. With your server up and
running:

1 bin/console server:run

Navigate to http://127.0.0.1:8000/admin/. You should see a fantastic login window to access your
admin, and you should also see a fantastic database error when you try to interact with that login
form. At this point, these things could happen:

• You see everyting alright, even the database error: dont’t worry, you are going in the right
way.
• You see the form and the error, but the page styles are not ok: try to clean the cache and
install the assets.
• I don’t see anything and/or you do have console errors: one, two, three… start again the
chapter!

We’ve finished configuring the admin and the Sonata User Bundle. The next step: generate the user
and the group structure so we can interact with the previous login form. We are going to create our
admin user.

Creating the user structure


You may be afraid of what we are going to do, but we are knowing each other very good and you
know I won’t let you waste your valuable time creating lines of code that others have already done
for you. You may have noticed in the previous section that one of the requirements was Sonata Easy
Extends bundle: it’s time to start using it.
The initial step is to create your own bundle that you will be used for everything that is user related.
In order to do so, copy and paste this command:
Chapter 4: User Bundle 26

1 bin/console sonata:easy-extends:generate SonataUserBundle -d src

The previous line will generate another bundle in the src folder, and because it’s a new bundle, you
have to enable it. Open and edit the app/AppKernel.php file, adding the next line:

1 new Application\Sonata\UserBundle\ApplicationSonataUserBundle(),

Now, you have to change a little piece of the configuration you added in the previous section of
the chapter, the one that is related to the user and the group classes. Modify the user_class and
group_class parameters within the fos_user configuration in the app/config/config.yml file, just
as it is in the following configuration block:

1 fos_user:
2 db_driver: orm # can be orm or odm
3 firewall_name: main
4 user_class: Application\Sonata\UserBundle\Entity\User
5 group:
6 group_class: Application\Sonata\UserBundle\Entity\Group
7 group_manager: sonata.user.orm.group_manager
8 service:
9 user_manager: sonata.user.orm.user_manager

There you have! Don’t you believe so? Execute the following command in a terminal to see what is
going to be added automatically to the database structure:

1 bin/console doctrine:schema:update --dump-sql

When you finish the celebration, execute the changes in order to create the database structure:

1 bin/console doctrine:schema:update --force

Now you can navigate to the login form and it shouldn’t launch a database error, but… you don’t
have the needed role to log in because you don’t even have a user. Let’s solve this problem.

Creating our admin user


As a Symfony console professional you are, you will know that you can create your own commands.
This is what FOSUserBundle have done for you, so you can create your first user without juggling.
Launch the following command to create your first user in the database:
Chapter 4: User Bundle 27

1 app/console fos:user:create

If you’ve followed all the steps, you will have a user that will be able to log in to your website,
but if you try it, you will notice that you are not allowed to see the admin: you don’t have the
correct permission! Why? If you go a little backwards and read the access_control section within
the security file, you will see that the ROLE_ADMIN role is needed in order to access the admin
area. We will take advantage of the actual moment to create a super admin assigning the ROLE_-
SUPER_ADMIN role to the recently created user.

1 app/console fos:user:promote

Right after assigning the role I told you, you will see that if you have previously logged in, you
won’t be able to access. This is because, once you are logged in, the user roles are serialized into the
session, so you do have to log out going to http://127.0.0.1:8000/logout (or in the profile bar at the
bottom) and navigate to http://127.0.0.1:8000/admin again.
What do you you think? With these first chapters of the book I should have conquered you. If it’s
not like that, don’t worry, we have a long journey together so I can reach your heart and win this
fight.
It’s time you stop reading for now and start working with what we have installed in these chapters.
The most important thing: open the routing file and navigate to every single route that is loaded
there. Remember that the routing file is app/config/routing.yml. Some fantastic routes are login,
register, profile, etc. You can start with this last one going to http://127.0.0.1:8000/profile/.

Tip 4
A lot of people forget that Sonata User Bundle is no more than a layer above FOSUserBundle. This
means that every single configuration that is allowed for the FOS bundle it’s also working for the
recently installed Sonata bundle. For example, the email confirmation feature that you can see in
this link¹⁷. You just have to add this piece of configuration and your users will have to confirm their
accounts by clicking to an activation link that the bundle will automatically send to their email
account. You won’t have to worry about this feature from now on, you just have to adapt the email
template.

¹⁷https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/emails.rst
Chapter 4: User Bundle 28

1 fos_user:
2 # ...
3 registration:
4 confirmation:
5 enabled: true

Besides, I know that you are thinking about “how ugly these white pages are”, and I also think so.
These won’t fit in any project, but you are able to modify the layout so it fits your base templating
and also add and/or modify every single element that appears in those pages. All this configuration
is here¹⁸. But, I recommend you to write down the link and wait, because in the following chapters
we will learn about the base templating for our project and for third party bundles so they fit our
needs.

Summary
In this chapter we have installed and configured Sonata User Bundle, a bundle that will allow us
to have a complete user management at a database and web structure levels. Also, with the Admin
Bundle, the admin part will is well covered too. With now doubts, the installation of those Sonata
bundles allow us to save a lot of time in our project so we can spend the time to the most important
part of it. We just have to worry about overriding the parts of the third party bundles that we need
to modify.
¹⁸https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/index.rst#next-steps
Chapter 5: MopaBootstrap
We already have our admin and our user system up and running. But, as far as you have noticed,
the base design of the installed bundles until now is very simple.
It’s time to give some life and color to our project and prepare a good base for the future of it. We
could start writing CSS files from scratch, or even download some CSS framework and insert it in
our project as assets in the templates (if this doesn’t sound familiar to you, it’s time to stop and
read the convenient documentation chapters). But I have to suggest you something much better that
derives into the next questions:

• Start writing CSS from scrath is like committing suicide. It would be an interesting didactical
exercise… but if you want to learn CSS from the very beginning, you must learn it with other
book because I won’t cover it in this one.
• Inserting a framework as a dwonloaded asset is perfectly valid, but… why do we have to do
this in this way when we already have a dependecy management system?
• CSS… are you in the 1990’s? Don’t you ever use CSS nowadays without a preprocessor.
Working without variables or functions will decrease your work efficency drastically.

So, what are we going to do? We will use Composer in order to install MopaBootstrapBundle, a
bundle that will allow us to have a base template to extend and which will already have Bootstrap
configured. Moreover, it contains a variety of extra components and it will allow us to have sassy
files (.scss) to work directly with them. How? There are a lot of ways to “walk throught the path”.
Symfony did have one of them preinstalled until version 2.7: Assetic. After a long time using this
technology, I realized that it had a lot of shortcomings and my development was becoming very
slow. After a series of intermediate changes, I finally started using Gulp and a new world full of
possibilities came up to me. In the next chapters, I will show you this world to you, but at this
moment, let’s install MopaBootstrap.

NOTE: if you are an experienced developer, you will know that coupling your code to
a huge bundle like MopaBootstrap is not the best decision. This chapter will help you
a lot if you are starting with the frontend design or maybe if you are an intermediate
developer. In the last chapter, I will show you how to cover this “coupling” the best way.

Installing MopaBootstrapBundle
As always, the first thing that we have to do is editing the depedency definition file composer.json.
This time, instead of using the composer require command, we can edit the file directly, so you can
learn how to do it this way too. Let’s add the following lines to the require section (be sure to check
the right versions in the Packagist website):
Chapter 5: MopaBootstrap 30

1 "mopa/bootstrap-bundle": "^3.0",
2 "twbs/bootstrap-sass": "^3.3.0"

Furthermore, in the same file, you should add some hooks at the end of it. They are just Symfony
commands that will be executed when Composer finishes the composer install or the composer
update processes. In the piece of code that you see right under this sentence, I just leave you the
line that you must add in each section:

1 {
2 "scripts": {
3 "post-install-cmd": [
4 ...
5 "Mopa\\Bundle\\BootstrapBundle\\Composer\\ScriptHandler::postInstall\
6 SymlinkTwitterBootstrapSass"
7 ],
8 "post-update-cmd": [
9 ...
10 "Mopa\\Bundle\\BootstrapBundle\\Composer\\ScriptHandler::postInstall\
11 SymlinkTwitterBootstrapSass"
12 ]
13 }
14 }

Right before downloading anything, we must enable and configure the bundle because the hooks
will get launch as soon as we launch the Composer process. Edit the app/AppKernel file to enable
the bundle:

1 new Mopa\Bundle\BootstrapBundle\MopaBootstrapBundle(),

We also need to modify the app/config/config.yml file to insert the configuration that is given to
us in the bundle documentation:

1 mopa_bootstrap:
2 form: ~ # Adds twig form theme support
3 menu: ~ # enables twig helpers for menu

Excelent! Now you can launch the update command to get the depencendies downloaded and
execute the hook that we inserted earlier on:
Chapter 5: MopaBootstrap 31

1 composer update

It is possible that you see other bundles getting updated. This is good (if you have the control),
specially if you work with dev-master versions (never do that in a production environment). You
should update your bundles periodically. If everything is correct, you should see something like this
at the end of the process:

1 Checking Symlink ... Creating Symlink: [...]/vendor/mopa/bootstrap-bundle/Mopa/B\


2 undle/BootstrapBundle/Resources/public/bootstrap-sass ... OK

Do you read that line? You are doing it all right!

Preparing the base template


I do know that you already read the Symfony templating¹⁹ chapter of the documentation, so I will
get directly to the main point. Before coding anything, I must write about the framework best
practices. This is one of the rules that surprise a lot of people who were used to work directly
in the bundle (specially the people coming from the previous versions of Symfony). Nowadays,
Symfony best practices say that your project templates must be in the app/Resources/views folder.
If you browse to that folder, you will see that you already have a base.html.twig template and a
default/index.html.twig. What is this? Symfony, by default, comes with the AppBundle bundle
created, and inside it, there is a controller with the route ‘/’ already working. We have seen this page
previously:

1 http://127.0.0.1:8000/

If you have opened the controller source code, you will see that the route is getting loaded with
an annotation. For everything that you code and for everything that you will be doing with me
in the next chapters, I recommend you using annotations; in order to load third party routing files,
creating redirections or loading a template without a controller, you should use the routes as we
have been already doing.
In this chapter we are just going to change the base template so it extends the one that Mopa-
Bootstrap gives us for free, and we will explain how it works. In the next chapter, we will
prepare the Sass part with Gulp so we can start modifying the styling. Let’s edit the app/Re-
sources/views/base.html.twig file and leave it like this:

1 {% extends 'MopaBootstrapBundle::base.html.twig' %}

What? Is that all? Correct, and if you visit the homepage again, you will see that we broke the entire
homepage… more or less. If you open the source code, you will see that you have a title, there exists
a meta with the viewport and some other details. But, continuing with the development, you will
see that the base template that you are extending contains all this code:
¹⁹http://symfony.com/doc/current/templating.html
Chapter 5: MopaBootstrap 32

1 {% from 'MopaBootstrapBundle::flash.html.twig' import session_flash %}


2
3 <!DOCTYPE html>
4
5 {% block html_tag %}
6 <html lang="{{ app.request.locale }}">
7 {% endblock html_tag %}
8
9 {% block head %}
10 <head>
11 <meta charset="{{ _charset }}" />
12 {% block head_style %}
13 {# Override this block to add your own files! #}
14 {# To use this without less or sass use the base.html.twig template as your \
15 base
16 # Be sure you understand whats going on: have a look into
17 # https://github.com/phiamo/MopaBootstrapBundle/blob/master/Resources/doc/c\
18 ss-vs-less.md
19 #}
20 {% endblock head_style %}
21
22 <meta name="viewport" content="width=device-width, initial-scale=1.0">
23
24 {% block head_script %}
25 {# Overwrite this block to add your own js here, to get them generated into \
26 final files
27 {% javascripts
28 'http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'
29 %}
30 <script type="text/javascript" src="{{ asset_url }}"></script>
31 {% endjavascripts %}
32 #}
33 {% endblock head_script %}
34
35 <title>{% block title %}Mopa Bootstrap Bundle{% endblock title %}</title>
36 {% block favicon %}<link rel="shortcut icon" href="{{ asset('favicon.ico') }\
37 }" />{% endblock %}
38 {% block head_bottom %}
39 {% endblock head_bottom %}
40 </head>
41 {% endblock head %}
42
Chapter 5: MopaBootstrap 33

43 {% block body_tag %}
44 <body>
45 {% endblock body_tag %}
46
47 {% block body_start %}
48 {% endblock body_start %}
49
50 {% block body %}
51 {% block navbar %}
52 <!-- No navbar included here to reduce dependencies, see https://github.com/\
53 phiamo/MopaBootstrapSandboxBundle/blob/master/Resources/views/base.html.twig for\
54 howto include it -->
55 {% endblock navbar %}
56
57 {% block container %}
58 {% block container_div_start %}<div class="{% block container_class %}contai\
59 ner{% endblock container_class %}">{% endblock container_div_start %}
60 {% block header %}
61 {% endblock header %}
62
63 {% block content_div_start %}<div class="content">{% endblock content_di\
64 v_start %}
65 {% block page_header %}
66 <div class="page-header">
67 <h1>{% block headline %}Mopa Bootstrap Bundle{% endblock headl\
68 ine %}</h1>
69 </div>
70 {% endblock page_header %}
71
72 {% block flashes %}
73 {% if app.session.flashbag.peekAll|length > 0 %}
74 <div class="row">
75 <div class="col-sm-12">
76 {{ session_flash() }}
77 </div>
78 </div>
79 {% endif %}
80 {% endblock flashes %}
81
82 {% block content_row %}
83 <div class="row">
84 {% block content %}
Chapter 5: MopaBootstrap 34

85 <div class="col-sm-9">
86 {% block content_content %}
87 <strong>Hier könnte Ihre Werbung stehen ... </strong>
88 {% endblock content_content %}
89 </div>
90 <div class="col-sm-3">
91 {% block content_sidebar %}
92 <h2>Sidebar</h2>
93 {% endblock content_sidebar %}
94 </div>
95 {% endblock content %}
96 </div>
97 {% endblock content_row %}
98
99 {% block content_div_end %}</div>{% endblock content_div_end %}
100
101 {% block footer_tag_start %}
102 <footer>
103 {% endblock footer_tag_start %}
104
105 {% block footer %}
106 <p>&copy; <a href="http://www.mohrenweiserpartner.de" target="_blank">Mo\
107 hrenweiser & Partner</a> 2011-2015</p>
108 {% endblock footer %}
109
110 {% block footer_tag_end %}
111 </footer>
112 {% endblock footer_tag_end %}
113 {% block container_div_end %}</div><!-- /container -->{% endblock container_\
114 div_end %}
115 {% endblock container %}
116
117 {% block body_end_before_js %}
118 {% endblock body_end_before_js %}
119
120 {% block foot_script %}
121 {# To only use a subset or add more js overwrite and copy paste this block
122 To speed up page loads save a copy of jQuery in your project and override th\
123 is block to include the correct path
124 Otherwise the regeneration is done on every load in dev more with use_contro\
125 ller: true
126 #}
Chapter 5: MopaBootstrap 35

127 {% block foot_script_assetic %}


128 {# Please add the javascripts you need in your project #}
129 {% endblock foot_script_assetic %}
130
131 <script type="text/javascript">
132 $(document).ready(function () {
133 $('[data-toggle="tooltip"]').tooltip();
134 $('[data-toggle="popover"]').popover();
135 });
136 </script>
137 {% endblock foot_script %}
138 {% endblock body %}
139
140 {% block body_end %}
141 {% endblock body_end %}
142 </body>
143 </html>

What the hell! What is this about? Let’s break this into pieces. Every {% block %} tag will let you
override the parts that the block is made of or will let you add more stuff. This will allow us to
add our CSS and JS files, modify the meta fields, override just the <body> tag to add, for example,
a new class to it, etc. The base template is very complete, and it should fit your needs for almost
every project. What happens if it doesn’t fit your needs? Feel free to override whatever you want,
or even make a new one from scratch. And… what about the rest? Blocks, blocks and more blocks,
some of them with basic default components that you can use; others with a very short JavaScript
content that will trigger the tooltips and the popovers that Boostrap contains; a lot of base code that I
leave you alone with it so you read it calmly. Before continuing, I suggest you a small change to start
the homepage from the very beginning: Edit app/Resources/views/default/index.html.twig and
leave it like this:

1 {% extends 'base.html.twig' %}
2
3 {% block content_content %}
4 Homepage.
5 {% endblock %}

If you refresh the homepage, what has happened? Now we just overrided the central piece of the
template and we can see a heading and a footer. It’s time to change the texts that we don’t need
anymore, so let’s edit the app/Resources/views/base.html.twig base template and adapt it to what
we want:
Chapter 5: MopaBootstrap 36

1 {% extends 'MopaBootstrapBundle::base.html.twig' %}
2
3 {% block title %}Aupa Bilbao!{% endblock %}
4
5 {% block headline %}Aupa Bilbao!{% endblock headline %}
6
7 {% block footer %}
8 <p>Sexy footer 2016</p>
9 {% endblock footer %}

We refresh the page and… et voilà! We modified the pieces that didn’t belong to our new website
so it fits our needs (didactical, of course). But as you have noticed, where are the Bootstrap default
styles? We must insert them with Sass.
There is a “base_sass_3.2” template but it uses Assetic for the SCSS to CSS precompilation. We will
take a further step adding Gulp, a technology that will make this precompilation process but it will
also give us some extra features that everybody must need.

Tip 5
MopaBootrap has a sandbox²⁰ that you can deploy in your computer or just visit it online to see
what it contains. In this tip, I’m going to help you to build your own menu thanks to the knp-menu
bundle that you already have as a Sonata dependency. It may be useful for you or you may want to
keep the control of the menus in your templates manually. Up to you.
The first thing we are going to do is creating the src/AppBundle/Menu folder, and inside it, we should
create the Builder.php file with the following content:

1 <?php
2
3 namespace AppBundle\Menu;
4
5 use Knp\Menu\FactoryInterface;
6
7 class Builder
8 {
9 public function mainMenu(FactoryInterface $factory, array $options)
10 {
11 $menu = $factory->createItem('root', array(
12 'navbar' => true,
13 ));

²⁰http://bootstrap.mohrenweiserpartner.de/
Chapter 5: MopaBootstrap 37

14 $menu->addChild('Home', array(
15 'icon' => 'home',
16 'route' => 'homepage',
17 ));
18 $dropdown = $menu->addChild('Subpages', array(
19 'dropdown' => true,
20 'caret' => true,
21 ));
22 $dropdown->addChild('Subpage 1', array('route' => 'homepage'));
23 $dropdown->addChild('Subpage 2', array('route' => 'homepage'));
24 $dropdown->addChild('Subpage 3', array('route' => 'homepage'));
25
26 return $menu;
27 }
28 }

The next step is inserting our menu in the base.html.twig template. The code block that you see
under these lines is the one that you need, if you want to keep your template ordered, insert it
between the title and the headline blocks:

1 {% block navbar %}
2 {% embed '@MopaBootstrap/Navbar/navbar.html.twig' with {'fixedTop': true } %}
3 {% block brand %}
4 <a class="navbar-brand" href="#">Aupa</a>
5 {% endblock %}
6
7 {% block menu %}
8 {{ mopa_bootstrap_menu('AppBundle:Builder:mainMenu', {'automenu':'na\
9 vbar', 'currentClass': ''}) }}
10 {% endblock %}
11 {% endembed %}
12 {% endblock navbar %}

Wonderful! Now, if you reload the homepage you will see the menu that we just created with a
bunch of links and sublinks that will keep you in the home. This menu is very ugly, but we will take
care of the styling magic in the next chapter.

Summary
In this chapter we have learnt to install the MopaBootstrapBundle and we have created a small base
template without styling. We have discarded the base Sass template just because it uses Assetic. The
Chapter 5: MopaBootstrap 38

aim of this book is that you learn to use the latest technologies (or at least, the ones that I used while
I was writing this book), and because of that we will be using Gulp to precompile our SCSS files. In
future chapters, we will give Gulp some vitamines and we will get a lot of usefull features.
Chapter 6: Gulp (1)
Step by step, our base project is taking shape. But, if I had to highlight a chapter until now, probably
I would highlight this one. Curiously, this is the first one in which we won’t work with Symfony
at all; instead of it, we will support our development on external tools to make the SCSS to CSS
precompilation and to give some vitamines to this process with a bunch of improvements.
If you haven’t worked with any preprocessor until this day, don’t worry. SCSS is a CSS3 superset.
What is this about? Every single line that previously worked on CSS it will now work with SCSS.
So… how will this help me? In this link²¹ you can read a short guide so you can see the power of
Sass: variables, mixins and the rest of the terminology. Once you start using it, you won’t go back
again.
In this chapter we are going to learn how to create our SCSS files and use them while we are
developing, so you won’t need to precompile over and over again once you make a change. Moreover,
in the tip I will explan you how the SCSS files are usually organized to work in an ideal way.

Installation
Like in every chapter, we have to install the needed requirements so the tools can work correctly.
First of all, you should know that in order to install Gulp²² you should have previously installed
Node.js²³ and npm²⁴. The first of them allows us to have a JavaScript server environment thanks
to the V8 Google engine; the other one is a dependency manager for Node and a lot of extra tools.
These are the commands you must use in an Ubuntu based operative system:

1 sudo apt-get install nodejs-legacy npm

Nothing that scares you at this moment, right? The next step is installing Gulp from npm. This
dependency manager has the ability to install a package as an operative system binary. In case of
Gulp, we need to install the binary in a global scope so we can execute the command everywhere, but
also as a local dependency need by our gulpfile.js file. In order to install it as a global dependency,
execute the next command:

²¹http://sass-lang.com/guide
²²http://gulpjs.com/
²³https://nodejs.org/
²⁴https://www.npmjs.com/
Chapter 6: Gulp (1) 40

1 sudo npm install gulp -g

We have now everything we need installed, so let’s go a further step. But… you may think that in
order to transform from SCSS to CSS you also need Ruby. With LibSass you just need Node.js in it’s
a lot quicker than the Ruby solution. We will install some Ruby gems in a future chapter, but for
now on, we have everything that we need installed.
Next step: create our first SCSS file.

Creating our first SCSS file


There are a lot of locations in where you can create the SCSS folder. Following the best practices,
assets must be placed in the web folder, but just the ones that are public. Understanding this sentence,
I usually create a folder in app/Resources/assets/scss, and with a gulp task I send the CSS output
to the web/css folder. In order to start, let’s create a global file called app/Resources/assets/sc-
ss/app.scss with the following content:

1 @import '../../../../web/bundles/mopabootstrap/sass/mopabootstrapbundle-3.2';

If you are new to Sass, what this line does is importing another SCSS file which contains everything
that is necessary for Bootstrap thanks to the previously installed bundle. Of course, we will be
adding more lines to this file.
Now that we have our ultra-complex SCSS file ready, it’s time to transform it so our browser can
understand what we want. We will be using a Gulp task for this aim, but first, we need to take some
previous steps that will help us in a short term period and also to the other possible developers that
want to deploy and work in this project. Execute the next command in the root of the project and
start typing what is asked to you:

1 npm init

You should have a file called package.json when you finish, with a content more or less like this
one:
Chapter 6: Gulp (1) 41

1 {
2 "name": "aupa_bilbao",
3 "version": "0.0.1",
4 "description": "Aupa Bilbao",
5 "main": "gulpfile.js",
6 "author": "Jon Torrado",
7 "license": "MIT"
8 }

Does this sound familiar to you? Here we are going to define the needed NPM packages in order to
make our Gulp tasks work. As with Composer, when you deploy this project into a new computer or
mabye another person is going to work with you, just typing npm install will make npm read this
file and download all the needed packages. But… what do we need? Wow, you are full of energy!
Let’s start with the first package. Even that we have it installed in a global way, it’s necessary to
install it as a local dependency as I told you earlier on:

1 npm install gulp --save-dev

Did you see the change in the package.json file? This is because of the --save-dev flag added at
the end of the command that allows to save the dependency in the dependency definition file after
installing it. As I told you, if a new developer comes to the project and he/she wants to deploy it in
his/her computer, just just need to install npm and then type npm install at the root of the project
and, let’s work! Now, we must install the rest of the packages that we are going to use:

1 npm install gulp-sass --save-dev

With this package, we already have everything that we need installed. Last step: define a Gulp task
that will make the precompilation work for us. In order to achieve this, create a gulpfile.js file in
the root of the project with the following content:

1 var gulp = require('gulp'),


2 sass = require('gulp-sass');
3
4 gulp.task('sass', function () {
5 return gulp.src('./app/Resources/assets/scss/app.scss')
6 .pipe(sass())
7 .pipe(gulp.dest('./web/css/'));
8 });

We made it! Execute the task, a task that we have called sass, to create the CSS file that our browser
will understand:
Chapter 6: Gulp (1) 42

1 gulp sass

If everything went all right, you should have a web/css/app.css file ready to be included in your
base template. Let’s edit app/Resources/views/base.html.twig and add that CSS file in the top
part of the file, always taking care of the blocks that we can override from the base MopaBootstrap
template that we are extending:

1 {% block head_style %}
2 <link href="{{ asset('css/app.css') }}" type="text/css" rel="stylesheet" />
3 {% endblock head_style %}

It’s time to see what we have created. Open your browser and type down the next URL: http://127.0.0.1:8000/.
Do you see it? We already have the styles that Bootstrap gives for free to us. Everything is in the
right place but… there is no interaction. Why? Easy: we have to include the JavaScript files. We will
be doing this later on, because at this moment, I have to help you to become a Gulp jedi master.

Minifying our CSS


One of the most interesting and easy processes that can be applied in order to reduce the size of our
assets is minifying our stylesheets. In this case, it will be our single app.css file, because using
Sass, we achieve getting a single stylesheet file that will combine all the SCSS files (we will explain
this in the chapter). Minifying this humongous stylesheet means removing all the characters that
aren’t really needed, even line breaks. So, won’t we be able to read the resulting stylesheet? Indeed,
but you won’t need it at all, because it’s not really needed when using Chrome devtools of Firebug.
Nevermind, I will give you the change of working with both stylesheets: the normal one and the
minified one.
Let’s start installing the needed packages to achieve this goal:

1 npm install gulp-rename gulp-cssnano --save-dev

After this, we have to edit our gulpfile.js file, and leave it like this:
Chapter 6: Gulp (1) 43

1 var gulp = require('gulp'),


2 sass = require('gulp-sass'),
3 rename = require('gulp-rename'),
4 cssnano = require('gulp-minify-css');
5
6 gulp.task('sass', function () {
7 return gulp.src('./app/Resources/assets/scss/app.scss')
8 .pipe(sass())
9 .pipe(gulp.dest('./web/css/'))
10 .pipe(rename({suffix: '.min'}))
11 .pipe(cssnano())
12 .pipe(gulp.dest('./web/css/'));
13 });

Do you see what we have done? Firstly, we transform the SCSS files to a single app.css file. Then,
we pipe this same exit to rename the file appending a .min to it, minifying the result with cssnano
and leaving it in the same exit folder, resulting in two files: app.css and app.min.css. Hey! Don’t
forget to edit your app/Resources/views/base.html.twig template file so it fits this change:

1 {% block head_style %}
2 {% if app.debug %}
3 <link href="{{ asset('css/app.css') }}" type="text/css" rel="stylesheet" />
4 {% else %}
5 <link href="{{ asset('css/app.min.css') }}" type="text/css" rel="stylesheet"\
6 />
7 {% endif %}
8 {% endblock head_style %}

I’ve added a little logic that will allow us to load each file depending on the environment. So, if you
browse to http://127.0.0.1:8000/app.php/, you will see that the app.min.css file is loaded instead of
the app.css file. After this, there is one more thing that we can do before serving the CSS… and this
is going to like you… a lot!

Autoprefixer
It’s possible that you already know this tool, but if you don’t, welcome to a new way to take
advantage of your time when writing SCSS code. There exists a bunch of CSS features that, in
order to make them work in every browswer, they need what is called vendor prefixes. An example
will show you what I mean, this one makes a rounded corner:
Chapter 6: Gulp (1) 44

1 -webkit-border-radius: 5px;
2 -moz-border-radius: 5px;
3 border-radius: 5px;

There exist more vendor prefixes, the ones for Internet Explorer and Opera. Here you have the
complete list:

• Android, Chrome, iOS, Safari: -webkit-


• Firefox: -moz-
• Internet Explorer: -ms-
• Opera: -o-

A lot of people solves this situation using Sass mixins, but the solution I suggest you is much better.
What if I told you that you just have to write the CSS line without vendor prefixes? Following the
previous example, just writing border-radius: 5px; should be enough, because Gulp will take care
of prepending the needed CSS adding all the prefixes for us. In order to get this magic wand, add
the following package:

1 npm install gulp-autoprefixer --save-dev

Of course, we need to modify the Gulp task. Edit gulpfile.js with the new feature:

1 var gulp = require('gulp'),


2 sass = require('gulp-sass'),
3 autoprefixer = require('gulp-autoprefixer'),
4 rename = require('gulp-rename'),
5 cssnano = require('gulp-minify-css');
6
7 gulp.task('sass', function () {
8 return gulp.src('./app/Resources/assets/scss/app.scss')
9 .pipe(sass({sourceComments: 'map'}))
10 .pipe(autoprefixer())
11 .pipe(gulp.dest('./web/css/'))
12 .pipe(rename({suffix: '.min'}))
13 .pipe(cssnano())
14 .pipe(gulp.dest('./web/css/'));
15 });

Now, every time we launch gulp sass, we will transform SCSS into CSS, we will add the vendor
prefixes to the app.css file, then we will minify this file and we will leave it like app.min.css. Isn’t
it great? Just one more thing: nobody likes to be launching this task over and over again every time
we edit a single SCSS file. Gulp solves this by default: the watch task.
Chapter 6: Gulp (1) 45

Gulp watch
Gulp comes with a default feature that works like this: “hey Gulp, every time I modify something
in this path, launch this task”. Do you see where am I going to? What we are going to do is telling
Gulp that every single time we edit a SCSS file it must call the previously created task. Edit the
gulpfile.js file and add this code at the end of it:

1 gulp.task('watch', function () {
2 gulp.watch('./app/Resources/assets/scss/**/*.scss', ['sass']);
3 });

In this task, a task that we called watch, of course, we are telling Gulp to be able to check for
modifications of the “.scss” files withing the app/Resources/assets/scss folder and subfolders,
and when it detects one change, call the sass task that we were using manually. Now, you just have
to execute gulp watch in a terminal, edit a file and then save it, try it!
Note: if you are using Ubuntu and this give you an error, try executing this command:

1 echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo s\


2 ysctl -p

Default task
If you don’t give gulp any parameter, this means that if you just type gulp in a terminal, the default
task will be executed. The most correct option is defining a task that can be interesting for our
project. I usually launch the sass task and then start watching. This way, whenever I start developing,
I just write gulp and off to go! Here I give you an example code, but feel free to modify this default
task:

1 gulp.task('default', ['sass', 'watch']);

With this last task, we’ve ended the first part of Gulp. In future chapters we are going to continue
working with our gulpfile.js so it gives us a lot more features. Don’t forget that this is a didactical
example, and you should check what others are doing with Gulp. For instance, you should read
about postcss²⁵. As an exercise, you could try changing autoprefixer for cssnext.
²⁵https://github.com/postcss/postcss
Chapter 6: Gulp (1) 46

Tip 6
Having our project well organized is vital for anyone implied in it. In this tip, I’m going to suggest
you one of the available options about organizing the SCSS files within a Symfony project, but I
want you to know that this is neither the unique one nor the best one option. Use it, check how you
feel working in this way and adapt it for you and for your projects.
First of all, you must know how Sass combine the files. You should have noticed that, when we
launched the Sass transformation with Gulp, it just created a single file. Sass will get all the files
from the folders that we tell him to check and it will generate a file for each single file that is not
starting with an underscore. Because of this, you should have an app.scss file and the rest of
the files should be included in this one, named as _file.scss. If you want to include a file in the
app.scss, you don’t need to write the underscore, neither the .scss.
After telling you this, well, writing this, let’s start talking about the folder structure. In the
app/Resources/assets/scss folder there should only live one single file: app.scss. The rest of the
files will be placed into new folders. The base folder will contain all the styles that will be applied
in a global way to all the platform. Here you have some examples:

• _colors.scss: all the color variables that are going to be used in the project.
• _common.scss: global styles, such as h1, a links, etc.
• _fonts.scss: all the fonts to be included. If you also want to insert some base styles such as font
sizing, you can rename this file to _typography.scss.

There are more files that can reside here, but I give you the chance to discover this as soon as you
develop new platforms. The next folder to talk about is components, in which we will hava a SCSS
file for each application component. But… what is a component? If you want to reuse something
between different pages, the best way to achieve this is by creating a component that can be included
wherever you want and a component that doesn’t depend with anyone more than itself: a carousel,
buttons, dropdowns, price tables, etc. Everyone of these will have a corresponding file in this folder.
The helpers folder will collect all the SCSS files that will help us with positioning or sizing elements.
For example:

• _align.scss: even though this is comming with Bootstrap, it will help us to create alignment
classes to the left, to the right, center and justified. Then we will be able to apply those classes
to the elements and we won’t apply the same style over and over to an element.
• _dividers.scss: this file will contain everything related to borders and other elements that
divides components between them.
• _icons.scss: to place and size the icons or images that are used in the correctly.
• _spacing.scss: in order to include spacing in the elements without having to apply the concrete
styling, just calling the mixins that are in this file (search on Google, is a very interesting
topic).
Chapter 6: Gulp (1) 47

The penultimate folder to comment out is layout. As you have guessed, this folder will contain at
least three files: the ones corresponding to the header, the content and the footer. Besides, you can
add new files to modify the styles of other elements for mobile or desktop.
The last one, the pages folder is where the specific page styles will reside. These styles are the ones
which don’t fit in the previously commented folders. The best example for this folder is the _404.scss
file, do you get it?
After this, we have finished this chapter’s trick and with it, one of the most interesting chapters in
the book. Do some tests and feel confortable with this stuff, you will work much quicker than you
previously did (and if this isn’t true, send me an email so I can learn how you work!)

Summary
In this chapter, we have learnt how to use Gulp tasks in order to tranform SCSS to CSS files. Besides,
we’ve created a watch task that will allow us to work directly with the files without having to launch
the precompile task over and over, automatically regenerating the resulting CSS file. In the next
chapter, we will be using a previously included bundle (until Symfony 2.7) called Assetic in order
to work with the JavaScript files, but you must know that you can also work with Gulp without a
problem.
Chapter 7: Assetic for JavaScript
It’s time to interact with the user, or at least the part that is related with the JavaScript of Bootstrap,
the framework that we are using in our project. We could continue using Gulp in a similar way to
the one that we did in the previous chapter for the Sass to CSS transformation. In fact, it’s something
that I trully recommend. However, Assetic is a good option at this point because it’s very didactical
and it came by default with Symfony until version 2.7.
In this chapter, we are going to learn how to work with JavaScript files in an easy and correct way.
In addition, in the trick, I will explain one of the possibilities to work with Symfony that I used to
follow in my developments. As always, it’s not the unique one nor the best, it’s simply one that
works well with me, and I hope it fits as well to you.

NOTE: this is just a didactical chapter. The actual recommendation is using Gulp in
order to work with JavaScript files. We will see this in the last chapter.

Installation
From Symfony 2.8, Assetic is not comming installed by default. So, if you are using this or a greater
version, the first thing you must do is downloading the Composer dependency:

1 composer require symfony/assetic-bundle

With this done, you have to enable the bundle. Edit the app/AppKernel.php file and add the
following instatiation line:

1 new Symfony\Bundle\AsseticBundle\AsseticBundle(),

To complete the installation, add the following configuration block to app/config/config.yml:

1 assetic:
2 debug: '%kernel.debug%'
3 use_controller: '%kernel.debug%'
4 filters:
5 cssrewrite: ~

Done! It’s time to configure the second part of the bundle.


Chapter 7: Assetic for JavaScript 49

Configuration
Before configuring anything, I would like to remember you that Assetic also works with Sass files,
transforming them to CSS, but it’s too slow and it will make you lose a lot of time comparing with
Gulp. Even so, I invite you to try it and tell me the results you obtain with both options or even any
other possible alternative.
The first thing we are going to do is downloading jQuery, just because Bootstrap needs it as a
dependency or it won’t work correctly. In this case, Bootstrap 3 needs jQuery 1.x, a version that you
can find on http://jquery.com/download/²⁶. You can download the uncompressed version because
we will take care of minifying it at the end of the chapter. You must place the downloaded file in
the web/js folder.

NOTE: there exists some options here. We could use npm in order to download jQuery,
but then we will have to create a Gulp task to copy the JavaScript file to the web public
folder. We could use another technology such as Bower, which allows to download the
dependencies in a specific configurable folder. We will see this later on.

We already have the rest of the files available, so let’s start working. Edit the app/Resources/views/base.html.twig
file and add the corresponding Assetic tag for JavaScript in the prepared MopaBootstrap base
template:

1 {% block head_script %}
2 {% javascripts output='js/compressed/app.min.js'
3 'js/jquery.js'
4 'bundles/mopabootstrap/js/modernizr-2.7.1-respond-1.4.2.min.js'
5 'bundles/mopabootstrap/bootstrap-sass/assets/javascripts/bootstrap/toolt\
6 ip.js'
7 'bundles/mopabootstrap/bootstrap-sass/assets/javascripts/bootstrap/*.js'
8 %}
9 <script type="text/javascript" src="{{ asset_url }}"></script>
10 {% endjavascripts %}
11 {% endblock head_script %}

Check that the filenames are the same in your project. As you can see in the previous code
block, the first thing we do is declaring the JavaScript outpput, which will be a file called
js/compressed/app.min.js. For the time being, the file is not minified even though we added a
.min to the filename, but we are preparing the road for the upcoming changes. The next lines include
the different JavaScript files needed by out application. All these files will be combined in a single
final file, taking care that the “popover” library needs “tooltip” to be loaded first. For this reason,
²⁶http://jquery.com/download/
Chapter 7: Assetic for JavaScript 50

we manually load the tooltip.js dependency first, because the asterisk loads the files in alphabetic
order (I give you this tip for free, but you will have noticed because the browser console gives you
this error). The intermediate line is just the HTML resulting line that the block will create.
Time to test what we have done. Browse to http://127.0.0.1:8000/ and click the top dropdown. Is it
working? I hope so, because if it’s not working, you will have to go back and check all the things
we’ve done in the last 3 chapters.

Minifying our JavaScript


Our next step is minifying the resulting JavaScript in order to make our assets smaller. There exists
a lot of ways to achieve this goal, some of them already written in the Assetic recipes that I leave
you in this link²⁷. We, for our example, are going to use UglifyJS option.
In this case, we need to install the uglifyjs binary, so execute the following command in a terminal:

1 npm install uglify-js --save-dev

The next step is configuring the Twig filter that we will use in some minutes to minify our JavaScript.
To configure this, edit the app/config/config.yml file and, in the assetic section, replace the content
with the following block:

1 assetic:
2 debug: "%kernel.debug%"
3 use_controller: false
4 bundles: [ ]
5 #node: /usr/bin/nodejs
6 filters:
7 cssrewrite: ~
8 uglifyjs2:
9 bin: "%kernel.root_dir%/../node_modules/uglify-js/bin/uglifyjs"

As you can see, the node binary is commented out. If your operative system has that binary in a
different route (that is the default one), you have to tell Assetic where that binary is.
The last step, modify the app/Resources/view/base.html.twig and add the recently configured
filter to the javascripts section:

²⁷https://symfony.com/doc/current/cookbook/assetic/index.html
Chapter 7: Assetic for JavaScript 51

1 {% block head_script %}
2 {% javascripts filter='uglifyjs2' output='js/compressed/app.min.js'
3 'js/jquery.js'
4 'bundles/mopabootstrap/js/modernizr-2.7.1-respond-1.4.2.min.js'
5 'bundles/mopabootstrap/bootstrap-sass/assets/javascripts/bootstrap/toolt\
6 ip.js'
7 'bundles/mopabootstrap/bootstrap-sass/assets/javascripts/bootstrap/popov\
8 er.js'
9 'bundles/mopabootstrap/bootstrap-sass/assets/javascripts/bootstrap/*.js'
10 %}
11 <script type="text/javascript" src="{{ asset_url }}"></script>
12 {% endjavascripts %}
13 {% endblock head_script %}

We’ve done it! Refresh the page that we have been using as an example during the last chapters
and you will see the JavaScript files minified automatically. But, be careful: this just happens when
working in the development environment. This means that it just happens when browsing with
app_dev.php (by default with the built in server). If you want to generate the minified final JavaScript
file so everything works in the production environment, you should execute the following command
in a terminal:

1 bin/console assetic:dump --no-debug

Don’t you like having everything minified in the dev environment? Don’t panic, you can deactivate
the filter just for the developent enviroment as it follows:

1 filter='?uglifyjs2'

Just adding a “?” at the start of the filter makes Assetic ignore that filter when working in the
development environment.
With everything that we’ve covered during the last chapters, you can start working in a complete
way with your project. For this reason, in the next chapter we will create a complete example of a
Symfony page, from the routing declaration to the template rendering. But, just before going into
this, I would like to recommend you some interesting related topics to this chapter.

Recommendations
I want to take advantage of this chapter section to talk about two topics that are, at least, advisable
for reading. The first one is Assetic related. As you have seen, there are filters to minify CSS and
JS, but there are also filters for image minification. Thank to jpegoptim, some malabarisms can be
Chapter 7: Assetic for JavaScript 52

done for treating the images before being served by the web server. In this link²⁸ you can read a
recipe that talks about this.
On the other hand, we must continue talking about JavaScript. If your application is JavasCript
intense, you should try to read about RequireJS: a JavaScript module loader file on demand with
dependencies. Without any doubt your pages will get and after and a before using a technology like
this, which is also compatible with almost every browser nowadays. Visit its webpage here²⁹ and
start using it.

Tip 7
In this chapter’s tip I’m going to tell you how I used to work with my projects (not with every
project) while I was developing them. As always, everyone has his/her own way of working and
his/her own obsessions, tricks and defects, but thanks to this difference richness we have a sector
that is growing as fast as light speed. If someone does ever tell you that his/her way of working is
the best one, he/she is lying you, and that’s why I won’t tell you this.
If you open the MopaBootstrap base template you will see a block like this:

1 {% block foot_script_assetic %}
2 {% endblock foot_script_assetic %}

As the name designates, this block allows us to insert JavaScript elements before the footer. Even
though the name contains the “assetic” work, we are obviously not forced to use this technology, so
we will take advantage of this block to insert the JS code inside the same template.
In every Twig file you work, you can add the following JS code that references the HTML that
template file renders, for example:

1 {% block foot_script_assetic %}
2 <script>
3 $(document).ready(function() {
4 $('#form').submit(function() {
5 console.log('Submit!');
6 });
7 });
8 </script>
9 {% endblock foot_script_assetic %}

²⁸https://symfony.com/doc/current/cookbook/assetic/jpeg_optimize.html
²⁹http://requirejs.org/
Chapter 7: Assetic for JavaScript 53

Logically, this code won’t get minified because it’s not placed into any JavaScript file, but it will
allow us to develop in an easier way because the JS code and the HTML page in which it should
work are just the same. When you finish your work and the version is more or less the final one,
remember to create the corresponding JavaScript file and copy the code you wrote in the template to
the new file. Moreover, you have to edit the %javacripts% Assetic block and add your own recently
created file that you should have placed in the web/js folder. When you finish, you should have
some JavaScript well organized files and a final app.min.js file with everything that our project
needs in order to work correctly.

Summary
In this chapter we have learnt to include the Bootstrap JavaScript necessary files and work with our
own JS files. At this point, we already have a solid base to start working in our Symfony project, so
in the next chapter we will be doing a complete example: from the routing to the rendering of the
template.
Chapter 8: full example
We have enough tools to start constructing our web project completely. Of course, we will learn
much more tools that will give us a lot of knowledgment to make our project more solid and
complete. but it’s time to stop a litte bit, consider what we have learnt until now and create a full
example to secure what we’ve seen in previous chapters.
In this chapter we will see how to work with Symfony since out app receives the “request” until we
give a “response” object. We will also use this chapter to talk about best practices in each section,
both the official ones and my personal ones.

Application flow
A lot of people say that Symfony as an MVC framework; others say that it’s a request-response
framework. I like the last description because it’s more like the way we are going to develop our
application.
In this chapter we are going to create a full homepage, with a carousel and some claims so we can
learn to work with the Bootstrap grid. In addition, we will create a contact form to work both with
Symfony forms and with services.
If you know exactly what you want to do in one of your projects, you must follow these steps:

1. Create the needed routing


2. Create the function (action) that executes the route inside a controller
3. Create the needed code inside that function, the code that will generate what the template
needs
4. Create the Twig template that renders what the controller has generated

Let’s go step by step resolving each of the previous points.

Creating the route


In Symfony there are several ways of creating routes. If you are generating a bundle so other people
are able to include it in their projects, I suggest you to use a YAML format in your routes, it will
be a lot easier for others to include your routing files; however, if you are developing your own
application, I suggest you to use annotations. Even in my work there are people who think different
than me and prefer working always with YAML, so as I always say: choose the one that makes you
feel more confortable, there isn’t one better than the other.
An example of a YAML router would be the next piece of code, placed in the routing.yml file:
Chapter 8: full example 55

1 home:
2 path: /home
3 defaults: { _controller: AppBundle:Home:index }

Understanding the previous code, we can extract what follows:

• The route will be called ‘home’


• The URL will be ‘/home’
• The function that will be executed will be ‘indexAction’ and will be placed in the ‘HomeCon-
troller’ controller inside the AppBundle.

Now, if you want to save some time and go directly to the controller code, you can achieve it
thanks to annotations. Annotations allow us to declare a route in the same controller just over the
function to be executed. This makes our code less decoupled but much easier to read.
The same route as the previous one in the HomeController controller would be as this:

1 /**
2 * @Route("/home", name="home")
3 */

You should also know that with annotations you don’t need to give a name to a route, it will
automatically choose it’s name in this way: nameofbundle_nameofcontroller_nameofaction. In this
case, the generated name would be app_home_index, easy, isn’t it? You can always see all your
application routes with the following Symfony command:

1 bin/console debug:router

In order to make annotations work, be sure that your controller is able to read annotations with the
corresponding use in the top of the file:

1 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

We now have our first route working. Next step: the action method.

Creating the action method


With annotations this is much easier. Just after declaring the route you have to declare the function
for that route. Following the previous example, our action method should be as this:
Chapter 8: full example 56

1 /**
2 * @Route("/home", name="home")
3 */
4 public function indexAction()
5 {
6
7 }

At this moment, I won’t bother you with the PHP code styling, but you will have a future chapter
that will take care of embarrash you. Talking about the controller, I must suppose that you know
how to create one. Symfony comes with a DefaultController created for free, so you just have to
copy it and rename it to HomeController, removing all the functions that you don’t really need.
Continuing with our didactical example, the route and the action functions are ready, it’s time to
start filling this method with PHP code.

Our own home


Before starting to code PHP as we were mad, we have to sit down and think what to show on screen
and how to show it. As I told you in the introduction chapter, our personal home will contain:

• Images carousel
• Claims
• Contact form

I wanted our home to have these elements because each of them will use a distinct technique that
will be didactically powerful.
The image carousell will be using the Symfony repositories to get the images (featured, for example)
and then the carousel component that Bootstrap provide us.
We will make the claims static but using the component structure that we commented in chapter 6.
We will use the Bootstrap grid and I will briefly explain how to make your content responsive.
The contact form will use the Symfony forms and the services too. To make it a little more complex,
we will insert it in a Bootstrap modal which we will be included by a controller call from a Twig
template.
You must understand and practice what I have showed you in this section because it’s basic for you
to develop Symfony applications in a correct way. Don’t rush to the next section and create a solid
base in your brain, it will be a lot easier to continue.
In the last part of this section, I would like to talk to you about “controllers”. A lot of people think that
controllers are a hodgepodges in where you can write a bunch of PHP lines (a lot) that then become
unmaintainable and, of course, impossible to reuse. We have to avoid this at all costs, because doing
it the right way won’t cost you a lot of time in a short term period and it will save a lot of time in
the future. Let’s start with the image carousel.
Chapter 8: full example 57

The image carousel


For the image carouse, firstly we are going to create the entity that will hold the information we
need. I’m just going to create two fields as well as the “id” that every entity should have, but I suggest
you to complicate it as much as you want.
In order to create a new entity, the easiest way is using a Symfony command:

1 bin/console doctrine:generate:entity

The prompt will ask you some questions, this is what I wrote:

• The Entity shortcut name: AppBundle:FeaturedImage


• Configuration format: annotation
• Field name: url / Field type: string / Field length: 255 / Is nullable: false / Unique: false
• Field name: active / Field type: boolean / Is nullable: false / Unique: false

While answering the second question, you can also use any other of the suggested formats. It’s
very similar to the routing system because, if you use YAML, for example, it will create you a
[bundle]/Resources/config/doctrine/FeaturedImage.orm.yml file where all configuration will
be placed. For this example, like in my own projects, we will be using annotations.
You now can see another two new files in your bundle:

• src/AppBundle/Entity/FeaturedImage.php
• src/AppBundle/Repository/FeaturedImageRepository.php

We will place all the database queries related to the FeaturedImage entity in the FeaturedIm-
ageRepository file. Wait some minutes and we will be using this feature.

To edit the database structure, remember that you should execute the following command in a
terminal:

1 bin/console doctrine:schema:update --force

If you are not sure what is going to happen when executing it, you can change the parameter --force
for --dump-sql and see what queries will be executed before launching the database changes.
Next step: create the admin class. Here is my src/AppBundle/Admin/FeaturedImageAdmin.php
source code:
Chapter 8: full example 58

1 <?php
2
3 namespace AppBundle\Admin;
4
5 use Sonata\AdminBundle\Admin\AbstractAdmin;
6 use Sonata\AdminBundle\Datagrid\ListMapper;
7 use Sonata\AdminBundle\Datagrid\DatagridMapper;
8 use Sonata\AdminBundle\Form\FormMapper;
9
10 class FeaturedImageAdmin extends AbstractAdmin
11 {
12 /**
13 * {@inheritdoc}
14 */
15 protected function configureFormFields(FormMapper $formMapper)
16 {
17 $formMapper
18 ->add('url', 'url')
19 ->add('active')
20 ;
21 }
22
23 /**
24 * {@inheritdoc}
25 */
26 protected function configureDatagridFilters(DatagridMapper $datagridMapper)
27 {
28 $datagridMapper
29 ->add('url')
30 ->add('active')
31 ;
32 }
33
34 /**
35 * {@inheritdoc}
36 */
37 protected function configureListFields(ListMapper $listMapper)
38 {
39 $listMapper
40 ->addIdentifier('id')
41 ->add('url')
42 ->add('active', null, array('editable' => true))
Chapter 8: full example 59

43 ->add('_action', 'actions', array(


44 'actions' => array(
45 'edit' => array()
46 )))
47 ;
48 }
49 }

I’ve added a couple of tricks more to this admin that were not present in previous chapters:

• In the form, I’ve added a second parameter to the “url” field, which corresponds to the “input
type”. With this parameter, we make sure that what it’s going to be typed in the form will be
a URL. You should also read the validation³⁰ chapter in the Symfony documentation.
• In the list section, Sonata allows an “editable” parameter that will allow to edit boolean fields
from the list view with just a click, without going to the form.

Don’t forget to add the service in the app/config/services.yml file:

1 sonata.admin.featured_image:
2 class: AppBundle\Admin\FeaturedImageAdmin
3 tags:
4 - { name: sonata.admin, manager_type: orm, group: "Content", label: "Fea\
5 tured Image" }
6 arguments: [~, AppBundle\Entity\FeaturedImage, ~]

It’s time to add some images. For this example, we will search on Google for images with this size:
1280x550. Here you have a search link³¹ with some images so you can insert some of them into the
database.
Ok, we’ve inserted some images, so our next step will be “retrieving” those images from the database.
In order to achieve this goal, we must create a new function inside the FeaturedImageReposi-
tory.php file that will contain the carousel needed images. It’s possible that in a future day the
images appearing in the carousel follow a distinct algorithm, and because of that, the function
must be called something like “findCarouselImages” instead of “findLastActiveImages” (with this
statement I’m trying to tell you that it’s possible that in the future, the images that will be appearing
in the carousel could be the last active images uploaded between date A and B, and it’s easier to keep
the same function name and edit the algorithm instead of changing the function name everywhere).
Edit src/AppBundle/Repository/FeaturedImageRepository.php and add the function that fit your
needs, for example:
³⁰https://symfony.com/doc/current/book/validation.html
³¹https://www.google.es/search?q=landscape&espv=2&biw=1680&bih=913&tbs=isz:ex,iszw:1280,iszh:550&tbm=isch&source=lnt
Chapter 8: full example 60

1 <?php
2
3 namespace AppBundle\Repository;
4
5 use Doctrine\ORM\EntityRepository;
6
7 /**
8 * FeaturedImageRepository
9 *
10 * This class was generated by the Doctrine ORM. Add your own custom
11 * repository methods below.
12 */
13 class FeaturedImageRepository extends EntityRepository
14 {
15 public function findCarouselImages()
16 {
17 return $this->createQueryBuilder('f')
18 ->where('f.active = :active')
19 ->setParameter('active', true)
20 ->setMaxResults(5)
21 ->getQuery()
22 ->getResult();
23 }
24
25 }

Caution:: repository classes are intermediates between code and database, so do not use repository
files to create business logic. This must be placed in a service if it can be reused o in a controller if
not. The previous repository function is an example, and it could have been done with the default
repository like this:

1 $em->getRepository('AppBundle:FeaturedImage')->findBy(['active' => true], ['id' \


2 => DESC], 5);

Finally, our src/AppBundle/HomeController.php should call the recently created function to obtain
the images and then pass the variable to the corresponding Twig template. Here you have the source
code:
Chapter 8: full example 61

1 <?php
2
3 namespace AppBundle\Controller;
4
5 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7
8 class HomeController extends Controller
9 {
10 /**
11 * @Route("/home", name="home")
12 */
13 public function indexAction()
14 {
15 $em = $this->getDoctrine()->getManager();
16 $fimageRepository = $em->getRepository("AppBundle:FeaturedImage");
17 $featuredImages = $fimageRepository->findCarouselImages();
18
19 return $this->render('home/index.html.twig', array(
20 'featuredImages' => $featuredImages
21 ));
22 }
23 }

We are almost at the end. The next step: create the app/Resources/views/home/index.html.twig
template, make it extend our base template and fill the correct block.

1 {% extends 'base.html.twig' %}
2
3 {% block container %}
4 <div class="container">
5 <h1>Here goes the carousel</h1>
6 </div>
7 {% endblock %}

Hey, what have you done here? So it results that our base template contains a heading, a sidebar
and a footer that we absolutely don’t need them for the homepage. I’ve just searched for another
block above them to override the part that I want. In this case, the container block fits quite good
and allows us to override the full template.
It’s possible that you already have noticed of an error and you have tried to solve the problem of the
topbar. As it is a fixed element, it doesn’t fill any space at all, and because of that, our page starting
point is covered by that bar. If you read the Bootstrap documentation you will find the suggested
fix. Create an app/Resources/assets/scss/base/_common.scss file with the following content:
Chapter 8: full example 62

1 body {
2 padding-top: 55px;
3 }

And of course, edit the app/Resources/assets/scss/app.scss and include the created file:

1 @import '../../../../web/bundles/mopabootstrap/sass/mopabootstrapbundle-3.2';
2 @import 'base/common';

Also rembeber that, if you don’t have gulp watch running, you must execute the Gulp command
that transforms our SCSS files to CSS. One you finish this, check http://127.0.0.1:8000/home and see
how it looks like.
To end this section, we have to show our carousel. You have the Bootstrap code in this link³². Copy
and paste it in your Twig template, and try to adapt it to show our featured images coming from the
database. If you don’t accomplish this, here you have the solution:

1 {% extends 'base.html.twig' %}
2
3 {% block container %}
4 <div class="container">
5 <div id="carousel-example-generic" class="carousel slide" data-ride="car\
6 ousel">
7 <ol class="carousel-indicators">
8 {% for i in 1..featuredImages|length %}
9 <li data-target="#carousel-example-generic" data-slide-to="{\
10 { i-1 }}"{% if loop.first %} class="active"{% endif %}></li>
11 {% endfor %}
12 </ol>
13 <div class="carousel-inner" role="listbox">
14 {% for featuredImage in featuredImages %}
15 <div class="item{% if loop.first %} active{% endif %}">
16 <img src="{{ featuredImage.url }}" alt="">
17 <div class="carousel-caption">
18 Image caption {{ loop.index }}
19 </div>
20 </div>
21 {% endfor %}
22 </div>
23
24 <!-- Controls -->
³²http://getbootstrap.com/javascript/#carousel
Chapter 8: full example 63

25 <a class="left carousel-control" href="#carousel-example-generic" ro\


26 le="button" data-slide="prev">
27 <span class="glyphicon glyphicon-chevron-left" aria-hidden="true\
28 "></span>
29 <span class="sr-only">Previous</span>
30 </a>
31 <a class="right carousel-control" href="#carousel-example-generic" r\
32 ole="button" data-slide="next">
33 <span class="glyphicon glyphicon-chevron-right" aria-hidden="tru\
34 e"></span>
35 <span class="sr-only">Next</span>
36 </a>
37 </div>
38 </div>
39 {% endblock %}

Impressive! With this first example we’ve learnt how to create a route and a function, create an
entity with a repository to retrieve the needed data, send the proper information to the template
and render the Bootstrap carousel element. If you want to improve this part, you could encapsulate
the Twig part in a reusable component… but we will learn how to do this in the next section of the
chapter.

The claims
With this section we will learn how to master (a little bit) the Bootstrap grid and we will also
manage our page with components. This is not a Bootstrap book, so I will rush when talking about
some of its elements. It’s your responsability to learn how to correctly use Bootstrap.
To be more agile, we won’t retrieve the claims from the database; instead, we will be creating them
directly from the previous used template and, before closing the main “div”, we must insert three
claims as it follows:

1 </div>{# closes carousel-example-generic #}


2 <section id="claims" class="row">
3 {% for i in 1..3 %}
4 {{ include('component/claim.html.twig') }}
5 {% endfor %}
6 </section>

As you can see, we’ve created a separate component in a different template so this component can
be included anywhere in our application. The claim code is also from the Bootstrap examples. I’ve
adapted it a little bit, but you can use whatever you like (app/Resources/views/component/claim.html.twig):
Chapter 8: full example 64

1 <div class="claim col-md-4 text-center">


2 <img class="img-circle" src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAA\
3 CH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt="Generic placeholder image" width="140"\
4 height="140">
5 <h2 class="claim__title">Claim</h2>
6 <p class="claim__text">Donec sed odio dui. Etiam porta sem malesuada magna m\
7 ollis euismod. Nullam id dolor id nibh ultricies vehicula ut id elit. Morbi leo \
8 risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magn\
9 a.</p>
10 <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
11 </div>

Let’s stop here a second and let me explain you briefly how the grid system works:

• In order to insert columns (all classes starting with col-) you must first create a row class tag.
• Being a mobile first solution, you should write the most concrete classes at the begining and
then the less concrete ones (from col-xs- to col-lg-).

You can read about all the available classes in this link³³.
Refresh the page to see the result, how does it look like? I also think that the webpage needs more “col-
ors”, so let’s create the corresponding SCSS app/Resources/assets/scss/component/_claim.scss
file:

1 .claim {
2 margin: 10px 0;
3
4 &__title {
5 color: #f22;
6 }
7
8 &__text {
9 font-style: italic;
10 }
11 }

As homework, you should remove that hexadecimal color from there and create the corresponding
variable in the _colors.scss file containing that value, and then use it. Be careful! Don’t forget to
update your app/Resources/assets/scss/app.scss file. Refresh again and see the changes.
Do you see the same goal as me? We’ve made a perfectly reusable component to use anywhere, and
if we had a style guide with the corporative colors, we just simply have to assign color variables to
³³http://getbootstrap.com/css/#grid-options
Chapter 8: full example 65

the components instead of repeating hexadecimal values over and over. Because… what happens if
the corporative colors change? You should use the “search and replace” feature in the whole project.
But, if you are using Sass, you just change the variable values and, that’s it!
Now, it’s time to start with something a bit comre complicated: forms and services.

Contact form
The first thing we are going to do in this seacton is creating the Symfony form. It’s very advisable
that you master this chapter³⁴ of the Symfony book because the forms’ power in Symfony is just
awesome. On the other had, MopaBootstrap has an easy way of rendering forms that you can read
here³⁵. In this example, we are going to render our form manually, but improving this part would
be a good homework.
Firstly, we have to create our form file src/AppBundle/Form/Type/ContactType.php. Here you have
the source code that we will be using for the example:

1 <?php
2
3 namespace AppBundle\Form\Type;
4
5 use Symfony\Component\Form\AbstractType;
6 use Symfony\Component\Form\FormBuilderInterface;
7 use Symfony\Component\OptionsResolver\OptionsResolver;
8 use Symfony\Component\Validator\Constraints\Collection;
9 use Symfony\Component\Validator\Constraints\NotBlank;
10 use Symfony\Component\Validator\Constraints\Length;
11 use Symfony\Component\Validator\Constraints\Email;
12
13 class ContactType extends AbstractType
14 {
15 public function buildForm(FormBuilderInterface $builder, array $options)
16 {
17 $builder
18 ->add('name', 'text', array(
19 'label' => 'Nombre',
20 'attr' => array(
21 'placeholder' => 'Introduce tu nombre',
22 'autocomplete' => 'off'
23 )

³⁴https://symfony.com/doc/current/forms.html
³⁵http://bootstrap.mohrenweiserpartner.de/mopa/bootstrap/forms/examples
Chapter 8: full example 66

24 ))
25 ->add('email', 'email', array(
26 'attr' => array(
27 'placeholder' => 'Para contactar contigo',
28 'autocomplete' => 'off'
29 )
30 ))
31 ->add('message', 'textarea', array(
32 'label' => 'Mensaje',
33 'attr' => array(
34 'cols' => 90,
35 'rows' => 10,
36 'placeholder' => '¿Qué necesitas saber?'
37 )
38 ))
39 ;
40 }
41
42 public function configureOptions(OptionsResolver $resolver)
43 {
44 $collectionConstraint = new Collection(array(
45 'name' => array(
46 new NotBlank(array('message' => 'El nombre no puede estar vacío'\
47 ))
48 ),
49 'email' => array(
50 new NotBlank(array('message' => 'El email no puede estar vacío')\
51 ),
52 new Email(array('message' => 'Email incorrecto'))
53 ),
54 'message' => array(
55 new NotBlank(array('message' => 'El mensaje debe ser más largo')\
56 ),
57 new Length(array('min' => 5))
58 )
59 ));
60
61 $resolver->setDefaults(array(
62 'constraints' => $collectionConstraint
63 ));
64 }
65
Chapter 8: full example 67

66 public function getBlockPrefix()


67 {
68 return 'contact';
69 }
70 }

As you see, I’ve already added some validation elements.


The next step is creating a ContactController with the needed function to load the template that will
render the created form. You can read about this in the form chapter that I linked some lines above:

1 <?php
2
3 namespace AppBundle\Controller;
4
5 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7 use Symfony\Component\HttpFoundation\Request;
8 use AppBundle\Form\Type\ContactType;
9
10 class ContactController extends Controller
11 {
12 /**
13 * @Route("/contact")
14 */
15 public function contactAction(Request $request)
16 {
17 $form = $this->createForm(new ContactType());
18 $form->handleRequest($request);
19 if ($form->isSubmitted() && $form->isValid()) {
20 // Form sent
21 die;
22 }
23
24 return $this->render('contact/contact.html.twig', array(
25 'contactform' => $form->createView()
26 ));
27 }
28 }

You can read in the previous source code that, at this moment, we are not doing anything when the
form is submitted. If the form hasn’t been submitted, we will render a template with the contact
Chapter 8: full example 68

form. Be careful, if you created the controller from scratch check the use that I wrote in the top part
of the file.
The template that we are going to use will receive the contact form that we must render as beautiful
as possible for our users. Let’s go!

1 {# app/Resources/views/contact/contact.html.twig #}
2 {% extends 'base.html.twig' %}
3
4 {% block content_content %}
5 {{ include('component/contact_form.html.twig') }}
6 {% endblock %}

Another component! In further sections we will be embedding this form in a Bootstrap modal, so it’s
a good idea to have everything as a component, it will allow us to reuse the code without repeating
it. The contact_form.html.twig template looks like this:

1 {% form_theme contactform 'bootstrap_3_layout.html.twig' %}


2
3 {{ form_start(contactform, {'attr': {'class': ''}}) }}
4 {{ form_row(contactform.name) }}
5 {{ form_row(contactform.email) }}
6 {{ form_row(contactform.message) }}
7 <div class="form-group">
8 <button type="submit" class="btn btn-success">Send</button>
9 </div>
10 {{ form_end(contactform) }}

Wow! Is that simple? Definitely, it is very simple. But, if you see the first line of this template file,
you will see that we are rendering the form with Bootstrap classing. You must enable this feature in
your app/config/config.yml file:

1 twig:
2 debug: "%kernel.debug%"
3 strict_variables: "%kernel.debug%"
4 form:
5 resources: ['bootstrap_3_layout.html.twig']

You can read about Bootstrap theming in this link³⁶, and here³⁷ you have all about form customiza-
tion.
³⁶http://symfony.com/blog/new-in-symfony-2-6-bootstrap-form-theme
³⁷http://symfony.com/doc/current/cookbook/form/form_customization.html
Chapter 8: full example 69

The form is almost working, but we are still missing a couple of things: - Make our form appear in
a Bootstrap modal - Send the needed email, indeed!
For the first goal, you must need to know how Bootstrap modals work, something that you can
read in the documentation³⁸. Did you read it? So let’s continue with the example.
The idea here is inserting a footer link that shows “contact”, but instead of redirecting you to the
contact page, it will popup a modal with the created form. Logically, in order to render the form in
every page (it is in the footer), every time the base template gets loaded, the contactform variable
must exist. This can be done in two ways:

• Create the form in every action of you project, something that is bad
• Make your base template call an action that will render that modal with the form

You agree with me, let’s stick with the second option. We have to create the modal as a component
so it can be reused in any other part of the application. My app/Resources/views/component/-
modal.html.twig is this one:

1 <div class="modal fade" id="{{ identifier }}" tabindex="-1" role="dialog">


2 <div class="modal-dialog" role="document">
3 <div class="modal-content">
4 <div class="modal-header">
5 <button type="button" class="close" data-dismiss="modal" aria-la\
6 bel="Close"><span aria-hidden="true">×</span></button>
7 <h4 class="modal-title">{{ title }}</h4>
8 </div>
9 <div class="modal-body">
10 {{ content|raw }}
11 </div>
12 </div>
13 </div>
14 </div>

I simplified a bit the Bootstrap example but you can improve it as much as you want. This component
receives three different variables: the modal identifier, in order to open it wherever we want; the
modal title; and the modal content. The last variable use the “raw” filter because it can contain
HTML text. With this filter, it won’t be escaped.
The next step is modifying our ContactController in order to renderize the modal when it comes
from a subrequest (a template that called a controller) or render the page if it’s not. Here you have
the needed modifications.

³⁸http://getbootstrap.com/javascript/#modals
Chapter 8: full example 70

1 /**
2 * @Route("/contact")
3 */
4 public function contactAction(Request $request) {
5 $form = $this->createForm(new ContactType(), null, array(
6 'action' => $this->generateUrl('app_contact_contact'),
7 ));
8 $form->handleRequest($request);
9 if ($form->isSubmitted() && $form->isValid()) {
10 // Form sent
11 die;
12 }
13
14 $requestStack = $this->get('request_stack')->getParentRequest();
15 if ($requestStack) {
16 $modalContent = $this->renderView('component/contact_form.html.twig', [
17 'contactform' => $form->createView()
18 ]);
19
20 return $this->render('component/modal.html.twig', array(
21 'identifier' => 'contactModal',
22 'title' => 'Contact Form',
23 'content' => $modalContent
24 ));
25 }
26
27 return $this->render('contact/contact.html.twig', array(
28 'contactform' => $form->createView()
29 ));
30 }

As you can see, I did a small trick to know if the process comes from a parent request thanks to
the “request_stack” service. This will allow us to identify what it has to be returned as a response.
In case of being a subrequest, we will first load the content of the form in a variable thanks to the
renderView method, instead of the render one which returns a Response object. The renderView
returns just a string. I also added the action parameter to force the form POST to the same controller
function, always using Symfony routes.
The last step is editing our base.html.twig and add to it the followig code in the footer section:
Chapter 8: full example 71

1 {% block footer %}
2 <p>Footer sexy 2015 - <a href="javascript:void(0)" data-toggle="modal" data-targ\
3 et="#contactModal">Contact</a></p>
4 {% endblock footer %}
5
6 {% block body_end %}
7 {{ render(controller('AppBundle:Contact:contact')) }}
8 {% endblock body_end %}

In that base template block, just before closing the body tag, the modal with our form will be loaded
in every single page. Do you want to try it? Refresh the page and click on the contact link in the
footer.
The view part is over. It’s time to give some back functionality to our contact form.

Sending an email
In this seciton we are going to learn two different things: sending an email (this was the easy one to
guess) and creating and using a Symfony service.
The first thing we are going to do is creating the service that will allow to send emails from our
application. It’s very similar to a controller, I’m sure you won’t have any problems to understand
what I show you in the following code block. This is the code for the src/AppBundle/Service/App-
Mailer.php file that will work for now:

1 <?php
2
3 namespace AppBundle\Service;
4
5 class AppMailer {
6
7 private $mailer;
8
9 public function __construct($_mailer) {
10 $this->mailer = $_mailer;
11 }
12
13 public function sendEmail($to, $subject, $text) {
14 $message = \Swift_Message::newInstance()
15 ->setSubject($subject)
16 ->setFrom('noreply@domain.com')
17 ->setTo($to)
Chapter 8: full example 72

18 ->setBody($text)
19 ;
20 return $this->mailer->send($message);
21 }
22
23 }

As you can see, we have a constructor which receives services to use inside this class, and a function
to send en email according to the received parameters. Swift Mailer already comes with Symfony,
so do not bother about it.
The next step is declaring the service. We just have to write the class and the arguments that the
constructor will receive. We must add the following code in our services.yml:

1 appmailer:
2 class: AppBundle\Service\AppMailer
3 arguments: ["@mailer"]

We just need to update our ContactController so it can send an easy email:

1 if ($form->isSubmitted() && $form->isValid()) {


2 $appmailer = $this->get('appmailer');
3 $text = "From: " . $form->get('name')->getData();
4 $text .= " Email: " . $form->get('email')->getData();
5 $text .= " Message: " . $form->get('message')->getData();
6 $appmailer->sendEmail(
7 'youremail@domain.com',
8 'Contact Form',
9 $text
10 );
11
12 return $this->redirectToRoute('app_contact_contact');
13 }

If you want to test it, you can edit your app/config/config_dev.yml file and add your Gmail acount
this way:
Chapter 8: full example 73

1 swiftmailer:
2 transport: gmail
3 username: your_gmail_username
4 password: your_gmail_password

As homework, you must improve the sent email by creating something a bit more sophisticated with
HTML thanks to the Twig template engine. You have the Symfony email documentation here³⁹.
With this, we have finished a complete Symfony example in which we have seen almost every “base
column” of the framework, and with that, you became a Symfony jedi. Even so, I have to tell you that
after several years working with this framework, I’m still learning nowadays, and that is wonderful.

Tip 8
It’s possible that sometimes you need to render a page without database code and without business
logic. This means that the controller function will have a single line to render the template. In this
case, Symfony allows us to render a template without having a controller. In order to do so, you
need an special route that cannot be done with annotations. Edit your routing.yml file and add
the following YAML route to it:

1 privacy_policy:
2 path: /privacy
3 defaults:
4 _controller: FrameworkBundle:Template:template
5 template: static/privacy.html.twig

Is this simple, we have avoided to create some lines of code that we just needed to load a template.
But if you are an advanded Symfony user, you will have noticed that there exists one problem: how
can I cache the content now without a Controller? Calm down, there is a solution for that:

1 privacy_policy:
2 path: /privacy
3 defaults:
4 _controller: FrameworkBundle:Template:template
5 template: static/privacy.html.twig
6 maxAge: 86400
7 sharedAge: 86400

³⁹http://symfony.com/doc/current/email.html
Chapter 8: full example 74

As this chapter has been very long, I will give you another tip. Imagine that you migrate some or
your URLs and, because of the SEO, you need to do some redirections. You can achieve this in several
ways without Symfony, but just because this book is not about Apache or others, I will explain you
how is this done in Symfony. Like in the previous example, you can catch the old route with a
controler and return a “return $this->redirect('...')”. However, we can also do the same with
YAML:

1 wpadmin:
2 path: /wp-admin
3 defaults:
4 _controller: FrameworkBundle:Redirect:redirect
5 route: sonata_admin_dashboard
6 permanent: true

With the previous route, we created a ficticious WordPress migration (wp-admin) to Symfony. Now
the users that browse to /wp-admin will be redirected to our Sonata made admin.

Summary
In this chapter, we have created a very complete example of several elements from Symfony and
Bootstrap. We have learnt to create new routes, new repositories for whatever it is related to
database, we have created services to decouple reusable functionality, we have improved our Twig
knowledgement, its templates and some other elements that are fontend related (Bootstrap based).
In the next chapter we will see other tools that will maximize our development even more.
Chapter 9: Doctrine Extensions
This is the first chapter in where we start talking about bundles you can use to develop and improve
your projects without being part of the base project we are building. I wanted to expend a full chapter
to a very interesting bundle: Doctrine Extensions. With this “plugin” you will be able to “vitaminize”
your entities with extra features that are normally repeated between projects. Storing the last time
an entity was edited, calculate the slug with a specific function or even more complex features like
field translation or version saving are automatically done with this bundle. As always, let’s start
with the installation.

Installation
If you search the name of this bundle as it is, you will see that it exists and you can install it.
However, you will have to create the filter associated services for your own. Most of them are
relatively simple for the 99% of the cases, and that’s why STOF created a bundle over Doctrine
Extensions with everything you need to start using those filters.
First of all, you must add the needed line to our dependency definition file composer.json:

1 composer require stof/doctrine-extensions-bundle

Then, you must enable the bundle in the app/AppKernel.php file in order to start using it:

1 new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),

Almost everything is done to start using it… but we missed the configuration part. This bundle is
a bit special, so we will be configuring the necessary parts while using it. In the next sections, I
will explain you how three of its filters are used, the rest of them are up to you (even though I will
explain them) and you will see if they fit your future project needs or they don’t.

Timestampable filter
We will start by one of the easiest filters of the bundle. The first thing we must know is what this
filter does. If you read the filter documentation, which is linked at the end of this section, you will
see that this filters allows to establish a date in a concrete field of the entity whenever:

• It is created
Chapter 9: Doctrine Extensions 76

• It is updated
• Some or one of its fields get updated
• A field get updated to a concrete value

We have to configure the filter to start using it. So, let’s edit the app/config/config.yml file and
add a new configuration block:

1 stof_doctrine_extensions:
2 orm:
3 default:
4 timestampable: true

From now on, we just have to add the new filters configuration to this block. I like to keep them
alphabetically ordered, but feel free to keep them the way you like more.
Filter activated, it’s time to use it. Edit the product entity and make it save whenever it gets
created, updated and when a product name changes. The following code is what I added in the
src/AppBundle/Entity/Product.php file just before the getters and the setters:

1 use Gedmo\Mapping\Annotation as Gedmo;


2 use Doctrine\ORM\Mapping as ORM;
3
4 //...
5
6 /**
7 * @Gedmo\Timestampable(on="create")
8 * @ORM\Column(type="datetime")
9 */
10 private $created;
11
12 /**
13 * @Gedmo\Timestampable(on="update")
14 * @ORM\Column(type="datetime")
15 */
16 private $updated;
17
18 /**
19 * @ORM\Column(name="name_changed", type="datetime", nullable=true)
20 * @Gedmo\Timestampable(on="change", field={"name"})
21 */
22 private $nameChanged;
Chapter 9: Doctrine Extensions 77

Did you understand what I’ve done? The first thing is adding the “use” in the top part of the file
that will allow us to write @Gedmo annotations. Then we have created a datetime variable for each
needed option. Remember to create the getters and the setters for this entity:

1 bin/console doctrine:generate:entities AppBundle:Product

And, indeed, as we added new columns, we have to update our database schema:

1 bin/console doctrine:schema:update --force

Everything prepared for the action, except for a small detail: how can we see if it’s working? There
are several ways, but just because we already built an amazing admin, we are going to use it now.
Edit the src/AppBundle/Admin/ProductAdmin.php file and add the following to the list function:

1 protected function configureListFields(ListMapper $listMapper)


2 {
3 $listMapper
4 ->addIdentifier('id')
5 ->add('name')
6 ->add('price')
7 ->add('description')
8 ->add('created')
9 ->add('updated')
10 ->add('priceChanged')
11 ->add('_action', 'actions', array(
12 'actions' => array(
13 'edit' => array()
14 )))
15 ;
16 }

In order to test it, browse to http://127.0.0.1:8000/admin and create a new product, edit it in various
ways and see how the fields are getting updated.
This filter documentation is available in this link⁴⁰. But I’m going to give a small tip a little earlier:
Symfony does have a tool to edit entity values before and after some events happen. This feature is
called “Lifecycle Callbacks”, and you should read about how it works in this link⁴¹ before you get
confortable with Gedmo.
⁴⁰https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/timestampable.md#timestampable-entity-example
⁴¹http://symfony.com/doc/current/book/doctrine.html#lifecycle-callbacks
Chapter 9: Doctrine Extensions 78

Slugabble filter
With this filter you will be able to create your own slugs just as easily as in the date update of
the previous example. If you don’t know what a slug is, let me explain it with an example. If
you have a product in your database called “My hello product”, the best URL to be considered as
“SEO friendly” would be something like /products/my-hello-product. This string without spaces,
lowercase, without accents and without special characters is called slug.
Let’s enable the filter to see it working. Edit the configuration file app/config/config.yml and add
the new filter:

1 stof_doctrine_extensions:
2 orm:
3 default:
4 sluggable: true
5 timestampable: true

Then, edit the product entity and add the slug field just after the name, to keep it in order:

1 /**
2 * @Gedmo\Slug(fields={"name"})
3 * @ORM\Column(length=105, unique=true)
4 */
5 private $slug;

Generate the getters and the setters with the same command we used previously. To keep the entity
in order, just as with variables, you should order the generated methods. You can delete all of them
manually and regenerate them again, or once generated cut and paste them in the place they should
be, but I suggest you to keep them in the same order as the variables:

1 bin/console doctrine:generate:entities AppBundle:Product

Finally, you must update the database but first you must delete all previously created products
because the slug is a unique field and, when you update the schema, all products will have the same
empty slug and and error will be launched: “duplicate entry”. You can delete them from the admin.
Once you finish, execute the following database schema update command from a terminal:

1 bin/console doctrine:schema:update --force

Let’s see it up and running. Again, edit the ProductAdmin class and add the new created field to the
list action:
Chapter 9: Doctrine Extensions 79

1 protected function configureListFields(ListMapper $listMapper)


2 {
3 $listMapper
4 ->addIdentifier('id')
5 ->add('name')
6 ->add('slug')
7 ->add('price')
8 ->add('description')
9 ->add('created')
10 ->add('updated')
11 ->add('nameChanged')
12 ->add('_action', 'actions', array(
13 'actions' => array(
14 'edit' => array()
15 )))
16 ;
17 }

Create a new product and see it the slug was generated, do you like it? If you look carefully, when
you update the entity name the slug also gets updated. Thinking about website in general, this could
produce a 404 error if the URL has already been shared, for example, in social networks. If you want
to avoid this, there exists an option to deactivate the slug update when an entity is modified. This
and a bunch of other possible configurations may be obtained adding the following parameters to
the annotation recently created:

• fields (required, default=[]) - list of fields for slug


• updatable (optional, default=true) - true to update the slug on sluggable field changes, false -
otherwise
• unique (optional, default=true) - true if slug should be unique and if identical it will be
prefixed, false - otherwise
• unique_base (optional, default=null) - used in conjunction with unique. The name of the entity
property that should be used as a key when doing a uniqueness check.
• separator (optional, default=”-“) - separator which will separate words in slug
• prefix (optional, default=””) - prefix which will be added to the generated slug
• suffix (optional, default=””) - suffix which will be added to the generated slug
• style (optional, default=”default”) - “default” all letters will be lowercase, “camel” - first word
letter will be uppercase, “upper”- all word letter will be uppercase and “lower”- all word letter
will be lowercase
• handlers (optional, default=[]) - list of slug handlers, like tree path slug, or customized, for
example see bellow
Chapter 9: Doctrine Extensions 80

Everything went OK until you read the “handlers” list item. What are they? In most of your projects
you won’t be using this feature, but it can be very helpful.
There are several default handlers that will allow you to generate slugs in a more detailed way. For
example: if you have the product entity related to a category, it’s possible that you like to have a
slug like “/category/product”. Would you like to do an example? Ok, you will do the next exercise
with this documentation⁴².
On the other hand, you have the sluggable filter documentation in this link⁴³.

Softdeleteable filter
How many times did you hear that “you must not remove database rows, just mark them as deleted”?
With this filter we will be able to achieve that goal without having to trouble with all the queries
you already developed, because this filter does everything by default. Do you understand this? Ok,
you will do in a minute.
The first thing is enabling the filter. Edit the configuration file app/config/config.yml:

1 stof_doctrine_extensions:
2 orm:
3 default:
4 sluggable: true
5 softdeleteable: true
6 timestampable: true

With this configuration, we will mark an entity as deleted when removing it. But we also want to
retrieve all not-as-deleted products when quering “give me all the database products”. To get this
done, you must also add an orm filter. Here you have the configuration block. You only have to add
the filters stuff, the other part is already created. Search for it in the app/config/config.yml file:

1 doctrine:
2 orm:
3 filters:
4 softdeleteable:
5 class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter
6 enabled: true

Now that the filter is configured, we must add a new property to our product entity in order to save
whenever it gets deleted:
⁴²https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/sluggable.md#some-other-configuration-options-for-slug-annotation
⁴³https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/sluggable.md
Chapter 9: Doctrine Extensions 81

1 <?php
2
3 namespace AppBundle\Entity;
4
5 use Gedmo\Mapping\Annotation as Gedmo;
6 use Doctrine\ORM\Mapping as ORM;
7
8 /**
9 * @ORM\Entity
10 * @ORM\Table(name="product")
11 * @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
12 */
13 class Product
14 {
15 //...
16
17 /**
18 * @ORM\Column(name="deleted_at", type="datetime", nullable=true)
19 */
20 private $deletedAt;
21 }

Did you see that? This annotation is a bit different, and it’s in an entity level. This will allow Doctrine
to know when to return an entity and how to set the deleted value in the database. As always,
generate the getters and the setters and update the database schema:

1 bin/console doctrine:generate:entities AppBundle:Product


2 bin/console doctrine:schema:update --force

You can browse to the admin section and delete one of your products. When you do it, you won’t
be able to see it in the admin but, if you browse your database with PhpMyAdmin or any other
software you like, you will see that it is still there. This is because of the second configuration that
we added. So, if you execute the following code, you will see that the deleted products are not
being returned:

1 $products = $em->getRepository("AppBundle:Product")->findAll();

If you need all products for whatever reason, even the deleted ones, you can deactivate the orm filter
like this:
Chapter 9: Doctrine Extensions 82

1 $em->getFilters()->disable('softdeleteable');
2 $products = $em->getRepository("AppBundle:Product")->findAll();

You have all the filter documentation in this link⁴⁴.

Other filters
There are a lot of filters that you can use thanks to this bundle. You will find how to configure and
use them in this link⁴⁵. I strongly recommend you to read all the filters options to see how much
code you can save thanks to others’ work. But, to finish with good sensations, here you have the
rest of the filters briefly explained:

• blameable: just as the timestampable** filter it allow you to set an entity field when an
event happens but with a *string instead of a datetime. If it’s in a text field, it will insert the
username; if it’s a relation, is will create the conection between the entities. Documentation⁴⁶.
• iptraceable: same as the previous point, but it just sets the user IP as a string. Documentation⁴⁷.
• loggable: tracks the entity changes and allows to manage its versions. Documentation⁴⁸.
• sortable: it keeps a position field to order the rows. Documentation⁴⁹.
• translatable: it allows to insert specific field for different languages, automatically loading
the concrete user translation when it browses the page. Documentation⁵⁰.
• tree it allows to implement the Nested-Set behaviour in an entity, supporting several
strategies. Documentation⁵¹.
• uploadable it allows go manage the file persistence with Doctrine such as moving, renaming
or removing those files automatically. Documentation⁵².

Tip 9
In this chapter we have seen how the slugs work. I’m sure you already experienced or you are about
to experience that a lot of routes will start the code as “give me the object whose slug is this one”.
Symfony developers saw this code repetition and they created a way of jumping over this step. To
do so, you just have to edit the action declaration so it receives the object and not the slug string.
Here you have an example:
⁴⁴https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/softdeleteable.md
⁴⁵https://github.com/stof/StofDoctrineExtensionsBundle/blob/master/Resources/doc/index.rst
⁴⁶https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/blameable.md
⁴⁷https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/ip_traceable.md
⁴⁸https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/loggable.md
⁴⁹https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/sortable.md
⁵⁰https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/translatable.md
⁵¹https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/tree.md
⁵²https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/uploadable.md
Chapter 9: Doctrine Extensions 83

1 /**
2 * @Route("/products/{slug}")
3 */
4 public function indexAction(Product $product)
5 {
6 // ...
7 }

As you see, instead of receiving the slug directly I’m telling Symfony to query the database for me.
Ah, another thing! If the product doesn’t exist, it will automatically launch a 404 exception without
you having to do it manually.
How many lines of code have I just saved you? This is nothing at all. This technique is called
ParamConverter and we are going to use it a lot deeper in chapter 13, just because with some simple
(well, not always so simple) annotations, we will have our entity objects ready just before starting
our action code.

Summary
In this chapter we have learnt to add several improvements to our entities thanks to the Doctrine
filters that give us a basic functionality but they got repeated over and over again in our projects.
Thanks to some simple annotations and a very small configuration, we have given those features to
our project and we have removed (or saved) lots of code lines.
In the next chapter we will be moving to something a bit more visual and we will be working with
a bundle that allows us to manage images before serving them to the users.
Chapter 10: LiipImagineBundle
This chapter may be a little shorter than the previous ones, but I would like to give it a whole chapter
to the image processing. If you search over Internet you will find some bundles that will allow you
to manage images with Symfony, but after testing some of them personally, the one that best works
in my own opinion is LiipImagineBundle. In this chapter I will explain you how to install the bundle,
configure it and work with it. Let’s start!

Installation
The installation is as simple as executing the following command:

1 composer require liip/imagine-bundle

And then enable the bundle within the AppKernel.php file:

1 public function registerBundles()


2 {
3 $bundles = array(
4 // ...
5
6 new Liip\ImagineBundle\LiipImagineBundle(),
7 );
8
9 // ...
10 }

Finally, you have to add some routes to the app/config/routing.yml file because without them,
our filters won’t work:

1 _liip_imagine:
2 resource: "@LiipImagineBundle/Resources/config/routing.xml"

With this done, everything is correct but… what are those filters? The filters are just bundle
configuration blocks that will allow us to apply some transformations to the images, both with
the Twig filter (do not confuse yourself with the filter concept of both topics) and applying the filter
to the image from our PHP code. In the next section I will explain how this works.
Chapter 10: LiipImagineBundle 85

Configuration
By default, this bundle creates the modified images in the web/media/cache folder, so we have to
allow our webserver to write on this folder. Here you have the commands that I executed to give
permissions just to the web server, so if you are interested in having other permissions you skip
these command and execute another ones:

1 mkdir -p web/media/cache
2 HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep \
3 -v root | head -1 | cut -d\ -f1`
4 sudo chown $HTTPDUSER:$HTTPDUSER web/media/cache

The bundle required configuration to work with Symfony is… “none”. This is a bit misleading
because, in fact, the bundle is working right now and it’s not giving any error, but it’s not doing
anything. Your work from now on will be editing the configuration file app/config/config.yml
and adding the filters you want to your project. Now I’m going to explain you the *default** filters,
but you must know that you are able to create your own filters.
Thumbnail This filter creates a thumbnail from a specific image. The configuration may receive two
different modes: the inset mode allows to make a relative resize, never exceedeing the limits given
by the configuration; the outbount mode cuts the image if after making that relative resizing the
dimensions are not correct. Yo may see it much better with an example configuration of the filter,
as you know, placed in the app/config/config.yml file:

1 liip_imagine:
2 filter_sets:
3 my_thumb_in:
4 filters:
5 thumbnail: { size: [32, 32], mode: inset } # Transforms 50x40 to\
6 32x26, without cutting
7 my_thumb_out:
8 filters:
9 thumbnail: { size: [32, 32], mode: outbound } # Transforms 50x40\
10 to 32x32, cutting the width

Both options allows an extra allow_upscale parameter that allows the image to be resized to a
bigger size. By default, this parameters is set to false.
Relative resize This filter allow to edit the size of an image with four different options. This
configuration example explain its functionality:
Chapter 10: LiipImagineBundle 86

1 liip_imagine:
2 filter_sets:
3 my_heighten:
4 filters:
5 relative_resize: { heighten: 60 } # Transforms 50x40 to 75x60
6 my_widen:
7 filters:
8 relative_resize: { widen: 32 } # Transforms 50x40 to 32x26
9 my_increase:
10 filters:
11 relative_resize: { increase: 10 } # Transforms 50x40 to 60x50
12 my_widen:
13 filters:
14 relative_resize: { scale: 2.5 } # Transforms 50x40 to 125x100

Upscale This filter allows to resize the image to a higher size:

1 liip_imagine:
2 filter_sets:
3 my_thumb:
4 filters:
5 upscale: { min: [800, 600] }

Crop This filter allows tu cut the image form a detailed point, at a detailed size:

1 liip_imagine:
2 filter_sets:
3 my_thumb:
4 filters:
5 crop: { start: [10, 20], size: [120, 90] }

Strip This filter allows to remove all image associated profiles and comments:

1 liip_imagine:
2 filter_sets:
3 my_thumb:
4 filters:
5 strip: ~

Background This filter allows to establish an image background color. By default, the color is #FFF
(white):
Chapter 10: LiipImagineBundle 87

1 liip_imagine:
2 filter_sets:
3 my_thumb:
4 filters:
5 background: { color: '#00FFFF' }

It can also receive a detailed size, so it will create a new image with the size given:

1 liip_imagine:
2 filter_sets:
3 my_thumb:
4 filters:
5 background: { size: [1026, 684], color: '#00FFFF' }

Watermark This filter is cool because it allows you to add a watermark to your images keeping the
aspect ratio:

1 liip_image:
2 filter_sets:
3 my_image:
4 filters:
5 watermark:
6 # Relative route to the watermark file (from "%kernel.root_d\
7 ir%/")
8 image: Resources/data/watermark.png
9 # Watermark size relative to the image original size
10 size: 0.5
11 # Position: you can choose between topleft,top,topright,left\
12 ,center,right,bottomleft,bottom,bottomright
13 position: center

Auto rotate This filter rotates the image based on the EXIF information. If you are going to chain
filters (I will explain this in a minute), you should call this filter before the others:

1 liip_imagine:
2 filter_sets:
3 my_thumb:
4 filters:
5 auto_rotate: ~

Rotate This filter rotates the image based on an specific angle (degrees):
Chapter 10: LiipImagineBundle 88

1 liip_imagine:
2 filter_sets:
3 my_thumb:
4 filters:
5 rotate: { angle: 90 }

Interlace This filter allows to establish a progressive image load:

1 liip_imagine:
2 filter_sets:
3 my_thumb:
4 filters:
5 interlace:
6 # Los modos pueden ser none, line, plane, partition
7 mode: line

Create your own filter Besides all the information exposed in this section, in this link⁵³ you have all
the needed information to create your own filter and make it work. Personally, I had never created a
filter just because I haven’t needed it, but if you do need it, I will appreciate a lot if you share reason
and the final result by Twitter so I can learn with you ;)
Multifilter Do you need 2 or more filter for your images? No problem, you can chain them this way:

1 liip_imagine:
2 filter_sets:
3 carousel:
4 quality: 90
5 filters:
6 relative_resize: { widen: 1920, allow_upscale: true }
7 thumbnail: { size: [1920, 1040], mode: outbound }

More configuration In addition to the configuration relative to filters, LiipImagineBundle allows


a great variety of feature modifications, such as the folder in where final pictures will be stored.
Here⁵⁴ you have all the information availabe about configuration.

Using with Twig


We reached the interesting part of the chapter: using the bundle. The first thing we are going to do
is configuring a filter to create a small thubmnail of an image, sized 60x60 for example. Edit the
app/config/config.yml and add the following filter at the end of the file:

⁵³http://symfony.com/doc/current/bundles/LiipImagineBundle/filters.html#custom-filters
⁵⁴http://symfony.com/doc/master/bundles/LiipImagineBundle/configuration.html
Chapter 10: LiipImagineBundle 89

1 liip_imagine:
2 filter_sets:
3 aupa_thumbnail:
4 filters:
5 thumbnail: { size: [60,60], mode: outbound }

The next step: download a big image and place it in the web/images/test.jpg folder (change the
name for whatever you like). Once downloaded, let’s use that image in one of our Twig templates.
For example, use the app/Resources/views/home/index.html.twig template. In order to use the
filter, you just add the following code:

1 <img src="{{ '/images/test.jpg' | imagine_filter('aupa_thumbnail') }}" alt=""/>

Now, after browsing to http://127.0.0.1:8000/home you will be able to see the 60x60 image. It’s time
to stop some minutes the reading and test the other filters. When you finish, continue with the next
section: using the filters with PHP.

Using with PHP


Imagine that you have to return a JSON response with the image after applying a filter to it or, for
whatever reason, you need to use one of your filters in a controller. LiipImagineBundle allows you
to do this using a service to whom you can access from the container, as any other service. The
getBrowserPath function will return an image route with the filter already applied, so you can work
with it as you want. The next code explains what we did in Twig but this time, with PHP:

1 // Create Liip thumbnail


2 $imageSrc = $this
3 ->get('liip_imagine.cache.manager')
4 ->getBrowserPath(
5 '/images/prueba.jpg',
6 'aupa_thumbnail'
7 );

What can you do with this route? Whatever you need, from sending it to a Twig to returning it as
an API response. You also have the option to return the image as a reponse thank to the filterAction
function:
Chapter 10: LiipImagineBundle 90

1 // Returns a RedirectResponse object


2 $imagemanagerResponse = $this->container
3 ->get('liip_imagine.controller')
4 ->filterAction(
5 $this->request,
6 'images/prueba.jpg',
7 'aupa_thumbnail'
8 );

Tip 10
Without any doubs this will be one of the tips that you are going to apply the most in your projects.
As you should know, since the cell phone appearance, all websites have a big problem: the biggest
resource files. This resources makes the mobile browsing not very successful because of the loading
time. Besides it can make our visitants angry for consuming their data contract. In HTML5 you do
have a possibility to reduce the image loading thanks to the <picture> tag.
This tag receives a series of images that will be loaded depending on the visitant viewport resolution,
so, if we add LiipImagineBundle with a bunch of filter to resize images automatically, we will have
the perfect recipe up and running:
The first step is declaring the needed filters:

1 liip_imagine:
2 filter_sets:
3 widen640:
4 filters:
5 relative_resize: { widen: 640 }
6 widen1024:
7 filters:
8 relative_resize: { widen: 1024 }

Now, edit the app/Resources/views/home/index.html.twig template to use the <picture> tag with
the recently created filters:
Chapter 10: LiipImagineBundle 91

1 <picture>
2 <source media="(max-width: 640px)" srcset="{{ '/images/prueba.jpg' | imagine\
3 _filter('widen640') }}">
4 <source media="(max-width: 1024px)" srcset="{{ '/images/prueba.jpg' | imagin\
5 e_filter('widen1024') }}">
6 <source media="(min-width: 1024px)" srcset="{{ '/images/prueba.jpg' }}">
7 <img src="{{ asset('images/prueba.jpg') }}" alt="">
8 </picture>

Do you understand what the previous code does? Inside the <picture> tag, all the <source> tags
will be read until one of them meets the requirements. That one is the one that will be displayed
on the screen. In case the browser does not support this tag, the <img> fallback will be rendered,
awesome!
However, not all browsers support this tag, but there exists a polyfill that will make it work in some
of them. Here⁵⁵ you have the link to that polyfill.

Summary
In this chapter we have used a bundle for image treatment thanks to filters. With some small and
simple configurations, the power that brings is humongous, and added to the tip 10 of this chapter,
it make LiipImagineBundle as one of the must have bundles for every project.
In the next chapter, we will see other bundles that you may be interested in, but we will not go into
detail with them as we did in this chapter.
⁵⁵https://github.com/scottjehl/picturefill
Chapter 11: ther bundles
In this chapter I would love to show you some of the bundles that I’ve worked with in my professional
career. These bundles have been a great help in certain moments. It’s not my intention to fill this
chapter of documentation and use cases as I did in previous chapters, but I will give you little portions
about what each of them do. If any of the explained bundles interest you, something that I’m really
sure, you just have to try it and start working with it.

EWZRecaptchaBundle
Let’s start the chapter with a really simple bundle, but also very powerful. I’m sure that in more than
one of your projects you must have used one of the infinite existing libraries for captcha elements.
With this bundle, you will be able to create the famous Google ReCaptcha in a very ease way but,
moreover, it’s fully integrated with the Symfony forms. In its GitHub repository⁵⁶ you can see how it
is installed, how it is configured and one of the most important things: how it is used. Even though
I said that I wouln’t copy any documentation, let me skip over this rule just once:

1 use EWZ\Bundle\RecaptchaBundle\Validator\Constraints\IsTrue as RecaptchaTrue;


2
3 public function buildForm(FormBuilder $builder, array $options)
4 {
5 // ...
6 $builder->add('recaptcha', 'ewz_recaptcha', array(
7 'mapped' => false,
8 'constraints' => array(
9 new RecaptchaTrue()
10 )
11 ));
12 }

Did you see how easy is to add a ReCaptcha field? You don’t need to map it to the entity and it will
manage to launch an exception if the received value it’s not correct for it’s own. But, I must give you
some “bad” news at this moment, “bad” because you may not find this casuistry in your projects.
When rendering the ReCaptcha this way, at the time I’m writing this book, the bundle has no option
to live together with other ReCaptcha fields in the same page. Do you dare with a pull request?
⁵⁶https://github.com/excelwebzone/EWZRecaptchaBundle
Chapter 11: ther bundles 93

AcceleratorCacheBundle
Things are changing a lot with the accelerator stuff in PHP, but even so, it’s interesting to know
how APC works as long as you have a PHP project with a PHP version below 5.5 (I hope you don’t).
Why this exact version? In PHP 5.5 and higher, we have OPCache by default, and before OPCache
we needed to install another byte code cache, as the mentioned APC.
Inside the app/config/config_prod.yml file you will be able to see different configurations ready
to be enabled depending on your project needs. All the elements that you enable will be sent to APC
cache and… what’s the problem with this? If you make any change in any of the elements that are
cached, you won’t see that changes until you clean the APC cache. That’s why this⁵⁷ bundle exists,
it will add a new Symfony command in order to clean the cached elements in an easy way, APC
and OPcache too.

FOSRestBundle
I’m going to give you some great news: you do have this bundle already installed because it’s
a SonataUserBundle dependency. And you will ask yourself, “what can I do with this bundle?
With FOSRestBundle⁵⁸ you will be able to create an API in a matter of minutes and with a lot
of added features out of the box. It’s a quite complex bundle because of the magic quantity it does
behind the scenes, so I suggest you to read the documentation carefully without rushing. Test every
documentation point and, very important, look others’ code to know how they use it. This is a well-
known and used bundle, and because of that it has it’s own documentation inside symfony.com.

NelmioApiDocBundle
I’ve heard that you recently created an API in your project, did you documented it? We, developers,
don’t like to document quite much but we have to make the effort and document everything we do
as good as possible. With this bundle⁵⁹ you will be able to document your API and the rest of the code
you develop easily, thanks to several annotations created for that goal. The bundle will be able to
create a website with all the stuff you developed and, of course, documented. In the bundle GitHub
documentation⁶⁰ you can see how to do what I told you here with some images which illustrate
how your documentation will be rendered as web mode. By the way: this bundle is already installed
thanks to SonataUserBundle too, so you don’t have any excuses to start working with it.
⁵⁷https://github.com/Smart-Core/AcceleratorCacheBundle
⁵⁸http://symfony.com/doc/master/bundles/FOSRestBundle/index.html
⁵⁹https://github.com/nelmio/NelmioApiDocBundle
⁶⁰https://github.com/nelmio/NelmioApiDocBundle/blob/master/Resources/doc/index.md
Chapter 11: ther bundles 94

DoctrineFixturesBundle
Fixtures allows us to do a controlled data load to the database. This inserted data can be used to run
tests or to do an initial load that the application we are developing requires. By default, Symfony
doesn’t have an easy way to load data as other frameworks or even CMS have, but this bundle will
help you to achieve it both with Doctrine ORM and ODM (MongoDB).
In the bundle documentation⁶¹ you can read about the installation (very interesting the App-
Kernel.php code where it gets instantiated) and the way to create load classes that implement
the FixtureInterface interface. Thanks to the load method that receives a manager (ORM or
ODM) as a paremeter and thanks to the app/console doctrine:fixtures:load and app/console
doctrine:mongodb:fixtures:load commands we will have a database full of information in
a matter of minutes. As you see in the previous documentaiton, this can get more and more
complicated, as much as you want. Here I would like to stop a bit and give you a little challenge:
our database doesn’t have a lot of entities so I suggest you to create an initial user loaded. The
“difficulty” here is that you will have to guess how the users are created thanks to Sonata User and
FOSUserBundle.

DoctrineMigrationsBundle
This bundle is an extension of the database abstraction layer and it offers you the possibility to
deploy new versions of your database programatically in a secure, easy and standarized way.
In the documentation⁶² you can read how it is installed and how it works, but all of this can be
sumarized in a bunch of migration files that contains the up() and down() methods. The up() method
contains the new stuff to be applied when launched (for example, “create the acme_hello table with
fields a, b, c, …“ ), while the down() method will only get called in case of undoing some deploy (for
example, “delete the acme_hello table”). The clases can be generated manually or just executing the
app/console doctrine:migrations:diff command which will generate the new class with all the
needs automagically. Of course, if you want to have the full control or just upload a concrete part
of those modifications, you can create and/or edit the generated file manually.
This bundle can be linked with the deploy system that will be explained in the chapter 15, and
without any doubts ot will make a before and an after in your deployments (or at least, it did to me).

FOSJSRoutingBundle
This bundle allows to generate routes in the “Symfony way” but from your JavaScript code. A lot
of people tell me that the solution is to insert that JavaScript “piece” of code in a Twig template and
there you will have access to the routing system, but, besides not beeing a best practice (just a patch),
⁶¹http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
⁶²http://symfony.com/doc/master/bundles/DoctrineMigrationsBundle/index.html
Chapter 11: ther bundles 95

this won’t solve the problem in all cases. If you need to pass JavaScript variables to the Symfony
route, you have no other option than create the route manually or use this bundle and work almost
as you have been doing until now.
You have everything you need to start testing it in the documentation⁶³. It may be a little weird
to start working with it if you are used to work with the Twig path and url functions and to the
$this->generateUrl of the Symfony controllers, but if it’s really necessary in your project, you will
get used to it very quickly.

HWIOAuthBundle
How many times did you visit a website and instead of filling up a boring and long form you
connected to it with your Facebook account? Everyday is more common that website allow you to
log in or even create a new account from a social network or from other usual website. In order to
make this work, the site from they get the information must implement the OAuth protocol. This
way, other developers can communicate with the site and obtain the needed information.
It may be possible that you had to create this whole process manually once in the past but, even
though it’s a very didactical way of learning how OAuth works, sometimes it’s much better not
losing time. HWIOAuthBundle⁶⁴ add the support to authenticate your users by OAuth 1.0a and
OAuth2 with more than 40 different providers, the main ones being Amazon, Dropbox, Facebook,
GitHub, Google, LinkedIn, Twitter, WordPress, Youtube and a very long etc. Unless you have a very
concrete need to develop this section from scratch, why would you do it? As always with Symfony:
spend your time in what you application really needs.

KnpSnappyBundle
Your website is awesome, but we get to the point that some users may need to download some
information as a report. Thanks to this bundle you will be able to create both pdf documents and
images from your HTML documents, and that’s because Snappy is wkhtmltopdf conversion utility
wrapper that it’s integrated in a trivial way into your Symfony project.
In the documentation⁶⁵ you will see that you must point out the wkhtmltopdf and wkhtmltoimage
binary locations, so before installing this bundle, go to this⁶⁶ website and download the needed files.
You wouldn’t have imagined that creating and returning pdf files was so easy, would you?
⁶³https://github.com/FriendsOfSymfony/FOSJsRoutingBundle/blob/master/Resources/doc/index.md
⁶⁴http://knpbundles.com/hwi/HWIOAuthBundle
⁶⁵https://github.com/KnpLabs/KnpSnappyBundle
⁶⁶http://wkhtmltopdf.org/
Chapter 11: ther bundles 96

JMSTranslationBundle
I hope you already felt in love with Symfony at this point of the book and you already read the full
Symfony documentation, but for this bundle it will be desirable to have the translation⁶⁷ chapter
read. As you see, Symfony is based in some translation files that can be generated thanks to the
translations:update command but the whole process is very bad if we talk about usability.

Thank to this bundle you will be able to vitaminize this process incredibily. As you can read
in the documentation⁶⁸, first of all you have to extract the strings to be translated with the
translations:extract command and, thanks to some imported routes in our routing.yml file,
we will be provided with a simple but effective translation interface.
Stop translating the files manually or with external software such as Pootle that difficult the process.
Now we can even generate a specific permission like ROLE_TRANSLATOR and just give him/her
the access to our translator in where he/she will be able to see what it’s changing and where it’s
been changed, even though the changes won’t be shown until you clean the cache. If your website
is multi language, without any doubts this is a must have bundle.

Tip 11
In this tip I would like to remember you a previous commented thing and offer you two more
websites that I hope you use in your future projects. The first thing I want to remember you is that
you visit knpbundles.com⁶⁹ with asiduity and check the most used bundles and the ones that are
being updated. It’s important that you keep in mind the bundles maintenance by the people who
created them or the community, or it may be possible you find a problem in a very short period of
time.
The second thing I want to share in this tip is a Symfony cheatsheet. In http://www.symfony2cheatsheet.com/⁷⁰
in where you will find some of the elements that are used daily when developing. The website is not
fully updated but, such as the $this->redirectToRoute() function that I use a lot, but it’s always
a good idea to have this webpage to get a glimpse and the official documentation to get a detailed
view.
The last thing, if you are going to upload your project to a production environment, you may
be interested in reading this http://www.symfony2-checklist.com/ ⁷¹, where you will find elements
related to perfomance, security. etc.
As always, I like to give an extra, so I share with you the official SensioLabs YouTube channel⁷² in
where you will be able to see lots of professional people talking about Symfony related stuff, things
⁶⁷http://symfony.com/doc/current/book/translation.html
⁶⁸http://jmsyst.com/bundles/JMSTranslationBundle
⁶⁹http://knpbundles.com/
⁷⁰http://www.symfony2cheatsheet.com/
⁷¹http://www.symfony2-checklist.com/
⁷²https://www.youtube.com/user/SensioLabs
Chapter 11: ther bundles 97

that they have done and how they did it, or just how a Symfony component works in a more detailed
way. No doubts here: you will learn a lot from other professionals all over the world.

Summary
In this chapter we have seen a great variety of bundles thay may fit in some of your projects. Without
going into detail, we have explained the aim of all of them and it’s your turn to work with the ones
that looks interesting to you.
In the next chapter, we will go a little further with our Gulp system and we will be giving it even
more power.
Chapter 12: Gulp (2)
During this chapter we will improve our gulpfile.js and give it a lot more power than it already
had. All the stuff it contains at this moment makes reference to the website styling, but it’s time
to work with the JavaScript files and keep our code with a style guide. Having a good code is
fundamental.

Sass Lint
I’m sure you edited other person fontend work and you thought “why does he/she works different
than me?”. Everyone of us have a different way of working and it’s something bad that slows down
future developers or frontenders. This is why style guides exist that are getting evolved periodically,
and we should follow them. Just with this way we will be able to increase productivity almost
immeadiately.
A very clear example is the order for every element of our website styles. Some people write in the
top part the spacing elements and the colors; others write the block disposition and then the sizes…
which one is better? There is no better option so, the solution that was chosen was alphabetically
ordered.
Well, this just frightened you. You will be thinking in reading all your CSS styles again and see if
everything is in the right place (although you already know that they are not). Again, Gulp to the
rescue with a tool called Sass Lint. This tool will be able to analyze the SCSS files that you tell him
to analyze and write the errors that are found on your screen.
To install this tool, execute the following command in a terminal:

1 npm install gulp-sass-lint --save-dev

Next step: create a Gulp task to shout the existing failures:


Chapter 12: Gulp (2) 99

1 var gulp = require('gulp'),


2 //...
3 sasslint = require('gulp-sass-lint'),
4 ;
5
6 gulp.task('sasslint', function () {
7 return gulp.src('./app/Resources/assets/scss/**/*.scss')
8 .pipe(sasslint())
9 .pipe(sasslint.format())
10 .pipe(sasslint.failOnError());
11 });

The best option in this point for you is to configure the rules that your Sass lint should follow. In
order to achieve so, create a .sass-lint.yml file in the root of your project and add there the rules
you want. Here you have one of my configuration files:

1 files:
2 include: '**/*.scss'
3 options:
4 formatter: stylish
5 merge-default-rules: false
6 rules:
7 bem-depth:
8 - 0
9 - max-depth: 1
10 border-zero:
11 - 1
12 - convention: zero
13 brace-style:
14 - 1
15 - allow-single-line: true
16 class-name-format:
17 - 1
18 - convention: hyphenatedbem
19 clean-import-paths:
20 - 1
21 - filename-extension: false
22 leading-underscore: false
23 empty-line-between-blocks:
24 - 1
25 - ignore-single-line-rulesets: true
26 extends-before-declarations: 1
Chapter 12: Gulp (2) 100

27 extends-before-mixins: 1
28 final-newline:
29 - 1
30 - include: true
31 force-attribute-nesting: 1
32 force-element-nesting: 0
33 force-pseudo-nesting: 0
34 function-name-format:
35 - 1
36 - allow-leading-underscore: true
37 convention: hyphenatedlowercase
38 hex-length:
39 - 1
40 - style: short
41 hex-notation:
42 - 1
43 - style: lowercase
44 id-name-format:
45 - 1
46 - convention: hyphenatedbem
47 indentation:
48 - 1
49 - size: 2
50 leading-zero:
51 - 1
52 - include: false
53 mixin-name-format:
54 - 1
55 - allow-leading-underscore: true
56 convention: >-
57 ^([\.\%]?[a-z]*[-]?[a-z0-9\-]*)(\.[a-z0-9\-]*)?(__[a-z0-9]*[-]?[a-z0-9\-\
58 ]*)?(--[a-z0-9]*[-]?[a-z0-9\-]*)?(\:[a-z]*)*$
59 convention-explanation: BEM pattern
60 mixins-before-declarations: 1
61 nesting-depth:
62 - 1
63 - max-depth: 3
64 no-color-keywords: 1
65 no-color-literals:
66 - 1
67 - allow-rgba: true
68 no-css-comments: 1
Chapter 12: Gulp (2) 101

69 no-debug: 1
70 no-duplicate-properties: 1
71 no-empty-rulesets: 1
72 no-extends: 0
73 no-ids: 1
74 no-important: 1
75 no-invalid-hex: 1
76 no-mergeable-selectors: 0
77 no-misspelled-properties:
78 - 1
79 - extra-properties: []
80 no-qualifying-elements:
81 - 1
82 - allow-element-with-attribute: false
83 allow-element-with-class: false
84 allow-element-with-id: false
85 no-trailing-whitespace: true
86 no-trailing-zero: 1
87 no-url-protocols: 1
88 no-vendor-prefixes:
89 - 1
90 - additional-identifiers: []
91 excluded-identifiers: []
92 ignore-non-standard: false
93 placeholder-in-extend: 1
94 placeholder-name-format:
95 - 1
96 - convention: hyphenatedbem
97 property-sort-order:
98 - 1
99 - ignore-custom-properties: false
100 quotes:
101 - 1
102 - style: single
103 shorthand-values:
104 - 1
105 - allowed-shorthands:
106 - 1
107 - 2
108 - 3
109 - 4
110 single-line-per-selector: 1
Chapter 12: Gulp (2) 102

111 space-after-bang:
112 - 1
113 - include: false
114 space-after-colon:
115 - 1
116 - include: true
117 space-after-comma: 1
118 space-around-operator: true
119 space-before-bang:
120 - 1
121 - include: true
122 space-before-brace:
123 - 1
124 - include: true
125 space-before-colon: 1
126 space-between-parens:
127 - 1
128 - include: false
129 trailing-semicolon: 1
130 url-quotes: 1
131 variable-for-property:
132 - 0
133 - properties: []
134 variable-name-format:
135 - 1
136 - allow-leading-underscore: true
137 convention: hyphenatedlowercase
138 zero-unit: 1

Finally, execute the same Gulp and see the result. If you haven’t edited anything of what we have
done in this book, you should see something like this:

1 app/Resources/assets/scss/components/_claim.scss
2 warning Color literals such as '#f22' should only be used in variable decla\
3 rations no-color-literals

This is basically telling us that we have one error:

• A color that is not declared as a variable. Solution: declare it as a variable and use the variable
always you need (_colors.scss)

This time was easy, wasn’t it? Try to keep all your projects as clean as this example from now own.
A little advice: modify the watch task so every time you edit a SCSS file you see if there are styling
errors.
Chapter 12: Gulp (2) 103

JSCS
You already have your SCSS files in a unsurpassable way, but what about our JavaScript files? There
are a lot of ways of checking the style guides, some of them are these ones:

• JSLint
• JSHint
• ESLint
• JSCS

Personally I like quite a lot ESLint but, as always, I suggest you to read about all of them, see how
they work and read about the pros and the cons of each of them.
For our base project, we will be using JSCS. The first step is downloading the dependency:

1 npm install gulp-jscs --save-dev

The next step is configuring the Gulp task that will alert you with the found JavaScript file errors:

1 var gulp = require('gulp'),


2 //...
3 jscs = require('gulp-jscs'),
4 ;
5
6 gulp.task('jscs', function () {
7 return gulp.src('./app/Resources/assets/js/**/*.js')
8 .pipe(jscs({
9 configPath: './vendor/sonata-project/core-bundle/Resources/public/vendor/b\
10 ootstrap/js/.jscsrc'
11 }))
12 .pipe(jscs.reporter());
13 });

JSCS needs a .jscsrc file in the root of your proyect that configures that it have to test in the process.
If you don’t have that file in the root, you can add a configPath parameter and tell it where the
file is. I take advantage of the file that comes by default inside the SonataCoreBundle vendor, but
as always: research and create the one that best adapts to your project. Besides, in the gulp-jscs
documentation⁷³ you have lots of possibilities to apply, a fix parameter that instead of alerting you
with the errors, it will fix them for you. I know you like this, don’t you?
In order to test that it’s correctly working, it’s time to create your first JavaScript file. For this demo,
I created an app/Resources/assets/js/test.js file with the following content:
⁷³https://github.com/jscs-dev/gulp-jscs
Chapter 12: Gulp (2) 104

1 (function ($) {
2 'use strict';
3
4 var string = "this is a string";
5 var body = $('body');
6 })(jQuery);

If you want this file to be included in your Symfony project, remember to edit your app/Re-
sources/views/base.html.twig template and add it to the corresponding section. For this example,
this is not necessary.
Now execute the gulp jscs task, what happens?

1 Invalid quote mark found at /home/jon/Projects/aupa_bilbao/app/Resources/assets/\


2 js/test.js :
3 2 | 'use strict';
4 3 |
5 4 | var string = "this is a string";
6 -----------------------^
7 5 | var body = $('body');
8 6 |})(jQuery);
9
10 jQuery identifiers must start with a $ at /home/jon/Projects/aupa_bilbao/app/Res\
11 ources/assets/js/test.js :
12 3 |
13 4 | var string = "this is a string";
14 5 | var body = $('body');
15 --------------^
16 6 |})(jQuery);
17 7 |

We have two errors in our JavaScript file:

• Invalid quote mark. Solution: change double quotes to single quotes.


• A jQuery variable not well named. Solution: prepend a $ to the ‘body’ variable.

The example is very trivial, but it won’t be so trivial when your aplication starts growing.

PHP CS
Until now we have make our SCSS and JavaScript files more beautiful, but what about our PHP? It’s
as important as the rest of them, even more knowing that we are working with a PHP framework,
so letting this column alone it’s not an option.
Chapter 12: Gulp (2) 105

For this section, we are going to work with PHP Code Sniffer, with its gulp version. You can find it
here⁷⁴. Firstly, the installation:

1 npm install gulp-phpcs --save-dev

As always, create the gulp task that will execute the recently installed module:

1 var gulp = require('gulp'),


2 //...
3 phpcs = require('gulp-phpcs'),
4 ;
5
6 gulp.task('phpcs', function () {
7 return gulp.src(['./src/AppBundle/**/*.php'])
8 .pipe(phpcs({
9 bin: './bin/phpcs',
10 standard: 'PSR2',
11 warningSeverity: 0
12 }))
13 .pipe(phpcs.reporter('log'));
14 });

If you read this task, you will see that a binary bin/phpcs gets executed. You have to install it as
well. To do so, you already have the necesary tool: Composer. Add the needed dependency to your
composer.json file executing the folowing command:

1 composer require squizlabs/php_codesniffer

After this, you will have the necesary inside the bin folder automatically. It’s time to test if our PHP
code is beautiful or if it’s not. Execute the gulp phpcs command again and… surprise! I’m sure you
were not expecting so many errors at all. Don’t get frightened, it’s someting normal that some IDEs
or code editors don’t comply.
This binary, phpcs, can’t fix our code automatically so you will need to read the errors one by one
and start fixing them… Well, I’m not going to be so bad and I’m going to help you a bit with this. If
you see the binary files that the new Composer dependency has downloaded inside the bin/ folder,
you will see the well known phpcs and another one: phpcbf. This is the one that will do magic and
it will automatically fix the code… provided that it’s possible, indeed!

NOTE: as an exercise, you should change the example standadrd ‘PSR2’ for the Symfony
standar. You can read more about this here⁷⁵
⁷⁴https://github.com/JustBlackBird/gulp-phpcs
⁷⁵https://github.com/leaphub/phpcs-symfony2-standard
Chapter 12: Gulp (2) 106

PHP CBF
It’s time to do some magic and have our errors fixed automatically. In the future, the next step will
be that our website get automatically developed with just a few voice commands or something like
that, but at this moment we will have to conform with the errors auto correction.
Step number one: download the gulp requirements.

1 npm install gulp-phpcbf --save-dev


2 npm install gulp-util --save-dev

As you guessed, you need one more element in this “recipe”. Step number two: create the gulp task
using the new “ingredients”:

1 var gulp = require('gulp'),


2 //...
3 phpcbf = require('gulp-phpcbf'),
4 gutil = require('gulp-util'),
5 ;
6
7 gulp.task('phpcbf', function () {
8 return gulp.src(['./src/AppBundle/**/*.php'])
9 .pipe(phpcbf({
10 bin: './bin/phpcbf',
11 standard: 'PSR2',
12 warningSeverity: 0
13 }))
14 .on('error', gutil.log)
15 .pipe(gulp.dest('src/AppBundle'));
16 });

Now, wave the magic wand executing the gulp phpcbf command and… et voilà, code clean and
beautiful. Is there something that phpcbf was unable to fix? Well, it’s not a big problem opening a
few files with the left errors and fix them manually after all the work it did for us, is it?

Executing PHP commands


This is something that a lot of people hate: mixing PHP commands, that in this case will be Symfony
commands, with PHP not related stuff. Without any doubts, it’s something that I don’t even like to do
but when I worked with MopaBootstrapBundle or even without it, having all commands centralized
in gulp can help quite a lot. To make you decide by yourself, let’s begin the pertinent example.
For this example, we are going to need a new dependency:
Chapter 12: Gulp (2) 107

1 npm install gulp-copy --save-dev

After installing it, create the needed tasks for your Symfony project::

1 var gulp = require('gulp'),


2 //...
3 copy = require('gulp-copy'),
4 cp = require('child_process')
5 ;
6
7 // Symfony & Mopa Stuff
8 gulp.task('fonts', function () {
9 return gulp.src('./web/bundles/mopabootstrap/fonts/bootstrap/*')
10 .pipe(copy('./web/fonts', {prefix: 7}));
11 });
12
13 gulp.task('installAssets', function () {
14 cp.execFile('bin/console assets:install', function (err, stdout, stderr) {
15 console.log(stdout);
16 console.log(stderr);
17 });
18 });
19
20 gulp.task('installMopa', function () {
21 cp.execFile('bin/console mopa:bootstrap:symlink:sass', function (err, stdout, \
22 stderr) {
23 console.log(stdout);
24 console.log(stderr);
25 });
26 });
27
28 gulp.task('clearCache', function () {
29 cp.execFile('bin/console cache:clear', function (err, stdout, stderr) {
30 console.log(stdout);
31 console.log(stderr);
32 });
33 });

With this code you will be able to execute the needed Symfony commands to start working correctly.
In addition to the previous shown tasks, I usually create a command that executes all of them and
other one to start working:
Chapter 12: Gulp (2) 108

1 gulp.task('symfony', ['fonts', 'installAssets', 'installMopa', 'clearCache']);


2
3 gulp.task('dev', ['sass', 'watch']);

With these commands, once the Symfony is deployed locally with it’s dependencies, it’s enough
executing gulp symfony and then gulp dev and start working.
Now, it’s your decision if you want to apply this or not in your projects.

Tip 12
When you decide working with this type of technologies, you must have a good documentation
explaining your users what the need to install. In our case, I have been telling you that you need
to install Ruby (it was necessary before for the scss_lint gem, and it will be mandatory for the
deployment with Capistrano), you need to install npm, and the rest of the elements that creates
our base project. But, if you notice, everything is “packaged”. So: PHP dependencies are declared in
a composer.json file. You install PHP and just with a composer install command, everything
necesary gets downloaded. The same with Node: you install Node and npm and thanks to the
package.json file and the npm install command you already have everything you need.

This chapter tip has to do with this feature but in a Ruby environment. It would be great to tell
developers to install Ruby and Rubygems and with just a single command everything needed gets
installed. This feature is not done by default but thanks to a gem, this can be achieved. Besides these
two ingredients, you hve to warn the rest of the developers to install the needed gem:

1 gem install bundler

This gem is able to read a Gemfile file placed in the same folder as the one from you will be executing
bundle install. So, we must create a Gemfile in the root of our project and we copy the following
content:

1 # Pull gems from RubyGems


2 source 'https://rubygems.org'
3 gem 'scss_lint', '~> 0.48.0'

You have to visit the https://rubygems.org/⁷⁶ website in order to see what you have to write in this
file and be careful with possible breaks this may cause. However, if your project needs and exact
version of a gem, thanks to this Gemfile all your teammates will use the same gem versions.
⁷⁶https://rubygems.org/
Chapter 12: Gulp (2) 109

Summary
In this chapter we have learnt to make Gulp more powerful and we have given it more sense in our
project. Thanks to the big opportunities it offers, we have improved our code quality and, the best
of all, practically effortless. There exist a lot more possibilites, and some of them will get mentioned
in the book epilogue.
Chapter 13: additional Symfony
configuration
In this chapter we will be resting a bit from developing and we stop to see certain configurations
that will allow you to either obtain an aditional element or improve your development time spent.
Each of of this tricks, some of them seen in previous chapters, will help you sooner or later so be
sure that you understand them as good as possible.

ParamConverter
In chapter 9 we saw one thing that, for me, is the great discover after a some time developing with
Symfony: the ParamConverter annotation. It’s so powerful that some times it isn’t even necessary
to write it, and that’s how I introduced it to you in that chapter’s tip. It sounds weird, but let’s see
its behaviour extending the example we already saw and adding more examples.
Let’s suppose that we have route that loads a blog post. The controller with the route as annotation
should be somethinkg like this:

1 /**
2 * @Route("/blog/{slug}", name="blog_show")
3 */
4 public function showAction($slug)
5 {
6 $em = $this->getDoctrine()->getManager();
7 $post = $em->getRepository("AppBundle:Post")->findBySlug($slug);
8 if (!$post) {
9 throw $this->createNotFoundException();
10 }
11 //...
12 }

Each one of your routes will receive an id or a slug and it will have to repeat more or less this piece
of code over and over… or maybe not. A conversion can be done directly to an entity this way:
Chapter 13: additional Symfony configuration 111

1 use AppBundle\Entity\Post;
2
3 /**
4 * @Route("/blog/{slug}", name="blog_show")
5 */
6 public function showAction(Post $post)
7 {
8 //...
9 }

As you see, now I forced to receive a Post as a parameter. How does this class of magic work? In the
route there exist a placeholder called “slug”. In our function we are reciving a Post type entity. What
the code is going to do internally is checking that the Post class contains a property called slug and
it will find by that field. In case it is not found, it will automatically launch a 404 exception.
The problem comes when parameters are not named as in the entities, when we have to make a
previous modification (lika a date) or when we have parameters that belong to different entities. In
those cases, we need the “@ParamConverter” annotation and, in some of them, a little code that will
be written in some of our repository files.
We complicate the example a little more, and now we want both the blog post and a post comment:

1 /**
2 * @Route("/blog/{id}/comments/{comment_id}")
3 * @ParamConverter("comment", class="AppBundle:Comment", options={"id" = "commen\
4 t_id"})
5 */
6 public function showAction(Post $post, Comment $comment)
7 {
8 //...
9 }

Obviously, we cannot use the {id} placeholder twice in the same route, so one of them has to be
called in a different way. For that reason, we have to map the comment_id parameter to the Comment
id of the entity. Even so, this is very simple, but check this example:
Chapter 13: additional Symfony configuration 112

1 /**
2 * @Route("/user/{first_name}/{last_name}")
3 * @ParamConverter("user", class="ApplicationSonataUserBundle:User", options={
4 * "repository_method" = "findByFullName",
5 * "mapping": {"first_name": "firstName", "last_name": "lastName"},
6 * "map_method_signature" = true
7 * })
8 */
9 public function showAction(User $user)
10 {
11 //...
12 }

Wow! What was that? Ok, let’s go step by step. The first thing we can read is the route, which
receives both the name and the surname of, supposedly, a user from our database. We are indicating
to the ParamConverter to execute the function findByFullName located in the UserRepository.php
repository file that will receive the parameters also received in this route. This function creation is
on our own, so let’s do it:

1 class UserRepository
2 {
3 public function findByFullName($firstName, $lastName)
4 {
5 //...
6 }
7 }

This function will return a User and, in addition to be used in our route and save a piece of code,
it’s a function perfectly reusable by the whole platform, something that without any doubts follows
the best practices that we have been talking about several times during the book.
These and many other examples, besides the creation of your own converters can be found in the
official Symfony documentation⁷⁷ of the @ParamConverter annotation.

Template without controller


You are developing your website and it turns out that you need to code the tipical privacy policy or
terms of service page. That are pages with a text on them that don’t usually change, so there is no
interest to put this content in a database. Our controller of any of these pages should be something
like this:
⁷⁷http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
Chapter 13: additional Symfony configuration 113

1 public function privacyAction()


2 {
3 return $this->render('static/privacy.html.twig');
4 }

What sense does this make, going through a controller just for rendering a template? This is
something that cannot be done with annotations but, as we saw in the chapter 8 tip and I repeat in
this section, we have a routing YAML file called routing.yml that will help us to avoid this litte and
“useless” code. It’s as easy as adding a route like this in your routing file:

1 app_privacy:
2 path: /privacy
3 defaults:
4 _controller: FrameworkBundle:Template:template
5 template: static/privacy.html.twig

If you read the HTTP Cache part of the Symfony book, you are going to tell me that with this system
the page cannot be cached. Don’t worry, here you have the solution for this problem too:

1 app_privacy:
2 path: /privacy
3 defaults:
4 _controller: FrameworkBundle:Template:template
5 template: 'static/privacy.html.twig'
6 maxAge: 86400
7 sharedAge: 86400

Redirecting without controller


You are working on your website SEO and you want an old URL to redirect to a new one. As in the
previous example, if you go through a controller, it will look like the following code:

1 public function redirectAction()


2 {
3 return $this->redirectToRoute('new_route');
4 }

And, as in the previous example, the solution is in the routing.yml file this way:
Chapter 13: additional Symfony configuration 114

1 redirect_route:
2 path: /old-page
3 defaults:
4 _controller: FrameworkBundle:Redirect:urlRedirect
5 route: new_route
6 permanent: true

This “trick” was also seen in chapter 8, but I didn’t tell you that it admits several configurations. I
explained you the tipical example so you know it’s existence, and your homework for this section
is reading this documentation⁷⁸ to know the possibilities that there exist.

Dump autoload
You should already read about this in the checklist I shared with you previously, but it’s so simple
and important that I expend a little section in this chapter.
When you execute composer to get the needed dependencies, an autoload which is not optimized
gets generated. They say that it can even slow down quit much if you have lots of classes in your
project. For that reason, an specific command was created so the generated autoload becomed an
improved version, and because it’s “free”, I suggest you to use it in your production environment:

1 composer dump-autoload --optimize

Hide logs
If you ever check what is inside the log files generated in the var/logs folder, you will see that
there exist a lot of information that isn’t necessary, or at least in the development log file. This
mainly lines that are not contributing at all: the events. Symfony uses monolog library as the log
file manager with a default configuration that I like to change everytime I create a new Symfony
project. The aim here is to make those lines disappear so our logs are full of relevant information.
Edit the app/config/config_dev.yml file to remove the events:

1 monolog:
2 handlers:
3 type: stream
4 path: %kernel.logs_dir%/%kernel.environment%.log
5 level: debug
6 channels: "!event"

The end of not needed lines!


⁷⁸http://symfony.com/doc/current/cookbook/routing/redirect_in_config.html
Chapter 13: additional Symfony configuration 115

Send 500 errors to an email


And now that we are talking about logs, I would like to give you one more advice. Every time you
upload a web to a production stage, it would be awesome that you notice errors before users email
you. As we won’t talk about testing until the end of this book, let’s try to reduce the time that error
is living in production.
A first approach should be reading the 500 errors form the Apache logs, or whatever web server we
use, every morning and then fix them. This could become tedious, we may skip some of them and,
very important: as we are such a good developers, we are going to lose a lot of time reading logs
without errors.
A second approach should be using any kind of software or create it by ourselves that read the log
files every ‘x’ minutes and send us an alert everytime it finds an anomaly. Without any doubts this
is much better but, what if you tell you that there is a simpler solution that doesn’t require almost
anything?
As the title of this sections says, Monolog can be configured to send an email everytime it detects
a 500 error in your website project. You just have to create a small configuration, this time in the
app/config/config_prod.yml:

1 monolog:
2 handlers:
3 mail:
4 type: fingers_crossed
5 action_level: critical
6 handler: buffered
7 buffered:
8 type: buffer
9 handler: swift
10 swift:
11 type: swift_mailer
12 from_email: error@website.com
13 to_email: webmaster@website.com
14 subject: Critical error detectd
15 level: debug

Be sure to have the email well configured ;)

PhpStorm configuration
When you are developing with the app_dev.php front controller (by default with bin/console
server:run), the error screens are quite descritive, but you can go a further step. If you development
Chapter 13: additional Symfony configuration 116

IDE is PhpStorm, you can enable a little configuration that will allow you to click on the error that
is displayed on those screens so that concrete file will be opened at the exact line of the error, ready
to fix!
In order to do this, you shuld edit the app/config/config.yml file and add the following configu-
ration:

1 framework:
2 ide: "phpstorm://open?file=%%f&line=%%l"

If after this you weren’t using PhpStorm, this is a good moment to, at least, give it an opportunity.

Moving the sessions folder


By default until version 3.0, Symfony stored the session files in the app/cache/<environment> folder.
If you do a modification at any level, suppose a visual modification in any template, and you want
it to be rendered, you will have to clean the cache folder. The collateral effect was that users got
logged out, because you were destroying all sessions.
They already fixed this configuration as I told you moving the sessions folder outside the cache.
You have the solution in the new app/config/config.yml file coming with the 3.x version of
Symfony (but you did have the solution in the Symfony documentation). The resulting folder
will be app/sessions/<environment> for Symfony 2.x or, with the default configuration file,
var/sessions/<environment> for Symfony 3.x. If you are using Symfony 2.x, this is the config
block you need:

1 framework:
2 session:
3 handler_id: session.handler.native_file
4 save_path: "%kernel.root_dir%/sessions/%kernel.environment%"

Tip 13
You may not have an SMTP server for sending emails or you may don’t want to you your Gmail
account for this, so what’s the solution? If you try to search for it in Google you will see that there
exist several free possibilities, but the one that is working the best is SparkPost. Nowadays, with the
free account you will be able to send up to 100,000 emails/month. If you want special features or
more email quantity, you will have to pay for the service. But I think 100,000 emails is quite a huge
amount of emails to make some tests… isn’t it?
To configure SparkPost in Symfony, you will have to make some little changes to the Swiftmailer
configuration. First of all: edit the app/config/config.yml file with the following code:
Chapter 13: additional Symfony configuration 117

1 swiftmailer:
2 transport: "%mailer_transport%"
3 host: "%mailer_host%"
4 username: "%mailer_user%"
5 password: "%mailer_password%"
6 spool: { type: memory }
7 port: "%mailer_port%"
8 encryption: "%mailer_encryption%"

If you compare this with the default configuration, you will see that I added the port and encryption
parameters. Both will get their values from the app/config/parameters.yml file so, depending
where the project is located, the behaviour will be changed. You must help other users to deploy
the project, and for that, you must edit the app/config/parameters.yml.dist file so the terminal
will ask them to fill the new values when the run a composer install. Edit the file and add the
default parameter values:

1 mailer_transport: smtp
2 mailer_host: 127.0.0.1
3 mailer_user: ~
4 mailer_password: ~
5 mailer_port: 25
6 mailer_encryption: null

In your case, as you already have the app running in your computer, you should fill the parame-
ters.yml file with the needed information. To get this information, you have to register yourself
at https://www.sparkpost.com/⁷⁹, and after following the first steps, you will be prompted with the
needed configuration. Be careful! The password won’t appear again so, if you lose it, you will have
to generate a new one. You will also need to validate the domain from where the emails will be sent.
Read the SparkPost documentation if you don’t know what I’m talking about. With the parameters
given, edit your app/config/parameters.yml file:

1 mailer_transport: smtp
2 mailer_host: smtp.sparkpostmail.com
3 mailer_user: SMTP_Injection
4 mailer_password: PASSWORD
5 mailer_port: 587
6 mailer_encryption: tls

You just need to edit the mailer_password parameter, which is the password that the website gave
to you. With this done, you will have your Symfony sending emails from SparkPost.
⁷⁹https://www.sparkpost.com/
Chapter 13: additional Symfony configuration 118

Summary
In this chapter we have seen some Symfony interesting configurations. There exist lots of configu-
rations but these ones are the configurations that I mostly use in my projects. In this link⁸⁰ you will
be able to see the configuration section of the Symfony Cookbook, a book in where you will find the
best additional recipes for your Symfony. You should check it quite often, they add some interesting
things constantly.
⁸⁰https://symfony.com/doc/current/cookbook/configuration/index.html
Chapter 14: modifying
SonataUserBundle
Until this point, we left apart the user stuff because it was given to us “for free”. But it’s possible
that the default features don’t fit to what your project needs. In this chapter we will see some of the
interesting configurations that you can edit in this bundle and some changes that you can accomplish
with code.

Modifying FOSUserBundle
Even if you have installed SonataUserBundle, as you should already know, this bundle directly
depends on FOSUserBundle, so we will check in detail this one because it’s the user base. Everything
you are about to do is in this section⁸¹ of the documentation, but you will be applying what you read
into practical examples. Be careful because, even the documentation is for FOSUserBundle, what we
are editing is the bundle that makes use of it: SonataUserBundle. Let’s start!

Overriding the templates


When installing your user bundle, you should have loaded a lot of routes in your routing.yml file:
the login page, the profile page, the sign in page… But all of them have the same white template as
a base that doesn’t fit with ours.
Like with the rest of the bundles, you are allowed to override this functionality and make them extend
from your base template. In addition, as the FOSUserBundle people (Sonata people too) already know
that this will happen in almost every project that uses their bundles, they divided almost every
response of their routes in two different templates: the one responsible for the layout of the page
and, inside this one, the template with the content. What is this for? Now developers can override
the part that they want to edit in each of them. Lastly, they give a layout.html.twig template that
the other templates extend, and we will annihilate it in this example.
In order to override a template in Symfony, you have two different options:

1. With a child bundle of the bundle that we want to edit.


2. With the app/ folder.
⁸¹https://symfony.com/doc/master/bundles/FOSUserBundle/index.html#next-steps
Chapter 14: modifying SonataUserBundle 120

In both options, the templates path must be exactly the same, both folder names and file names.
Besides, you have to keep in mind that Symfony will look for the template firstly in the app/ folder,
then in a child bundle and lastly in the concrete bundle, but once it’s found in that order, it won’t
keep on searching.
What we are going to do in this example is editing the FOSUserBundle base template. To do so,
you must have a little knowledge about how the views are structured in this bundle. Browse to
vendor/friendsofsymfony/user-bundle/Resources/views and take a break to see what’s inside
that folder.
Are you ready? As you have seen, the layout.html.twig template is in the root of the views, just
the one we want to edit. As all your templates reside in the app/ folder, the correct thing is that you
keep on creating all the templates there, even the overriding ones. Here comes the trick: you have to
create the exact same folder structure that you are about to edit, so create the app/Resources/FOS-
UserBundle/views/ folder and in the root of it, copy the previous layout.html.twig that we saw in
the Sonata vendor. Once you did this, you must clean the Symfony cache so it “finds out” the new
template:

1 bin/console cache:clear

From now on, you do have the control of the base template. Before making any modifications
without any aim, look what the default template contains and try to imagine how you can fit it
into you base template. Try to do it yourself, but I leave here my own solution, because there are
plenty of them:

1 {% extends 'base.html.twig' %}
2
3 {% block content %}
4 <div>
5 {% block fos_user_content %}
6 {% endblock fos_user_content %}
7 </div>
8 {% endblock content %}

If you now open the login page http://127.0.0.1:8000/login you will se your base template working
but… there’s a “little” error. The right part of the page, the sign in section, it’s a full embedded page,
so the headline and the footer are there too. Why is this happening?
It’s important that you understand what’s happening. Open the login template file and search for
the part that shows the embedded sign in section. It’s exactly this one:
Chapter 14: modifying SonataUserBundle 121

1 <form action="{{ path("fos_user_security_check") }}" method="post">


2 {% if csrf_token %}
3 <input type="hidden" name="_csrf_token" value="{{ csrf_token }}" />
4 {% endif %}
5
6 <label for="username">{{ 'security.login.username'|trans }}</label>
7 <input type="text" id="username" name="_username" value="{{ last_username }}\
8 " required="required" />
9
10 <label for="password">{{ 'security.login.password'|trans }}</label>
11 <input type="password" id="password" name="_password" required="required" />
12
13 <input type="checkbox" id="remember_me" name="_remember_me" value="on" />
14 <label for="remember_me">{{ 'security.login.remember_me'|trans }}</label>
15
16 <input type="submit" id="_submit" name="_submit" value="{{ 'security.login.s\
17 ubmit'|trans }}" />
18 </form>

The register.html.twig template contains this code:

1 {% extends "@FOSUser/layout.html.twig" %}
2
3 {% block fos_user_content %}
4 {% include "FOSUserBundle:Registration:register_content.html.twig" %}
5 {% endblock fos_user_content %}

Until the last version, for Symfony 2.*, that extends in the first line of the the file was creating two
layouts in the same page. You couln’t override this template and delete the extends line because,
this way, if you browsed to http://127.0.0.1:8000/register/, you found a register form without any
layout. The solution was easy: edit the block with renders the sign in form in the login.html.twig
template with an HTML code that explains the benefits of signing in providing a link to the sign in
page. In fact, this is what lots of websites usually do, but as we like the “difficult stuff”, we are going
to override the controller.

Overriding the controllers


As with the templates, we are able to override controllers to edit or completely modify the code
it executes. This doesn’t mean that you have to override the full controller, as we did with the
templates, because we can just override the function that we need to change.
The first thing you must do is to tell our user bundle that it’s parent is FOSUserBundle. Open the
ApplicationSonataUserBundle.php file and add the following lines at the bottom:
Chapter 14: modifying SonataUserBundle 122

1 public function getParent()


2 {
3 return 'FOSUserBundle';
4 }

In the previous section we were interested in modifying the registerAction inside the Registra-
tionController provided by FOSUserBundle. There are no controllers within the app folder so this
time, you have to create the neede code inside your UserBundle, in the src folder.
In the Controller folder of this bundle, create the same file as it is in the vendor, RegistrationCon-
troller.php, with the function you want to override:

1 <?php
2
3 namespace Application\Sonata\UserBundle\Controller;
4
5 use FOS\UserBundle\Controller\RegistrationController as BaseController;
6
7 class RegistrationController extends BaseController
8 {
9 public function registerAction()
10 {
11 //...
12 }
13 }

Do you see the trick here? We wrote a use of the original controller and we renamed it as
BaseController because of the name collision. After this, we make the controller extend the original
one and that’s all. It’s time to insert the logic we want. We don’t want to change a lot really so just
copy and paste the original code and edit the response using the new template you want to render.
With this method, you have to be careful when you upgrade your versions because de default
controller can changed and you must adapt yours. Check it periodically and make the needed
changes to your controller so you keep it updated.

Modifying the login and the sign in


At this moment, your database contains users whose login is their “username”. In a lot of platforms,
the “username” is also the email of the user. You can achieve this with FOSUserBundle as easy as
adding a configuration to the corresponding section of the app/config/security.yml file:
Chapter 14: modifying SonataUserBundle 123

1 security:
2 providers:
3 fos_userbundle:
4 id: fos_user.user_provider.username_email

This just makes the first step because, with this configuration, the register will still ask the user to
fill in the username and the email, and you know that both things should be the same. Here you
have some homework: you should change the sign in form so it just ask for the email. In addition
to this, when the email is established, this should be copied to the username or the database will
launch an error when creating the user.
You can do this by you own, come on! If you want a little help for the last part, here you have the
modification needed to you User.php file:

1 /**
2 * Overrides default setEmail function
3 * @param String $email
4 */
5 public function setEmail($email)
6 {
7 $email = is_null($email) ? '' : $email;
8 parent::setEmail($email);
9 $this->setUsername($email);
10 }

Moreover, remember to override the RegistrationFormType.php. In this link⁸² there is a little help.

Modifying the sign in (2) and the emails


How many times did you register and received an email to confirm your email account? We are not
going to be less than them so let’s develop this feature in our base project. Did I say develop? Sorry
about that, I wanted to say “configure”:

1 fos_user:
2 # ...
3 registration:
4 confirmation:
5 enabled: true
⁸²https://symfony.com/doc/master/bundles/FOSUserBundle/overriding_forms.html
Chapter 14: modifying SonataUserBundle 124

You have finished. With this configuration done in the app/config/config.yml file your users will
receive an email notification when they sign in and they won’t be able to log in until they confirm
their email account. Be sure to configure your SMTP server or the won’t get any emails!
On the other hand, there exist a lot of emails that are sent from FOSUserBundle and you can edit
as any other template. What you cannot edit in those templates is who sends them, and some other
stuff. This configuration and some other are described in this link⁸³. I don’t consider important to
copy and paste the full configuration written on this page so, once you read it, continue with the
following section.

Other modifications
In the previous sections I wanted to share with you the modifications that I usually manage in my
projects. I also shared the link to the part of the documentation in where it is explained all these
modifications and many others than can be done, and I consider that reading them is very interesting.
It’s so interesting that I share again the same link so you read all the information slowly. You don’t
have to do this right know, but keep in mind where it is when the moment arrives.

Documentation: https://symfony.com/doc/master/bundles/FOSUserBundle/index.html#next-
steps⁸⁴

Tip 14
The User base class that we created earlier on just contains an $id and everything else is
inherited from SonataUserBundle. Obviously, it’s possible that you need more fields for your
projects. Those fields will be added in the entity you created in previous chapters, located in
src/Application/Sonata/UserBundle/Entity/User.php. But you have one more problem with
this: everything you add won’t be rendered in the corresponding admin.
If you want to show the new fields in the admin, you have to edit it. The first step is telling the Sonata
bundle to take your admin class instead of their default one. Edit the app/config/config.yml file
with this modification:

1 sonata_user:
2 admin:
3 user:
4 class: Application\Sonata\UserBundle\Admin\UserAdmin

After this, you have to create the class seen above, so create your UserAdmin.php file with the
following content:
⁸³https://symfony.com/doc/master/bundles/FOSUserBundle/emails.html
⁸⁴https://symfony.com/doc/master/bundles/FOSUserBundle/index.html#next-steps
Chapter 14: modifying SonataUserBundle 125

1 <?php
2
3 namespace Application\Sonata\UserBundle\Admin;
4
5 use Sonata\AdminBundle\Datagrid\ListMapper;
6 use Sonata\AdminBundle\Form\FormMapper;
7 use Sonata\UserBundle\Admin\Entity\UserAdmin as BaseUserAdmin;
8
9 class UserAdmin extends BaseUserAdmin
10 {
11 protected function configureFormFields(FormMapper $formMapper)
12 {
13 parent::configureFormFields($formMapper);
14 }
15
16 protected function configureListFields(ListMapper $listMapper)
17 {
18 parent::configureListFields($listMapper);
19 }
20 }

As I gave you the previous code, everything will be like the default admin. It’s time to edit the list
fields, the form fields or whatever other section you want to edit with your needs. You can even
delete the parent:: code line and rewrite everything completely, you have the power!

Summary
In this chapter we have learnt to manage a little better our platform users editing or adding some
features that give our project more power and flexibility. With this, we finished developing our solid
base for our project, the next step will be moving it to a production stage. In the next chapter you
will learn how to do a deployment with a tool created for it.
Chapter 15: deployment
I’m sure you suffered this chapter more than once. The first deployments were done by FTP,
uploading all files or, if you certainly new the ones that have been edited, just those ones. After
this situation, lots of developers started using a version control software such as Git to do the
deployments much easier, because you just have to bring the needed changes in a much controlled
and secure situation, being able to go back in case of error or something special.
Nowadays we use deployment systems. There are a great variety of them and written in different
development languages, so in this chapter, we are going to see some of the different possibilities that
exist without going into detail and we will create a full deployment with one of them.

Magallanes
Magallanes is a deployment tool written in PHP that allows you to deploy mainly other PHP tools.
It’s very easy to use and manage, but it’s specially easy to extend adding our own tasks. It allows you
to deploy your application to any servers you want, both by rsync and by ssh, and after this, execute
the needed tasks so the deployment ends the way we want, such as the Composer dependency
installation.
It’s very easy to use this tool with Symfony because it’s installed through Composer and the creator
is a fan of this framework, so there are lots of tasks by default that works quite good with our
deployment. However, after testing it in some professional projects, the tool was lack of features,
such as shared folders and files management that don’t change over deployments: parameters.yml,
var/logs, …

Anyway, here⁸⁵ you can read a post written by me about how to deploy your Symfony application
with magallanes. It’s not fully functional but that missing stuff is easily fixable with the tasks we
are able to create.

Ansible
This tool is huge and allows a great variety of actions. Just seeing the companies using the tool in their
homepage gives you an idea of the power this software has. It’s written in Python, a development
language that is trendy right now, but this shouldn’t make you leaving this tool aside.
I’ve heard a lot of people saying that this is the “definitive tool”, but I’m a great defender of using
the tool that best fits your needs, including the need to be comforable with it. Personally, it was a
⁸⁵https://medium.com/@jontorrado/deploying-a-symfony2-project-with-magallanes-28abe452c54c
Chapter 15: deployment 127

bit difficult to get in touch with this tool, even I can see it’s power. On the other hand, if you search
on GitHub you will see a lot of people using Ansible as a deployment tool for their Symfony project.
Just search for “Symfony ansible” and you will see the results.
We are not going to use this tool in this book but here you have a post written by me about how to use
Ansible to deploy Symfony applications: https://medium.com/@jontorrado/deploying-a-symfony-
application-with-ansible-c517c26ccbfc⁸⁶.

Capifony
It’s possibly the most used tool to make Symfony deployments… or at least it was. This is
because of the use of Capistrano 2 behind the scenes, and there’s been a while since Capistrano
3 was launched with lots of improvements. Even so, I suggest to take a glimpse at their webpage
http://capifony.org/⁸⁷.
The tool is written in Ruby but the only thing you should know about Ruby is “copying and pasting”,
because the great majority of the configurations are already done for you and you just have to change
the server IPs, the folders and some parameters like that. If you browse to their GitHub repository
you will see that more than 100 people contributed to this software with more than 700 commits,
something to keep in mind if what you want is an active community that keeps the software updated
(Capifony is unmaintained bacause of Capistrano 3).
After reading this, you may feel that you want to try Capistrano 3 so, let’s start with the action and
read the next section of the chapter.

Capistrano
Capistrano is a remote server automation tool written in Ruby. It supports the development and
execution of tasks and it includes a great variety of them by default for deployment tasks. As you
imagined, it’s also valid for many other things apart from deployments sot, won’t this tool be quite
big for what we want?
I’ve always heard the sentence “go to buy the bread with a Ferrari”, and lots of times we act like
this. We complicate our developments with huge and difficult software to create something tiny
that won’t use almost anything that we created as a base. However, there are some exceptions that
worths it because they don’t penalize our development time. In addition, if we want to increase the
features in a future and automate something remote, we won’t be able to do this with Magallanes
and we would have to change everything to a system like Ansible or Capistrano, with the time we
will be losing in this change.
So my advice here is that you use a one of these systems that already have a default configuration
created by the community and will be extremely easy to start using it just copying and pasting some
⁸⁶https://medium.com/@jontorrado/deploying-a-symfony-application-with-ansible-c517c26ccbfc
⁸⁷http://capifony.org/
Chapter 15: deployment 128

piece of codes. In most of my projects I use Capistrano as a deployment system, so in the next section
I will explain you how to deploy a Symfony application with a plugin for Capistrano 3 created for
this.

Deploying with capistrano-symfony


Does Capifony with Capistrano 3 exist? Or is it planned to be developed shortly? Well, actually
it won’t get developed because it’s really not necessary. There exist a Capistrano 3 plugin called
capistrano-symfony that is based on Capifony and already have everything we need. If we add here
that the installation is as easy as adding a line to our Gemfile, we are going in a good way.
Let’s go step by step creating what it’s needed to deploy your Symofny project to a remote server.
If you don’t have one, you can deploy to your own computer or to a virtual machine to test it.
The first thing we are going to do is installing the requirements. Open your Gemfile and add the
following line:

1 source 'https://rubygems.org'
2 #...
3 gem 'capistrano-symfony', '~> 1.0.0.rc1'

After executing the installation command bundle install, it’s possible that you see a WARNING
in the installation. Don’t worry about it, it’s just because the gem changed it’s repository but you
have the good one, they just left the message there.
The next thing is configuring our Capistrano in order to load all the requirements for the deployment.
Create a Capfile file in the root of your project with the following content:

1 # Capfile
2 require 'capistrano/setup'
3 require 'capistrano/deploy'
4 require 'capistrano/symfony'
5 require 'capistrano/scm/git'
6 install_plugin Capistrano::SCM::Git

That setup require is needed for the Capistrano::DSL:Module stages; the deploy require is for the
tasks that we are going to define in the last part of our deploy:***; the symfony require contains
all the needed dependencies, such as capistrano/composer that we will obviously need for the
dependecy installation.
With this done, create a config folder in the root of your project and, inside it, create a config/de-
ploy.rb file. Here you must configure all the tasks that have to be done in each server, wherever is
going to be deployed. Before doing anything I must tell you that your project should be inside a Git
repository, so I will suppose you already did that. If you don’t, you can use Bitbucket or GitHub to
continue with the example:
Chapter 15: deployment 129

1 ############################################
2 # Setup project
3 ############################################
4
5 set :application, "aupa-bilbao"
6 set :repo_url, "https://github.com/groupname/repository.git"
7
8 ############################################
9 # Setup Capistrano
10 ############################################
11
12 set :log_level, :info
13 set :use_sudo, false
14
15 set :ssh_options, {
16 forward_agent: true
17 }
18
19 set :keep_releases, 3
20
21 ############################################
22 # Linked files and directories (symlinks)
23 ############################################
24
25 set :linked_files, ["app/config/parameters.yml"]
26 set :linked_dirs, [fetch(:log_path), fetch(:web_path) + "/uploads"]
27 set :file_permissions_paths, [fetch(:log_path), fetch(:cache_path)]
28
29 set :composer_install_flags, '--no-interaction --optimize-autoloader'
30
31 namespace :compile_and_upload do
32
33 desc 'Compile and upload'
34
35 task :gulp do
36 if fetch(:env) == "prod"
37 run_locally do
38 execute "gulp prod"
39 end
40 else
41 on roles(:all) do |host|
42 execute "cd #{release_path}; npm install && gulp prod"
Chapter 15: deployment 130

43 end
44 end
45 end
46
47 task :upload do
48 if fetch(:env) == "prod"
49 on roles(:all) do |host|
50 upload! "#{fetch(:web_path)}/css", "#{release_path}/#{fetch(:web_name)}/\
51 css", recursive: true
52 upload! "#{fetch(:web_path)}/js", "#{release_path}/#{fetch(:web_name)}/j\
53 s", recursive: true
54 end
55 end
56 end
57 end
58
59 namespace :deploy do
60 after :updated, 'composer:install_executable'
61 after :updated, 'compile_and_upload:gulp'
62 after :updated, 'compile_and_upload:upload'
63 end

What the hell is this? Read it again, slowly, because it’s Ruby and you may haven’t notice that you
really understand almost everything written in the previous piece of code. You just have to know
two things:

• The Capistrano Flow, explained here⁸⁸


• The capistrano-symfony exposed variables, explained here⁸⁹

The first part of the code is just configuring some deployment variables. If you don’t understand
some of them, you can read what they do in the Capistrano documentaton or in the capistrano-
symfony documentation, depending on the variable name. In the second part, what I do is create
two different tasks: the first one for Gulp and the other one for the needed file upload, why?
We are going to make a difference between the testing servers and the production servers. It’s
possible that you may have npm installed in the machine with all the rest of the needed software,
just they way we work while developing. However, in a production environment, we don’t need to
have all that software installed, just with the final CSS and JS files is enough, and the rest of the
software is definitely not needed.
⁸⁸http://capistranorb.com/documentation/getting-started/flow/
⁸⁹https://github.com/capistrano/symfony/#settings
Chapter 15: deployment 131

Right now you are wondering where is the testing and the production configuration. For that, we
must create a deploy folder inside the recently created config folder and there, create a file for each
deployment stage we want. For this example, create the config/deploy/pre.rb that references the
testing environment:

1 ######################################################################
2 # Setup Server
3 ######################################################################
4 server "dev.company.com", user: "sshuser", roles: %w{web}
5 set :deploy_to, "/path/to/your/deployment/directory"
6 set :env, "dev"
7
8 ######################################################################
9 # Capistrano Symfony - https://github.com/capistrano/symfony/#settings
10 ######################################################################
11 set :file_permissions_users, ['www-data']
12 set :webserver_user, "www-data"
13
14 ######################################################################
15 # Setup Git
16 ######################################################################
17 set :branch, "master"

This file is much simpler and you just have to declare the variables that modify the deploy.rb main
file. The first line is the server to be connected with an SSH conection to deploy the project. Here
you can write down your own computer IP if you have a SSH server running.
In the deploy_to variable you must write the path in where the Symfony project will be deployed.
In the next section we will talk about the final folder result in the destination.
The env variable references the stage environment and, if you remember our deploy.rb file, this
variable will make our assets to be compiled locally and then uploaded or do this process in the
destination server. If you establish this variable as “dev”, the system will suppose that you have
Gulp installed in the destination computer, so make sure that it’s really installed.
The file permission variable and the webserver variable are used for the cache, logs and web/uploads
folder. They allow that user to have write permissions on that folders. You should know, of course,
what’s your server webserver username, of you will find an important error that won’t allow you
to view the deployed website.
Lastly, you must tell Git the branch to “pull” the infomation from. Logically, in the testing stage and
in production stage, it’s very possible that you have different branches, so this is where you must
edit that configuration.
You may have got mad with this section, so read it calmly, test this in your computer and you will
se that it’s very easy. Besides, I want to add another thing: in the rest of your projects you can copy
Chapter 15: deployment 132

and paste every single line that we’ve done here and, just changing some basic lines, you will have
your deployment system ready to go.
But now, how do you start the process? You must remember the name of the files you created inside
the config/deploy folder. Those are the ones who tell you the stage for the following command:

1 cap pre deploy

There it is! Everything worked like a charm (I hope so), but you just realized that your code contains
a very important bug, can you go back? If you previously did a deploy, you can go back to a
previous deployment with the information provided in this link⁹⁰. Moreover, you may need to edit
the deploy.rb file so the “undo” tasks get done, such as database modifications.
Do you remember the DoctrineMigrationsBundle”? It’s that to start using it and you take advantage
of the Capistrano deployment flow to launch the different commands that the bundle offers you.
Here⁹¹ you have all the needed information so you can test it.
To finish this section, some homework for you: we didn’t keep in mind the sessions folder until
now, so everytime a deployment is done, we will have a problem. Modify the deploy.rb file so the
app/sessions folder is also kept between deployments.

Capistrano-symfony: final result


If you have never used a deployment system, it’s convenient that you read this carefully to
understand how they work.
The final result is composed by some folders. The first one is called repo, where you can find
everything related to the version control software. The next folder is releases, where you can
find a folder for each deployment done that hasn’t been removed. The name of the folder is
the concrete date where the deployment was done. Here you will be answering yourself: will
I have to change the DocumentRoot of my Apache server everytime I do a deployment? The
answer is, obviously, that you don’t. The deployment system creates a symbolic link called
“current” that links to the latest deployed folder inside the releases folder. So, when a “rollback”
is done, the defined tasks are launched and it just changes the “current” link to the previous
folder. Relating this with your Apache configuration, your DocumentRoot must be something like
/path/to/your/deployment/directory/current/web, and you won’t need to change it anytime.
The last folder is shared, in where all shared files and folders are located. Those files are the ones
that do not change between deployments, such us the parameters.yml configuration file. They are
defined in the deploy.rb.
In summary:
⁹⁰http://capistranorb.com/documentation/getting-started/flow/#rollback-flow
⁹¹http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html#running-migrations-during-deployment
Chapter 15: deployment 133

• A repo folder with all the version control software related stuff
• A releases folder with a folder for each deployment
• A current symbolic link that links to a folder inside releases, normally the latest succesful
deploy
• A shared folder that contains all the files and folders that won’t change between deployments,
such as configuration or uploads (and the homework!)

I hope this helps you in a big way to reduce your deployment time and the problems you could have
when doing it by hand or when doing it with a version control software.

Tip 15
The tip with this chapter is system engineering related, something that lots of developers know
nothing about but I love. When you are about to deploy, both with the Capistrano showed tool or
with sFTP or SSH, you will need a server password to connect. This is a security problem, but it’s
also very easy to solve.
We must ask the user who is going to deploy a computer identification and then give that computer
the permission to connect with a concrete user. That computer will have the permission to connect
to that computer with that user without password, something that reduces the security problem
because we won’t give password to anybody and we will have the people who are able to connect
controlled. In order to achieve this, you must follow these steps.
The user who is going to connect must give the system administrator his/her public key. He/she may
have one created. To check it, just execute the following command:

1 ls -al ~/.ssh

Inside that folder there should exist an id_rsa.pub file whose content is what we need. If that file
is not created or if you want to create another now, you must execute the following command:

1 ssh-keygen -t rsa -C "email@domain.com"

With this command, you should have that file created. The content of this file will be given to the
system administrator.
That system administrator will have to connect to the destination server. In it, he/she must browse to
the authorized_keys file located in the .ssh folder too, which is also located in each user home. So,
the $HOME/.ssh/authorized_keys file must be edited, adding the content of the given id_rsa.pub.
When this is done, the user will be able to do a ssh user@server.domain.com and log in without
password, so he/show won’t be asked for a password when doing a cap pre deploy, fantastic!

Note: some server distributions have the authorized_keys conection disabled by default.
Edit the SSH configuration to check if the feature is enabled. Also, check the file
permissions: 600 to .ssh and 700 to authorized_keys.
Chapter 15: deployment 134

Summary
In this chapter we have learnt to deploy our Symfony project to a remote server with different
options. The example done here contains a solid base for your future projects but it’s also easily
extensible for your future requirements. With this, we have completed the wholy cycle of our
application development, so the last thing to talk about is how tests are done, explained in the
next chapter and written by Beñat Espiña.
Chapter 16: testing
As we are developers that love the things well done, you must keep in mind this chapter quite a lot
because it’s about a topic that nowadays is taking more and more relevance, but not as much as it
should take: testing.
The purpose here is to do an interesting introduction about what the testing world contains, specially
in PHP, and obviously for our loved Symfony framework.
PHP is a development language with certain age and, like any other old stuff related to development,
it comes from a dark period, totally ignoring every design pattern and best practices about software
architecture. But this is the past because for some years, people like the Symfony creator, Fabien
Potencier, with many others, have forced PHP to become a serious language, taking advantage of
other language own features like JAVA or Python. Version 5 was just the first step for what is was
going to come and in few years, we move away from the 5000 lines WordPress functions.php mixing
superglobal variables with functions and HTML everywhere, to a complete complex and robust
ecosystem, with tools like this book’s framework, Composer as a dependency manager, Doctrine as
ORM, classes, namespaces, QA tools everywhere and lots of other things. This brought another way
to develop with PHP using the previous mentioned software architectures, and from this the DDD
and microservices concepts, and the appliance of design patterns like inheritance, polymorphism or
dependecy injection. The PHP growth as language is now unstoppable so take a seat because PHP7
is here with lots of interesting updates.

What is testing? Do I need it?


To start this section, let’s answer the second question. Short and easy answer: YES. But as we need
to find a pragmatic vision in the real development world, I would say that yes with nuances.
For those who have never seen nothing about testing, I like to define it as one of the mainstays that
makes the code solid, robust and maintainable, what we usually call Clean code. As with the design
patterns, testing didn’t appear recently, it has a long story, but we must say that it’s a habit that is
entering the sofware construction world in the last years, in fact, the theory is well known but in
the reality, most of the companies are not investing time at all.
Testing consists of generating code that forces to pass a series of use cases to your software returning
a possitive or a negative result. Those use cases types can be defined at lots of levels inside a software
architecture. We have two big families when it comes to a concretion level: unit tests and functional
tests. The first ones are the lowest in the hierarchy and they take over the definition of very concrete
use cases making their complexity, and with it, the application code that they are testing, simple
and basic. They shouldn’t know anything outside their scope, neither communicate with third party
Chapter 16: testing 136

services nor databases. However, funtional tests include a concrete feature of the application and in
order to test if it works correctly they require a database connection, mailing systems, etc. Unit tests
are very fast and even there are options to allow them to execute in less time, they normally take
about 20-30 seconds to execute about 1,000 tests. Functional tests, as we repeated earlier on, require
connections to other system parts and they have more complexity, so execution becomes heavier.
The response time of this section is much higher.
As the time goes on and testing becomes more robust and solid, there are people who say that in
lots of situations, unit tests make no sense and just the functional part should be tested; on the other
hand there are people who think that testing an application 100% is a loss of time too. I don’t agree
with any of those statements. I did never agree the first of them but, the second one, conversly, there
was a time that I almost believe in it. Even so, the practice and the everyday use made me see that
a test may seem to be absurd but, tomorrow, that test can save you from a disaster. The reality is
that in lots of chances, investing time in testing a software 100% in a business world is non-viable
because of the date managed, but it’s undeniable that you should test absolutely everything (this is
a personal opinion) if you have time to do so. On the other hand, I found cases that, if I had omitted
the unit tests, details that are essential such as a correct balance, could have provoked a caos in the
application.
To end this introduction, and answering the first question, the yes with nuances is very bound to if
the application is already built and you have to maintain the code that was written by other person,
because testing that won’t be profitable and a lot of effort will be done for the work it will avoid us
to do. In those cases, it may be interesting to analyze and see the most critical parts and focus on
them, but this can vary depending on the concrete case. Other interesting solution could be, when
evolving the software, test the recently created code ensuring that the generated code since that date
is a clean code. Anyway, testing should exists as a basic premise in our future developments but not
as something optional or something guru related.

TDD vs BDD
These are acronyms that are being used more and more in development posts, documentations, best
practices or even job offers. I wrote a versus in the title but they are not antagonistic techniques. In
fact, BDD is a concrete interpetation of TDD based on the design.
TDD means Test Driven Development. This technique is based on developing unit tests, then build
the code that covers that unit test making it succeed and, finally, refactor the code.
BDD means Behaviour Driven Development. They are really similar techniques in their essence, the
most important different can be found in the concept itself. While in TDD tests must be created
before the code and then create the code so tests succeed, in BDD tests are design requirements or
specifications. The BDDism has another field more related to the software business logic and for
covering the different existing levels there are two subpractices: *storyBDD more focused on the
functional part and specBDD more focused on the unit testing part.
Chapter 16: testing 137

Testing tools in PHP


Going deeper in the PHP development, there exists lots of tools that are just for testing such as
libraries, methodology frameworks, etc. I know there I will be missing lots of different views, but
the next sections are short description about three of the most famous testing tools.

PHPUnit
The well known PHP testing father. If you test or if you don’t, if you a PHP developer, you
should have heard something about it. It’s a testing framework for PHP developer oriented. Most
of the libraries and open-source frameworks use PHPUnit as a testing tool, such as Symfony. It’s
a framework that belongs to the xUnit familly and, in order to check if a code is correct, it uses
assertions.
In order to use any testing tool, is essential to use a mocking library. In the PHP world you can find
two different options that are above the rest of them: Mockery and Prophecy. Since PHPUnit 4.5,
Prophecy is there by default.
PHPUnit installation is very easy. Just execute the following Composer command:

1 composer require --dev phpunit/phpunit

Here you have a basic example from the official website that can may help you to start using the
tool for the first time:
Production code:

1 <?php
2
3 namespace LearnSymfony;
4
5 class Money
6 {
7 private $amount;
8
9 public function __construct($amount)
10 {
11 $this->amount = $amount;
12 }
13
14 public function getAmount()
15 {
16 return $this->amount;
Chapter 16: testing 138

17 }
18
19 public function negate()
20 {
21 return new self(-1 * $this->amount);
22 }
23 }

The test:

1 <?php
2
3 namespace LearnSymfony;
4
5 class MoneyTest extends \PHPUnit_Framework_TestCase
6 {
7 public function testCanBeNegated()
8 {
9 $a = new Money(1);
10 $b = $a->negate();
11 $this->assertEquals(-1, $b->getAmount());
12 }
13 }

Once we have both the code and the test, you just need to execute vendor/bin/phpunit to get a
result like this.

1 $ vendor/bin/phpunit tests/
2 PHPUnit 4.8.6 by Sebastian Bergmann and contributors.
3 .
4 Time: 655 ms, Memory: 4.25Mb
5 OK (1 test, 1 assertion)

PhpSpec
It’s a much younger tool, created for a more bbd approach. The difference with PHPUnit, a xUnit
tool, PhpSpec is a testing framework part of the xSpec family. This means that it’s more focused in
the behaviour than in the code structure. We previously talked about Prophecy. Well, this mocking
framework is developed by the same group of people that are behind the PhpSpec core, being a direct
dependency of this tool.
Chapter 16: testing 139

Going back to something more theoric where some basic TDD and BDD features were explained,
PhpSpec is a tool clearly focused to practise the BDD technique, but even more, inside the BDD,
concretely the specBDD subpractice.
The great input that this tool makes besides being a testing framework is that it works like a charm
as a design tool because it has a powerful code generator that is improving with every version. The
idea is that the test or the specification generates the base code thanks to some terminal questions
prompted.
As with PHPUnit, PhpSpec is also installed with Composer:

1 composer install --dev phpspec/phpspec

Also, the same way we created the practical example with PHPUnit, here you have the base example
of the official documentation:

1 vendor/bin/phpspec desc Markdown


2 Specification for Markdown created in spec.

This file is a base structure of a xPec class in PhpSpec that can be autogenerated with the previous
command.

1 <?php
2
3 namespace spec;
4
5 use PhpSpec\ObjectBehavior;
6 use Prophecy\Argument;
7
8 class MarkdownSpec extends ObjectBehavior
9 {
10 function it_is_initializable()
11 {
12 $this->shouldHaveType('Markdown');
13 }
14 }

We add a new specification to aour xSpec example class:


Chapter 16: testing 140

1 function it_converts_plain_text_to_html_paragraphs()
2 {
3 $this->toHtml("Hi, there")->shouldReturn("<p>Hi, there</p>");
4 }

And now we run the tests:

1 vendor/bin/phpspec run
2 > spec\Markdown
3 � it converts plain text to html paragraphs
4 Class Markdown does not exist.
5 Do you want me to create it for you? [Y/n]

PhpSpec notices that the Markdown class does not exists so it invites us to tell the tool to create the
class for us. Execute the command again and this time the tool tells us that the toHtml method does
not exist and it invites us again to create the method for us.

1 vendor/bin/phpspec run
2 > spec\Markdown
3 � it converts plain text to html paragraphs
4 Method Markdown::toHtml() not found.
5 Do you want me to create it for you? [Y/n]

This just creates our production code, without us having to code anything. Final result:

1 <?php
2
3 class Markdown
4 {
5 public function toHtml($argument1)
6 {
7 // TODO: write logic here
8 }
9 }

If we now execute vendor/bind/phpspec run, an error will be launched as the one showed in the
following block. It is saying that we should implement the toHtml method in order to satisfy the
spec defined requirements:
Chapter 16: testing 141

1 > spec\Markdown
2 � it converts plain text to html paragraphs
3 Expected "<p>Hi, there</p>", but got null.
4 1 examples (1 failed)
5
6 <?php
7
8 class Markdown
9 {
10 public function toHtml()
11 {
12 return "<p>Hi, there</p>";
13 }
14 }

Once implemented the method correctly, the final result is as follows:

1 vendor/bin/phpspec run
2 > spec\Markdown
3 � it converts plain text to html paragraphs
4 1 examples (1 passed)

Behat
If PhpSpec is the best tool to do specBDD, then *Behat is the best tool for the **storyBDD* prac-
tice. As we already know, the **storyBDD is at a higher level focusing on the business/functional
scope.
One of the most important limitations that is said anywhere when generating functional tests and
I like to make clear that, in order to run this type of tests, you need an application, with its entry
point, endpoint, front controller or whatever we want to call it, because without it, Behat cannot
be executed. For example, if we follow the example that is being built during the whole book, there
shouldn’t be any problem because it’s a Symfony web application with the front controller, in this
case app.php or app_dev.php. However, if you create a new open-source bundle to be used by our
future Symfony applications and you want to functional test that bundle, you should create your
own bootstrap.php front controller and all the basic stack that makes a Symfony application work.
Behat contains two different parts: the code part, with PHP classes with special Behat annotations
that it knows how to interpet them as a concept called steps; and other part with Gherkin (a language
similar to the human language) that generates scenarios that contain steps that connect with the
mentioned PHP code.
Exactly as with the previous tools, Behat is installed with Composer but, as we are required to install
some dependencies apart from Behat, we recommend yo to copy and paste these four dependencies
directly to the require-dev section of the composer.json file and then execute composer update.
Chapter 16: testing 142

1 "require-dev": {
2 (...)
3
4 "behat/behat" : "~3.0",
5 "behat/symfony2-extension" : "~2.0",
6 "behat/mink-extension" : "~2.0",
7 "behat/mink-browserkit-driver": "~1.2"
8 }

Here you have a very simple example of how this storyBDD tool works. It’s not as easy to
demonstrate as with the previous ones so we must show a more didactical and real example. If
you want to go into detail, I suggest you to read this link⁹² from the official documentation.
The PHP method that belongs to the CommandContext class that, as you can see, it has a @When
annotation which is one of the reserved words that the Behat parser interprets and conects with the
Gherkin written code.

1 /**
2 * @When /^I run "([^"]*)" interactive command$/
3 */
4 public function iRunInteractiveCommand($command)
5 {
6 $this->loadCommands();
7 $command = $this->application->find($command);
8 $this->tester = new CommandTester($command);
9 $helper = $command->getHelper('dialog');
10 $helper->setInputStream($this->getInputStream('Test\\n'));
11 $this->inputs['command'] = $command->getName();
12 $this->tester->execute($this->inputs, $this->options);
13 $this->initialize();
14 }

The following Gherkin code has not much to say about, not technical people can understand easily
what every scenario resolves.

⁹²http://docs.behat.org/en/latest/quick_intro_pt1.html
Chapter 16: testing 143

1 @user
2 Feature: Manage users via cli
3 In order to manage users cli
4 As a console
5 I want to be able create a new user
6
7 Scenario: Executing command to create a new user
8 Given the following command inputs:
9 | email | me@learnsymfony.com |
10 | username | learnSymfony |
11 | firstName | Learn |
12 | lastName | Symfony |
13 | password | 123456 |
14 When I run "learnSymfony:user:create" interactive command
15 Then I should see the following output:
16 """
17 A new learnSymfony user has been created
18 """

The following YAML code corresponds to the behat.yml configuration file in where some differente
Behat options are defined, with folders and paths where contexts are located which are no more than
PHP classes. It’s important to know the existence of the Behat\Symfony2Extension extension that
gives some important details to Behat when it is used in a Symfony application.

1 default:
2 formatters:
3 pretty:
4 verbose: true
5 paths: true
6 snippets: false
7 suites:
8 user:
9 paths:
10 features: src/AppBundle
11 contexts:
12 AppBundle\Behat\Context\CommandContext
13 filters:
14 tags: "@user"
15 extensions:
16 Behat\MinkExtension:
17 sessions:
18 default:
Chapter 16: testing 144

19 symfony2: ~
20 browser_name: firefox
21 show_auto: false
22 Behat\Symfony2Extension: ~

Lastly, we can launch Behat as if we were launching PHPUnit or PhpSpec. Execute the following
command:

1 vendor/bin/behat

Continuous integration
Continuous Integration is a software development practice where members of a team
integrate their work frequently, usually each person integrates at least daily - leading
to multiple integrations per day. Each integration is verified by an automated build
(including test) to detect integration errors as quickly as possible.

That sentence is the way Martin Fowler defines the continuous integration concept. It’s a way of
development that implies automatic continuous deployments as a way of finding possible errors in
an earlier development stage.
In the next sections, I will show you two of the more consolidated alternatives existing in the
integration tools family: Jenkins and Travis.

Jenkins
If there is a software directly associated to the continuous integration concept that is Jekins. It’s
written in JAVA and it runs in a servlet container servers, such as Apache Tomcat. It was firstly
developed with a different name, Hudson, and it’s two big secrets for the success have been: being
the first ones (and open-source) and it’s plugin based architecture. This has done that, nowadays,
Jenkins has a lot of plugins for almost everything.

Travis
Another alternative, much newer and conceptually very different: Travis. It is the king of continuous
integration for open-source projects, libraries, etc. Great part of the code hosted on GitHub as open
source that uses continuous integration use Travis. It’s simplicity has been a key factor for the
success, because you have to download and install Jenkins in your systems, and configure a lot
of different options, Travis starts working with just four easy configurations in a YAML file as if it
was magic, making continuous integration quick in this kind of repositories. I won’t talk here about
Travis configuration because in this chapter tip, you will find all the information you need.
Chapter 16: testing 145

Tip 16
In the previous section, we talked about continuous integration explaining quickly and easily what
it is and two of the most used tools for the PHP and the rest of the development communities.
Well, in this tip we are going to explain in easily how to configure a .travis.yml file so our favorite
continuous integration server reports us our Behat and PhpSpec tests with green and red colors in
that build of our Symfony application, allowing us (or not allowing) to deploy.

1 language: php
2 # it tells Travis the language we are using.
3
4 php:
5 - 5.4
6 - 5.5
7 - 5.6
8 - 7.0
9 - hhvm
10 # the different environments the build must be excecuted
11
12 env:
13 - SYMFONY_VERSION=2.3.*
14 - SYMFONY_VERSION=2.7.*
15 - SYMFONY_VERSION=dev-master
16 # shows the different Symfony versions in where it must be executed
17 # the application build
18
19 before_install:
20 - sudo apt-get update
21 - sudo apt-get -qq install ruby-sass
22 # Travis use this block to execute commands before the environment
23 # installation. Here we update Ubuntu packages and we install Sass
24
25 install:
26 - sudo apt-get install apache2 libapache2-mod-fastcgi
27 - sudo a2enmod rewrite actions fastcgi alias
28 - echo "cgi.fix_pathinfo = 1" >> ~/.phpenv/versions/$(phpenv version-name)/e\
29 tc/php.ini
30 - sudo cp -f scripts/travis/vhost /etc/apache2/sites-available/default
31 - sudo sed -e "s?%TRAVIS_BUILD_DIR%?$(pwd)?g" --in-place /etc/apache2/sites-\
32 available/default
33 - sudo service apache2 restart
34 - cat /etc/apache2/sites-available/default
Chapter 16: testing 146

35 # It may be the most important Travis block, where we prepare


36 # the environment to make the build executable. Because it is a
37 # PHP project, we need a web server (Apache in this case)
38
39 - sudo apt-get install node
40 - npm install -g bower
41 - npm install -g gulp
42 - gem install scss-lint
43 - cd src/AppBundle
44 - npm install
45 - bower install
46 - cd -
47
48 before_script:
49 - export MINK_EXTENSION_PARAMS='base_url=http://localhost'
50 - composer self-update
51 - composer require symfony/symfony:${SYMFONY_VERSION}
52 - app/console doctrine:database:create --env=test
53 - app/console doctrine:schema:create --env=test
54 - app/console cache:warmup --env=test
55 - app/console assets:install --symlink
56 - cd src/AppBundle
57 - gulp
58 - cd -
59 # application configurations
60
61 script:
62 - bin/phpspec run -fpretty
63 - bin/behat --stop-on-failure --suite=main
64 # this block executes our tests
65
66 matrix:
67 allow_failures:
68 - php: hhvm
69 - php: 7.0
70 - env: SYMFONY_VERSION=dev-master
71 # The matrix blocks contains lots of possibilities. Here we are telling Travis
72 # that we are allowed to fail the build in the hhvm or PHP7 environments or
73 # or in the dev-master branch
Chapter 16: testing 147

Summary
In this chapter we have seen a short but intense introduction to the surrounding testing world from
a Symfony point of view. We have analized the different testing practices and their main PHP tools;
we have briefly explained some concepts about continuous integration seeing the two most famous
alternatives: Jenkins and Travis. Lastly, in the tip, we took advantage of the open-source aim of
this book, we showed a complete .travis.yml of a Symfony application. The goal here was to show
an starting point so you can follow learning something as important as testing. This book is not
about testing but, if you are willing to read more about it, you can read a book written by Fernando
Arconada in spanish called Testing for Symfony2 Applications⁹³.
⁹³https://leanpub.com/testingsymfony2
Epilogue
Our journey come to its end after more than 150 intense pages full of code and, specially, desire to
learn. This is just the “advanced” starting of something much bigger, but we will go step by step
because the most important thing right now is securing the actual knowledge master them. It’s
possible that I write about more technologies and other ways of working in future books, ways that
nowadays professionals use daily, but I will like to say goodbye with some of the technologies I use
and I consider interesting so you check them out.

Bower
This is commonly named as a “must have”… or at least it was. It’s a package manager for the
web. And you are thinking… another one? In fact, another one, but this one is commonly used
for front libraries: jQuery, Foundation, slick carousel, etc. With the bower.json file you will be able
to establish the front dependencies that you want in the version you need them, and thanks to the
.bowerrc file you will be able to download the desired libraries in the path you want (you better
put them inside the app/Resources/assets/vendor/ folder or I will get angry). You won’t need to
download the libraries manually again and checking for upgrade versions in not very friendly pages.
Be careful: you can also use npm for front libraries too.

Gassetic
Assetic is the tool that I told you not to use but we did use it to didactically see how it works. A lot of
people will tell you the same as me: use a task tool to do this action. However, there are people who
still have a special affection for Assetic and that’s why they created Gassetic⁹⁴. I haven’t checked
how it works so, use it in your project and tell me how it is performing so I can add a little section
to this book.

Gulp with JavaScript


Without any doubts, this is something that I leave you as homework… well not everything because
I give you the solution later on. Removing Assetic in order to improve our Gulp is basic in a
professional web. I hope you do it without reading the solution because that will show that you
understood quite good this book.
⁹⁴https://github.com/romanschejbal/gassetic
Epilogue 149

PostCSS
PostCSS is just an API with lots of plugins that use this API to make a lot of stuff. Don’t look at the
technology name because it’s full of powerful abilites, even preprocessing ones. As I’m writing these
lines, a lot of people is write more and more PostCSS modules such as Autoprefixer, cssnext, grid,
… Almost anything you can think about CSS precessing and postprocessing. It’s also the quickest
of the nowadays solutions, and that why a lot of people is changing from Sass to PostCSS. You can
browse some of the plugins here⁹⁵

Symfony made developments


If you are bored of the tipical WordPress for your developments, here you have an spanish post⁹⁶ so
you can read about CMS made with Symfony. I’ve tested some of them and, besides not being so
long-lived as WordPress, some of them have an splendind path.
I would like to add an awesome eCommerce system made with Symfony. Sylius⁹⁷ is a platform ready
to build and scale an eCommerce website easily and quickly, thanks to a component based structure.

DDD
We already talked about TDD and BDD, but you can’t finish this book without reasearching about
DDD. Without any doubts, if you want to become a great developer, everything that surrounds DDD
and design patterns should be familiar for you. This is going to be a hard work, but the resulting
scene won’t leave you indifferent.

Did you like the book?


I finish the book with this section. I’ve been writing each line of this book for several weeks with all
the passion I usually transport with me to my offline and online lessons. I consider that I shouldn’t
put a price to what I showed you and it’s you the one that decides the price of what you have read.
If you liked it and you think that you must express your gratitude to my work, you know taht you
can return back to the leanpub page and pay for it, knowing that the 50% of what you pay will be
destinated to Watchi.
⁹⁵http://postcss.parts/
⁹⁶http://symfony.es/noticias/2014/08/15/un-repaso-a-los-cms-basados-en-symfony/
⁹⁷http://sylius.org
Epilogue 150

Thanks to readers
The most important part of this book is you, so thank you for your time reading this book, I will
express my gratitude if I receive some feedback about it, even the good or the bad one. Thanks to
the reader for being with me, for spending your precious time with me behind these lines. I hope
that thanks to them you became a better developer.

S-ar putea să vă placă și