ausi/slug-generator
Generate clean, customizable slugs for URLs and filenames using PHP’s Transliterator (CLDR). Supports many scripts (Cyrillic, Greek, CJK), locale-aware conversions, configurable valid chars and delimiters, and consistent ASCII output via simple options.
## Getting Started
### Minimal Setup
1. **Installation**
```bash
composer require ausi/slug-generator
Add to composer.json if using a monorepo or custom package management.
Note: Now fully compatible with PHP 8.0.
Basic Usage
use Ausi\SlugGenerator\SlugGenerator;
$slugGenerator = new SlugGenerator();
$slug = $slugGenerator->generate('Café au lait');
// Output: "cafe-au-lait"
First Use Case: Blog Post Slugs
$title = "How to Build a Laravel API in 2024";
$slug = $slugGenerator->generate($title);
// Output: "how-to-build-a-laravel-api-in-2024"
Laravel Integration
Add to config/app.php under aliases:
'SlugGenerator' => Ausi\SlugGenerator\SlugGenerator::class,
Then inject via constructor or resolve via app(SlugGenerator::class).
Note: Type-hinting now works seamlessly with PHP 8.0.
// In Post model
protected static function boot()
{
static::creating(function ($post) {
$post->slug = app(SlugGenerator::class)->generate($post->title);
});
}
use Ausi\SlugGenerator\SlugGenerator;
public function rules()
{
return [
'title' => 'required|string',
'slug' => 'nullable|unique:posts,slug,' . request()->post,
];
}
public function withValidator($validator)
{
$validator->after(function ($validator) {
$slug = app(SlugGenerator::class)->generate($this->title);
$validator->errors()->addUnless(
$this->slug === $slug || !Post::where('slug', $slug)->exists(),
'slug',
'A slug has already been generated for this title.'
);
});
}
Route::get('/posts/{slug}', [PostController::class, 'show'])
->where('slug', '[\w\-]+');
// In PostController
public function show(string $slug) // PHP 8.0 type hinting
{
$post = Post::where('slug', $slug)->firstOrFail();
// ...
}
$slugGenerator = new SlugGenerator();
$slugGenerator->setOptions([
'separator' => '-',
'lowercase' => true,
'remove_accents' => true,
'limit' => 100,
'transliterate' => true, // Improved fallback rules in v1.1.1
]);
$slug = $slugGenerator->generate('Café au lait!');
// Output: "cafe-au-lait"
// app/Services/SlugService.php
class SlugService
{
public function generateUniqueSlug(string $modelClass, string $field, string $value): string
{
$slug = app(SlugGenerator::class)->generate($value);
$count = $modelClass::where($field, 'like', $slug . '%')->count();
return $count > 0 ? $slug . '-' . $count : $slug;
}
}
// Usage in controller
$slug = app(SlugService::class)->generateUniqueSlug(Post::class, 'slug', $request->title);
Database Indexing
Ensure the slug column in your database is indexed for performance:
Schema::create('posts', function (Blueprint $table) {
$table->string('slug')->unique();
$table->index('slug');
});
SeoMetaPackage Integration
If using spatie/laravel-seo, sync slugs with meta titles:
public function getSlug(): string
{
return $this->slug ?: app(SlugGenerator::class)->generate($this->title);
}
Testing Mock the slug generator in tests:
$this->app->instance(SlugGenerator::class, Mockery::mock(SlugGenerator::class));
Caching Generated Slugs Cache slugs for frequently accessed models:
$slug = Cache::remember("slug_{$post->id}", now()->addHours(1), function () use ($post) {
return app(SlugGenerator::class)->generate($post->title);
});
Unicode Handling
$slugGenerator->setOptions(['transliterate' => true]);
Collisions
Performance
Separators in Input
"Hello, World!" may produce unexpected results.punctuation and separator options:
$slugGenerator->setOptions([
'punctuation' => 'replace',
'separator' => '_',
]);
PHP 8.0 Type Safety
string return types) to leverage PHP 8.0 improvements:
public function generate(string $text): string
Log Generated Slugs
$slugGenerator->generate($title, function (string $slug) {
Log::debug("Generated slug: {$slug} from '{$title}'");
});
Inspect Options Dump the current options to debug:
dd($slugGenerator->getOptions());
Character-by-Character Debugging Check how individual characters are processed:
$slugGenerator->setCallback(function (string $char, int $index, string $slug) {
Log::debug("Processing char '{$char}' at index {$index} in '{$slug}'");
});
Custom Separators Use underscores for SEO-friendly URLs:
$slugGenerator->setOptions(['separator' => '_']);
Preserve Keywords Avoid truncating important keywords:
$slugGenerator->setOptions(['limit' => 50]);
Localization Generate slugs in the model’s locale:
$slug = app(SlugGenerator::class)->generate(__($post->title));
Fallback for Empty Input Handle empty/whitespace-only input:
$slug = $slugGenerator->generate($title ?: 'untitled-post');
Extend the Generator Create a custom generator for project-specific rules:
class CustomSlugGenerator extends SlugGenerator
{
public function generate(string $text): string
{
$slug = parent::generate($text);
return preg_replace('/-+/', '-', $slug);
}
}
Environment-Specific Config Override options per environment:
'options' => [
'separator' => env('SLUG_SEPARATOR', '-'),
'limit' => env('SLUG_LIMIT', 100),
'transliterate' => env('SLUG_TRANSLITERATE', false),
],
Avoid Overwriting
Use updateIfChanged to prevent unnecessary database updates:
if ($post->title !== $request->title && $post->slug !== $slug) {
$post->slug = $slug;
$post->save();
}
Bulk Processing For bulk operations, batch slug generation:
$titles = $posts->pluck('title');
$slugs = collect($titles)->map(function (string $title) {
return app(SlugGenerator::class)->generate($title);
});
PHP 8.0 Features Leverage PHP 8.0 features like named arguments for clarity:
$slug = $slugGenerator->generate(
How can I help you explore Laravel packages today?