Skip to content
Closed
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
- Replace the `behat/transliterator` with `symfony/string` for the Sluggable extension for its default transliteration and urlization steps"

## [3.20.0] - 2025-04-04
### Fixed
- SoftDeleteable: Resolved a bug where a soft-deleted object isn't remove from the ObjectManager (#2930)
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]
);
Comment on lines +114 to +117
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling the urlizer here is the only way I could work out fixing this test failure:

1) Gedmo\Tests\Sluggable\Handlers\BothSlugHandlerTest::testSlugUpdates
   Failed asserting that two strings are identical.
   --- Expected
   +++ Actual
   @@ @@
   -'web/developer/upd-gedi'
   +'web/developer/upd gedi'

/tests/Gedmo/Sluggable/Handlers/BothSlugHandlerTest.php:64

$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
Loading