Формы в Symfony
В предыдущем уроке мы наконец-то сделали кое-что полезное, а именно - заполнили базу фейковыми данными, вывели все записи и одну запись по её slug. Но давайте приблизимся к реальности ещё на шаг - и создадим форму добавления записи, как это происходит в реальных приложениях.
В Symfony каждая форма принадлежит конкретной сущности, что даёт нам удобство её обработки. Формы, как и многое другое во фреймворке, можно создавать через консоль. Название формы должно состоять из названия сущности и слова Type (т.е. PostType, UserType). Форму можно создать с помощью компонента maker следующей командой:
php bin/console make:form
Symfony попросит вас ввести имя сущности, с которой связана форма. Введите Post. Дальше у вас сгенерируется папка Form, где вы найдёте класс PostType. Перейдя в него, вы должны увидеть следующий код:
В методе configureOptions() Symfony указывает, какая сущность будет источником данных для нашей формы. На данный момент нас интересует метод buildForm, который, собственно, и строит нашу форму из полей нашей сущности. Мы смело можем удалить поля slug и created_at, поскольку они будут генерироваться автоматически. Кроме того, нам доступны некоторые настройки для нашей формы: так, например, метод в add() мы можем указать, какой тип данных будет принимать то или иное поле - TextType или TextareaType, а также установить label, если он нужен. Вот так будет выглядеть наш метод теперь:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'label' => ''
])
->add('body', TextareaType::class, [
'label' => ' '
])
;
}
Обратите внимание на то, что классы TextType и TextareaType должны быть загружены по следующим неймспейсам:
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
Вы могли заметить, что в нашей форме не хватает одной важной детали. Да, я говорю о кнопке. Добавлять её напрямую в билдер - плохая практика. Дело в том, что мы используем одну форму как для сохранения сущности, так и для её обновления, отсюда нам потребуется менять название кнопки, поэтому её мы будем добавлять непосредственно в вёрстку.
Итак, закончив, с созданием формы (да, на этом всё), используем же её для создания первой нашей публикации! Для этого перейдите в класс PostsController.php и создайте метод addPost(). Также не забудьте назначить маршрут нашему методу в аннотации Route и добавить имя.
Для начала нужно понять, где хранятся данные, передаваемые пользователем через форму. Как я уже упоминал в одной из предыдущих статьях, Symfony - HTTP фреймворк, данные он получает из класса Request и отдаёт в виде Response. Поэтому одним из параметров нашего метода будет объект $request, а вторым - знакомый нам класс Slugify. Прежде чем начнём писать наш метод, нам надо зарегистрировать класс Slugify как сервис в файле config/services.yaml. Вот как это будет выглядеть:
Строка autowire: true позволяет переложить на Symfony автоматическую загрузку нашего сервиса.
Кстати говоря, эту запись можно убрать
App\DataFixtures\AppFixtures:
$slugify: 'Cocur\Slugify\Slugify'
Теперь мы зарегистрировали класс как сервис, а не как аргумент другого класса, в результате чего он стал нам доступен глобально.
Итак, приступим к созданию нашего метода.
Сначала мы создаём объект нашей сущности, которую и будем сохранять.
$post = new Post();
Дальше мы создаём форму с помощью метода createForm, который обязательным параметром принимает наш класс PostType и объект класса Post.
$form = $this->createForm(PostType::class, $post);
Далее мы обрабатываем наш $request с помощью метода handleRequest, который стал нам доступен после создания инстанса формы.
$form->handleRequest($request);
Теперь мы проверяем, нажата ли кнопка под формой и является ли она валидной в соответствии с теми правилами валидации, которые мы применили к нашей сущности. Если оба этих условия выполняются, мы устанавливаем slug с помощью уже известного нам метода slugify, куда передаём title статьи. Время для поля created_at берём текущее, которое возвращает нам объект класса DateTime() по умолчанию.
if ($form->isSubmitted() && $form->isValid()) {
$post->setSlug($slugify->slugify($post->getTitle()));
$post->setCreatedAt(new \DateTime());
Вызываем наш Manager, подготавливаем (persist) и сохраняем пост (flush). После сохранения редиректим пользователя на страницу со всеми постами.
$em = $this->getDoctrine()->getManager();
$em->persist($post);
$em->flush();
return $this->redirectToRoute('blog_posts');
}
А сам метод addPosts() возвращает шаблон, куда в качестве аргумента передаём $form->createView(). Этот метод создаст саму форму в нашей вёрстке.
return $this->render('posts/new.html.twig', [
'form' => $form->createView()
]);
Вот как полностью выглядит наш метод:
/**
* @Route("/posts/new", name="new_blog_post")
*/
public function addPost(Request $request, Slugify $slugify)
{
$post = new Post();
$form = $this->createForm(PostType::class, $post);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$post->setSlug($slugify->slugify($post->getTitle()));
$post->setCreatedAt(new \DateTime());
$em = $this->getDoctrine()->getManager();
$em->persist($post);
$em->flush();
return $this->redirectToRoute('blog_posts');
}
return $this->render('posts/new.html.twig', [
'form' => $form->createView()
]);
}
Теперь создайте шаблон в папке templates/posts под именем new.html.twig. Всё, что он будет делать, это рендерить форму. Вот как будет выглядеть наш шаблон:
{% extends 'base.html.twig' %}
{% block body %}
{{ form_start(form) }}
{{ form_row(form.title) }}
{{ form_row(form.body, { 'attr': {
'rows' : '10',
'cols' : '10' }}) }}
<button class="btn btn-default" type="submit">Сохранить</button>
{{ form_end(form) }}
{% endblock %}
Twig позволяет по-разному рендерить форму: можно сразу вывести всю форму с помощью такой записи {{ form(form) }}, однако в этом случае будет сложнее управлять каждым полем отдельно - его стилями или названием. Поэтому мы используем теги form_start и form_end, между которыми выводим каждое поле в отдельности. Записи form.title и form.body соответствует реальным полям в нашей таблице и в свойствах нашей сущности. Также мы добавляем rows и cols для form.body (вы же помните, как мы указали, что это TextareaType?), чтобы форма не выглядела маленькой. До тегов form_end вы должны не забыть добавить кнопку. Теперь наша форма и метод-handler, которые её обрабатывает, готовы. Вы можете перейти по указанному адресу и попробовать написать и сохранить первую запись.
Ну что же, на этом уроке мы освоили очередной важный компонент фреймворка - формы. На следующем уроке мы напишем первое CRUD приложение, в результате чего вы научитесь создавать, редактировать и удалять записи.
Комментарии