voku/urlify
PHP URL slugifier/transliterator (URLify.js port) that converts UTF-8 strings into readable, URL-safe slugs and filenames. Supports many languages via mapping tables with Portable ASCII fallback, plus helpers like downcode/transliterate and custom char rules.
Install via Composer:
composer require voku/urlify
First Use Case: Convert a blog post title to a URL slug:
use voku\helper\URLify;
$slug = URLify::filter('Laravel 10: New Features & Best Practices');
echo $slug; // "laravel-10-new-features-best-practices"
Where to Look First:
filter, downcode, transliterate).Bind voku/urlify as a singleton for reusable slug generation:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton('slugger', function () {
return new \voku\helper\URLify();
});
}
Usage in Controllers:
public function store(Request $request)
{
$slug = app('slugger')->filter($request->title);
// Save to database...
}
Automatically generate slugs from titles:
// app/Models/Post.php
protected $appends = ['slug'];
public function getSlugAttribute()
{
return app('slugger')->filter($this->title);
}
Usage:
$post = Post::find(1);
echo $post->slug; // "laravel-best-practices"
Validate slugs before saving:
// app/Http/Requests/StorePostRequest.php
public function rules()
{
return [
'title' => 'required|string',
'slug' => 'required|string|unique:posts,slug',
];
}
public function withValidator($validator)
{
$validator->after(function ($validator) {
$slug = app('slugger')->filter($this->title);
if ($slug !== $this->slug) {
$validator->errors()->add('slug', 'Slug does not match title.');
}
});
}
Generate slugs in API responses:
// app/Http/Resources/PostResource.php
public function toArray($request)
{
return [
'title' => $this->title,
'slug' => app('slugger')->filter($this->title),
'url' => route('posts.show', ['post' => $this->slug]),
];
}
Use slugs for dynamic routes:
// routes/web.php
Route::get('/posts/{slug}', [PostController::class, 'show'])
->where('slug', '[\w\-]+');
Controller:
public function show(Post $post)
{
// $post is automatically resolved via slug
}
Extend default replacements for domain-specific needs:
// app/Providers/AppServiceProvider.php
public function boot()
{
\voku\helper\URLify::add_chars([
'©' => '(c)', '®' => '(r)', '™' => '(tm)',
]);
}
Prioritize language rules for multilingual content:
$germanSlug = \voku\helper\URLify::filter('Straße', 60, 'de');
// "strasse" (German-specific rule)
$turkishSlug = \voku\helper\URLify::filter('İstanbul', 60, 'tr');
// "istanbul" (Turkish-specific rule)
Process arrays of strings (e.g., tags, categories):
$tags = ['Café', 'Naïve', 'Über'];
$slugs = array_map([\voku\helper\URLify::class, 'filter'], $tags);
// ["cafe", "naive", "uber"]
Locale Mismatches:
ü becomes ue in de locale but u in tr. Test with target languages.URLify::filter('Straße', 60, 'de'); // "strasse"
Duplicate Slugs:
URLify::filter() does not check uniqueness. Handle conflicts in your application logic (e.g., append -2):
$slug = $this->generateUniqueSlug($title);
private function generateUniqueSlug($title, $count = 1)
{
$slug = URLify::filter($title);
return Post::where('slug', $slug)->exists()
? $this->generateUniqueSlug($title, $count + 1)
: $slug . ($count > 1 ? "-$count" : "");
}
Reserved Characters:
#, ?). Sanitize further if needed:
$cleanSlug = preg_replace('/[^a-z0-9\-]/', '', $slug);
Performance:
Str::slug():
// Benchmark comparison
$strSlug = Str::slug('Café');
$vokuSlug = URLify::filter('Café');
Custom Rules Overrides:
URLify::add_chars(['€' => 'euro']); // Adds to default behavior
Inspect Transliteration:
downcode() to see raw transliterated output before filtering:
echo URLify::downcode('Café'); // "Cafe"
echo URLify::filter('Café'); // "cafe"
Check Language Maps:
ı → i vs. German ß → ss.Log Custom Mappings:
\voku\helper\URLify::add_chars(['€' => 'euro']);
error_log(print_r(\voku\helper\URLify::get_chars(), true));
Test Edge Cases:
"Hello! How's it going?").Custom Separator Logic:
URLify::add_array_to_seperator(['/']);
echo URLify::filter('Hello/World'); // "hello-world"
Word Removal:
URLify::remove_words(['the', 'a'], 'en');
echo URLify::filter('The Quick Brown Fox'); // "quick-brown-fox"
Facade for Laravel:
// app/Facades/Slugger.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Slugger extends Facade { public static function getFacadeAccessor() { return 'slugger'; } }
Usage:
use App\Facades\Slugger;
$slug = Slugger::filter('Laravel Tips');
Fallback to Laravel’s Str::slug:
public function generateSlug($title)
{
$vokuSlug = URLify::filter($title);
$laravelSlug = Str::slug($title);
return $vokuSlug === $laravelSlug ? $vokuSlug : $laravelSlug;
}
Default Locale:
// ❌ Unpredictable (uses no locale)
URLify::filter('Straße');
// ✅ Explicit
URLify::filter('Straße', 60, 'de');
Max Length Parameter:
maxLength parameter truncates after slug generation. Test with long strings:
echo URLify::filter('A Very Long Title For A Blog Post', 10);
// "a-very-lo
How can I help you explore Laravel packages today?