Era Host hosting
EraHost – Free Domain, Cheap Hosting!
Client Area
Support 24/7
Menu

mbstring.func_overload and mbstring.internal_encoding in PHP: Configuration and Migration

28 min read
11.09.2026

An old website is moved to a new VPS, switched to another PHP version, and suddenly the installer or documentation requires mbstring.func_overload and mbstring.internal_encoding. The administrator adds the directives to php.ini, restarts PHP-FPM, but PHP 8 still does not show the expected setting. Sometimes the website works perfectly while the compatibility checker continues to report an error.

Copying the old configuration as-is is risky. mbstring.func_overload could change the behavior of standard PHP string functions, so the same strlen() call could have different semantics on two servers. mbstring.internal_encoding solved a different problem: it specified the internal encoding used by mbstring. These directives have different purposes and, more importantly during migration, different statuses in modern PHP versions.

With PHP 8, the task is no longer to find the correct values for the old directives. You need to determine where the requirement comes from, whether the application depended on the old behavior, which PHP runtime actually serves the website, and which string operations are supposed to count characters rather than bytes. In legacy projects, the problem may not be located in php.ini at all: the check can live in a CMS, a Composer package, .user.ini, or a compatibility layer that survived several server migrations.

Short answer: mbstring.func_overload was deprecated in PHP 7.2 and removed in PHP 8.0. mbstring.internal_encoding is a different directive: it was deprecated back in PHP 5.6 and should not be treated as an equivalent of the completely removed func_overload. New code should not rely on it. The mb_internal_encoding() function remains a separate mbstring API for reading and changing the current internal encoding.

Why does an old website require mbstring.func_overload or mbstring.internal_encoding?

If PHP 8 is running correctly but the CMS still requires mbstring.func_overload, first locate the source of that requirement. PHP 8 no longer supports the directive, so repeatedly adding it to php.ini will not change anything. The configuration may not be the problem at all.

Several scenarios appear repeatedly in legacy migrations. An administrator may be following an outdated installation guide and copying settings literally:

mbstring.func_overload = 0
mbstring.internal_encoding = UTF-8

In another case, the requirement is built into the CMS install checker. The directives may also remain in an old php.ini or .user.ini even though the application no longer uses them. Another possibility is a check inside a Composer dependency or compatibility library. Only after these cases are excluded should you assume that the application itself genuinely relied on overloaded string functions.

Start by locating every direct reference:

rg "mbstring\.func_overload|mbstring\.internal_encoding" /path/to/site

rg "MB_OVERLOAD_" /path/to/site

If ripgrep is not installed:

grep -R -n "mbstring.func_overload" /path/to/site
grep -R -n "mbstring.internal_encoding" /path/to/site
grep -R -n "MB_OVERLOAD_" /path/to/site

The location of the match already tells you a lot. A line found only in an old README is a documentation issue. A check inside an installer that blocks PHP 8 points to application compatibility. A condition that changes which string functions are called depending on the overload value may indicate a real runtime dependency.

Where the setting is found What it usually means What to check next
Old guide or README The requirement may have been written for PHP 5.x or PHP 7.x Compare it with the current application and PHP versions
php.ini or .user.ini A legacy configuration may have survived the migration Check whether the current runtime reads it and whether the code depends on it
CMS install checker The check itself may be older than the PHP version Determine whether the checker is outdated or the application is genuinely incompatible
vendor/ An outdated Composer dependency may be involved Identify the package and check for a PHP 8 compatible release
Application code There may be a direct dependency on legacy behavior Audit string semantics and tests

Two requirements that look similar can mean very different things. If a CMS expects mbstring.func_overload = 0, it may simply be checking that overload is disabled. That does not mean the application needs func_overload. If the code expects func_overload = 2 and uses ordinary strlen() or substr() calls as if they were multibyte-aware, migration will require source code changes.

The practical rule is simple: on PHP 8, an old checker does not prove that the server is misconfigured. Find the code that generates the requirement first.

How do you check the PHP version, loaded php.ini, and actual mbstring settings?

mbstring must be checked in the PHP SAPI that actually executes the website. The php -i command over SSH can show a perfectly valid configuration while the website continues to run with different settings through PHP-FPM. This is one of those cases where the administrator edits the correct file — just not the one used by the site.

How to identify php.ini for CLI

Start with the command-line runtime:

php -v
php --ini
php -i | grep "Loaded Configuration File"

php -v shows the CLI version. php --ini shows the main configuration file and additional INI directories. If this reports PHP 8.3, that does not prove the domain is also served by PHP 8.3.

On a VPS with several PHP versions, the mismatch can be significant: the system php command may point to one version, one FPM pool may run another, and a neighboring domain may use a third. Until CLI and Web runtimes are matched, comparing mbstring values is not useful.

How to identify php.ini for PHP-FPM or Apache

Create a temporary diagnostic file in the document root of the affected website:

<?php
echo 'PHP: ', PHP_VERSION, PHP_EOL;
echo 'SAPI: ', PHP_SAPI, PHP_EOL;
echo 'php.ini: ', php_ini_loaded_file() ?: 'none', PHP_EOL;
echo 'Scanned INI: ', php_ini_scanned_files() ?: 'none', PHP_EOL;
?>

Open it in a browser. If SSH reports PHP 8.3 but this file shows PHP 8.1 FPM, the mismatch has been found. Do not investigate mb_internal_encoding() yet: first determine which runtime is supposed to serve the domain.

Delete the diagnostic file after the check. It exposes configuration paths and environment details that do not need to remain publicly accessible.

How to check mbstring without a full phpinfo() page

A small diagnostic script is enough:

<?php
echo 'PHP version: ', PHP_VERSION, PHP_EOL;
echo 'SAPI: ', PHP_SAPI, PHP_EOL;
echo 'php.ini: ', php_ini_loaded_file() ?: 'none', PHP_EOL;

echo 'mbstring loaded: ';
var_dump(extension_loaded('mbstring'));

echo 'func_overload: ';
var_dump(ini_get('mbstring.func_overload'));

echo 'legacy internal_encoding INI: ';
var_dump(ini_get('mbstring.internal_encoding'));

echo 'default_charset: ';
var_dump(ini_get('default_charset'));

if (extension_loaded('mbstring')) {
    echo 'mb_internal_encoding(): ';
    var_dump(mb_internal_encoding());

    echo 'mb_regex_encoding(): ';
    var_dump(mb_regex_encoding());
}
?>

The interpretation matters more than the command itself. If PHP 8 returns false for ini_get('mbstring.func_overload'), that is expected because the directive has been removed. If extension_loaded('mbstring') returns false, calls to mb_strlen(), mb_substr(), and other mbstring functions become a separate problem.

Result What it means Next action
PHP 8 + ini_get('mbstring.func_overload') === false Expected state Do not try to restore the removed directive
PHP 7.4 + func_overload has a value The legacy mechanism still exists Record the value before migration
extension_loaded('mbstring') === false mbstring is not loaded in this SAPI Check the package/module and the configuration of this PHP runtime
CLI PHP differs from Web PHP You are comparing different runtimes Identify the correct runtime before continuing
CLI and Web load different php.ini files Editing one configuration does not necessarily affect the other Modify the configuration used by the required SAPI

Do not call mb_internal_encoding() blindly in CLI either. Check the extension first:

php -m | grep -i '^mbstring$'

php -r '
if (extension_loaded("mbstring")) {
    echo mb_internal_encoding(), PHP_EOL;
} else {
    echo "mbstring is not loaded", PHP_EOL;
}
'

After changing a global php.ini, the relevant PHP-FPM or Apache process usually needs a reload or restart. .user.ini behaves differently: PHP may cache per-directory INI files, so a change may not become visible immediately. If the setting was changed there, check user_ini.cache_ttl and repeat the diagnostic after the configuration has been reloaded.

What did mbstring.func_overload actually change, and why was it dangerous?

mbstring.func_overload was problematic not because it supported UTF-8, but because it could silently change the meaning of ordinary PHP functions. The code called strlen(), yet with string overloading enabled it could receive multibyte-aware behavior. The source file still looked exactly the same as it did on a server without overload.

The simplest example is the length of a UTF-8 string:

<?php
$text = 'Привет';

echo strlen($text), PHP_EOL;
echo mb_strlen($text, 'UTF-8'), PHP_EOL;
?>

In modern PHP, strlen() counts bytes. mb_strlen() with UTF-8 counts characters. For Cyrillic text the numbers differ, and that difference is normal: the functions answer different questions.

With func_overload, the danger existed at the global configuration level. A library could expect strlen() to return a byte length, while the server configuration changed the semantics of that call. Moving the same source code to another server could therefore change behavior without a single source code modification.

Historically, mbstring.func_overload used a bitmask. One of the old flags also covered mail-related functions; this should not be confused with additional parameters passed to PHP mail(), which concern the mail transport rather than mbstring overloading. During migration, the string-function flag is usually the most relevant one. Auditing only strlen(), substr(), and strpos() is not enough: the old mechanism also affected functions such as strrpos(), stripos(), strripos(), strstr(), stristr(), strrchr(), and substr_count().

Operation Standard function Multibyte counterpart Where the difference matters
Length strlen() mb_strlen() Usernames, titles, comment limits
Substring substr() mb_substr() Truncating UTF-8 text
Finding a position strpos() mb_strpos() Character positions in multibyte strings
Reverse position search strrpos() mb_strrpos() Parsers and suffix handling
Case-insensitive search stripos() mb_stripos() Non-ASCII text
Counting occurrences substr_count() mb_substr_count() Text processing and parsing

If an application validates the length of a user's display name, it probably needs characters. If a library checks a binary buffer, network payload, or the raw bytes of a cryptographic value, it needs byte semantics instead. A generic variable name such as $data tells you nothing.

Do not globally replace strlen() with mb_strlen(). First determine the semantics of the data. A PHP string can contain user-facing UTF-8 text, ASCII, serialized content, or arbitrary bytes, and those cases require different operations.

What happens to mbstring.func_overload in PHP 7.2 and PHP 8?

The behavior depends on the PHP generation. Before PHP 7.2, mbstring.func_overload was an active setting. In PHP 7.2 through 7.4 the mechanism still existed but was deprecated. PHP 8 removed it completely.

PHP version Status What you should expect What to do
Before PHP 7.2 Available The directive may have an active value Record the configuration before migration
PHP 7.2–7.4 Deprecated The directive may still be present Remove the application's dependency on it
PHP 8.x Removed The directive does not exist Fix the application or outdated checker

On PHP 8, you can verify the state with:

php -v
php -i | grep -i func_overload

php -r 'var_dump(ini_get("mbstring.func_overload"));'

An empty grep result and false from ini_get() do not require a fix. That is expected. Do not search for another mbstring package or keep adding mbstring.func_overload = 0 to different INI files.

There is another legacy trace that is easy to miss: MB_OVERLOAD_MAIL, MB_OVERLOAD_STRING, and MB_OVERLOAD_REGEX. Older compatibility code sometimes checked these constants in addition to the INI setting. In PHP 8 they are no longer part of a usable overload mechanism.

rg "MB_OVERLOAD_(MAIL|STRING|REGEX)" /path/to/site

If the application fails specifically because it references an old overload constant, that is direct evidence of incompatible code. The configuration cannot fix it.

Another case is an old checker:

$overload = ini_get('mbstring.func_overload');

if ($overload != 0) {
    // application reports configuration error
}

On older PHP versions this logic could make sense because the application wanted to make sure overload was disabled. On PHP 8 the check itself is obsolete because the mechanism cannot be enabled anymore. If the application code does not otherwise depend on func_overload, fix the compatibility condition instead of bringing back an old PHP runtime.

What does mbstring.internal_encoding mean today, and how is it different from mb_internal_encoding()?

mbstring.internal_encoding and mb_internal_encoding() are not two ways to express the same configuration. The first is a deprecated INI directive. The second is an mbstring API function that reads or changes the current internal encoding.

This distinction becomes especially visible during migrations. An administrator runs:

var_dump(ini_get('mbstring.internal_encoding'));
var_dump(mb_internal_encoding());

and sees that the first result is empty while the second returns UTF-8. That is not necessarily a contradiction. An empty legacy directive does not mean that mbstring has no current internal encoding.

What does ini_get('mbstring.internal_encoding') show?

ini_get() reads the specific INI directive. mbstring.internal_encoding has been deprecated since PHP 5.6, so modern code should not be designed around it. The fact that an old php.ini contained:

mbstring.internal_encoding = UTF-8

does not mean the same line should be copied to a new PHP 8 server.

What does mb_internal_encoding() show?

mb_internal_encoding() returns the current internal character encoding used by mbstring when a corresponding function does not receive an explicit encoding.

<?php
if (!extension_loaded('mbstring')) {
    exit('mbstring is not loaded');
}

echo mb_internal_encoding(), PHP_EOL;
?>

Application code can also change the value for the current execution context:

mb_internal_encoding('UTF-8');

This does not convert incoming data to UTF-8. If the application receives Windows-1251 bytes, calling mb_internal_encoding('UTF-8') does not transform those bytes.

Explicit encoding has a different meaning

When the encoding of a specific value is known, it can be passed directly to the function:

$length = mb_strlen($title, 'UTF-8');
$part   = mb_substr($title, 0, 50, 'UTF-8');

This code depends less on global internal encoding. During migration that is useful because the expected encoding is visible directly in the source.

Internal encoding and regex encoding are separate

mb_regex_encoding() controls the encoding used by multibyte regular expressions separately. Calling:

mb_internal_encoding('UTF-8');

does not mean that regex-related encoding settings can be ignored if the application uses multibyte regex functions.

mbstring.internal_encoding

A deprecated INI directive. Do not copy it automatically from an old PHP configuration.

mb_internal_encoding()

An active API for reading or changing the current mbstring internal encoding.

Encoding passed to a function

Defines the encoding for a specific operation and makes the code's expectations explicit.

So the situation where the INI directive is empty while mb_internal_encoding() returns UTF-8 is not automatically an error. The actual problem begins when the application expects one encoding but the data or the function uses another.

What should replace mbstring.func_overload in modern PHP?

There is no direct replacement for mbstring.func_overload in PHP 8, and one is not needed. Instead of changing function behavior globally, modern code should explicitly use byte-oriented string functions for byte data and mb_* functions when it needs multibyte character semantics.

The fact that a variable has the PHP type string is not enough to make that decision. A PHP string may contain a username, JSON, a hexadecimal hash, Base64 data, a filename, an HTTP payload, or an arbitrary binary buffer. Determine the meaning of the value first.

Example data What usually needs to be measured Typical approach
Username Characters mb_strlen()
Article title Characters mb_strlen(), mb_substr()
UTF-8 description Characters mbstring with a known encoding
Hex hash Fixed ASCII representation Standard string functions may be correct
Raw binary hash Bytes strlen()
UUID ASCII string format Standard functions after format validation
Base64 ASCII representation or decoded bytes, depending on the stage First determine what the variable contains
Binary payload Bytes Standard string functions
Data with unknown encoding Encoding must be checked first Do not apply mb_* blindly

For example, a user-visible name limit can be written explicitly:

<?php
$name = 'Олександр Петров';

if (mb_strlen($name, 'UTF-8') > 20) {
    $name = mb_substr($name, 0, 20, 'UTF-8');
}
?>

For a binary block with a defined byte length, ordinary strlen() remains the correct choice:

<?php
$expectedBytes = 32;

if (strlen($binaryBlock) !== $expectedBytes) {
    throw new RuntimeException('Unexpected binary block length');
}
?>

Replacing strlen() with mb_strlen() here would not make the code more modern. It would change the meaning of the check.

JSON cannot automatically be placed in one category either. If you are enforcing a byte-size limit on a prepared HTTP payload, you need the number of bytes. If you are validating the length of a decoded name field, character-aware logic may be required.

Filenames have the same ambiguity. A user-facing filename length limit and the actual byte length accepted by a filesystem are separate concerns. There is no safe global rule that says all ordinary string functions should become mbstring functions.

Migration rule: first determine whether the value represents human-readable text or a byte sequence. Only then choose between strlen()/substr() and mb_strlen()/mb_substr().

How do you find code that actually depended on mbstring.func_overload?

Searching only for the text mbstring.func_overload is not enough. A legacy application may never read the directive directly and still depend on it because developers wrote ordinary strlen() calls expecting the server configuration to change their behavior.

Start with direct dependencies

rg "mbstring\.func_overload|MB_OVERLOAD_(MAIL|STRING|REGEX)" .

Without ripgrep:

grep -R -n "mbstring.func_overload" .
grep -R -n "MB_OVERLOAD_" .

If several matches are found, classify them. An environment check is one scenario. A condition that selects different functions based on overload is another. A compatibility wrapper that changes application behavior deserves more attention than a comment or README reference.

Then search for potentially overloaded functions

rg "\b(strlen|substr|strpos|strrpos|stripos|strripos|strstr|stristr|strrchr|substr_count|strtolower|strtoupper)\s*\(" .

In a large project this can easily return hundreds of matches. That does not mean hundreds of lines need to be rewritten. At this stage, grep only shows where to investigate.

Filter the results first:

  • tests and fixtures that do not participate in production execution;
  • generated files;
  • vendor code that should be upgraded through Composer rather than manually edited;
  • helpers that clearly work with byte-oriented data;
  • application code that genuinely manipulates user-facing text.
Code found What to determine Possible action
ini_get('mbstring.func_overload') Compatibility check or runtime logic? Update the checker or rewrite the dependent code
defined('MB_OVERLOAD_STRING') Legacy compatibility layer? Remove the dependency on the old API
strlen($title) Bytes or characters? Possibly replace with mb_strlen()
strlen($rawHash) Raw bytes? Keep strlen()
substr($text, 0, 50) User-facing UTF-8 text? Possibly use mb_substr()
strpos($buffer, ...) Text or a binary protocol? Do not change it until the data is classified

What to do if func_overload is found in vendor

Manually editing a file under vendor/ is a poor first step. The next composer install or dependency update may remove the change while leaving the underlying compatibility issue unresolved.

If the path looks like this:

vendor/example/legacy-library/src/Compatibility.php

identify the package first:

composer show example/legacy-library

Then check whether a newer release is available:

composer outdated example/legacy-library

You need to determine three things: whether a PHP 8 compatible release exists, whether the current composer.json permits it, and whether the CMS itself is tied to the old package branch. Updating an old library blindly can create a larger compatibility problem than the original mbstring issue.

If a current package release has already removed the func_overload dependency, upgrading the dependency and testing is the clean path. If the application is pinned to an obsolete package version, solve the problem at application level or use a controlled patch mechanism instead of modifying production files inside vendor by hand.

Test suspicious code instead of guessing from function names

For every candidate, define the expected behavior. For example:

$title = 'Привет PHP';

var_dump(strlen($title));
var_dump(mb_strlen($title, 'UTF-8'));

If the UI rule says “no more than 20 characters,” the correct semantic is clear. If the value belongs to a binary format where the field size is defined in bytes, the other result is correct.

Let the test confirm the hypothesis. Not the other way around.

Why can mbstring.internal_encoding, default_charset, and explicit UTF-8 produce different results?

default_charset, the current mbstring internal encoding, and the encoding passed directly to a function operate at different levels. Seeing default_charset = UTF-8 in php.ini does not prove that every $value in the application contains valid UTF-8.

Check the actual values first

<?php
echo 'default_charset: ';
var_dump(ini_get('default_charset'));

echo 'legacy mbstring.internal_encoding: ';
var_dump(ini_get('mbstring.internal_encoding'));

if (extension_loaded('mbstring')) {
    echo 'mb_internal_encoding(): ';
    var_dump(mb_internal_encoding());
}
?>

If the legacy INI directive is empty while mb_internal_encoding() returns UTF-8, do not immediately try to synchronize them by adding another line to php.ini. In modern PHP, the more useful questions are which internal encoding is active and where the data actually came from.

What changes when an mb_* function receives no encoding?

Consider mb_strlen():

<?php
$text = 'Привет';

echo mb_internal_encoding(), PHP_EOL;

var_dump(mb_strlen($text));
var_dump(mb_strlen($text, 'UTF-8'));
?>

The first call uses the current internal encoding. The second explicitly states how the string must be interpreted. During a server migration, the second form is easier to audit because the expected encoding is visible in the source code.

This does not mean every mbstring call must specify an encoding argument. But when the input format is known and the result matters, an explicit value removes a hidden dependency on the environment.

What happens when mb_convert_encoding has no from_encoding?

A similar dependency appears with mb_convert_encoding(). If the source encoding is not passed explicitly, the function relies on the current internal encoding. This code:

$converted = mb_convert_encoding($value, 'UTF-8');

depends more heavily on the runtime configuration than this version:

$converted = mb_convert_encoding(
    $value,
    'UTF-8',
    'Windows-1251'
);

If the old server's internal encoding happened to match the incoming data and the new server's did not, the symptom may look like “PHP 8 broke the encoding.” The real issue is that the application relied on an implicit source encoding for years.

Check whether the input is valid UTF-8 at all

Another trap is assuming that a correct default_charset repairs invalid or incorrectly encoded bytes. Before changing mbstring configuration, validate the input:

<?php
if (!mb_check_encoding($value, 'UTF-8')) {
    echo 'Input is not valid UTF-8';
}
?>

If mb_check_encoding() returns false, setting default_charset = UTF-8 does not make the data valid UTF-8. Check the source: an old file, import process, external API, incorrect conversion, or data that was already damaged before the migration.

default_charset = UTF-8 does not convert data. It does not repair a Windows-1251 file, change the MySQL connection charset, or restore a string that was corrupted at an earlier stage.

The database is a separate boundary as well. PHP may use UTF-8 while the MySQL connection uses the wrong character set. If the database environment also changed during migration, you can first check the MySQL version and then compare connection charset and application settings. Changing mbstring in this situation only hides the symptom.

A useful diagnostic order for a suspicious string is: validate it with mb_check_encoding(), check the active internal encoding, and then inspect the point where the value was read or converted. Do not start by restoring a deprecated INI directive.

If the problem appears inside a specific CMS, the CMS encoding itself may be another layer of the investigation. For Bitrix projects, for example, it can be useful to separately verify the Bitrix site encoding settings instead of trying to repair CMS-level encoding through mbstring.internal_encoding.

How do you safely migrate a legacy PHP application from func_overload to PHP 8?

Build the migration around application behavior rather than a list of lines in php.ini. Record the old runtime and baseline results first, remove implicit dependencies second, and only then compare the application under PHP 8.

Record the old runtime

On the source server, save at least:

php -v
php --ini
php -i | grep -i mbstring
php -r 'echo ini_get("default_charset"), PHP_EOL;'

php -r '
if (extension_loaded("mbstring")) {
    echo mb_internal_encoding(), PHP_EOL;
}
'

For a website served by PHP-FPM, collect the same information through the Web SAPI. If CLI uses PHP 7.4 while the site runs on PHP-FPM 7.3, both values belong in the migration notes.

If the old runtime still supports func_overload, record its active value as well:

php -r 'var_dump(ini_get("mbstring.func_overload"));'

Do not change the legacy setting before establishing a baseline. Otherwise you lose the behavior you are supposed to compare against PHP 8.

Build a set of control operations

You do not need to test every page manually. Concentrate on operations where string semantics affect the result:

  • creating and editing content containing Cyrillic or other multibyte text;
  • username, title, and comment length limits;
  • excerpt truncation;
  • substring search;
  • case-insensitive search;
  • import and export;
  • JSON and external APIs;
  • filenames;
  • authentication, tokens, and hashes;
  • cron jobs;
  • CLI scripts;
  • queues or separate workers if the application uses them.

Record an expected result for each test. “The form works” is too vague. A useful baseline says, for example, “a 30-character Cyrillic username is accepted and the 31st character is rejected” or “the excerpt is truncated to 100 characters without a broken UTF-8 sequence.”

Remove the implicit dependency on overload

Legacy code such as:

if (strlen($name) > 30) {
    $name = substr($name, 0, 30);
}

may need to become:

if (mb_strlen($name, 'UTF-8') > 30) {
    $name = mb_substr($name, 0, 30, 'UTF-8');
}

but only if $name really is UTF-8 user-facing text and the business rule is expressed in characters. Applying the same refactoring to a binary buffer would be incorrect.

Resolve Composer dependencies before switching PHP

If func_overload appears under vendor/, identify the package and version:

composer show vendor/package

Then check whether a compatible release exists:

composer outdated vendor/package

If upgrading the dependency requires jumping through several major CMS or framework releases, that is a separate migration risk. Do not mix it into one uncontrolled mbstring change. Determine the smallest compatible upgrade path first.

Compare the old and new runtimes on staging

Repeat the same scenarios on a staging copy running PHP 8. An HTTP 200 response from the homepage does not prove that string processing is correct.

Test Old PHP PHP 8 Success criterion
Cyrillic username length Recorded result Repeated result Matches the business rule in characters
UTF-8 title truncation Control text The same text No multibyte character is split
Binary length Number of bytes The same number Byte semantics remain unchanged
Import of a legacy file Control result Repeated import No new encoding corruption appears
cron/CLI Control output New output No differences caused by another SAPI or configuration

After switching, verify more than the Web runtime

A common legacy-server pattern is that the site works correctly after changing the FPM version while a nightly cron job still executes the system CLI PHP. Everything looks fine during the day, and then an import or background task corrupts strings overnight.

After migration, compare:

php -v
php --ini

with the Web diagnostics. If cron uses an absolute path to a PHP binary, check that exact executable. If a supervisor or systemd worker is involved, verify which PHP executable the process actually launches.

A controlled migration is not “switch PHP and watch the error log.” You need a known baseline, targeted code changes, and the same data passing through the new runtime afterward.

How do you verify that the application works correctly after removing legacy mbstring settings?

After migration, there is no need to repeat the entire php.ini audit. The final test answers a different question: does the application process text and byte-oriented data correctly after the legacy behavior is gone?

Test UTF-8 with real operations

A minimal smoke test can be placed in a temporary PHP file:

<?php
$tests = [
    'ASCII',
    'Привет',
    'Україна',
    'PHP Привет',
    'Test 123 Привіт'
];

foreach ($tests as $value) {
    echo $value, PHP_EOL;
    echo 'valid UTF-8: ';
    var_dump(mb_check_encoding($value, 'UTF-8'));
    echo 'bytes: ', strlen($value), PHP_EOL;
    echo 'chars: ', mb_strlen($value, 'UTF-8'), PHP_EOL;
    echo PHP_EOL;
}
?>

This test does not prove full application compatibility, but it clearly separates three concepts: UTF-8 validity, byte length, and character length.

Test truncation and search

<?php
$text = 'Тестовая строка PHP';

echo mb_substr($text, 0, 8, 'UTF-8'), PHP_EOL;
var_dump(mb_strpos($text, 'PHP', 0, 'UTF-8'));
?>

Then repeat equivalent operations through the actual forms, templates, and APIs of the application. The helper script verifies the mechanics; the application workflow verifies the integration.

Test invalid input

If the application imports legacy files or accepts data from external systems, do not test only clean UTF-8. Incoming data may contain invalid byte sequences.

if (!mb_check_encoding($value, 'UTF-8')) {
    // log or reject according to application rules
}

A migration should not leave you in a state where the old environment silently tolerated the wrong encoding while the new code silently converts it using an incorrect assumption.

Test byte-sensitive code

Select at least one path that genuinely requires bytes: a binary file, raw hash, protocol field, or another known binary format. Verify that it was not converted to mb_* functions together with user-facing text.

This is a classic mass-refactoring mistake: the Cyrillic text issue is fixed, but at the same time the semantics of code unrelated to text are changed.

Test Web, cron, and CLI separately

Identical source code does not guarantee an identical runtime. If HTTP requests use PHP-FPM 8.2 while cron launches /usr/bin/php 8.0 with another php.ini, browser tests do not cover background tasks.

After running cron or CLI jobs, inspect the application log and PHP error log. Pay particular attention to undefined constants or functions in old compatibility code and to places where string lengths or positions changed after the upgrade.

Acceptance test after an mbstring migration
  1. Enter and save ASCII text.
  2. Repeat with Cyrillic and other UTF-8 text used by the application.
  3. Test user-facing field length limits.
  4. Test title and excerpt truncation.
  5. Test search and character positions in multibyte strings.
  6. Test at least one external or imported data source.
  7. Use mb_check_encoding() where the input encoding may be uncertain.
  8. Test a byte-sensitive code path separately from text processing.
  9. Run cron and CLI tasks using the same commands used in production.
  10. Inspect the PHP error log and application log after the tests.
The migration can be considered successful when:
  • the old checker or application code no longer requires mbstring.func_overload to exist;
  • UTF-8 strings pass validation, length limits, truncation, and search with the expected results;
  • no corrupted characters appear;
  • invalid input is detected where the application is supposed to validate it;
  • byte-oriented operations retain byte semantics;
  • Web, cron, and CLI use the expected PHP versions and produce correct results;
  • no new compatibility errors appear in the logs after the control tests.

If these checks pass, the absence of mbstring.func_overload in PHP 8 is no longer a problem: the application no longer depends on the hidden behavior of the legacy runtime. That is the actual goal of the migration.

Frequently asked questions
No. mbstring.func_overload was removed in PHP 8. If legacy code or an installer still requires it, the compatibility check or application code must be updated.
No. The directive no longer exists in PHP 8. If an application still expects it, locate the outdated checker or legacy dependency instead.
It is a different case from func_overload. mbstring.internal_encoding is deprecated and should not be used as the basis of new code, while mb_internal_encoding() remains a separate API.
Because they report different things. ini_get() reads the legacy INI directive, while mb_internal_encoding() returns the current internal encoding used by mbstring.
No. Use mb_strlen() when you need the number of multibyte characters. Keep strlen() where the code intentionally measures bytes.
Use mb_check_encoding($value, 'UTF-8'). If it returns false, changing default_charset or internal encoding will not repair the invalid byte sequence.
CLI and the website may use different PHP versions, SAPIs, and php.ini files. Compare php -v and php --ini with PHP_VERSION, PHP_SAPI, and php_ini_loaded_file() through the Web runtime.
Related articles
Setting mbstring.func_overload for Bitrix
mbstring.internal_encoding — Complete PHP Guide
mbstring.func_overload Bitrix — Complete Configuration Guide