Полноценный CRUD в Symfony
На прошлых уроках мы научились создавать сущности, контроллеры, отображения, формы. Познакомились с репозиториями и с тем, как выводить все данные и одну конкретную запись. Настало время объединить эти знания и написать первое CRUD приложение. Другими словами, мы научимся создавать, удалять, редактировать и просматривать одну запись.
Создавать и отображать мы уже умеем, но давайте пройдёмся по нашему контроллеру ещё раз.
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Entity\Post;
use App\Form\PostType;
use App\Repository\PostRepository;
use Cocur\Slugify\Slugify;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class PostsController extends AbstractController
{
/** @var PostRepository $postRepository */
private $postRepository;
public function __construct(PostRepository $postRepository)
{
$this->postRepository = $postRepository;
}
Инжектим наш репозиторий, который нам нужен для чтения данных, в конструктор. Ещё раз напоминаю вам, что репозиторий в Symfony работает с конкретной сущностью, позволяя нам выполнять различные выборки.
Вывод всех данных
Как выводить все данные - вы уже знаете. Если нет, напоминаю вам, как выглядит код:
/**
* @Route("/posts", name="blog_posts")
*/
public function posts()
{
$posts = $this->postRepository->findAll();
return $this->render('posts/index.html.twig', [
'posts' => $posts
]);
}
Методы findAll, find, findBy и другие довольно часто используются разработчиками, поэтому предоставляются фреймворком по умолчанию (мы ещё научимся с вами писать собственные запросы). Вы обращаетесь к репозиторию и просите его достать все данные, которые потом передаёте в шаблон.
Вывод одной записи
/**
* @Route("/posts/{slug}", name="blog_show")
*/
public function show(Post $post)
{
return $this->render('posts/show.html.twig', [
'post' => $post
]);
}
Тут Symfony сравнивает {slug} со свойством slug в сущности Post. Если есть - возвращает нам данные. Теперь важное: этот метод вы должны написать ниже всех остальных! Мы уже создали метод для создания записи, роутинг которого выглядит следующим образом:
/**
* @Route("/posts/new", name="new_blog_post")
*/
Когда вы переходите по какому-то маршруту на сайте, Symfony начинает искать совпадение сверху вниз. Вы переходите по маршруту /posts/new, чтобы создать новую запись, но метод show() у нас находится вторым по счёту и его роутинг выглядит так:
/**
* @Route("/posts/{slug}", name="blog_show")
*/
Как вы думаете, что произойдёт, когда вы перейдёте по маршруту /posts/new? Правильно, Symfony начнёт искать его среди slug'ов нашей сущности и, не найдя совпадения, выкинет ошибку. Почему именно по slug? Потому что вы поставили этот action вторым по счёту, и Symfony именно с ним и найдёт совпадение, так как слово new ничем не отличается от обычного slug. Поэтому метод show() вы должны поставить самым последним в нашем контроллере.
Создание записи
Мы буквально на прошлом уроке научились создавать запись и научились работать с формами, но я ещё раз напомню вам код нашего метода:
/**
* @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()
]);
}
Как вы помните из основ ООП, если наш метод от чего-то зависит, это можно передать или в качестве его аргументов, или в конструктор класса. Поскольку классы Request и Slugify нам нужны не во всех методах нашего класса, нет смысла перегружать конструктор ими. Собственно, что мы делаем: создаём объект класса Post, форму на основе его полей. Дальше мы проверяем, отправлена ли форма и валидна ли она. Если всё это выполняется, мы производим некоторые действия над созданием slug'а и текущего времени, потом подготавливаем данные и сохраняем. Делаем редирект на роут со всеми постами и рендерим нашу форму.
Редактирование записи
Метод edit(), который мы сейчас создадим, будет выглядеть несколько иначе, но в целом принцип ничем не отличается от создания.
/**
* @Route("/posts/{slug}/edit", name="blog_post_edit")
*/
public function edit(Post $post, Request $request, Slugify $slugify)
{
$form = $this->createForm(PostType::class, $post);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$post->setSlug($slugify->slugify($post->getTitle()));
$this->em->flush();
return $this->redirectToRoute('blog_show', [
'slug' => $post->getSlug()
]);
}
return $this->render('posts/new.html.twig', [
'form' => $form->createView()
]);
}
Начнём с первого - роутинга. Да, именно так нужно делать правильные ендпоинты: действие, которое мы выполняем в данный момент - удаление или редактирование - должно стоять в конце.
/posts/{slug}/edit
/posts/{slug}/delete
Объект Post нам создавать не нужно, поскольку он уже есть в форме. Мы просто создаём новый slug, если он изменился, и сохраняем данные. Поскольку мы обновляем данные, а не сохраняем их в первый раз, делать persist() нам не нужно. Обратите внимание, что мы делаем дальше, мы редиректим на роут blog_show с параметрами slug. То есть просматривая запись и решив её отредактировать, вы возвращаетесь туда же, но уже по новому slug. В конце мы рендерим ту же форму, которую использовали для создания. Вы вольны создать другую.
Также не забудьте в шаблоне, где вы отображаете данные (т.е., например, в show.html.twig), сделать ссылку на редактирование:
<a href="{{ path('blog_post_edit', {'slug': post.slug}) }}">Редактировать</a>
Удаление данных
В менеджере Doctrine уже есть готовые методы по удалению данных. Нам нужно всего лишь ими воспользоваться следующим образом:
/**
* @Route("/posts/{slug}/delete", name="blog_post_delete")
*/
public function delete(Post $post)
{
$em = $this->getDoctrine()->getManager();
$em->remove($post);
$em->flush();
return $this->redirectToRoute('blog_posts');
}
Опять передаём в качестве аргументов наш объект Post. В остальном здесь всё понятно: удаляем и делаем редирект на страницу всех постов. Также не забудьте сделать кнопку, аналогичную редактированию:
<a href="{{ path('blog_post_delete', {'slug': post.slug}) }}">Удалить</a>
На это всё, мы сделали CRUD. Но какое приложение существует без стилей? Наверняка вы уже втайне подключили bootstrap к своему проекту. В следующем уроке мы познакомимся с таким инструментом как вебпак.
Комментарии