Создаем новую колонку slug в требуемой сущности, в моем случае это Post
#[ORM\Column(length: 255, unique: true)] private ?string $slug = null;
Генерируем миграцию командой (в моем случае приложение развернуто в Docker):
docker compose run --rm php-cli php bin/console make:migration
Новое поле slug не предполагает пустого значения, поэтому потребуется слегка изменить миграцию
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Добавляем колонку SLUG.
*/
final class Version20260708100240 extends AbstractMigration
{
public function getDescription(): string
{
return 'Добавляем колонку SLUG';
}
public function up(Schema $schema): void
{
// Создаем колонку с возможным значением NULL
$this->addSql('ALTER TABLE post ADD slug VARCHAR(255)');
// Заполняем для всех уже существующих статей
$this->addSql('UPDATE post SET slug = concat(\'article-\', id)');
// Убираем возможность значения NULL в колонке slug
$this->addSql('ALTER TABLE post ALTER COLUMN slug SET NOT NULL');
// Добавляем уникальный индекс на эту колонку
$this->addSql('CREATE UNIQUE INDEX UNIQ_5A8A6C8D989D9B62 ON post (slug)');
}
public function down(Schema $schema): void
{
$this->addSql('DROP INDEX UNIQ_5A8A6C8D989D9B62');
$this->addSql('ALTER TABLE post DROP slug');
}
}
После того как мы выполним миграцию, устанавливаем пакет Symfony для работы с строками, командой
docker compose run --rm php-cli composer require symfony/string
Добавляем нового слушателя событий и привязываем его к событиям Doctrine для сущности Post
<?php
namespace App\EventListener;
use App\Entity\Post;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
use Doctrine\ORM\Events;
use Symfony\Component\String\Slugger\SluggerInterface;
#[AsEntityListener(event: Events::prePersist, entity: Post::class)]
#[AsEntityListener(event: Events::preUpdate, entity: Post::class)]
class PostEntityListener
{
public function __construct(
private SluggerInterface $slugger,
) {
}
public function prePersist(Post $post): void
{
$post->computeSlug($this->slugger);
}
public function preUpdate(Post $post): void
{
$post->computeSlug($this->slugger);
}
}
В модель Post добавляем метод для вычисления slug
public function computeSlug(SluggerInterface $slugger): void
{
/** @var non-empty-string $title */
$title = $this->getTitle();
$this->slug = $slugger->slug($title)->lower()->toString();
}
Теперь при создании и изменении статьи, slug автоматически будет генерироваться по title. Учитывайте, что события preUpdate не сработает, если Вы ничего не поменяли в модели.
После не забудьте поменять роут в контроллере
#[Route('/post/{slug:post}', name: 'post')]
Теперь в шаблонах twig мы може генерировать ссылки на посты вот таким путем
{{ path('post', { slug: post.slug }) }}
1 Сообщений
nebster 08.07.2026 16:11
Проверка связи
Ответить