elstc / cakephp-slug-guard
CakePHP plugin for URL-safe slug validation and reserved-word collision prevention
Package info
github.com/elstc/cakephp-slug-guard
Type:cakephp-plugin
pkg:composer/elstc/cakephp-slug-guard
Requires
- php: >=8.2
- cakephp/cakephp: ^5.0
Requires (Dev)
- cakephp/cakephp-codesniffer: ^5.1
- cakephp/migrations: ^4.0 | ^5.0
- phpunit/phpunit: ^10.5 || ^11.5 || ^12.0
README
A CakePHP plugin that ensures user-chosen slugs are safe and conflict-free for use in URLs.
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
-
Install the plugin via Composer:
composer require elstc/cakephp-slug-guard
-
Load the plugin. This adds
$this->addPlugin('Elastic/SlugGuard')to yoursrc/Application.php:bin/cake plugin load Elastic/SlugGuard
-
Run the migration to create the
reserved_slugstable:bin/cake migrations migrate --plugin Elastic/SlugGuard
-
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:
- Application config file:
config/reserved-slugs.txt(in app root) - 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:
importadds slugs from the file to the database (existing slugs are preserved).syncmakes 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