elstc/cakephp-slug-guard

CakePHP plugin for URL-safe slug validation and reserved-word collision prevention

Maintainers

Package info

github.com/elstc/cakephp-slug-guard

Type:cakephp-plugin

pkg:composer/elstc/cakephp-slug-guard

Statistics

Installs: 104

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

5.1.0 2026-03-30 00:02 UTC

This package is auto-updated.

Last update: 2026-04-30 00:07:06 UTC


README

CI Latest Stable Version Total Downloads Software License

A CakePHP plugin that ensures user-chosen slugs are safe and conflict-free for use in URLs.

日本語ドキュメント / Japanese

Version Map

CakePHP PHP Plugin Branch
5.x >= 8.2 5.x cake5
  • SlugValidator validates that a string is well-formed for use as a subdomain label or URL path segment (lowercase alphanumeric + hyphens, length constraints).
  • IsNotReservedSlug compares slugs against a reserved-word list so they never collide with system routes or well-known paths.
  • IsNotRouteConflict compares slugs against first-level URL segments extracted from the CakePHP route table to prevent collisions with application routes.

Installation

Prerequisites

  • A CakePHP 5.x application
  • PHP >= 8.2
  • A database connection configured in your app (config/app_local.php)

Steps

  1. Install the plugin via Composer:

    composer require elstc/cakephp-slug-guard
  2. Load the plugin. This adds $this->addPlugin('Elastic/SlugGuard') to your src/Application.php:

    bin/cake plugin load Elastic/SlugGuard
  3. Run the migration to create the reserved_slugs table:

    bin/cake migrations migrate --plugin Elastic/SlugGuard
  4. Import the default reserved slugs (~710 common reserved words):

    bin/cake slug_guard sync

Verify Installation

Run the following command to confirm the reserved slugs were imported:

bin/cake slug_guard list --count

Expected: a count of approximately 710 reserved slugs.

Usage

Validation Rule

Add the IsNotReservedSlug rule to your table's buildRules() method:

use Elastic\SlugGuard\Model\Rule\IsNotReservedSlug;

public function buildRules(RulesChecker $rules): RulesChecker
{
    $rules->add(new IsNotReservedSlug('slug'), 'reservedSlug', [
        'errorField' => 'slug',
        'message' => 'This slug is reserved.',
    ]);

    return $rules;
}

Custom field name

$rules->add(new IsNotReservedSlug('username'), 'reservedSlug', [
    'errorField' => 'username',
    'message' => 'This username is reserved.',
]);

Custom reserved slugs table

You can use any table as the reserved slugs backend by implementing SlugExistenceInterface:

// src/Model/Table/CustomReservedSlugsTable.php
namespace App\Model\Table;

use Cake\ORM\Table;
use Elastic\SlugGuard\Model\Table\SlugExistenceInterface;

class CustomReservedSlugsTable extends Table implements SlugExistenceInterface
{
    public function slugExists(string $slug): bool
    {
        return $this->exists(['slug' => $slug]);
    }
}

Then pass the table name to IsNotReservedSlug:

$rules->add(new IsNotReservedSlug('slug', 'CustomReservedSlugs'), 'reservedSlug', [
    'errorField' => 'slug',
    'message' => 'This slug is reserved.',
]);

Route Conflict Rule

Add the IsNotRouteConflict rule to prevent slugs from colliding with application routes (e.g. /admin, /api, /posts):

use Elastic\SlugGuard\Model\Rule\IsNotRouteConflict;

public function buildRules(RulesChecker $rules): RulesChecker
{
    $rules->add(new IsNotRouteConflict('slug'), 'routeConflict', [
        'errorField' => 'slug',
        'message' => 'This slug conflicts with an application route.',
    ]);

    return $rules;
}

This rule extracts first-level URL path segments from all registered CakePHP routes at runtime and rejects any slug that matches.

Slug Format Validator

The SlugValidator class provides a static method for validating slug format (lowercase alphanumeric and hyphens):

use Elastic\SlugGuard\Validation\SlugValidator;

// Use as a Validator provider
$validator->setProvider('slugValidator', SlugValidator::class);
$validator->add('slug', 'validSlug', [
    'rule' => ['isValid'],
    'provider' => 'slugValidator',
    'message' => 'Only lowercase letters, numbers, and hyphens are allowed.',
]);

Configuration Options

minLength

Minimum slug length. Default: 4

maxLength

Maximum slug length. Default: 24

$validator->add('slug', 'validSlug', [
    'rule' => ['isValid', 3, 32],
    'provider' => 'slugValidator',
]);

CLI Commands

List reserved slugs

bin/cake slug_guard list
bin/cake slug_guard list --count
bin/cake slug_guard list --search admin

List route paths

bin/cake slug_guard routes
bin/cake slug_guard routes --count

Output is pipe-friendly (one path per line, no decoration). See Route-based workflow below.

Add reserved slugs

bin/cake slug_guard add my-reserved-slug
bin/cake slug_guard add slug-one slug-two slug-three

Supports stdin for pipe usage:

bin/cake slug_guard routes | bin/cake slug_guard add

Remove reserved slugs

bin/cake slug_guard remove my-reserved-slug
bin/cake slug_guard remove slug-one slug-two slug-three

Also supports stdin:

cat slugs-to-remove.txt | bin/cake slug_guard remove

Import slugs from a file

bin/cake slug_guard import /path/to/slugs.txt

File format: one slug per line, # for comments, empty lines are ignored.

Sync with a file

# Sync (auto-detects app config file or falls back to plugin built-in)
bin/cake slug_guard sync

# Sync with a specific file
bin/cake slug_guard sync --file /path/to/slugs.txt

# Preview changes without applying
bin/cake slug_guard sync --dry-run

When --file is not specified, the sync command resolves the seed file in the following order:

  1. Application config file: config/reserved-slugs.txt (in app root)
  2. Plugin built-in seed file

You can override the application config file path via Configure:

// In config/app.php or config/app_local.php
'SlugGuard' => [
    'syncFile' => CONFIG . 'my-custom-slugs.txt',
],

Reserved Slugs List File

The plugin ships with a default reserved slugs list at config/reserved-slugs.txt. This file contains ~710 common reserved words (e.g. admin, api, login, settings, www) that could conflict with system routes or well-known paths.

File format

  • One slug per line
  • Lines starting with # are comments
  • Empty lines are ignored
# System routes
admin
api
login

# Social media
facebook
twitter
youtube

Using the built-in list

The built-in list is used as a fallback by the sync command when no app-level config file exists. You can also import it directly:

bin/cake slug_guard import vendor/elstc/cakephp-slug-guard/config/reserved-slugs.txt

Using a custom list

Create your own file following the same format and use it with import or sync:

# Import additional slugs from a custom file
bin/cake slug_guard import /path/to/my-slugs.txt

# Sync the database to match your custom file exactly
bin/cake slug_guard sync --file /path/to/my-slugs.txt

Note: import adds slugs from the file to the database (existing slugs are preserved). sync makes the database match the file exactly — slugs not in the file will be removed.

Route-based Workflow

The slug_guard routes command extracts first-level URL path segments from your application's route table. Use it to automatically reserve paths that would conflict with user-generated slugs.

Pipe directly to the database

bin/cake slug_guard routes | bin/cake slug_guard add

Append to your reserved slugs file, then sync

bin/cake slug_guard routes >> config/reserved-slugs.txt
bin/cake slug_guard sync