Behaviors

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.