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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ permissions:

jobs:
testsuite:
uses: ADmad/.github/.github/workflows/testsuite-with-db.yml@master
uses: ADmad/.github/.github/workflows/testsuite-with-db.yml@cake6
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

cs-stan:
uses: ADmad/.github/.github/workflows/cs-stan.yml@master
uses: ADmad/.github/.github/workflows/cs-stan.yml@cake6
secrets: inherit
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Slug comes with the following configuration options:
generating the slug.
- `maxLength`: maximum length of a slug. Defaults to the field's limit as
defined in the schema (when possible). Otherwise, no limit.
- `slugger`: class that implements the `Muffin\Slug\SlugInterface`. Defaults
- `slugger`: class that implements the `Muffin\Slug\SluggerInterface`. Defaults
to `Muffin\Slug\Slugger\CakeSlugger`.
- `unique:`: tells if slugs should be unique. Set this to a callable if you
want to customize how unique slugs are generated. Defaults to `true`.
Expand Down
13 changes: 8 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@
"source": "https://github.com/usemuffin/slug"
},
"require": {
"cakephp/orm": "^5.0"
"php": ">=8.5",
"cakephp/orm": "^6.0@dev"
},
"require-dev": {
"cakephp/cakephp": "^5.0",
"phpunit/phpunit": "^10.1 || ^11.1",
"cocur/slugify": "^4.3"
"cakephp/cakephp": "^6.0@dev",
"cocur/slugify": "^4.3",
"phpunit/phpunit": "^13.0"
},
"autoload": {
"psr-4": {
Expand All @@ -53,5 +54,7 @@
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
7 changes: 7 additions & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<ruleset name="Slug">
<rule ref="CakePHP"/>

<file>src/</file>
<file>tests/</file>
</ruleset>
5 changes: 1 addition & 4 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
parameters:
level: 7
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
treatPhpDocTypesAsCertain: false
level: 8
bootstrapFiles:
- tests/bootstrap.php
paths:
Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" processIsolation="false" stopOnFailure="false" bootstrap="./tests/bootstrap.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" cacheDirectory=".phpunit.cache">
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" processIsolation="false" stopOnFailure="false" bootstrap="./tests/bootstrap.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/13.0/phpunit.xsd" cacheDirectory=".phpunit.cache">
<testsuites>
<testsuite name="Slug TestSuite">
<directory>./tests/TestCase</directory>
Expand Down
12 changes: 5 additions & 7 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@
</projectFiles>

<issueHandlers>
<MissingClosureReturnType errorLevel="info" />

<PropertyNotSetInConstructor errorLevel="info" />
<MissingClosureParamType errorLevel="info" />

<DocblockTypeContradiction errorLevel="info" />
<RedundantConditionGivenDocblockType errorLevel="info" />
<MissingAbstractPureAnnotation errorLevel="info" />
<MissingInterfaceImmutableAnnotation errorLevel="info" />
<MissingOverrideAttribute errorLevel="info" />
<MissingPureAnnotation errorLevel="info" />
<ClassMustBeFinal errorLevel="info" />
</issueHandlers>
</psalm>
81 changes: 41 additions & 40 deletions src/Model/Behavior/SlugBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class SlugBehavior extends Behavior
* generating the slug.
* - maxLength: Maximum length of a slug. Defaults to the field's limit as
* defined in the schema (when possible). Otherwise, no limit.
* - slugger: Class that implements the `Muffin\Slug\SlugInterface`.
* - slugger: Class that implements the `Muffin\Slug\SluggerInterface`.
* Can either be a class name or instance. Defaults to
* `Muffin\Slug\Slugger\CakeSlugger`.
* - unique: Tells if slugs should be unique. Set this to a Closure if you
Expand All @@ -52,7 +52,7 @@ class SlugBehavior extends Behavior
*
* @var array<string, mixed>
*/
protected array $_defaultConfig = [
protected array $defaultConfig = [
'field' => 'slug',
'displayField' => null,
'separator' => '-',
Expand All @@ -75,9 +75,6 @@ class SlugBehavior extends Behavior
'implementedFinders' => [
'slugged' => 'findSlugged',
],
'implementedMethods' => [
'slug' => 'slug',
],
'onUpdate' => false,
'onDirty' => false,
];
Expand All @@ -87,18 +84,18 @@ class SlugBehavior extends Behavior
*
* @var \Muffin\Slug\SluggerInterface|null
*/
protected ?SluggerInterface $_slugger = null;
protected ?SluggerInterface $slugger = null;

/**
* Constructor.
*
* @param \Cake\ORM\Table $table The table this behavior is attached to.
* @param array $config The config for this behavior.
* @param array<string, mixed> $config The config for this behavior.
*/
public function __construct(Table $table, array $config = [])
{
if (!empty($config['implementedEvents'])) {
$this->_defaultConfig['implementedEvents'] = $config['implementedEvents'];
$this->defaultConfig['implementedEvents'] = $config['implementedEvents'];
unset($config['implementedEvents']);
}
parent::__construct($table, $config);
Expand All @@ -113,19 +110,20 @@ public function __construct(Table $table, array $config = [])
public function initialize(array $config): void
{
if (!$this->getConfig('displayField')) {
$this->setConfig('displayField', $this->_table->getDisplayField());
$this->setConfig('displayField', $this->table->getDisplayField());
}

if ($this->getConfig('maxLength') === null) {
/** @psalm-suppress PossiblyNullArrayAccess */
$this->setConfig(
'maxLength',
$this->_table->getSchema()->getColumn($this->getConfig('field'))['length']
// @phpstan-ignore offsetAccess.notFound
$this->table->getSchema()->getColumn($this->getConfig('field'))['length'],
);
}

if ($this->getConfig('unique') === true) {
$this->setConfig('unique', $this->_uniqueSlug(...));
$this->setConfig('unique', $this->uniqueSlug(...));
}
}

Expand All @@ -136,29 +134,29 @@ public function initialize(array $config): void
*/
public function getSlugger(): SluggerInterface
{
return $this->_slugger ??= $this->_createSlugger($this->getConfig('slugger'));
return $this->slugger ??= $this->createSlugger($this->getConfig('slugger'));
}

/**
* Set slugger instance.
*
* @param \Muffin\Slug\SluggerInterface|array|class-string<\Muffin\Slug\SluggerInterface> $slugger Sets slugger instance.
* @param \Muffin\Slug\SluggerInterface|array<string, mixed>|class-string<\Muffin\Slug\SluggerInterface> $slugger Sets slugger instance.
* Can be SluggerInterface instance or class name or config array.
* @return void
*/
public function setSlugger(SluggerInterface|array|string $slugger): void
{
$this->_slugger = $this->_createSlugger($slugger);
$this->slugger = $this->createSlugger($slugger);
}

/**
* Create slugger instance
*
* @param \Muffin\Slug\SluggerInterface|array|class-string<\Muffin\Slug\SluggerInterface> $slugger Sets slugger instance.
* @param \Muffin\Slug\SluggerInterface|array<string, mixed>|class-string<\Muffin\Slug\SluggerInterface> $slugger Sets slugger instance.
* Can be SluggerInterface instance or class name or config array.
* @return \Muffin\Slug\SluggerInterface
*/
protected function _createSlugger(SluggerInterface|array|string $slugger): SluggerInterface
protected function createSlugger(SluggerInterface|array|string $slugger): SluggerInterface
{
if (is_string($slugger)) {
return new $slugger();
Expand Down Expand Up @@ -188,7 +186,7 @@ public function implementedEvents(): array
/**
* Callback for Model.buildValidator event.
*
* @param \Cake\Event\EventInterface $event The beforeSave event that was fired.
* @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The buildValidator event that was fired.
* @param \Cake\Validation\Validator $validator Validator instance.
* @param string $name Validator name.
* @return void
Expand All @@ -207,9 +205,9 @@ public function buildValidator(EventInterface $event, Validator $validator, stri
/**
* Callback for Model.beforeSave event.
*
* @param \Cake\Event\EventInterface $event The afterSave event that was fired.
* @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired.
* @param \Cake\Datasource\EntityInterface $entity The entity that was saved.
* @param \ArrayObject $options Options.
* @param \ArrayObject<string, mixed> $options Options.
* @return void
*/
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void
Expand Down Expand Up @@ -245,7 +243,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array
return;
}

$parts = $this->_getPartsFromEntity($entity);
$parts = $this->getPartsFromEntity($entity);
if (empty($parts)) {
return;
}
Expand All @@ -258,12 +256,13 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array
* Gets the parts from an entity
*
* @param \Cake\Datasource\EntityInterface $entity Entity
* @return array
* @return array<int, mixed>
*/
protected function _getPartsFromEntity(EntityInterface $entity): array
protected function getPartsFromEntity(EntityInterface $entity): array
{
$parts = [];
foreach ((array)$this->getConfig('displayField') as $displayField) {
/** @psalm-suppress InvalidArgument */
$value = Hash::get($entity, $displayField);

if ($value === null && !$entity->isNew()) {
Expand All @@ -281,14 +280,14 @@ protected function _getPartsFromEntity(EntityInterface $entity): array
/**
* Custom finder.
*
* @param \Cake\ORM\Query\SelectQuery $query Query.
* @param \Cake\ORM\Query\SelectQuery<\Cake\Datasource\EntityInterface|array<array-key, mixed>> $query Query.
* @param string $slug Slug to search for.
* @return \Cake\ORM\Query\SelectQuery Query.
* @return \Cake\ORM\Query\SelectQuery<\Cake\Datasource\EntityInterface|array<array-key, mixed>> Query.
*/
public function findSlugged(SelectQuery $query, string $slug): SelectQuery
{
return $query->where([
$this->_table->aliasField($this->getConfig('field')) => $slug,
$this->table->aliasField($this->getConfig('field')) => $slug,
]);
}

Expand All @@ -310,10 +309,10 @@ public function slug(EntityInterface|string $entity, ?string $string = null, ?st
}
$string = $entity;
} elseif ($string === null) {
$string = $this->_getSlugStringFromEntity($entity, $separator);
$string = $this->getSlugStringFromEntity($entity, $separator);
}

$slug = $this->_slug($string, $separator);
$slug = $this->slugify($string, $separator);

$unique = $this->getConfig('unique');
if (!is_string($entity) && $unique) {
Expand All @@ -330,16 +329,17 @@ public function slug(EntityInterface|string $entity, ?string $string = null, ?st
* @param string $separator Separator
* @return string
*/
protected function _getSlugStringFromEntity(EntityInterface $entity, string $separator): string
protected function getSlugStringFromEntity(EntityInterface $entity, string $separator): string
{
$string = [];
foreach ((array)$this->getConfig('displayField') as $field) {
if ($entity->getError($field)) {
throw new InvalidArgumentException(sprintf(
'Error while generating the slug, the field `%s` contains an invalid value.',
$field
$field,
));
}
/** @psalm-suppress InvalidArgument */
$string[] = Hash::get($entity, $field);
}

Expand All @@ -351,13 +351,13 @@ protected function _getSlugStringFromEntity(EntityInterface $entity, string $sep
*
* @param \Cake\Datasource\EntityInterface $entity Entity.
* @param string $slug Slug
* @return array
* @return array<string, mixed>
*/
protected function _conditions(EntityInterface $entity, string $slug): array
protected function conditions(EntityInterface $entity, string $slug): array
{
/** @var string $primaryKey */
$primaryKey = $this->_table->getPrimaryKey();
$field = $this->_table->aliasField($this->getConfig('field'));
$primaryKey = $this->table->getPrimaryKey();
$field = $this->table->aliasField($this->getConfig('field'));

$conditions = [$field => $slug];

Expand All @@ -371,7 +371,7 @@ protected function _conditions(EntityInterface $entity, string $slug): array
$id = $entity->get($primaryKey);
if ($id !== null) {
/** @psalm-suppress PossiblyInvalidArrayOffset */
$conditions['NOT'][$this->_table->aliasField($primaryKey)] = $id;
$conditions['NOT'][$this->table->aliasField($primaryKey)] = $id;
}

return $conditions;
Expand All @@ -385,16 +385,16 @@ protected function _conditions(EntityInterface $entity, string $slug): array
* @param string $separator Separator.
* @return string Unique slug.
*/
protected function _uniqueSlug(EntityInterface $entity, string $slug, string $separator): string
protected function uniqueSlug(EntityInterface $entity, string $slug, string $separator): string
{
$field = $this->_table->aliasField($this->getConfig('field'));
$conditions = $this->_conditions($entity, $slug);
$field = $this->table->aliasField($this->getConfig('field'));
$conditions = $this->conditions($entity, $slug);

$i = 0;
$suffix = '';
$length = (int)$this->getConfig('maxLength');

while ($this->_table->exists($conditions)) {
while ($this->table->exists($conditions)) {
$i++;
$suffix = $separator . $i;
if ($length && $length < mb_strlen($slug . $suffix)) {
Expand All @@ -413,16 +413,17 @@ protected function _uniqueSlug(EntityInterface $entity, string $slug, string $se
* @param string $separator String to use as separator/separator.
* @return string Slug.
*/
protected function _slug(string $string, string $separator): string
protected function slugify(string $string, string $separator): string
{
/** @var array<string, string> $replacements */
$replacements = $this->getConfig('replacements');
$slugger = $this->getSlugger();
$slug = $slugger->slug(str_replace(array_keys($replacements), $replacements, $string), $separator);
if ($this->getConfig('maxLength')) {
$slug = Text::truncate(
$slug,
$this->getConfig('maxLength'),
['ellipsis' => '']
['ellipsis' => ''],
);
}

Expand Down
Loading
Loading