symfony/polyfill-intl-grapheme
Native PHP polyfill for Intl Grapheme functions when ext-intl isn’t available. Works with UTF-8 strings and provides grapheme-aware length, substring, splitting, extraction, and case-sensitive/insensitive position and search functions.
Installation:
composer require symfony/polyfill-intl-grapheme
Add to composer.json under require-dev if only needed for testing.
First Use Case: Reverse RTL text (e.g., Arabic) correctly:
use Symfony\Component\Polyfill\Intl\Grapheme\GraphemeStr;
$rtlText = "مدم"; // Arabic palindrome
$reversed = GraphemeStr::reverse($rtlText); // Returns "مدم" (correct)
Where to Look First:
Laravel Str Helper Extension:
Extend Laravel’s Str helper for consistency:
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Str;
use Symfony\Component\Polyfill\Intl\Grapheme\GraphemeStr;
public function boot()
{
Str::macro('graphemeReverse', function ($string) {
return GraphemeStr::reverse($string);
});
}
Usage:
$reversed = Str::graphemeReverse("👨👩👧👦"); // Handles emoji clusters
Form Validation: Validate RTL palindromes (e.g., usernames):
use Illuminate\Validation\Rule;
$rules = [
'username' => [
'required',
Rule::palindrome()->ignoreCase(),
function ($attribute, $value, $fail) {
if (!GraphemeStr::reverse($value) === $value) {
$fail('The '.$attribute.' must be a grapheme palindrome.');
}
},
],
];
Search/Autocomplete: Reverse queries for RTL languages in Elasticsearch:
$query = GraphemeStr::reverse(request('q'));
$results = Search::query($query)->get();
Blade Directives: Create a custom Blade directive for RTL text:
// app/Providers/BladeServiceProvider.php
Blade::directive('reverseGraphemes', function ($expression) {
return "<?php echo GraphemeStr::reverse({$expression}); ?>";
});
Usage:
@reverseGraphemes($rtlText)
Text Processing Workflows:
$tokens = GraphemeStr::split("مدم");
$substr = GraphemeStr::substr("مدم", 0, 2); // "م"
Combine with intl Extension:
If available, prefer native IntlGraphemeClusterIterator for full Unicode support:
if (extension_loaded('intl')) {
$iterator = new IntlGraphemeClusterIterator($string);
} else {
// Fallback to polyfill
}
Performance Optimization: Cache reversed strings for static content (e.g., RTL menus):
$cacheKey = 'rtl_menu_reversed';
$reversed = Cache::remember($cacheKey, now()->addHours(1), function () {
return GraphemeStr::reverse($rtlMenuText);
});
Testing:
Use GraphemeStr in PHPUnit tests for RTL edge cases:
$this->assertEquals("مدم", GraphemeStr::reverse("مدم"));
$this->assertEquals("👨👩👧👦", GraphemeStr::reverse("👨👩👧👦"));
UTF-8 Validation: The polyfill requires UTF-8 encoded strings. Invalid UTF-8 will throw exceptions:
try {
GraphemeStr::reverse("\xFF"); // Invalid UTF-8
} catch (\InvalidArgumentException $e) {
// Handle error
}
Fix: Validate input with mb_check_encoding() or iconv.
PHP 8.6+ Requirement:
The grapheme_strrev() function (added in v1.37.0) only works on PHP 8.6+. For older versions:
grapheme_str_split() + array_reverse() + implode() as a fallback.intl extension if available.PCRE Version Issues:
Older PCRE versions (<10.44) may cause incorrect splitting in grapheme_str_split(). Upgrade PCRE or use a fallback:
if (version_compare(PCRE_VERSION, '10.44') < 0) {
// Custom splitting logic
}
Emoji Cluster Handling: Some emoji sequences (e.g., "👨👩👧👦") may not reverse as expected if the underlying PCRE library has bugs. Test thoroughly with emoji combinations.
Case-Insensitive Search:
grapheme_stripos()/grapheme_strripos() do not support RTL case folding. For RTL languages, use IntlChar::foldCase() if the intl extension is available.
Enable Error Reporting:
Ensure E_ALL is enabled to catch UTF-8 or PCRE-related errors.
Log Grapheme Clusters: Debug splitting issues by logging grapheme clusters:
$clusters = GraphemeStr::split($string);
\Log::debug('Grapheme clusters:', $clusters);
Compare with Native intl:
If available, compare results with the intl extension:
$nativeResult = IntlGraphemeClusterIterator::reverse($string);
$polyfillResult = GraphemeStr::reverse($string);
$this->assertEquals($nativeResult, $polyfillResult);
Check for False Positives:
grapheme_strpos() may return false for invalid positions. Always validate:
$pos = GraphemeStr::position($haystack, $needle);
if ($pos === false) {
// Handle not found
}
Custom Grapheme Functions: Extend the polyfill by creating wrapper classes:
class RTLStringHelper {
public static function reverseWithFallback($string) {
try {
return GraphemeStr::reverse($string);
} catch (\Exception $e) {
return strrev($string); // Fallback
}
}
}
Laravel Service Provider:
Register the polyfill globally in AppServiceProvider:
public function register()
{
$this->app->singleton('grapheme', function () {
return new GraphemeStr();
});
}
Event Listeners: Use the polyfill in event listeners for RTL text processing:
public function handle(TranslatedEvent $event)
{
if (Str::contains($event->text, ['م', 'ש', 'س'])) {
$event->text = GraphemeStr::reverse($event->text);
}
}
Middleware: Process RTL requests in middleware:
public function handle($request, Closure $next)
{
if ($request->isRtl()) {
$request->merge(['reversed_query' => GraphemeStr::reverse($request->query('q'))]);
}
return $next($request);
}
Autoloading:
Ensure the polyfill is autoloaded in composer.json:
"autoload": {
"psr-4": {
"App\\": "app/",
"Symfony\\Component\\Polyfill\\Intl\\Grapheme\\": "vendor/symfony/polyfill-intl-grapheme/src/"
}
}
Environment-Specific Loading: Load the polyfill only in RTL-enabled environments:
if (config('app
How can I help you explore Laravel packages today?