Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ a release.

## [Unreleased]

### Changed
- Sluggable: Replaced abandoned `behat/transliterator` with `symfony/string` for default transliteration and urlization steps (#2985)

## [3.20.1] - 2025-09-14
### Fixed
- Compatibility with `doctrine/mongodb-odm` ^2.11 (#2945)
Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,17 @@
},
"require": {
"php": "^7.4 || ^8.0",
"behat/transliterator": "^1.2",
"doctrine/collections": "^1.2 || ^2.0",
"doctrine/deprecations": "^1.0",
"doctrine/event-manager": "^1.2 || ^2.0",
"doctrine/persistence": "^2.2 || ^3.0 || ^4.0",
"psr/cache": "^1 || ^2 || ^3",
"psr/clock": "^1",
"symfony/cache": "^5.4 || ^6.0 || ^7.0"
"symfony/cache": "^5.4 || ^6.0 || ^7.0",
"symfony/string": "^5.4 || ^6.0 || ^7.0"
},
"require-dev": {
"behat/transliterator": "^1.2",
"doctrine/annotations": "^1.13 || ^2.0",
"doctrine/cache": "^1.11 || ^2.0",
"doctrine/common": "^2.13 || ^3.0",
Expand All @@ -72,6 +73,7 @@
"symfony/yaml": "^5.4 || ^6.0 || ^7.0"
},
"conflict": {
"behat/transliterator": "<1.2 || >=2.0",
"doctrine/annotations": "<1.13 || >=3.0",
"doctrine/common": "<2.13 || >=4.0",
"doctrine/dbal": "<3.7 || >=5.0",
Expand Down
8 changes: 0 additions & 8 deletions src/Sluggable/Handler/InversedRelativeSlugHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,6 @@ class InversedRelativeSlugHandler implements SlugHandlerInterface
*/
protected $sluggable;

/**
* $options = array(
* 'relationClass' => 'objectclass',
* 'inverseSlugField' => 'slug',
* 'mappedBy' => 'relationField'
* )
* {@inheritdoc}
*/
public function __construct(SluggableListener $sluggable)
{
$this->sluggable = $sluggable;
Expand Down
20 changes: 8 additions & 12 deletions src/Sluggable/Handler/RelativeSlugHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,15 @@ class RelativeSlugHandler implements SlugHandlerInterface
*
* @var array<string, mixed>
*/
private $usedOptions;
private array $usedOptions = [];

/**
* Callable of original transliterator
* which is used by sluggable
* Callable of original transliterator which is used by the sluggable listener.
*
* @var callable
* @var callable(string, string, object): string
*/
private $originalTransliterator;

/**
* $options = array(
* 'separator' => '/',
* 'relationField' => 'something',
* 'relationSlugField' => 'slug'
* )
* {@inheritdoc}
*/
public function __construct(SluggableListener $sluggable)
{
$this->sluggable = $sluggable;
Expand Down Expand Up @@ -120,6 +111,10 @@ public function transliterate($text, $separator, $object)
$this->originalTransliterator,
[$text, $separator, $object]
);
$result = call_user_func_array(
$this->sluggable->getUrlizer(),
[$result, $separator, $object]
);
$wrapped = AbstractWrapper::wrap($object, $this->om);
$relation = $wrapped->getPropertyValue($this->usedOptions['relationField']);
if ($relation) {
Expand All @@ -135,6 +130,7 @@ public function transliterate($text, $separator, $object)

$result = $slug.$this->usedOptions['separator'].$result;
}

$this->sluggable->setTransliterator($this->originalTransliterator);

return $result;
Expand Down
30 changes: 8 additions & 22 deletions src/Sluggable/Handler/TreeSlugHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use Gedmo\Sluggable\SluggableListener;
use Gedmo\Tool\Wrapper\AbstractWrapper;

use function Symfony\Component\String\u;

/**
* Sluggable handler which slugs all parent nodes
* recursively and synchronizes on updates. For instance
Expand All @@ -40,36 +42,24 @@ class TreeSlugHandler implements SlugHandlerWithUniqueCallbackInterface
*/
protected $sluggable;

/**
* @var string
*/
private $prefix;
private string $prefix = '';

/**
* @var string
*/
private $suffix;
private string $suffix = '';

/**
* True if node is being inserted
*
* @var bool
*/
private $isInsert = false;
private bool $isInsert = false;

/**
* Transliterated parent slug
*
* @var string
*/
private $parentSlug;
private string $parentSlug = '';

/**
* Used path separator
*
* @var string
*/
private $usedPathSeparator;
private string $usedPathSeparator = self::SEPARATOR;

public function __construct(SluggableListener $sluggable)
{
Expand Down Expand Up @@ -106,11 +96,7 @@ public function postSlugBuild(SluggableAdapter $ea, array &$config, $object, &$s

// if needed, remove suffix from parentSlug, so we can use it to prepend it to our slug
if (isset($options['suffix'])) {
$suffix = $options['suffix'];

if (substr($this->parentSlug, -strlen($suffix)) === $suffix) { // endsWith
$this->parentSlug = substr_replace($this->parentSlug, '', -1 * strlen($suffix));
}
$this->parentSlug = u($this->parentSlug)->trimSuffix($options['suffix'])->toString();
}
}
}
Expand Down
57 changes: 37 additions & 20 deletions src/Sluggable/SluggableListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
use Gedmo\Sluggable\Handler\SlugHandlerInterface;
use Gedmo\Sluggable\Handler\SlugHandlerWithUniqueCallbackInterface;
use Gedmo\Sluggable\Mapping\Event\SluggableAdapter;
use Gedmo\Sluggable\Util\Urlizer;
use Symfony\Component\String\Slugger\AsciiSlugger;

use function Symfony\Component\String\u;

/**
* The SluggableListener handles the generation of slugs
Expand Down Expand Up @@ -60,6 +62,7 @@
* relationField?: string,
* relationSlugField?: string,
* separator?: string,
* urilize?: bool,
* }>,
* uniqueOverTranslations: bool,
* useObjectClass?: class-string,
Expand All @@ -78,20 +81,16 @@ class SluggableListener extends MappedEventSubscriber
/**
* Transliteration callback for slugs
*
* @var callable
*
* @phpstan-var callable(string $text, string $separator, object $object): string
* @var callable(string, string, object): string
*/
private $transliterator = [Urlizer::class, 'transliterate'];
private $transliterator;

/**
* Urlize callback for slugs
*
* @var callable
*
* @phpstan-var callable(string $text, string $separator, object $object): string
* @var callable(string, string, object): string
*/
private $urlizer = [Urlizer::class, 'urlize'];
private $urlizer;

/**
* List of inserted slugs for each object class.
Expand Down Expand Up @@ -121,6 +120,28 @@ class SluggableListener extends MappedEventSubscriber
*/
private array $managedFilters = [];

public function __construct()
{
parent::__construct();

$this->setTransliterator(
static fn (string $text, string $separator, object $object): string => u($text)->ascii()->toString()
);

/*
* Note - Requiring the call to `lower()` in this chain contradicts with the `style` configuration
* which doesn't require or enforce lowercase styling by default, but the Behat transliterator applied
* this styling so it is used for B/C
*/

$this->setUrlizer(
static fn (string $text, string $separator, object $object): string => (new AsciiSlugger())
->slug($text, $separator)
->lower()
->toString()
);
}

/**
* Specifies the list of events to listen
*
Expand Down Expand Up @@ -412,25 +433,21 @@ private function generateSlug(SluggableAdapter $ea, object $object): void
switch ($options['style']) {
case 'camel':
$quotedSeparator = preg_quote($options['separator']);
$slug = preg_replace_callback('/^[a-z]|'.$quotedSeparator.'[a-z]/smi', static fn ($m) => strtoupper($m[0]), $slug);
$slug = preg_replace_callback(
'/^[a-z]|'.$quotedSeparator.'[a-z]/smi',
static fn (array $m): string => u($m[0])->upper()->toString(),
$slug
);

break;

case 'lower':
if (function_exists('mb_strtolower')) {
$slug = mb_strtolower($slug);
} else {
$slug = strtolower($slug);
}
$slug = u($slug)->lower()->toString();

break;

case 'upper':
if (function_exists('mb_strtoupper')) {
$slug = mb_strtoupper($slug);
} else {
$slug = strtoupper($slug);
}
$slug = u($slug)->upper()->toString();

break;

Expand Down
7 changes: 7 additions & 0 deletions src/Sluggable/Util/Urlizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@
namespace Gedmo\Sluggable\Util;

use Behat\Transliterator\Transliterator;
use Gedmo\Exception\RuntimeException;

if (!class_exists(Transliterator::class)) {
throw new RuntimeException(sprintf('Cannot use the "%s" class when the "behat/transliterator" package is not installed.', Urlizer::class));
}

/**
* Transliteration utility
*
* @deprecated since gedmo/doctrine-extensions 3.21, will be removed in version 4.0.
*
* @final since gedmo/doctrine-extensions 3.11
*/
class Urlizer extends Transliterator
Expand Down
24 changes: 6 additions & 18 deletions tests/Gedmo/Sluggable/Fixture/Handler/People/Occupation.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,14 @@
class Occupation
{
/**
* @var int|null
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: Types::INTEGER)]
private $id;
private ?int $id = null;

/**
* @ORM\Column(length=64)
Expand All @@ -48,8 +46,6 @@ class Occupation
private ?string $title = null;

/**
* @var string|null
*
* @Gedmo\Slug(handlers={
* @Gedmo\SlugHandler(class="Gedmo\Sluggable\Handler\TreeSlugHandler", options={
* @Gedmo\SlugHandlerOption(name="parentRelationField", value="parent"),
Expand All @@ -68,7 +64,7 @@ class Occupation
#[Gedmo\SlugHandler(class: TreeSlugHandler::class, options: ['parentRelationField' => 'parent', 'separator' => '/'])]
#[Gedmo\SlugHandler(class: InversedRelativeSlugHandler::class, options: ['relationClass' => Person::class, 'mappedBy' => 'occupation', 'inverseSlugField' => 'slug'])]
#[ORM\Column(length: 64, unique: true)]
private $slug;
private ?string $slug = null;

/**
* @Gedmo\TreeParent
Expand All @@ -87,48 +83,40 @@ class Occupation
private Collection $children;

/**
* @var int|null
*
* @Gedmo\TreeLeft
*
* @ORM\Column(type="integer")
*/
#[ORM\Column(type: Types::INTEGER)]
#[Gedmo\TreeLeft]
private $lft;
private ?int $lft = null;

/**
* @var int|null
*
* @Gedmo\TreeRight
*
* @ORM\Column(type="integer")
*/
#[ORM\Column(type: Types::INTEGER)]
#[Gedmo\TreeRight]
private $rgt;
private ?int $rgt = null;

/**
* @var int|null
*
* @Gedmo\TreeRoot
*
* @ORM\Column(type="integer")
*/
#[ORM\Column(type: Types::INTEGER)]
#[Gedmo\TreeRoot]
private $root;
private ?int $root = null;

/**
* @var int|null
*
* @Gedmo\TreeLevel
*
* @ORM\Column(name="lvl", type="integer")
*/
#[ORM\Column(name: 'lvl', type: Types::INTEGER)]
#[Gedmo\TreeLevel]
private $level;
private ?int $level = null;

public function __construct()
{
Expand Down
Loading