doctrine/inflector · error · RuntimeException
preg_replace returned null for value "%s"
Error message
preg_replace returned null for value "%s"
What it means
tableize() converts CamelCase to snake_case via preg_replace with the 'u' (UTF-8) modifier on the pattern '~(?<=\w)([A-Z])~u' (src/Inflector.php:234). PCRE refuses to process a subject that is not valid UTF-8, makes preg_replace return null, and the library throws RuntimeException instead of returning a broken table name. So this error almost always means the string you passed contains byte sequences that are not valid UTF-8 — legacy single-byte encodings, binary data, or a multibyte character cut in half.
Source
Thrown at src/Inflector.php:237
/** @var WordInflector */
private $pluralizer;
public function __construct(WordInflector $singularizer, WordInflector $pluralizer)
{
$this->singularizer = $singularizer;
$this->pluralizer = $pluralizer;
}
/**
* Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.
*/
public function tableize(string $word): string
{
$tableized = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $word);
if ($tableized === null) {
throw new RuntimeException(sprintf(
'preg_replace returned null for value "%s"',
$word
));
}
return mb_strtolower($tableized);
}
/**
* Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'.
*/
public function classify(string $word): string
{
return str_replace([' ', '_', '-'], '', ucwords($word, ' _-'));
}
/**
* Camelizes a word. This uses the classify() method and turns the first character to lowercase.View on GitHub (pinned to 288d99b85c)
Solutions
- Find where the non-UTF-8 bytes enter (the exception message includes the offending value) and fix the source: database charset, file encoding, or upstream API output.
- Sanitize at the boundary before calling tableize(): if (!mb_check_encoding($word, 'UTF-8')) { $word = mb_convert_encoding($word, 'UTF-8', 'Windows-1252'); } using the real source encoding.
- Replace substr() with mb_substr() (or mb_strcut()) anywhere multibyte strings can be truncated.
- For MySQL, connect with utf8mb4 (charset in the DSN or SET NAMES utf8mb4) so reads are valid UTF-8.
- If the cause is unclear, reproduce with the same value and call preg_last_error_msg() right after the failing preg_* call to confirm PREG_BAD_UTF8_ERROR.
Example fix
// before
$table = $inflector->tableize($rawName); // $rawName is Windows-1252 or truncated UTF-8 -> RuntimeException
// after
if (! mb_check_encoding($rawName, 'UTF-8')) {
$rawName = mb_convert_encoding($rawName, 'UTF-8', 'Windows-1252'); // adjust to the actual source encoding
}
$table = $inflector->tableize($rawName); Defensive patterns
Strategy: validation
Validate before calling
use Doctrine\Inflector\Inflector;
if (! mb_check_encoding($word, 'UTF-8')) {
$word = mb_convert_encoding($word, 'UTF-8', 'Windows-1252'); // use the actual source encoding
}
$table = $inflector->tableize($word); Type guard
function isValidUtf8(string $value): bool
{
return mb_check_encoding($value, 'UTF-8');
} Try / catch
use RuntimeException;
try {
$table = $inflector->tableize($word);
} catch (RuntimeException $e) {
if (str_contains($e->getMessage(), 'preg_replace returned null')) {
// input was not valid UTF-8: re-encode once and retry, or reject at this boundary
$table = $inflector->tableize(mb_convert_encoding($word, 'UTF-8', 'Windows-1252'));
} else {
throw $e;
}
} Prevention
- Validate encoding at system boundaries (HTTP input, DB reads, file imports) with mb_check_encoding().
- Use mb_substr()/mb_strcut() instead of substr() on any string that may contain multibyte characters.
- Configure MySQL connections as utf8mb4 and keep source files as UTF-8.
- Never pass binary data or raw uploads directly to tableize().
When it happens
Trigger: Calling $inflector->tableize($word) where $word is not valid UTF-8: text read from a latin1/Windows-1252 database column or CSV/Excel file, scraped filenames or external payloads, or a UTF-8 string truncated mid-character by substr() instead of mb_substr(). The 'u' modifier makes PCRE raise PREG_BAD_UTF8_ERROR, preg_replace returns null, and the null check at src/Inflector.php:236 throws.
Common situations: MySQL connection not set to utf8mb4 so retrieved text arrives as latin1; imports of Windows-1252 files; user-uploaded filenames passed through tableize(); strings sliced with substr() on multibyte content; data pipelines where an upstream system encodes to ISO-8859-1. Often appears after a data migration or a change of data source, and surfaces deep inside ORM/console tooling that tableizes entity or class names.
Related errors
AI-assisted analysis of doctrine/inflector@288d99b85c (2026-08-21).
Data as JSON: /api/errors/e5083125373eb3af.
Report an issue: GitHub.