Last active
July 11, 2024 09:58
-
-
Save Nek-/918835e0a8d5f816c9647cff2822dddf to your computer and use it in GitHub Desktop.
Slugify using Symfony 7+ and Doctrine 3+
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Example | |
{ | |
#[ORM\Column(length: 255)] | |
private string $name; | |
#[ORM\Column(length: 255, unique: true)] | |
#[Slug(targetProperty: 'name', unique: true)] | |
private string $slug; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\Tooling\Doctrine\Slug; | |
#[\Attribute(\Attribute::TARGET_PROPERTY)] | |
class Slug | |
{ | |
public function __construct( | |
public string $targetProperty, | |
public bool $unique = true, | |
public bool $updateOnChange = false | |
) { | |
if (true === $this->updateOnChange) { | |
throw new \Exception('This behavior is not supported yet, please change the SlugifyListener to add its support.'); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\Tooling\Doctrine\Slug; | |
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener; | |
use Doctrine\ORM\Event\PrePersistEventArgs; | |
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; | |
use Symfony\Component\String\Slugger\AsciiSlugger; | |
#[AsDoctrineListener('prePersist')] | |
class SlugifyListener | |
{ | |
public function __construct(private PropertyAccessorInterface $accessor) | |
{ | |
} | |
public function prePersist(PrePersistEventArgs $args): void | |
{ | |
$entity = $args->getObject(); | |
$reflection = new \ReflectionObject($entity); | |
$entityManager = $args->getObjectManager(); | |
foreach ($reflection->getProperties() as $property) { | |
/** @var \ReflectionAttribute<Slug>[] $attributes */ | |
$attributes = $property->getAttributes(Slug::class); | |
if (empty($attributes)) { | |
continue; | |
} | |
/** @var Slug $slugAttribute */ | |
$slugAttribute = $attributes[0]->newInstance(); | |
$slug = $this->slugify( | |
$entity, | |
$property, | |
$slugAttribute | |
); | |
if (empty($slug)) { | |
return; | |
} | |
if ($slugAttribute->unique) { | |
$metadata = $entityManager->getClassMetadata($entity::class); | |
$table = $metadata->getTableName(); | |
$sqlField = $metadata->getColumnName($property->getName()); | |
$connection = $entityManager->getConnection(); | |
$resultSet = $connection->executeQuery(<<<SQL | |
SELECT $sqlField FROM $table WHERE $sqlField LIKE '$slug%' | |
SQL)->fetchAllAssociative(); | |
if (!empty($resultSet)) { | |
$slug = $this->incrementSlug(array_map(fn ($item) => $item['slug'], $resultSet), $slug); | |
$this->accessor->setValue($entity, $property->getName(), $slug); | |
} | |
} | |
} | |
} | |
/** | |
* @param array<string, string>|null $changeSet | |
*/ | |
private function slugify(object $entity, \ReflectionProperty $property, Slug $slugAttribute, ?array $changeSet = null, bool $isUpdate = false): ?string | |
{ | |
if (null !== $changeSet && !\array_key_exists($slugAttribute->targetProperty, $changeSet)) { | |
return null; | |
} | |
if ($isUpdate && $slugAttribute->updateOnChange) { | |
return $this->accessor->getValue($entity, $property->getName()); | |
} | |
$originalValue = $this->accessor->getValue($entity, $slugAttribute->targetProperty); | |
$slug = (new AsciiSlugger())->slug($originalValue)->toString(); | |
$this->accessor->setValue($entity, $property->getName(), $slug); | |
return $slug; | |
} | |
/** | |
* @param array<int, string> $slugsInDatabase | |
*/ | |
private function incrementSlug(array $slugsInDatabase, string $slug): string | |
{ | |
if (1 === \count($slugsInDatabase)) { | |
return $slug . '-1'; | |
} | |
if (1 !== preg_match('/-(\d+)$/', $slugsInDatabase[0], $matches)) { | |
return $slug . '-1'; | |
} | |
$i = ((int) $matches[1]) + 1; | |
return $slug . '-' . $i; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment