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 !== '' )); } }