Multibyte-Strings in PHP 8.4: Umlaute und Unicode korrekt verarbeiten
AI generated
<?php
8.4
PHP · Strings · Encoding · Unicode
Multibyte-Strings in PHP 8.4:
Umlaute, UTF-8 und mb_-Funktionen richtig verarbeiten

strlen() zählt Bytes, nicht Zeichen, und liefert bei Umlauten und Unicode-Zeichen fast immer falsche Werte. Dieser Leitfaden zeigt, wie UTF-8 kodiert ist, warum mb_-Funktionen für Multibyte-Strings unverzichtbar sind und wie Truncation, Sortierung, Escaping und reguläre Ausdrücke mit Umlauten in PHP 8.4 zuverlässig funktionieren.

18 Min. Lesezeit mb_strlen · mb_substr · UTF-8 · Collator PHP 8.4

1. Warum strlen() bei Umlauten und Unicode falsche Ergebnisse liefert

Die Funktion strlen() zählt Bytes, keine Zeichen. Solange ein String nur aus ASCII-Zeichen besteht, ist das kein Problem, weil jedes ASCII-Zeichen genau ein Byte belegt. Sobald aber Umlaute, das ß oder andere Unicode-Zeichen im String stehen, liefert strlen() einen Wert, der höher ist als die tatsächliche Zeichenanzahl. Der String "Größe" hat fünf Zeichen, aber sieben Bytes, weil ö und ß in UTF-8 jeweils zwei Bytes belegen. Wer das nicht weiß, baut auf Basis von strlen() Prüfungen, die bei Multibyte-Strings systematisch falsche Ergebnisse produzieren.

Das betrifft nicht nur akademische Sonderfälle, sondern alltägliche Aufgaben: Längenvalidierung von Formularfeldern, das Kürzen von Produktnamen für Meta-Tags, die Begrenzung von Kommentarlängen oder das Abschneiden von Texten für Vorschau-Snippets. Wird dabei substr() statt mb_substr() verwendet, kann ein Mehrbyte-Zeichen mitten durchgeschnitten werden. Das Ergebnis ist eine ungültige UTF-8-Byte-Sequenz, die im Browser als Ersatzzeichen, als Fragezeichen im Kästchen oder im schlimmsten Fall als komplett leerer String nach einer Escaping-Funktion erscheint.

In deutschsprachigen Projekten ist dieses Problem besonders relevant, weil Umlaute und das ß in praktisch jedem Datensatz vorkommen können: Kundennamen, Adressen, Produktbezeichnungen, Freitextfelder. Ein Shop-System, das strlen() zur Validierung der maximalen Zeichenlänge eines Firmennamens nutzt, schneidet bei einem Namen mit vielen Umlauten faktisch früher ab, als der Nutzer erwartet, und der Fehler fällt oft erst im Live-Betrieb auf, wenn ein konkreter Kunde einen Namen mit Sonderzeichen eingibt.


<?php

declare(strict_types=1);

// German product attribute containing umlauts (5 characters, 7 bytes in UTF-8)
$label = 'Größe';

echo strlen($label) . PHP_EOL;     // 7 - counts bytes, not characters
echo mb_strlen($label) . PHP_EOL;  // 5 - counts actual characters

// Byte-based truncation can cut a multibyte character in half
$brokenCut = substr($label, 0, 3);
$correctCut = mb_substr($label, 0, 3);

// $brokenCut ends with a lone lead byte of "oe" (0xC3) - invalid UTF-8
// $correctCut is "Grö" - three full characters, valid UTF-8
var_dump(mb_check_encoding($brokenCut, 'UTF-8'));  // false
var_dump(mb_check_encoding($correctCut, 'UTF-8')); // true

2. Byte vs. Zeichen: wie UTF-8 kodiert ist und warum das zählt

UTF-8 ist eine Kodierung mit variabler Byte-Länge. ASCII-Zeichen (Codepunkte 0 bis 127) belegen genau ein Byte und sind zu klassischem 7-Bit-ASCII identisch, was UTF-8 abwärtskompatibel macht. Zeichen außerhalb dieses Bereichs, wie deutsche Umlaute, das ß oder kyrillische und asiatische Schriftzeichen, belegen zwei, drei oder vier Bytes. Das führende Byte einer Mehrbyte-Sequenz trägt in seinen oberen Bits die Information, wie viele Folgebytes zur Sequenz gehören, die Folgebytes selbst beginnen alle mit dem Bitmuster 10. Diese Struktur erlaubt es, eine UTF-8-Sequenz an jeder Stelle im String zu erkennen und zu validieren, ohne den gesamten String von vorne parsen zu müssen.

Genau diese Struktur wird verletzt, wenn eine byteorientierte Funktion wie substr() oder str_split() eine Mehrbyte-Sequenz mittendrin abschneidet. Das Ergebnis ist kein gültiges UTF-8 mehr, sondern ein String mit einem verwaisten Lead-Byte oder Folgebyte. Viele nachgelagerte Funktionen reagieren auf ungültiges UTF-8 nicht mit einer Exception, sondern mit stillem Fehlverhalten: htmlspecialchars() kann ohne den richtigen Fallback einen leeren String zurückgeben, json_encode() schlägt fehl und liefert false, und Datenbanktreiber können die komplette restliche Zeichenkette verwerfen. Der Fehler entsteht an einer Stelle im Code, zeigt sich aber oft erst mehrere Funktionsaufrufe später.

Wichtig ist außerdem die Unterscheidung zwischen Unicode-Codepunkten und sogenannten Graphem-Clustern. Ein einzelnes sichtbares Zeichen kann aus mehreren Codepunkten bestehen, etwa ein Buchstabe mit einem separaten kombinierenden Akzent-Zeichen, oder ein Emoji mit Hautton-Modifikator. Die mb_-Funktionen von PHP arbeiten auf Codepunkt-Ebene, nicht auf Graphem-Ebene. Für die allermeisten deutschsprachigen Anwendungsfälle mit Umlauten und ß reicht das, weil diese Zeichen als einzelne, vorkomponierte Codepunkte kodiert sind. Wer jedoch mit komplexen Emoji-Kombinationen oder Schriftsystemen mit kombinierenden Zeichen arbeitet, sollte zusätzlich die grapheme_*-Funktionen der intl-Erweiterung in Betracht ziehen.

3. Die mbstring-Erweiterung: mb_strlen, mb_substr, mb_strtoupper korrekt einsetzen

Die mbstring-Erweiterung stellt für praktisch jede native String-Funktion ein Multibyte-fähiges Gegenstück bereit, das auf Zeichen statt auf Bytes arbeitet. mb_strlen() zählt Zeichen, mb_substr() schneidet an Zeichengrenzen, mb_strtoupper() und mb_strtolower() wandeln Groß- und Kleinschreibung unter Berücksichtigung der übergebenen Kodierung um. In den meisten modernen PHP-Distributionen, auch in Standard-Docker-Images, ist mbstring bereits als Standarderweiterung aktiviert, muss also in der Regel nicht separat installiert werden, sollte aber in composer.json als ext-mbstring explizit deklariert werden, damit die Abhängigkeit dokumentiert und bei Deployments geprüft wird.

Jede mb_-Funktion akzeptiert einen optionalen Encoding-Parameter, der angibt, in welcher Kodierung die Eingabe interpretiert werden soll. Wird der Parameter weggelassen, greift PHP auf die interne Kodierung zurück, die über mb_internal_encoding() gesetzt wird und standardmäßig UTF-8 ist. In der Praxis ist es sinnvoll, den Encoding-Parameter trotzdem explizit mitzugeben, wenn eine Funktion mit Daten aus unsicherer Quelle arbeitet, etwa mit Datei-Uploads oder externen APIs, deren Kodierung nicht garantiert UTF-8 ist.

Ein Detail, das häufig übersehen wird: mb_strtoupper() behandelt das deutsche ß in der Standardeinstellung nicht automatisch als "SS", weil die klassische Großschreibregel des ß historisch uneinheitlich gehandhabt wurde und der eigentliche Großbuchstabe ẞ erst 2017 offiziell in den deutschen Duden aufgenommen wurde. Je nach ICU-Version und PHP-Build kann das Ergebnis variieren, weshalb bei rechtlich relevanten Ausgaben wie Rechnungsadressen ein expliziter Test mit den eigenen Zieldaten sinnvoll ist.


<?php

declare(strict_types=1);

$productName = 'Bücherregal für Küchenutensilien';

// Correct character count regardless of umlauts
$charCount = mb_strlen($productName, 'UTF-8');

// Safe substring: always cuts on character boundaries, never mid-byte
$excerpt = mb_substr($productName, 0, 20, 'UTF-8');

// Correct uppercase conversion for German umlauts
$upper = mb_strtoupper($productName, 'UTF-8');

// Naive strtoupper() leaves multibyte characters untouched (ASCII-only)
$naiveUpper = strtoupper($productName);

echo $charCount . PHP_EOL;   // 32
echo $excerpt . PHP_EOL;     // "Bücherregal für Küch" (exactly 20 characters)
echo $upper . PHP_EOL;       // "BÜCHERREGAL FÜR KÜCHENUTENSILIEN"
echo $naiveUpper . PHP_EOL;  // "BüCHERREGAL FüR KüCHENUTENSILIEN" - ü stays lowercase

4. Fallstricke bei substr(), str_split() und strpos() mit Mehrbyte-Zeichen

substr() arbeitet grundsätzlich mit Byte-Offsets. Solange Start- und Endposition zufällig auf Zeichengrenzen fallen, funktioniert der Aufruf scheinbar korrekt, aber das ist reiner Zufall und bricht bei der nächsten Textänderung. str_split() ist noch unmittelbarer betroffen: Die Funktion teilt einen String standardmäßig in Blöcke fester Byte-Länge, ganz ohne Rücksicht auf Zeichengrenzen. Bei einem String mit Umlauten erzeugt str_split($text, 1) deshalb keine Liste einzelner Zeichen, sondern eine Liste einzelner Bytes, von denen einige für sich genommen kein gültiges UTF-8 mehr darstellen.

strpos() liefert bei Erfolg eine Byte-Position, keine Zeichenposition. Wird dieser Rückgabewert anschließend an ein normales substr() weitergereicht, ist das intern konsistent, weil beide Funktionen mit Bytes arbeiten. Sobald aber ein Entwickler den Rückgabewert von strpos() mit einer Zeichenanzahl aus mb_strlen() vergleicht oder ihn an mb_substr() übergibt, entsteht ein Offset-Fehler, weil ein Byte-Index und ein Zeichen-Index vermischt werden. Diese Fehler sind besonders tückisch, weil sie sich nicht sofort als Absturz zeigen, sondern als leicht verschobene, augenscheinlich fast korrekte Textausschnitte.

Die Faustregel lautet deshalb: Sobald ein String potenziell Umlaute, das ß oder andere Nicht-ASCII-Zeichen enthalten kann, gehört die mb_-Variante konsequent überall verwendet, nicht nur an einzelnen Stellen. Ein gemischter Einsatz aus strpos() und mb_substr() im selben Datenfluss ist eine der häufigsten Ursachen für schwer reproduzierbare Encoding-Bugs in gewachsenen Codebasen.

Standardfunktion Verhalten bei Umlauten/Unicode mb_-Gegenstück Empfehlung
strlen() zählt Bytes, zu hoher Wert bei Umlauten mb_strlen() immer für Zeichenlängen-Validierung nutzen
substr() kann Mehrbyte-Zeichen mittendrin abschneiden mb_substr() für jede nutzerbezogene Textausgabe verwenden
strtoupper() lässt ä, ö, ü, ß unverändert (ASCII-only) mb_strtoupper() mit explizitem Encoding-Parameter aufrufen
str_split() zerlegt in Bytes statt in Zeichen mb_str_split() für Zeichen-Arrays immer die mb_-Variante
strpos() liefert Byte-Offset, nicht Zeichen-Offset mb_strpos() Offset nie mit mb_substr() mischen

5. Groß-/Kleinschreibung und Sortierung mit Umlauten

Neben der reinen Groß- und Kleinschreibung ist die Sortierung von Strings mit Umlauten ein eigenständiges Problem. Die native sort()-Funktion vergleicht Strings byteweise. In UTF-8 liegen die Bytes von Umlauten und anderen Nicht-ASCII-Zeichen zahlenmäßig deutlich über den Bytes der ASCII-Buchstaben, weil ihr Lead-Byte mit dem Bitmuster 110 oder höher beginnt. Das führt dazu, dass Wörter mit Umlaut am Anfang in einer byteweise sortierten Liste ans Ende rutschen, weit hinter Wörter, die mit "Z" beginnen, was der intuitiven deutschen alphabetischen Reihenfolge widerspricht.

Für korrekte, sprachabhängige Sortierung stellt die intl-Erweiterung die Klasse Collator bereit. Ein Collator, der mit dem Locale de_DE instanziiert wird, sortiert nach den tatsächlichen deutschen Kollationsregeln: Umlaute werden in der Nähe ihres Grundbuchstabens einsortiert, die Groß-/Kleinschreibung wird bei der Primärsortierung meist ignoriert und erst bei völliger Gleichheit als Tie-Breaker herangezogen. Das entspricht dem Sortierverhalten, das Endnutzer aus Adressbüchern, Produktlisten und Telefonverzeichnissen erwarten.

Für Datenbankabfragen gilt eine ähnliche Regel: Die Sortierung sollte möglichst in der Datenbank selbst mit einer passenden Collation wie utf8mb4_de_0900_ai_ci in MySQL erfolgen, statt Datensätze unsortiert zu laden und anschließend in PHP mit sort() zu sortieren. Wo eine Sortierung dennoch in PHP nötig ist, etwa nach dem Zusammenführen mehrerer Datenquellen, ist Collator die einzige verlässliche Wahl für Texte mit Umlauten.


<?php

declare(strict_types=1);

$words = ['Apfel', 'Öl', 'Zug', 'Über', 'ärgerlich'];

// Byte-based sort() compares raw UTF-8 byte values - umlauts land in the wrong place
$byteSorted = $words;
sort($byteSorted);

// Locale-aware sort with the intl extension respects German collation rules
$collator = new Collator('de_DE');
$localeSorted = $words;
$collator->sort($localeSorted);

// $byteSorted places every umlaut-starting word after ASCII "Zug" (wrong for German)
// $localeSorted follows the expected German dictionary order (Apfel, aergerlich, Oel, Ueber, Zug)
print_r($byteSorted);
print_r($localeSorted);

6. Encoding-Erkennung und -Konvertierung: mb_detect_encoding, mb_convert_encoding, iconv

Nicht jede Datenquelle liefert zuverlässig UTF-8. CSV-Exporte aus älteren ERP-Systemen, Windows-Anwendungen und manche E-Mail-Clients liefern häufig ISO-8859-1 oder Windows-1252, in denen Umlaute mit einem einzelnen Byte kodiert sind, das aber ein anderes Zeichen darstellt als der entsprechende UTF-8-Codepunkt. Werden solche Daten ohne Konvertierung in ein UTF-8-System eingespeist, entsteht das klassische Mojibake: lesbare, aber falsche Zeichen wie "ä" statt "ä".

mb_detect_encoding() versucht, die Kodierung eines Strings heuristisch zu bestimmen, ist aber ohne explizite Kandidatenliste unzuverlässig, weil viele Byte-Sequenzen in mehreren Kodierungen gültig sind. In der Praxis sollte die Funktion immer mit einer eingeschränkten, plausiblen Liste an möglichen Kodierungen aufgerufen werden, etwa ['UTF-8', 'Windows-1252', 'ISO-8859-1'], und mit striktem Modus, damit nur wirklich valide Sequenzen erkannt werden. mb_convert_encoding() übernimmt anschließend die eigentliche Konvertierung in die interne Zielkodierung, in nahezu allen modernen PHP-Projekten UTF-8.

Als Alternative oder Ergänzung steht iconv() zur Verfügung, das über die Betriebssystembibliothek libiconv arbeitet und mit den Suffixen //TRANSLIT und //IGNORE zusätzliche Optionen bietet: //TRANSLIT ersetzt Zeichen, die in der Zielkodierung nicht existieren, durch die nächstliegende Annäherung, etwa ein Umlaut durch die entsprechende Umschreibung, während //IGNORE nicht konvertierbare Zeichen stillschweigend entfernt. Diese Optionen sind besonders nützlich, wenn Multibyte-Strings in reine ASCII-Kontexte überführt werden müssen, etwa für Dateinamen, URL-Slugs oder Systeme, die kein Unicode unterstützen.


<?php

declare(strict_types=1);

// CSV export from a legacy system, often Windows-1252 or ISO-8859-1
$rawLine = file_get_contents('legacy-export.csv');

// Detect encoding with an explicit candidate list - never trust blind auto-detection
$detected = mb_detect_encoding($rawLine, ['UTF-8', 'Windows-1252', 'ISO-8859-1'], true);

// Convert to a known internal encoding before any further processing
$normalized = mb_convert_encoding($rawLine, 'UTF-8', $detected ?: 'ISO-8859-1');

// iconv as an alternative, with transliteration for non-ASCII target systems
$transliterated = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $normalized);

var_dump(mb_check_encoding($normalized, 'UTF-8')); // true after normalization
echo $transliterated . PHP_EOL; // umlauts approximated as ASCII, e.g. ae, oe, ue

7. HTML-Ausgabe und Escaping: htmlspecialchars mit korrektem Encoding-Parameter

htmlspecialchars() ist die zentrale Funktion, um nutzergenerierte Inhalte sicher in HTML auszugeben und XSS-Angriffe über spitze Klammern und Anführungszeichen zu verhindern. Bis PHP 8.1 war die Standardkodierung dieser Funktion ISO-8859-1, obwohl längst die meisten Projekte auf UTF-8 liefen. Traf ein UTF-8-String mit Umlauten auf diese falsche Standardannahme, konnte htmlspecialchars() historisch einen leeren String zurückgeben, sobald die Funktion auf eine für die angenommene Kodierung ungültige Byte-Sequenz stieß. Seit PHP 8.1 ist UTF-8 der Standardwert, was diese Klasse von Bugs für neue Projekte entschärft, aber in Altcode mit explizit gesetztem ISO-8859-1-Parameter bleibt das Risiko bestehen.

Zusätzlich zum Encoding-Parameter empfiehlt sich der Flag-Kombination ENT_QUOTES | ENT_SUBSTITUTE. ENT_QUOTES escaped sowohl doppelte als auch einfache Anführungszeichen, was in HTML-Attributen mit einfachen Anführungszeichen wichtig ist. ENT_SUBSTITUTE sorgt dafür, dass ungültige Byte-Sequenzen durch das Unicode-Ersatzzeichen U+FFFD ersetzt werden, statt die gesamte Ausgabe stillschweigend zu einem leeren String zu machen. Gerade bei Multibyte-Strings aus unsicheren Quellen, etwa Freitextfeldern oder importierten Daten, ist dieses Verhalten entscheidend, weil eine leere Ausgabe im Fehlerfall nicht als Fehler auffällt, ein sichtbares Ersatzzeichen aber sofort auf ein Encoding-Problem hinweist.

8. Reguläre Ausdrücke mit Unicode: preg_match mit dem u-Modifier

PCRE, die reguläre Ausdrücke hinter preg_match() und verwandten Funktionen, arbeitet standardmäßig byteorientiert. Zeichenklassen wie \w oder \d beziehen sich ohne weitere Angabe ausschließlich auf den ASCII-Bereich. Ein Umlaut oder das ß wird von \w ohne Modifikator schlicht nicht erfasst, weil das führende Byte dieser Zeichen außerhalb des ASCII-Bereichs liegt und die Folgebytes ebenfalls nicht als Wortzeichen erkannt werden. Das Ergebnis: Ein Muster wie /\w+/ zerlegt "Straße" in die Fragmente "Stra" und "e" und "Königsallee" in "K" und "nigsallee", weil an jeder Umlaut- oder ß-Stelle die Zeichenklasse abbricht.

Der Modifikator u schaltet PCRE in den UTF-8-Modus um. Das Muster interpretiert die Eingabe dann als Folge von Unicode-Codepunkten statt als Folge einzelner Bytes, wodurch \w auch Umlaute und andere Buchstaben aus dem erweiterten Unicode-Bereich korrekt als Wortzeichen erkennt. Zusätzlich öffnet der u-Modifier den Zugriff auf Unicode-Eigenschaftsklassen wie \p{L} für beliebige Buchstaben aus jedem Schriftsystem oder \p{N} für Zahlen, was insbesondere bei international genutzten Formularen relevant ist.

Ein wichtiger Sicherheitsaspekt: Enthält die Eingabe ungültiges UTF-8, liefert preg_match() mit dem u-Modifier nicht 0, sondern false zurück, und preg_last_error() meldet PREG_BAD_UTF8_ERROR. Wird der Rückgabewert unvorsichtig als reinen Boolean interpretiert, etwa mit einem einfachen if (!preg_match(...)), wird dieser Fehlerfall wie ein simples Nicht-Match behandelt, was in einer Validierungsroutine dazu führen kann, dass eigentlich fehlerhafte Eingaben als gültig durchgewunken werden. Deshalb sollte Eingabe, die an ein /u-Muster geht, vorher mit mb_check_encoding() geprüft werden.


<?php

declare(strict_types=1);

$input = 'Straße Königsallee 42';

// Without the u modifier, \w only matches ASCII word characters
preg_match_all('/\w+/', $input, $withoutU);

// With the u modifier, PCRE switches to UTF-8 mode and \w includes umlauts
preg_match_all('/\w+/u', $input, $withU);

print_r($withoutU[0]); // ["Stra", "e", "K", "nigsallee", "42"] - broken at every umlaut/eszett
print_r($withU[0]);    // ["Straße", "Königsallee", "42"] - words stay intact

// Always validate encoding before running a /u pattern - invalid UTF-8 makes
// preg_match() return false instead of 0, which is easy to miss in validation code
if (!mb_check_encoding($input, 'UTF-8')) {
    throw new InvalidArgumentException('Input is not valid UTF-8');
}

9. mb_internal_encoding, php.ini-Einstellungen und Best Practices im Projekt

mb_internal_encoding() legt die Standardkodierung fest, die alle mb_-Funktionen verwenden, wenn kein expliziter Encoding-Parameter übergeben wird. In den meisten aktuellen PHP-Installationen ist der Standardwert bereits UTF-8, dennoch empfiehlt es sich, diesen Wert einmal zentral im Bootstrap der Anwendung explizit zu setzen, statt sich auf die Konfiguration der jeweiligen Serverumgebung zu verlassen. Die früher gebräuchliche php.ini-Direktive mbstring.internal_encoding ist seit PHP 8.0 entfernt, die Kodierung muss also zwingend über den Funktionsaufruf oder über mb_internal_encoding() im Code gesteuert werden, nicht mehr über eine globale Server-Einstellung.

Konsistenz über den gesamten Stack ist der entscheidende Erfolgsfaktor: Die HTTP-Response-Header sollten charset=UTF-8 deklarieren, die HTML-Seite ein passendes <meta charset="UTF-8"> enthalten, die Datenbanktabellen mit utf8mb4 statt dem veralteten utf8 angelegt werden, das in MySQL nur einen Teil des Unicode-Bereichs abdeckt, und die Verbindungscodierung des Datenbanktreibers ebenfalls explizit auf UTF-8 gesetzt werden. Ein einzelnes falsch konfiguriertes Glied in dieser Kette, etwa eine Datenbankverbindung mit latin1, kann Umlaute im gesamten System unbrauchbar machen, selbst wenn der PHP-Code selbst korrekt mit mb_-Funktionen arbeitet.

Da statische Analysewerkzeuge wie PHPStan die falsche Verwendung von strlen() statt mb_strlen() nicht automatisch erkennen können, weil beide Funktionen syntaktisch valide Aufrufe mit korrekten Typen sind, lohnt sich eine projektspezifische Coding-Standard-Regel oder ein Custom-Sniff, der die byteorientierten String-Funktionen bei nutzergenerierten Daten markiert. Ergänzend sollte jeder Code-Review-Prozess bei neuen String-Operationen die Frage stellen, ob der bearbeitete String garantiert reines ASCII ist, denn nur dann ist der Verzicht auf die mb_-Variante tatsächlich unbedenklich.

10. Zusammenfassung

Multibyte-Strings sind in jedem deutschsprachigen PHP-Projekt die Regel, nicht die Ausnahme, weil Umlaute und das ß in Namen, Adressen und Freitexten allgegenwärtig sind. Die byteorientierten nativen String-Funktionen von PHP, allen voran strlen(), substr(), str_split() und strpos(), ignorieren Zeichengrenzen und liefern bei Multibyte-Strings systematisch falsche Ergebnisse: zu hohe Längen, mittendrin abgeschnittene Zeichen und Byte- statt Zeichen-Offsets. Die mbstring-Erweiterung mit mb_strlen(), mb_substr(), mb_strtoupper() und mb_str_split() löst diese Probleme, indem sie konsequent auf Zeichenebene statt auf Byteebene arbeitet.

Darüber hinaus verlangen Sortierung, Encoding-Konvertierung, HTML-Escaping und reguläre Ausdrücke jeweils eigene, unicode-bewusste Werkzeuge: Collator für sprachgerechte Sortierung, mb_detect_encoding() und mb_convert_encoding() für Daten aus Fremdsystemen, htmlspecialchars() mit ENT_QUOTES | ENT_SUBSTITUTE für sicheres Escaping, und der u-Modifier für Unicode-fähige reguläre Ausdrücke. Wer diese Werkzeuge konsequent und projektweit statt punktuell einsetzt, vermeidet die typischen, schwer reproduzierbaren Encoding-Bugs, die erst im Produktivbetrieb mit echten Kundendaten sichtbar werden.

Multibyte-Strings in PHP 8.4 - Das Wichtigste auf einen Blick

Längen und Substrings

Immer mb_strlen() und mb_substr() statt strlen() und substr() bei Multibyte-Strings mit Umlauten oder Unicode-Zeichen verwenden.

Sortierung

Collator aus der intl-Erweiterung für sprachgerechte Sortierung mit Umlauten nutzen, niemals das byteweise vergleichende sort().

Encoding-Konvertierung

Fremddaten mit mb_detect_encoding() und expliziter Kandidatenliste prüfen, dann mit mb_convert_encoding() auf UTF-8 normalisieren.

Regex und Escaping

Den u-Modifier bei Unicode-Regex und ENT_QUOTES | ENT_SUBSTITUTE bei htmlspecialchars() konsequent einsetzen.

11. FAQ: Multibyte-Strings und Umlaute in PHP

1Was ist der Unterschied zwischen strlen() und mb_strlen()?
strlen() zählt Bytes, mb_strlen() zählt Zeichen. Bei ASCII identisch, bei Umlauten und Unicode liefert strlen() einen zu hohen Wert.
2Warum liefert strlen() bei Umlauten einen zu hohen Wert?
Umlaute und ß belegen in UTF-8 zwei Bytes statt einem. strlen() zählt jedes Byte, "Größe" hat also fünf Zeichen aber sieben Bytes.
3Ist mbstring in PHP 8.4 standardmäßig verfügbar?
In den meisten Distributionen ja, sollte aber trotzdem explizit als ext-mbstring in composer.json deklariert werden.
4Wie schneide ich einen Multibyte-String sicher ab?
Mit mb_substr() statt substr(). mb_substr() schneidet auf Zeichenebene und nie mitten in einer Mehrbyte-Sequenz.
5Warum funktioniert strtoupper() nicht mit Umlauten?
strtoupper() kennt nur ASCII-Regeln und lässt ä, ö, ü, ß unverändert. mb_strtoupper() mit Encoding-Parameter wandelt sie korrekt um.
6Wie sortiere ich Strings mit Umlauten korrekt?
Mit Collator aus der intl-Erweiterung, Locale de_DE. sort() vergleicht Bytes und sortiert Umlaute fälschlich hinter alle ASCII-Buchstaben.
7Was macht der u-Modifier bei regulären Ausdrücken?
Er schaltet PCRE in den UTF-8-Modus. \w erkennt dann auch Umlaute korrekt als Wortzeichen statt an ihnen abzubrechen.
8Wie erkenne ich gültiges UTF-8?
Mit mb_check_encoding($string, 'UTF-8'). Vor jeder /u-Regex-Verarbeitung prüfen, da ungültiges UTF-8 sonst preg_match() auf false setzt.
9mb_convert_encoding oder iconv?
mb_convert_encoding für Standard-Konvertierungen zwischen bekannten Kodierungen. iconv mit //TRANSLIT und //IGNORE für Transliteration in ASCII-Kontexte.
10Sollte ich mb_internal_encoding() immer setzen?
Einmal zentral im Bootstrap auf UTF-8 setzen genügt. Die alte php.ini-Direktive mbstring.internal_encoding wurde in PHP 8.0 entfernt.

Mironsoft

PHP-Entwicklung mit sauberem Encoding und Unicode-Sicherheit

Kämpft euer PHP-Code noch mit Umlauten und kaputten Zeichen?

Wir prüfen eure Codebasis auf byteorientierte String-Funktionen, Encoding-Inkonsistenzen und fehlerhafte Regex-Muster, und ersetzen sie durch robuste, Multibyte-sichere Lösungen, die auch mit echten Kundendaten zuverlässig funktionieren.

Encoding-Audit

Systematische Prüfung auf strlen()/substr() bei Multibyte-Strings und fehlende mb_-Aufrufe

Refactoring

Konsequente Umstellung auf mbstring-Funktionen, Collator-Sortierung und sicheres Escaping

Stack-Konsistenz

UTF-8-Konsistenz über Datenbank, HTTP-Header und Frontend hinweg absichern