Unique Slugs

In our Database and Model tutorial we covered a very simple example to create a slug for use with a record. One problem with this example is that it doesn't create unique slugs, and since our database requires unique slugs, this could become an issue with two users having the same first and last name.

Create a Custom Method

In our Users table model, /src/Model/Table/UsersTable.php, we want a function that will check if a different user already exists with the same slug we are trying to create. We can use the email address, which must be unique, to verify it isn't the same user.

  /**
   * Check if user other than current user exists with same slug
   */
  protected function userExists(string $slug, string $email): bool
  {
    $query = $this->find()
      ->where(['slug' => $slug])
      ->where(['email !=' => $email]);
    return ($query->first() !== null);
  }

Use the Custom Method to Create a Unique Slug

Now we can adjust our beforeSave() function to create a unique slug for our user.

  public function beforeSave(EventInterface $event, $entity, $options)
  {
    // Need to add a value that uniquely identifies the current record
    $entity->slug = $this->getSlug($entity->full_name, $entity->email);
  }
  
  /**
   * Return a unique slug for passed in string
   */
  protected function getSlug(string $name, string $email): string
  {
    $slugCount = 0;
    $slugNotUnique = true;
    // set slug
    $baseSlug = mb_strtolower(Text::slug($name));
    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->userExists($slug, $email)) {
        $slugCount++;
      } else {
        $slugNotUnique = false;
      }
    } while ($slugNotUnique);
    return $slug;
  }

Now the function will append the current running count of users with the same slug to the end (e.g. bob-newhart-1, bob-newhart-2, etc.). Go ahead and test it by creating a user with the same first and last name as an existing user.

Within our current restrictions (i.e. slug length 65) we will have an issue after 999. We could correct this with an if statement and using substr() to trim our slug down, or we could increase the size of our slug field, but what are the odds of 1001 users with the same first and last name in your application? Adjust accordingly.