Skip to content

Commit 17bf17b

Browse files
committed
bug #1062 Make blog post titles unique (javiereguiluz, Javier Eguiluz)
This PR was merged into the master branch. Discussion ---------- Make blog post titles unique I'm trying to fix #1045. We should make the `slug` unique because it's what we use to look for blog posts in the database. However, I can't make this work. If I define `UniqueEntity` on `title`, it works ... but if I define it on `slug`, it doesn't work (I can create any number of posts with the same slug). Anybody sees the error? Thanks! Commits ------- 178e2b1 Added a functional test 7486dc8 Added a form event to set the slug a4a316a Make blog post titles unique
2 parents e01aebb + 178e2b1 commit 17bf17b

File tree

4 files changed

+50
-6
lines changed

4 files changed

+50
-6
lines changed

src/Controller/Admin/BlogController.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
use Symfony\Component\HttpFoundation\Request;
2222
use Symfony\Component\HttpFoundation\Response;
2323
use Symfony\Component\Routing\Annotation\Route;
24-
use Symfony\Component\String\Slugger\SluggerInterface;
2524

2625
/**
2726
* Controller used to manage blog contents in the backend.
@@ -70,7 +69,7 @@ public function index(PostRepository $posts): Response
7069
* to constraint the HTTP methods each controller responds to (by default
7170
* it responds to all methods).
7271
*/
73-
public function new(Request $request, SluggerInterface $slugger): Response
72+
public function new(Request $request): Response
7473
{
7574
$post = new Post();
7675
$post->setAuthor($this->getUser());
@@ -86,8 +85,6 @@ public function new(Request $request, SluggerInterface $slugger): Response
8685
// However, we explicitly add it to improve code readability.
8786
// See https://symfony.com/doc/current/forms.html#processing-forms
8887
if ($form->isSubmitted() && $form->isValid()) {
89-
$post->setSlug($slugger->slug($post->getTitle())->lower());
90-
9188
$em = $this->getDoctrine()->getManager();
9289
$em->persist($post);
9390
$em->flush();
@@ -133,13 +130,12 @@ public function show(Post $post): Response
133130
* @Route("/{id<\d+>}/edit", methods="GET|POST", name="admin_post_edit")
134131
* @IsGranted("edit", subject="post", message="Posts can only be edited by their authors.")
135132
*/
136-
public function edit(Request $request, Post $post, SluggerInterface $slugger): Response
133+
public function edit(Request $request, Post $post): Response
137134
{
138135
$form = $this->createForm(PostType::class, $post);
139136
$form->handleRequest($request);
140137

141138
if ($form->isSubmitted() && $form->isValid()) {
142-
$post->setSlug($slugger->slug($post->getTitle())->lower());
143139
$this->getDoctrine()->getManager()->flush();
144140

145141
$this->addFlash('success', 'post.updated_successfully');

src/Entity/Post.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
use Doctrine\Common\Collections\ArrayCollection;
1515
use Doctrine\Common\Collections\Collection;
1616
use Doctrine\ORM\Mapping as ORM;
17+
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
1718
use Symfony\Component\Validator\Constraints as Assert;
1819

1920
/**
2021
* @ORM\Entity(repositoryClass="App\Repository\PostRepository")
2122
* @ORM\Table(name="symfony_demo_post")
23+
* @UniqueEntity(fields={"slug"}, errorPath="title", message="This title was already used in another blog post, but they must be unique.")
2224
*
2325
* Defines the properties of the Post entity to represent the blog posts.
2426
*

src/Form/PostType.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
use Symfony\Component\Form\AbstractType;
1818
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
1919
use Symfony\Component\Form\FormBuilderInterface;
20+
use Symfony\Component\Form\FormEvent;
21+
use Symfony\Component\Form\FormEvents;
2022
use Symfony\Component\OptionsResolver\OptionsResolver;
23+
use Symfony\Component\String\Slugger\SluggerInterface;
2124

2225
/**
2326
* Defines the form used to create and manipulate blog posts.
@@ -28,6 +31,14 @@
2831
*/
2932
class PostType extends AbstractType
3033
{
34+
private $slugger;
35+
36+
// Form types are services, so you can inject other services in them if needed
37+
public function __construct(SluggerInterface $slugger)
38+
{
39+
$this->slugger = $slugger;
40+
}
41+
3142
/**
3243
* {@inheritdoc}
3344
*/
@@ -64,6 +75,16 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
6475
'label' => 'label.tags',
6576
'required' => false,
6677
])
78+
// form events let you modify information or fields at different steps
79+
// of the form handling process.
80+
// See https://symfony.com/doc/current/form/events.html
81+
->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) {
82+
/** @var Post */
83+
$post = $event->getData();
84+
if (null !== $postTitle = $post->getTitle()) {
85+
$post->setSlug($this->slugger->slug($postTitle)->lower());
86+
}
87+
})
6788
;
6889
}
6990

tests/Controller/Admin/BlogControllerTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,31 @@ public function testAdminNewPost()
105105
$this->assertSame($postContent, $post->getContent());
106106
}
107107

108+
public function testAdminNewDuplicatedPost()
109+
{
110+
$postTitle = 'Blog Post Title '.mt_rand();
111+
$postSummary = $this->generateRandomString(255);
112+
$postContent = $this->generateRandomString(1024);
113+
114+
$client = static::createClient([], [
115+
'PHP_AUTH_USER' => 'jane_admin',
116+
'PHP_AUTH_PW' => 'kitten',
117+
]);
118+
$crawler = $client->request('GET', '/en/admin/post/new');
119+
$form = $crawler->selectButton('Create post')->form([
120+
'post[title]' => $postTitle,
121+
'post[summary]' => $postSummary,
122+
'post[content]' => $postContent,
123+
]);
124+
$client->submit($form);
125+
126+
// post titles must be unique, so trying to create the same post twice should result in an error
127+
$client->submit($form);
128+
129+
$this->assertSelectorTextSame('form .form-group.has-error label', 'Title');
130+
$this->assertSelectorTextContains('form .form-group.has-error .help-block', 'This title was already used in another blog post, but they must be unique.');
131+
}
132+
108133
public function testAdminShowPost()
109134
{
110135
$client = static::createClient([], [

0 commit comments

Comments
 (0)