Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Polyfill Mbstring Laravel Package

symfony/polyfill-mbstring

Provides a partial, native PHP implementation of the Mbstring extension, letting applications use multibyte string functions even when the mbstring extension is unavailable. Part of the Symfony Polyfill family and released under the MIT license.

View on GitHub
Deep Wiki
Context7

Getting Started

You almost certainly already have it. symfony/polyfill-mbstring arrives transitively via laravel/framework → illuminate/support, symfony/console, symfony/var-dumper, and most of the Symfony ecosystem. Confirm before doing anything:

composer why symfony/polyfill-mbstring
composer show symfony/polyfill-mbstring   # check the installed version
php -m | grep mbstring                    # is the native extension loaded?

First thing to verify: are you actually on the polyfill? There is no flag, no config key, no service provider. The package works by declaring global mb_* functions only if they don't already exist, at Composer autoload time. So a single ReflectionFunction tells you which implementation your request is using:

$file = (new ReflectionFunction('mb_strlen'))->getFileName();

// vendor/symfony/polyfill-mbstring/... => you are on the polyfill
// false (internal function)              => native ext-mbstring

Run that in php artisan tinker on your laptop, in CI, and on production. This is the single most important diagnostic for this package.

Where to look first (read-only, never edit): vendor/symfony/polyfill-mbstring/bootstrap.php (PHP 8+ entry), bootstrap72.php (PHP 7 declarations), and Mbstring.php (the actual implementations). Skim Mbstring.php only when a function misbehaves — the class is not an API you should ever call directly.

Your first real use case is almost always multilingual or user-generated content: truncating an Arabic/Hebrew/CJK review into an excerpt, or sanitizing an inbound webhook payload. Laravel's Str already routes through mb_*, so the polyfill activates transparently:

use Illuminate\Support\Str;

Str::limit($review->body, 120);       // mb_substr under the hood
Str::length($comment->body);          // mb_strlen
Str::padLeft($code, 10, '·');         // mb_str_pad (Laravel 10.35+)

// A control-character-laden payload from a third party
$clean = mb_scrub($payload['message'], 'UTF-8');   // fixed in v1.38.2

Implementation Patterns

Never call the polyfill class directly. Symfony\Polyfill\Mbstring\Mbstring::mb_substr() bypasses the native extension when it is installed, so you lose the ~5–10x native speedup and get subtly different behavior. Always call the global mb_* function (or a Laravel Str helper that does) and let the environment decide the implementation.

Prefer Str / Stringable in app code, drop to mb_* in hot paths or where you control encoding. Str is testable, chainable, and already encoding-safe. Reach for raw mb_* when you need an explicit $encoding argument — which you usually should pass, see Gotchas.

Sanitize at the boundary, then validate length. A practical FormRequest pipeline for user-generated content:

protected function prepareForValidation(): void
{
    $body = (string) $this->input('body');

    // 1. Reject bytes that aren't valid UTF-8 at all
    if (! mb_check_encoding($body, 'UTF-8')) {
        $body = mb_scrub($body, 'UTF-8');
    }

    // 2. Strip control chars (mb_scrub handles ill-formed sequences, not all controls)
    $body = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $body);

    $this->merge(['body' => $body]);
}

public function rules(): array
{
    // Laravel's `max` on strings already uses mb_strlen — characters, not bytes
    return ['body' => ['required', 'string', 'max:2000']];
}

Normalize encodings on ingest from third parties. Legacy CSV imports, SOAP feeds, and payment-gateway callbacks frequently ship Latin-1/Shift-JIS:

$utf8 = mb_convert_encoding($raw, 'UTF-8', 'SJIS-win');

The polyfill delegates to iconv here, which is why ext-iconv is effectively required.

Case-fold for search, don't fold at query time. Normalize once on write so WHERE clauses stay indexable:

$model->title_normalized = mb_convert_case($title, MB_CASE_FOLD, 'UTF-8');

Pad for fixed-width surfaces. Console tables, receipts, terminal output, and RTL alignment in admin dashboards:

$this->line(mb_str_pad("مرحبا", 20, ' ', STR_PAD_LEFT));
$this->line(mb_str_pad("東京", 20, ' ', STR_PAD_RIGHT));

Note the signature order: mb_str_pad($string, $length, $pad_string, $pad_type, $encoding) — the encoding is fifth, unlike mb_substr where it's third.

Test the polyfill path explicitly. Because the polyfill only declares functions that are missing, you cannot unit-test it on a machine with ext-mbstring loaded. Add one CI job in a container built without mbstring:

FROM php:8.2-cli-alpine
RUN apk add --no-cache icu-dev oniguruma-dev \
 && docker-php-ext-install intl pdo_mysql
# deliberately do NOT install mbstring

Then run your multilingual feature tests there. This is how you catch the mb_scrub() and mb_str_pad() regressions the changelog fixes at v1.38.2 before your users do.

Gotchas and Tips

Silent degradation is the whole risk. Nothing logs, nothing warns; you just quietly get a 5–10x slower, partial implementation. If you care about performance, enforce the extension in production while keeping the polyfill for developer machines and slim containers:

"require": { "ext-mbstring": "*" },
"config": { "platform-check": true }

Use --ignore-platform-req=ext-mbstring only locally. Otherwise, put the ReflectionFunction check above into a health-check endpoint or a boot-time assertion in AppServiceProvider so you know which mode you're in.

Partial coverage — guard with function_exists(). HTTP/mail-oriented functions (mb_send_mail, mb_http_output, mb_language) are not part of the polyfill. Also, mb_detect_encoding and mb_convert_case exist but historically diverged in ordering and behavior from native. When writing portable library code, function_exists('mb_str_pad') || ... is still the right guard for newer functions.

Always pass $encoding explicitly. The polyfill's handling of mb_internal_encoding() and default-encoding fallbacks is where it deviates most from native. mb_strlen($s, 'UTF-8') costs nothing and removes the class of bug entirely.

Invalid UTF-8 is the #2 killer after performance. Native mb_* returns false or '' for malformed input; the polyfill has repeatedly been patched toward that behavior (v1.38.0 mb_strlen, v1.43.0 ill-formed UTF-8 and mb_convert_case endless loops). Practical rule: mb_check_encoding() before any length/truncate/pad, or your Str::limit() can yield an empty string on a single bad byte from a webhook.

mb_str_pad() is not str_pad(). Argument order differs from other mb_* functions (encoding last), and it historically had RTL/fixed-width bugs — fixed in v1.38.2. If you rely on it, pin ^1.38.2 minimum and add an assertion test.

Alpine/musl is a distinct runtime. iconv //IGNORE isn't supported on musl, and v1.38.0 added a plain-iconv fallback. If you deploy on php:*-alpine, exercise your encoding-conversion paths there; glibc-passing tests can fail in production.

PHP 7 declarations live in bootstrap72.php. If you see Cannot redeclare mb_*() on legacy runtimes, it's an old polyfill version colliding with a partially-loaded extension. Stay current with composer update symfony/polyfill-mbstring --with-dependencies — it's a transitive dependency, so don't add it to require just to bump it unless you need a floor.

None of this fixes your database. utf8mb4 columns, config/database.php charset/collation, and MySQL's own byte-vs-char length semantics are independent of mb_*. A polyfill-correct string still mangles in a utf8 (3-byte) column when someone pastes an emoji.

Extension points are minimal by design. There's no config, no container binding, no service to swap. The one worthwhile extension point is your own thin wrapper (App\Support\Text) around the handful of mb_* calls you actually use, so you can unit-test behavior with a fake and swap the backend later without grepping the codebase.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package