Это вторая часть перевода (первая часть) статьи о том, как сделать простенький проект на Симфонии за 1 час. В ней мы наладим валидацию форм, изменим формат URL, сделаем админку и закроем в нее доступ.
В прошлой серии мы сделали форму для добавления комментариев (и попутно заметили, что автор оригинальной статьи накосячил в пункте прицепления комментариев к посту). Теперь нам надо следовать фундаментальному принципу безопасности: не доверяй введенным пользователем данным. Поэтому организуем проверку того, что пользователь будет отправлять через форму.
Когда мы просим Симфонию сгенерировать классы, она генерирует не только их, но еще и элементы формы. А еще она добавляет к этим элементам несколько проверок, которые посчитает нужными, опираясь на схему модели данных. Поскольку в схеме написано, что для таблицы blog_post поле title является обязательным (required), у пользователя ничего не выйдет, если он попробует отправить форму, не заполняя это поле. Кроме того, Симфония поймет, что нельзя отправлять в этом поле строку длиннее 255 символов (опять-таки, она черпает эти знания из схемы модели данных).
Давайте заменим (перезапишем, override) некоторые из этих проверок в классе BlogCommentForm. Открываем файл (найдете его самостоятельно?) и добавляем в метод configure() следующие строки:
Переопределяя метод для поля email, мы переопределяем поведение по умолчанию (default behaviour).
Теперь у нас есть все, для того, чтобы объяснить пользователю, что он неправ, когда тот введет невалидный email-адрес. Форма стала дуракоустойчивее! Тут надо обратить внимание вот на что: во-первых, когда форма содержит какие-то данные, то при отправке формы эти данные сохраняются специальным образом. Поэтому если пользователь отправил форму с частично некорректными данными, ему не придется заполнять форму заново. Во-вторых, ошибки, возникшие при заполнении тех или иных полей, отображаются рядом с этими полями, а не где-нибудь в общей куче ошибок над формой.
Теперь самое время внести ясность в вопрос о том, как обрабатывается форма и сохраняются данные. В этом нелегком деле используются экшены, которые мы правили в прошлой серии. Они содержатся в этом файле:
Вот что нам надо:
После создания экземпляра класса формы происходит следующее:
Внимание! В Симфонии 1.2 кое-что изменилось (оригинальная статья написана для Симфонии 1.1). Во-первых, создание формы и проверка на метод POST вынесены в экшен executeCreate:
Возможно, тут возникнет некоторая путаница относительно того, какой экшен зачем нужен. Например, executeNew — это экшен вывода формы при добавлении нового комментария. А executeCreate — это экшен обработки данных при отправке формы добавления нового комментария.
Чтобы сделать какое-то поле обязательным, достаточно в файле схемы данных (schema.yml) прописать required:true для данного поля. Например,
О валидации данных читаем подробнее в главе form validation.
Вы уже поняли принцип, по которому формируется URL для модулей и экшенов? Все это настраивается: и чтобы пользователю было приятнее смотреть на эти адреса, и поисковым машинам тоже. Предлагаю использовать заголовок поста при создании URL.
Проблема в том, что название может содержать специальные символы, типа пробелов. Если вы просто преобразуете их в escape-последовательность, то URL будет содержать страшненькие %20, поэтому нам надо расширить модель новым методом класса BlogPost, чтобы он делал из заголовка поста что-нибудь URL-красивенькое. Чтобы сделать это, открываем файл BlogPost.php из папки sf_sandbox/lib/model/ и добавляем туда следующее:
Думаю, вам несложно будет дописать пару замен, чтобы сделать из русского заголовка поста хорошую транслитерацию.
А теперь надо сделать экшен permalink для модуля post. Добавляем новый метод в sf_sandbox/apps/frontend/modules/post/actions/actions.class.php:
Вопрос! ребята, я понимаю, что это просто жесть так программировать! Выбрать для каждой записи остальные стопицот записей и искать: а нет ли среди них того, чего нам надо? Нет ли способа поизящнее подобрать ID по заданному title? Или я зря парюсь, и подсистема кэширование Симфонии все сделает как надо?
Список постов теперь может вызывать экшен permalink вместо show для каждого поста. В файле sf_sandbox/apps/frontend/modules/post/templates/indexSuccess.php удалим колонку с ID поста, и заменим содержимое ячейки, где выводится заголовок Title с этого:
на это:
Остался всего один шаг: подредактировать routing.yml, расположенный в папке sf_sandbox/apps/frontend/config/ и в самом начале добавить правила:
Теперь смотрим в браузер и видим новые URL в действии. Если вдруг выскочила ошибка, значит надо почистить кэш – это правила роутинга еще не начали работать. Как это делается, мы уже говорили:
Об адресах читаем в главе smart URLs.
Окей, наш блог становится все краше. Только вот незадача: каждый умник сейчас может запросто зайти и поправить любой пост. Не гуд это, как вы догадываетесь. Вобщем, сейчас нам надо убрать всю функциональность по редактированию постов и комментариев из публичной части (а позже – добавить ее в админку).
В шаблоне sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php удалим ссылку на редактирование поста. Найдите сами чего там надо удалить (кто совсем в танке, эти строки есть в оригинальной статье).
То же самое делаем для шаблона sf_sandbox/apps/frontend/modules/post/templates/indexSuccess.php, там мы удаляем ссылку на создание поста (танкисты ищут это место в оригинальной статье).
Естественно, удалением ссылок мы не отделаемся. Функционал тоже надо удалить. Открываем sf_sandbox/apps/frontend/modules/post/actions/actions.class.php и удаляем экшены executeEdit и executeDelete.
Примечание! В Симфонии 1.2 надо еще удалить экшен executeUpdate. Кто читал все внимательно, тот уже сам догадался об этом.
Все, теперь из публичной части можно добавлять, но нельзя редактировать посты. С комментариями, думаю, сможете справиться самостоятельно.
А если кто-то начнет в нашем блоге писать про расовую неравноправность? Или оставлять комментарии в духе «+1»? Нам нужен инструмент, который позволил бы чистить блог от всякого мусора. Ну и по настроению, исправлять опечатки. Нам нужна админка.
Админка – это отдельное приложение (application), и его надо создать. Запускаем в командной строке:
Возможно, вы не заметили, но мы только что использовали admin generator. На самом деле с его помощью можно делать вещи, куда более сложные и интересные, чем просто создание форм добавления-редактирования-удаления. Почитайте о нем на досуге.
Точно так же, как мы делали для приложения frontend, отредактируем главный шаблон (layout) (apps/backend/templates/layout.php) и добавим глобальную навигацию:
Теперь мы можем получить доступ к админке в окружении разработки, набрав следующее:
Тут проявляется великая мощь генератора админки, пользоваться которой можно ловко и умело, редактируя конфиг. Заменим содержимое файла sf_sandbox/apps/backend/modules/post/config/generator.yml на следующее:
Посмотрите: среди полей таблицы blog_post админка будет искать поле (или getter-метод) nb_comments. Поскольку это не хранимое, а генерируемое значение, нам надо добавить getter-метод в нашу модель (sf_sandbox/lib/model/BlogPost.php):
Теперь обновим админку и любуемся изменениями:
По-прежнему каждый умник может залезть в админку и стереть все посты про Ксению Собчак. Не дадим ему это сделать: запаролим админку. В папке apps/backend/config/, правим файл security.yml, пишем в него следующее:
Теперь нельзя войти в модули админки, не авторизуясь. Постойте, откуда взять логин с паролем и вообще всякие формы авторизации-регистрации? Есть гениальное решение — плагины! Воспользуемся плагином, который как раз занимается этими вопросами — sfGuardPlugin. Пишем следующее в командной строке:
Тут у вас может возникнуть проблема, если не установлен PEAR. Самое время его установить.
Эта команда скачает плагин из хранилища плагинов Симфонии. В конце должно быть выдано сообщение, что все прошло успешно:
Теперь плагин надо включить. Правим файл sf_sandbox/apps/backend/config/settings.yml, включаем системный логин следующим образом. Раскомментируем все, что содержится в ключе all: и добавим следующее:
Теперь добавим нового системного пользователя, пишем в файл sf_sandbox/apps/backend/lib/myUser.class.php вместо всего что там есть следующее:
Теперь мы должны пересоздать модель, формы и фильтры, и обновить БД:
Как и раньше, при запуске задания propel:insert-sql, Стифония удалит все таблицы и создаст их заново. Поскольку во время разработки это будет встречаться довольно часто, имеет смысл записать начальные и тестовые данные в fixtures (см. подробнее в главе populating a database).
Теперь снова надо почистить кэш. После этого, наконец, создаем нового пользователя:
Примечание: почему-то в оригинальном тексте в параметрах к этой команде указывается еще и название приложения. Возможно, это было нужно ранее, но в 1.2 точно не нужно.
Теперь сделаем модуль управления постами post модулем по умолчанию при входе в админку. Для этого открываем файл apps/backend/config/routing.yml и ищем там ключ homepage. Меняем default на post.
Итак, если мы теперь попытаемся залезть в админку, мы получим следующую картинку:
Читаем больше в главе security.
Час прошел. Я думаю, что у вас, как и у меня, в первый раз все заняло куда больше времени. Поверьте, при совсем небольшой сноровке все будет получаться очень легко и непринужденно – Симфония располагает к этому.
Теперь можно использовать оба приложения в рабочем окружении:
Тут у вас, возможно, возникнут сообщения об ошибках. Это все из-за того, что мы поменяли модель, и не обновили кэш. Чистим кэш быстренько:
А теперь смотрим и любуемся скоростью работы приложений! Без комментариев.
К сожалению, ошибся с выбором типа топика. Это перевод статьи My first symfony project, автор Fabien Potencier. Не знаю как поправить.
Валидация форм
В прошлой серии мы сделали форму для добавления комментариев (и попутно заметили, что автор оригинальной статьи накосячил в пункте прицепления комментариев к посту). Теперь нам надо следовать фундаментальному принципу безопасности: не доверяй введенным пользователем данным. Поэтому организуем проверку того, что пользователь будет отправлять через форму.
Когда мы просим Симфонию сгенерировать классы, она генерирует не только их, но еще и элементы формы. А еще она добавляет к этим элементам несколько проверок, которые посчитает нужными, опираясь на схему модели данных. Поскольку в схеме написано, что для таблицы blog_post поле title является обязательным (required), у пользователя ничего не выйдет, если он попробует отправить форму, не заполняя это поле. Кроме того, Симфония поймет, что нельзя отправлять в этом поле строку длиннее 255 символов (опять-таки, она черпает эти знания из схемы модели данных).
Давайте заменим (перезапишем, override) некоторые из этих проверок в классе BlogCommentForm. Открываем файл (найдете его самостоятельно?) и добавляем в метод configure() следующие строки:
$this->validatorSchema['email'] = new sfValidatorEmail( array('required' => false), array('invalid' => 'The email address is not valid'));
Переопределяя метод для поля email, мы переопределяем поведение по умолчанию (default behaviour).
Теперь у нас есть все, для того, чтобы объяснить пользователю, что он неправ, когда тот введет невалидный email-адрес. Форма стала дуракоустойчивее! Тут надо обратить внимание вот на что: во-первых, когда форма содержит какие-то данные, то при отправке формы эти данные сохраняются специальным образом. Поэтому если пользователь отправил форму с частично некорректными данными, ему не придется заполнять форму заново. Во-вторых, ошибки, возникшие при заполнении тех или иных полей, отображаются рядом с этими полями, а не где-нибудь в общей куче ошибок над формой.
Теперь самое время внести ясность в вопрос о том, как обрабатывается форма и сохраняются данные. В этом нелегком деле используются экшены, которые мы правили в прошлой серии. Они содержатся в этом файле:
/sf_sandbox/apps/frontend/modules/comment/actions/actions.class.php:
Вот что нам надо:
$this->form = new BlogCommentForm(BlogCommentPeer::retrieveByPk($request->getParameter('id'))); if ($request->isMethod('post')) { $this->form->bind($request->getParameter('blog_comment')); if ($this->form->isValid()) { $blog_comment = $this->form->save(); $this->redirect('post/show?id='.$blog_comment->getBlogPostId()); } }
После создания экземпляра класса формы происходит следующее:
- Происходит проверка, что форма была передана методом POST
- Действительно был получен параметр-массив blog_comment. Метод getParameter() сумеет определить, что этот параметр является массив значений, а не одиночным значением, и возвращает ассоциативный массив (например, значение поля blog_comment[author] будет записано в этом массиве под ключом author)
- Полученный ассоциативный массив будет подцеплен к форме, и этот процесс называется биндингом (binding), в результате которого значения из этого массива заполнят соответствующие поля в экземпляре класса формы. После этого запускается проверка формы, и поле за полем происходит проверка, корректные ли данные в форме, или нет.
- Только в том случае, если все проверки прошли, происходит сохранение данных, и страница перенаправляется на экшен show.
Внимание! В Симфонии 1.2 кое-что изменилось (оригинальная статья написана для Симфонии 1.1). Во-первых, создание формы и проверка на метод POST вынесены в экшен executeCreate:
public function executeCreate(sfWebRequest $request) { $this->forward404Unless($request->isMethod('post')); $this->form = new BlogCommentForm(); $this->processForm($request, $this->form); $this->setTemplate('new'); }
Возможно, тут возникнет некоторая путаница относительно того, какой экшен зачем нужен. Например, executeNew — это экшен вывода формы при добавлении нового комментария. А executeCreate — это экшен обработки данных при отправке формы добавления нового комментария.
Чтобы сделать какое-то поле обязательным, достаточно в файле схемы данных (schema.yml) прописать required:true для данного поля. Например,
author: { type: varchar(255),required:true }
О валидации данных читаем подробнее в главе form validation.
Меняем формат URL
Вы уже поняли принцип, по которому формируется URL для модулей и экшенов? Все это настраивается: и чтобы пользователю было приятнее смотреть на эти адреса, и поисковым машинам тоже. Предлагаю использовать заголовок поста при создании URL.
Проблема в том, что название может содержать специальные символы, типа пробелов. Если вы просто преобразуете их в escape-последовательность, то URL будет содержать страшненькие %20, поэтому нам надо расширить модель новым методом класса BlogPost, чтобы он делал из заголовка поста что-нибудь URL-красивенькое. Чтобы сделать это, открываем файл BlogPost.php из папки sf_sandbox/lib/model/ и добавляем туда следующее:
public function getStrippedTitle() { $result = strtolower($this->getTitle()); // strip all non word chars $result = preg_replace('/\W/', ' ', $result); // replace all white space sections with a dash $result = preg_replace('/\ +/', '-', $result); // trim dashes $result = preg_replace('/\-$/', '', $result); $result = preg_replace('/^\-/', '', $result); return $result; }
Думаю, вам несложно будет дописать пару замен, чтобы сделать из русского заголовка поста хорошую транслитерацию.
А теперь надо сделать экшен permalink для модуля post. Добавляем новый метод в sf_sandbox/apps/frontend/modules/post/actions/actions.class.php:
public function executePermalink($request) { $posts = BlogPostPeer::doSelect(new Criteria()); $title = $request->getParameter('title'); foreach ($posts as $post) { if ($post->getStrippedTitle() == $title) { $request->setParameter('id', $post->getId()); return $this->forward('post', 'show'); } } $this->forward404(); }
Вопрос! ребята, я понимаю, что это просто жесть так программировать! Выбрать для каждой записи остальные стопицот записей и искать: а нет ли среди них того, чего нам надо? Нет ли способа поизящнее подобрать ID по заданному title? Или я зря парюсь, и подсистема кэширование Симфонии все сделает как надо?
Список постов теперь может вызывать экшен permalink вместо show для каждого поста. В файле sf_sandbox/apps/frontend/modules/post/templates/indexSuccess.php удалим колонку с ID поста, и заменим содержимое ячейки, где выводится заголовок Title с этого:
<td><?php echo $blog_post->getTitle() ?></td>
на это:
<td><?php echo link_to($blog_post->getTitle(), '@post?title='.$blog_post->getStrippedTitle()) ?></td>
Остался всего один шаг: подредактировать routing.yml, расположенный в папке sf_sandbox/apps/frontend/config/ и в самом начале добавить правила:
list_of_posts: url: /latest_posts param: { module: post, action: index } post: url: /blog/:title param: { module: post, action: permalink }
Теперь смотрим в браузер и видим новые URL в действии. Если вдруг выскочила ошибка, значит надо почистить кэш – это правила роутинга еще не начали работать. Как это делается, мы уже говорили:
$ php symfony cc
Об адресах читаем в главе smart URLs.
Чистим фронтенд (публичную часть)
Окей, наш блог становится все краше. Только вот незадача: каждый умник сейчас может запросто зайти и поправить любой пост. Не гуд это, как вы догадываетесь. Вобщем, сейчас нам надо убрать всю функциональность по редактированию постов и комментариев из публичной части (а позже – добавить ее в админку).
В шаблоне sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php удалим ссылку на редактирование поста. Найдите сами чего там надо удалить (кто совсем в танке, эти строки есть в оригинальной статье).
То же самое делаем для шаблона sf_sandbox/apps/frontend/modules/post/templates/indexSuccess.php, там мы удаляем ссылку на создание поста (танкисты ищут это место в оригинальной статье).
Естественно, удалением ссылок мы не отделаемся. Функционал тоже надо удалить. Открываем sf_sandbox/apps/frontend/modules/post/actions/actions.class.php и удаляем экшены executeEdit и executeDelete.
Примечание! В Симфонии 1.2 надо еще удалить экшен executeUpdate. Кто читал все внимательно, тот уже сам догадался об этом.
Все, теперь из публичной части можно добавлять, но нельзя редактировать посты. С комментариями, думаю, сможете справиться самостоятельно.
Делаем админку
А если кто-то начнет в нашем блоге писать про расовую неравноправность? Или оставлять комментарии в духе «+1»? Нам нужен инструмент, который позволил бы чистить блог от всякого мусора. Ну и по настроению, исправлять опечатки. Нам нужна админка.
Админка – это отдельное приложение (application), и его надо создать. Запускаем в командной строке:
$ php symfony generate:app backend $ php symfony propel:init-admin backend post BlogPost $ php symfony propel:init-admin backend comment BlogComment
Возможно, вы не заметили, но мы только что использовали admin generator. На самом деле с его помощью можно делать вещи, куда более сложные и интересные, чем просто создание форм добавления-редактирования-удаления. Почитайте о нем на досуге.
Точно так же, как мы делали для приложения frontend, отредактируем главный шаблон (layout) (apps/backend/templates/layout.php) и добавим глобальную навигацию:
<div id="navigation"> <ul style="list-style:none;"> <li><?php echo link_to('Посты', 'post/index') ?></li> <li><?php echo link_to('Комментарии', 'comment/index') ?></li> </ul> </div> <div id="content"> <?php echo $sf_data->getRaw('sf_content') ?> </div>
Теперь мы можем получить доступ к админке в окружении разработки, набрав следующее:
http://localhost/sf_sandbox/web/backend_dev.php/post
Тут проявляется великая мощь генератора админки, пользоваться которой можно ловко и умело, редактируя конфиг. Заменим содержимое файла sf_sandbox/apps/backend/modules/post/config/generator.yml на следующее:
generator: class: sfPropelAdminGenerator param: model_class: BlogPost theme: default fields: title: { name: Title } excerpt: { name: Excerpt } body: { name: Body } nb_comments: { name: Comments } created_at: { name: Creation date } list: title: Post list layout: tabular display: [=title, excerpt, nb_comments, created_at] object_actions: _edit: ~ _delete: ~ max_per_page: 5 filters: [title, created_at] edit: title: Post detail fields: title: { type: input_tag, params: size=53 } excerpt: { type: textarea_tag, params: size=50x2 } body: { type: textarea_tag, params: size=50x10 } created_at: { type: input_date_tag, params: rich=on }
Посмотрите: среди полей таблицы blog_post админка будет искать поле (или getter-метод) nb_comments. Поскольку это не хранимое, а генерируемое значение, нам надо добавить getter-метод в нашу модель (sf_sandbox/lib/model/BlogPost.php):
public function getNbComments() { return count($this->getBlogComments()); }
Теперь обновим админку и любуемся изменениями:
Ограничиваем доступ в админку
По-прежнему каждый умник может залезть в админку и стереть все посты про Ксению Собчак. Не дадим ему это сделать: запаролим админку. В папке apps/backend/config/, правим файл security.yml, пишем в него следующее:
all: is_secure: on
Теперь нельзя войти в модули админки, не авторизуясь. Постойте, откуда взять логин с паролем и вообще всякие формы авторизации-регистрации? Есть гениальное решение — плагины! Воспользуемся плагином, который как раз занимается этими вопросами — sfGuardPlugin. Пишем следующее в командной строке:
$ php symfony plugin:install sfGuardPlugin
Тут у вас может возникнуть проблема, если не установлен PEAR. Самое время его установить.
Эта команда скачает плагин из хранилища плагинов Симфонии. В конце должно быть выдано сообщение, что все прошло успешно:
$ php symfony plugin:install sfGuardPlugin >> plugin installing plugin "sfGuardPlugin" >> sfPearFrontendPlugin Attempting to discover channel "pear.symfony-project.com"... >> sfPearFrontendPlugin downloading channel.xml ... >> sfPearFrontendPlugin Starting to download channel.xml (663 bytes) >> sfPearFrontendPlugin . >> sfPearFrontendPlugin ...done: 663 bytes >> sfPearFrontendPlugin Auto-discovered channel "pear.symfony-project.com", alias >> sfPearFrontendPlugin "symfony", adding to registry >> sfPearFrontendPlugin Attempting to discover channel >> sfPearFrontendPlugin "plugins.symfony-project.org"... >> sfPearFrontendPlugin downloading channel.xml ... >> sfPearFrontendPlugin Starting to download channel.xml (639 bytes) >> sfPearFrontendPlugin ...done: 639 bytes >> sfPearFrontendPlugin Auto-discovered channel "plugins.symfony-project.org", alias >> sfPearFrontendPlugin "symfony-plugins", adding to registry >> sfPearFrontendPlugin downloading sfGuardPlugin-2.2.0.tgz ... >> sfPearFrontendPlugin Starting to download sfGuardPlugin-2.2.0.tgz (18,589 bytes) >> sfPearFrontendPlugin ...done: 18,589 bytes >> sfSymfonyPluginManager Installation successful for plugin "sfGuardPlugin"
Теперь плагин надо включить. Правим файл sf_sandbox/apps/backend/config/settings.yml, включаем системный логин следующим образом. Раскомментируем все, что содержится в ключе all: и добавим следующее:
# (Some stuff here) all: .actions: login_module: sfGuardAuth # To be called when a non-authenticated user login_action: signin # Tries to access a secure page secure_module: sfGuardAuth # To be called when a user doesn't have secure_action: secure # The credentials required for an action .settings: enabled_modules: [default, sfGuardAuth, sfGuardGroup, sfGuardPermission, sfGuardUser]
Теперь добавим нового системного пользователя, пишем в файл sf_sandbox/apps/backend/lib/myUser.class.php вместо всего что там есть следующее:
class myUser extends sfGuardSecurityUser { }
Теперь мы должны пересоздать модель, формы и фильтры, и обновить БД:
$ php symfony propel:build-model $ php symfony propel:build-forms $ php symfony propel:build-filters $ php symfony propel:build-sql $ php symfony propel:insert-sql
Как и раньше, при запуске задания propel:insert-sql, Стифония удалит все таблицы и создаст их заново. Поскольку во время разработки это будет встречаться довольно часто, имеет смысл записать начальные и тестовые данные в fixtures (см. подробнее в главе populating a database).
Теперь снова надо почистить кэш. После этого, наконец, создаем нового пользователя:
$ symfony guard:create-user habr oh_no_123456
Примечание: почему-то в оригинальном тексте в параметрах к этой команде указывается еще и название приложения. Возможно, это было нужно ранее, но в 1.2 точно не нужно.
Теперь сделаем модуль управления постами post модулем по умолчанию при входе в админку. Для этого открываем файл apps/backend/config/routing.yml и ищем там ключ homepage. Меняем default на post.
Итак, если мы теперь попытаемся залезть в админку, мы получим следующую картинку:
Читаем больше в главе security.
Заключение
Час прошел. Я думаю, что у вас, как и у меня, в первый раз все заняло куда больше времени. Поверьте, при совсем небольшой сноровке все будет получаться очень легко и непринужденно – Симфония располагает к этому.
Теперь можно использовать оба приложения в рабочем окружении:
frontend: http://localhost/sf_sandbox/web/index.php/ backend: http://localhost/sf_sandbox/web/backend.php/
Тут у вас, возможно, возникнут сообщения об ошибках. Это все из-за того, что мы поменяли модель, и не обновили кэш. Чистим кэш быстренько:
$ php symfony cc
А теперь смотрим и любуемся скоростью работы приложений! Без комментариев.
К сожалению, ошибся с выбором типа топика. Это перевод статьи My first symfony project, автор Fabien Potencier. Не знаю как поправить.