UTF-8-Probleme
- mariadb
- php
- unicode
Hi,
meine Transportmittel-Zeichen (siehe anderer Thread) machen mir noch Probleme.
Anzeige im Java-Client paßt. Übertragung ans Server-PHP paßt auch (wenn ich den Wert in $_POST direkt per echo ausgebe, sehe ich im Java-Client immer noch meine Lokomotive usw.
Das Url-Encodieren im Client und Decodieren im PHP scheint also zu funktionieren.
Schreibe ich den Wert in die Datenbank, landen dort statt der Lokomotive vier Fragezeichen, wobei Umlaute und scharfes s korrekt in der Datenbank landen (auch meine Pfeilchen \u2192). So sieht's zumindest im PHP-Myadmin aus.
Hole ich den Wert per PHP wieder aus der DB und zeige ihn im Java-Client an, sind's immer noch 4 Fragezeichen.
Daraus schließe ich mal, daß das kein Anzeigefehler im PHP-Myadmin ist.
Sondern ein Problem beim Schreiben in die DB.
Setze ich den Wert im PHP-MyAdmin, kommt eine Warnung
#1366 Incorrect string value: '\xF0\x9F\x8F\xA0\xE2\x86...' for column dbs16104270.tabellenname.spaltenname at row 1
Und in die DB wird anstelle der 4 Fragezeichen nur 1 Fragezeichen geschrieben, der Rest des Strings ist korrekt incl. Umlauten.
Was habe ich falsch gemacht? Collation der DB, der Tabelle und der betroffenen Spalte ist utf8mb3_general_ci (das war so voreingestellt). Sollte doch also für UTF-8 passen …
Oder liegt's daran, daß die Zeichen "surrogates" sind?
Die DB ist eine MariaDB 11.8.
Muß ich sonst noch was beachten?
Danke im Voraus!
cu,
Andreas a/k/a MudGuard
Lieber MudGuard,
Schreibe ich den Wert in die Datenbank, landen dort statt der Lokomotive vier Fragezeichen, wobei Umlaute und scharfes s korrekt in der Datenbank landen (auch meine Pfeilchen \u2192). So sieht's zumindest im PHP-Myadmin aus.
welche „Kollation“ ist denn in der fraglichen Spalte eingestellt? Was genau tut der Code, der in diese Spalte schreibt?
Liebe Grüße
Felix Riesterer
Hi,
Schreibe ich den Wert in die Datenbank, landen dort statt der Lokomotive vier Fragezeichen, wobei Umlaute und scharfes s korrekt in der Datenbank landen (auch meine Pfeilchen \u2192). So sieht's zumindest im PHP-Myadmin aus.
welche „Kollation“ ist denn in der fraglichen Spalte eingestellt? Was genau tut der Code, der in diese Spalte schreibt?
Ich zitiere mich mal:
Collation der DB, der Tabelle und der betroffenen Spalte ist utf8mb3_general_ci (das war so voreingestellt).
<?php
try {
$pdo = new PDO("mysql:host=$db_host; dbname=$db_name;", $db_user, $db_pass);
} catch (PDOException $e) {
die();
}
$insertSql = "INSERT INTO tabelle (spalte1, spalte2) VALUES (:wert1, :wert2);
$insertStatement = $pdo->prepare($insertSql);
$insertStatement->execute([
'spalte1' => $_POST['param1'],
'spalte2' => $_POST['param2']
]);
(Spaltennamen usw. anonymisiert, Validierung der Werte hab ich weggelassen, das hat ja mit dem Codierungs-Problem nix zu tun)
Danke schon mal für Deine Zeit.
cu,
Andreas a/k/a MudGuard
Lieber MudGuard,
<?php try { $pdo = new PDO("mysql:host=$db_host; dbname=$db_name;", $db_user, $db_pass); } catch (PDOException $e) { die(); } $insertSql = "INSERT INTO tabelle (spalte1, spalte2) VALUES (:wert1, :wert2); $insertStatement = $pdo->prepare($insertSql); $insertStatement->execute([ 'spalte1' => $_POST['param1'], 'spalte2' => $_POST['param2'] ]);
da sehe ich nirgends ein SET NAMES utf8. In meiner DB-Klasse ist der Aufruf dieser:
$t->pdo = new \PDO(
sprintf(
'mysql:dbname=%1$s;host=%2$s;port=%3$s;charset=UTF8;',
$settings['name'],
$settings['host'],
$settings['port']
),
$settings['user'],
$settings['pw'],
array(
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::MYSQL_ATTR_FOUND_ROWS => true,
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
)
);
Vielleicht hilft Dir das ja schon weiter?
Liebe Grüße
Felix Riesterer
Hi,
da sehe ich nirgends ein
SET NAMES utf8. In meiner DB-Klasse ist der Aufruf dieser:
ich hab sowohl das charset=UTF8 im ersten PDO-Parameter ergänzt als auch das Array so übernommen wie von Dir vorgeschlagen.
Geändert hat's leider nix.
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::MYSQL_ATTR_FOUND_ROWS => true, \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
was hat's mit den Backslashes auf sich? (bin kein PHP-Kenner, ich stümper da eher so vor mich hin ...)
cu,
Andreas a/k/a MudGuard
Hallo MudGuard,
die Backslashes besagen, dass PDO im Root-Namespace liegt.
Namespaces waren eine Idee für das gescheiterte PHP 6, die dann 2009 in PHP 5.3 erschien.
Rolf
Hi,
Hallo MudGuard,
die Backslashes besagen, dass PDO im Root-Namespace liegt.
Namespaces waren eine Idee für das gescheiterte PHP 6, die dann 2009 in PHP 5.3 erschien.
Rolf
Ok.
Aber warum braucht's dann keinen bei new PDO(...)php?
cu,
Andreas a/k/a MudGuard
Hallo MudGuard,
wenn es bei new PDO(…) keinen braucht, dann auch eigentlich nicht bei den PDO-Konstanten. Sofern denn beide Stellen im gleichen Namespace liegen.
Durch die namespace-Anweisung beginnst Du den Gültigkeitsbereich eines Namespaces. Alle Namen werden daraufhin innerhalb dieses Namespaces verwendet.
namespace Foo;
$errmode = PDO::ERRMODE_EXCEPTION; // sollte fehlschlagen!
$errmode = \PDO::ERRMODE_EXCEPTION; // sollte gelingen!
Du kannst aber auch vereinbaren, dass bestimmte Namen anderswo liegen
namespace Foo;
use \PHP as PHP;
$errmode = PDO::ERRMODE_EXCEPTION; // sollte jetzt gelingen!
$errmode = \PDO::ERRMODE_EXCEPTION; // sollte gelingen!
Das liest Du Dir besser im PHP Handbuch im Detail durch, es kann vertrackt sein.
Rolf
Lieber MudGuard,
was hat's mit den Backslashes auf sich?
mein Projekt bindet fremde Skripte ein, die ihrerseits Namespaces verwenden, also verwendet mein Projekt auch welche. Das bedeutet, dass Klassennamen mit dem passenden Namespace notiert werden müssen (nicht können). Wenn Du mit dem use-Operator Aliase definierst, um z.B. Schreibarbeit zu sparen, dann kannst Du für diesen Namespace die Backslashes weg lassen:
namespace MyProject;
use \OtherProject\SomeNameSpace
class Project {
}
$a = new \MyProject\Project(); // geht immer
$b = new Project(); // geht, weil im passenden Namespace
$c = new \OtherProject\SomeNameSpace\SomeClass(); // geht immer
$d = new SomeClass(); // funktioniert intern wie bei $c wegen `use`-Operator oben
Wenn Du nun eine Funktion aufrufen willst, muss klar sein, in welchem Namespace diese liegt.
namespace MyProject;
function chr ($x) {
return $x + 1234;
}
$a = chr(65); // 1299
$b = \chr(65); // 'A'
Wenn in einem Namensraum eine Funktion mit einem Namen definiert wird, für die es in einem anderen Namensraum eine gleichlautende gibt, welche ist dann beim Funktionsaufruf gemeint? Für die Variable $a ist das klar, weil das im Namensraum MyProject steht, also die Funktion \MyProject\chr gemeint ist. Will man die altbekannte Funktion chr aus dem globalen Namensraum haben, muss man das wie bei Variable $b notieren.
Liebe Grüße
Felix Riesterer
Hallo MudGuard,
Collation der DB, der Tabelle und der betroffenen Spalte ist utf8mb3_general_ci (das war so voreingestellt). Sollte doch also für UTF-8 passen …
Nein. Nicht für Emojis.
Das mb3 im Collation-Namen heißt: UTF8 mit maximal 3 Bytes. Das entspricht der UCS-2 Codierung. Bedeutet: 4+6+6=16 Bits für Codepoint-Nummern. Mit MB3 schaffst Du also nur die Basic Plane. Bis Unicode v3.0 gab es nichts anderes, und die Datenbankhersteller haben wohl gedacht, dass man dann mit 3 Bytes auskommt. Unicode v3.1 hat dann 2001 allen UCS-2 Verwendern einen Schreck versetzt.
Vor allem bedeutet mb3, dass man intern mit 16-bit Zeichendarstellung auskommt. Für mb4 brauchst Du 32-bit Darstellung oder musst mit UTF-16 Surrogaten rumhexen, so, wie es JavaScript macht. Man braucht also entweder doppelt so viel Speicher für die Daten, oder muss für bestimmte Operationen langsameren Code verwenden.
Emojis liegen in Plane 1, deswegen funktionieren die mit der mb3-Collation nicht, du brauchst die utf8mb4_general_ci Collation. Die bietet 3+6+6+6=21 Bits = 2097152 Codepoints, das reicht für die 1114112 (17×65536) Codepoints der 17 Unicode-Planes aus.
Man kann Default-Collations auf Server-, Datenbank- und Tabellenebene eingeben. Aber wenn Du sie nachträglich änderst, werden die darunter liegenden Ebenen nicht mitgeändert. Du musst jede Spalte deiner Datenbank, die Emojis können soll, auf mb4 ändern.
Rolf
Hi,
Collation der DB, der Tabelle und der betroffenen Spalte ist utf8mb3_general_ci (das war so voreingestellt). Sollte doch also für UTF-8 passen …
Nein. Nicht für Emojis.
Das
mb3im Collation-Namen heißt: UTF8 mit maximal 3 Bytes. Das entspricht der UCS-2 Codierung. Bedeutet: 4+6+6=16 Bits für Codepoint-Nummern. Mit MB3 schaffst Du also nur die Basic Plane. Bis Unicode v3.0 gab es nichts anderes, und die Datenbankhersteller haben wohl gedacht, dass man dann mit 3 Bytes auskommt. Unicode v3.1 hat dann 2001 allen UCS-2 Verwendern einen Schreck versetzt.Vor allem bedeutet mb3, dass man intern mit 16-bit Zeichendarstellung auskommt. Für mb4 brauchst Du 32-bit Darstellung oder musst mit UTF-16 Surrogaten rumhexen, so, wie es JavaScript macht. Man braucht also entweder doppelt so viel Speicher für die Daten, oder muss für bestimmte Operationen langsameren Code verwenden.
Emojis liegen in Plane 1, deswegen funktionieren die mit der mb3-Collation nicht, du brauchst die utf8mb4_general_ci Collation. Die bietet 3+6+6+6=21 Bits = 2097152 Codepoints, das reicht für die 1114112 (17×65536) Codepoints der 17 Unicode-Planes aus.
Man kann Default-Collations auf Server-, Datenbank- und Tabellenebene eingeben. Aber wenn Du sie nachträglich änderst, werden die darunter liegenden Ebenen nicht mitgeändert. Du musst jede Spalte deiner Datenbank, die Emojis können soll, auf mb4 ändern.
Das klingt nach einer guten Erklärung.
Daß es nicht an der Verbindung zur DB liegt, hatte ich schon vermutet, da ja andere UNICODE-Chars außerhalb der ASCII-Range sauber gespeichert wurden.
Betroffen ist nur eine einzige Spalte, also werde ich die anpassen. Ich melde mich, sobald ich durch bin mit den Tests.
Auf jeden Fall schon mal auch Dir Danke für Deine Zeit!
cu,
Andreas a/k/a MudGuard
Hi,
Man kann Default-Collations auf Server-, Datenbank- und Tabellenebene eingeben. Aber wenn Du sie nachträglich änderst, werden die darunter liegenden Ebenen nicht mitgeändert.
PHPMyAdmin bietet dafür ne Checkbox "change all table collations" an und wenn man die aktiviert auch noch eine "change all columns" collations.
Alles geändert (vorher die Tabelle geleert, sind ja bis jetzt nur Test-Datensätze drin), es wird jetzt im PHPMyAdmin jeweils die mb4-Collation angezeigt.
Beim Einfügen gibt's aber immer noch 4 Fragezeichen.
Vielleicht sollte ich das Detektivteam der 3 Fragezeichen konsultieren … 😁
cu,
Andreas a/k/a MudGuard
Hi,
Beim Einfügen gibt's aber immer noch 4 Fragezeichen.
nur per PHP.
Im PHPMyAdmin kann ich jetzt →⥁🏠✈⛴🚤🚗🚂🚃🚌🚠 einfügen, und das wird dann auch wieder so angezeigt.
Im Java-Client kommt aber leider nur →⥁?✈⛴?????? wieder an (auch im Browser, wenn ich die Download-Url dort eingebe - das Problem liegt also nicht im Java-Client). Die vier überlebenden Zeichen sind keine Surrogates …
Das Problem liegt also irgendwo im PHP->MariaDB-Bereich.
cu,
Andreas a/k/a MudGuard
Hallo
Du musst jede Spalte deiner Datenbank, die Emojis können soll, auf mb4 ändern.
Als Ergänzung sei gesagt, dass es sinnvoll ist, nicht nur „jede Spalte deiner Datenbank, die Emojis können soll“ sondern alle Spalten und Tabellen auf utf8mb4 umzustellen, wie es MudGuard schlussendlich auch getan hat.
Oracle als Hersteller von MySQL empfiehlt das selbst, weil sie vorhaben in einer zukünftigen Version von MySQL die Angabe von utf8…irgendwas nicht mehr als Alias für die entsprechenden utf8mb3-Angaben sondern für die korrespondierenden utf8mb4-Angaben zu setzen. Spätestens dann würde es mit einem Mischmasch von utf8mb3 und utf8mb4 zu Problemen kommen können (auch wenn utf8mb3 eine Teilmenge von utf8mb4 ist).
Die hier wieder einmal dokumentierten Fallstricke der verschiedenen anzupassenden Code-Stellen würde schon mit einiger Wahrscheinlichkeit dafür sorgen.
Tschö, Auge
Hi,
so, jetzt tut's!
Es waren nötig: die Collation mind. der betroffenen Spalte auf utf8mb4_general_ci setzen (ich hab's für die gesamte DB und alle Tabellen und alle String-Spalten machen lassen)
beim Öffnen der Connection im PHP muß es nicht set NAMES=UTF8 sein (das wird wie UTF8mb3 interpretiert, sondern explizit NAMES UTF8mb4), also
return new PDO(
"mysql:host=$db_host; dbname=$db_name;charset=UTF8",
$db_user,
$db_pass,
array(
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::MYSQL_ATTR_FOUND_ROWS => true,
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4'));
beim Charset muß es UTF8 bleiben.
Nochmal ein Dankeschön an Felix und Rolf!
cu,
Andreas a/k/a MudGuard