Behaviors are ways to enable the reuse of Model functions. For example, instead of adding our slug creation function into every model, which could be a lot of redundant code, we can create a Behavior to encapsulate the code and add the Behavior to our Model's initialize()
, just like the Timestamp Behavior.
Create Your Behavior
Behaviors are stored in the /src/Model/Behavior
directory and we'll name ours SluggableBehavior.php
.
<?php
declare(strict_types=1);
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
use Cake\Utility\Text;
class SluggableBehavior extends Behavior
{
/**
* Return a unique slug for passed in value
*/
public function getSlug($entity, $field = 'name'): string
{
// set slug
$baseSlug = mb_strtolower(Text::slug($entity->{$field}));
// Check if existing slug matches $field
if ($entity->slug && substr($entity->slug, 0, strlen($baseSlug)) == $baseSlug) {
return $entity->slug;
}
$slugCount = 0;
$slugNotUnique = true;
do {
// reset slug
$slug = $baseSlug;
// if slug count > 0 append slug count
if ($slugCount) {
$slug .= '-' . $slugCount;
}
// Check for existing slug with different email
if ($this->recordExists($slug, $entity->id)) {
$slugCount++;
} else {
$slugNotUnique = false;
}
} while ($slugNotUnique);
return $slug;
}
/**
* Check if user other than current user exists with same slug
*/
protected function recordExists(string $slug, int|null $id): bool
{
// New record
if ($id === null) {
$query = $this->_table->find()
->where(['slug' => $slug]);
} else {
$query = $this->_table->find()
->where(['slug' => $slug])
->where(['id !=' => $id]);
}
return ($query->first() !== null);
}
}
Add The Behavior To Your Models
Edit each of your Table Models that needs a slug, like /src/Models/Table/UsersTable.php
and add the Sluggable Behavior. Be sure to remove the old getSlug()
and recordExists()
functions from your Model and update beforeSave()
.
public function initialize(array $config): void
{
...
$this->addBehavior('Sluggable');
...
}
...
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void
{
$entity->slug = $this->getSlug($entity, 'full_name');
}
That's all there is to it, you don't need to add the redundant code to every table, just add your new Behavior.