mb_ucfirst(mb_strtolower($word)),
self::splitWords($value)
);
return implode('', $words);
}
/**
* Convert a string to camelCase.
*/
public static function camelCase(string $value): string
{
return mb_lcfirst(self::pascalCase($value));
}
/**
* Split a string into words, respecting Unicode word boundaries.
*/
public static function words(string $value, int $words = 100, string $end = ' ...'): string
{
preg_match('/^\s*+(?:\S+\s*+){1,' . $words . '}/u', $value, $matches);
if (! isset($matches[0]) || mb_strlen($value) === mb_strlen($matches[0])) {
return $value;
}
return rtrim($matches[0]) . $end;
}
/**
* Sanitizes and normalizes a text string (strips HTML, removes non-printable control characters, ensures valid UTF-8).
*/
public static function sanitize(?string $value): ?string
{
if ($value === null || $value === '') {
return $value;
}
// Decode HTML entities then strip tags
$value = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$value = strip_tags($value);
// Normalize newlines, temporarily convert to
to preserve paragraph breaks
$value = str_replace(["\r\n", "\r"], "\n", $value);
$value = str_replace("\n", '
', $value);
// Ensure valid UTF-8 (handling Windows-1252 / ISO-8859-1 legacy strings)
$value = StringEncoder::detectAndConvertToUtf8($value);
// Remove astral symbols (4-byte UTF-8) that may break storage/processing
$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $value) ?? '';
// Remove control/non-printable characters but keep TAB(0x09), LF(0x0A) and CR(0x0D)
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\xA0\xAD]/u', '', $value) ?? '';
// Revert
back to newlines and final strip_tags as safety
$value = str_replace('
', "\n", $value);
return strip_tags($value);
}
/**
* Split an arbitrary string into individual word tokens.
*
* @return list
*/
private static function splitWords(string $value): array
{
// 1. Separate camelCase transitions (e.g., "fooBar" -> "foo Bar")
$value = preg_replace('/(?<=[a-z0-9])([A-Z])/u', ' $1', $value) ?? $value;
// 2. Separate acronyms followed by regular words (e.g., "HTTPResponse" -> "HTTP Response")
$value = preg_replace('/(?<=[A-Z])([A-Z][a-z])/u', ' $1', $value) ?? $value;
// 3. Replace non-alphanumeric characters with whitespace
$value = preg_replace('/[^\p{L}\p{N}]+/u', ' ', $value) ?? $value;
return array_values(array_filter(
explode(' ', trim($value)),
static fn (string $word): bool => $word !== ''
));
}
}