yemenifree/laravel-arabic-numbers-middleware
A Laravel middleware that automatically converts Arabic-Indic numerals to standard Western digits in incoming requests, so user input from Arabic keyboards is normalized before it reaches your controllers, validation, and database.
This is a small, single-purpose package: a Laravel middleware that normalizes digit characters in incoming requests so controllers and validators only ever see one numeral system. It does not touch responses, models, or views — that's your job (see below).
Install and publish
composer require yemenifree/laravel-arabic-numbers-middleware
php artisan vendor:publish --provider="Yemenifree\ArabicNumbers\ArabicNumbersServiceProvider"
Check config/arabic-numbers.php (or whatever the README names it) for the direction toggle and any except/only lists. The defaults are usually what you want: convert input to Latin digits.
Register it
Add the middleware to a group or a route. For a mostly-Arabic app, put it in the web group so every form submission is normalized; for an API, attach it per-route.
// app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
// ... TrimStrings, ConvertEmptyStringsToNull ...
\Yemenifree\ArabicNumbers\Middleware\ArabicNumbersMiddleware::class,
],
];
Use the exact class name from the README — the namespace is the one thing worth copy-pasting rather than guessing.
First use case: an Arabic-locale form with a phone or quantity field. A user types ١٢٣٤٥٦٧٨, hits submit, and your FormRequest rule ['phone' => 'required|digits:8'] passes because the middleware already rewrote the input to 12345678 before validation runs. That single flow — Arabic input, Latin validation/DB — is the package's whole reason to exist.
Global normalization + per-route exceptions. Register globally in web, then use the config's except list to skip routes where conversion is wrong (file uploads, webhook receivers, signed URLs). If the config doesn't support route patterns in your version, create a thin subclass that checks $request->routeIs(...) before delegating to the parent.
Normalize in, format out. The middleware is request-only. For the reverse direction (displaying 1234 as ١٢٣٤), either flip the direction config for read-only routes or, better, format at the presentation layer. A Blade directive or a small helper wrapping str_replace over the digit table keeps display logic out of middleware:
Blade::directive('arabic', fn ($expr) => "<?php echo strtr($expr, ['0'=>'٠','1'=>'١', /* ... */]); ?>");
Order it against the built-in stack. Place it after TrimStrings and ConvertEmptyStringsToNull so you're converting trimmed strings and not producing ' ١٢٣ '. It must be before any FormRequest resolution — middleware always is, but if you're doing $request->validate() inside a route closure the same rule applies.
Convert route parameters too. If you accept IDs in the path (/orders/١٢٣), route-model binding happens after route middleware, so registering the middleware on the route is enough if the package reads route parameters. If it doesn't, add a Route::bind or a prependMiddleware that calls $request->route()->setParameter(...).
Pair with a JSON response normalizer for APIs. Clients that send Arabic digits often expect to receive them. A tiny terminating middleware or an API Resource with() that runs numberToArabic on selected fields gives you symmetric behavior without pulling the request middleware into the response path.
Use the underlying conversion directly when you need it outside HTTP. If the package exposes a helper/class for the character mapping, reuse it in casts, imports (CSV parsing of Arabic numerals), and queued jobs. Don't duplicate the digit table.
Test it like any middleware. Feature-test the endpoint with Arabic digits in the body and assert on the FormRequest/controller output:
$this->post('/register', ['phone' => '١٢٣٤٥٦٧٨'])
->assertSessionHasNoErrors();
$this->assertDatabaseHas('users', ['phone' => '12345678']);
That test doubles as regression protection for the package's exact behavior.
٠١٢٣٤٥٦٧٨٩, U+0660–0669) and Eastern Arabic/Persian (۰۱۲۳۴۵۶۷۸۹, U+06F0–06F9) are different code points. Confirm both are mapped — many "Arabic numbers" packages only handle the first. Add a unit test with ۱۲۳ and ١٢٣ if your users are mixed.$request->replace() is shallow by default. If the middleware doesn't recurse, nested fields like items[0][qty] sail through unconverted. Test a nested payload before trusting it in production; if it fails, wrap the conversion in array_walk_recursive yourself.$request->getContent() for a hand-parsed JSON body, or php://input directly, returns the original Arabic digits. Stay on $request->all() / $request->input().request, not query. A GET /search?q=١٢٣ may still be Arabic in $request->query('q'). Verify, and fall back to $request->merge($request->query->all())-style handling if needed.except/only config or a route-level opt-out; a corrupted HMAC signature is a very confusing 403.UploadedFile instances should be skipped. If the package walks the whole input array naively, a $request->file('x') in the same request may be mangled. Check is_string() before converting.١٢٫٥ uses the Arabic decimal separator ٫ (U+066B), which the middleware typically does not map. ١٢.٥ with an ASCII dot converts cleanly to 12.5, but ١٢٫٥ becomes 12٫5 and fails numeric. Normalize separators separately if you expect them.digits:8 and integer behave differently on a mixed string like 12٣٤. The middleware normalizes it, but decide whether you want mixed input accepted. Add regex:/^[0-9]+$/ on top if you don't.integer validation and integer column casts will break. Treat "to Latin" as the input direction and "to Eastern" as a display-only concern.strtr with the same two digit tables — don't fight the package's limitations for a feature this simple.$request->all() inside a throwaway middleware placed before the converter and compare with an after dump. It isolates whether the package or your own code is doing the rewriting.How can I help you explore Laravel packages today?