如何将 PHP PDO ODBC 与 SQL 服务器和 Unicode 字符一起使用?

How to use PHP PDO ODBC with SQL Server and Unicode Characters?

PHP 如果没有一些手动编码,PDO ODBC 似乎无法存储 NVARCHAR 字符 (UTF-16)。这似乎是整个互联网上的一个非常普遍的 "bug",似乎没有人有明确的解决方案。

如何重现错误

  1. 尝试使用 PDO 插入以下日文字符:こんにちは(表示你好)
  2. 以下内容将存储在您的数据库中:ã“ã‚“ã«ã¡ã¯
  3. 然后通过PDO获取并打印到屏幕上,你会得到:こんにちは

还不错,但也不好。 PHP 有效,但是当您有其他不在 PHP 中的应用程序从您的数据库访问该信息时,它们将得到错误的字符串:ã“ã‚“ã«ã¡ã¯.

理想情况下,您希望到处都有 こんにちは

症状

PDO 似乎没有任何 NVARCHAR 的概念,也就是使用 16 位编码的字符。事实上,您通过 PDO 传递给 SQL SERVER 或从 SQL SERVER 检索的所有内容都将以 8 位为单位。如何"prove"呢?在这里:

  1. 您从日文字符串开始 こんにちは

您首先必须知道 PHP 将字符串视为二进制,并且(如果这样设置)它将以 UTF-8 格式存储它们。

因此,如果我们查看 こんにちは 的二进制表示,您将得到 E38193E38293E381ABE381A1E381AF,这也是 SQL 服务器将为您提供 ã“ã‚“ã«ã¡ã¯ 的二进制表示. (取决于您的整理)

  1. 接下来,让我们把它变成UTF-16,因为那是NVARCHAR的格式。

    $utf16_string = mb_convert_encoding('こんりちは', 'UTF-16LE');

以下将こんにちは的PHP中的二进制表示更改为533093306B3061306F30,这正是SQL中こんにちは的二进制表示 SERVER NVARCHAR .

  1. 接下来尝试通过 PDO 将其保存在 SQL 中,您将得到以下内容:S0“0k0a0o0

S0“0k0a0o0 在 VARCHAR 中的 SQL SERVER 二进制表示是 533093306B3061306F30 这也是 こんにちは 在 NVARCHAR 中的二进制表示。

一个肮脏的解决方案

您可以使用以下内容通过 PDO ODBC 在 SQL 服务器中保存和检索 unicode 数据,但它很丑陋...

  1. 您想将数据转换为与 SQL SERVER NVARCHAR 完全相同的二进制表示 将存储它

    mb_convert_encoding('こんニちは', 'UTF-16LE');

  2. 您想在 SQL 服务器端以二进制形式接收它,然后将其转换为 NVARCHAR。

    @binary VARBINARY(40) SELECT @string = CONVERT(NVARCHAR(20), @binary);

  3. 此时您的数据库中有 こんにちは。要检索它,您需要将其作为二进制

  4. 重新发送到 PHP
  5. 一旦你得到了PHP中的二进制文件,PHP就已经把它转换成了一个十六进制字符串...所以,你想把十六进制字符串转换成一个二进制,然后将编码从 utf-16 更改为 utf-8

    $result = mb_convert_encoding(hex2bin($string), 'UTF-8', 'UTF-16LE');

当您将它回显到您的网页时,您将带着您的 こんにちは 返回。

基本上,这就是 SQL 驱动程序应该为我做的,而不是我手动做的。

我是不是忘记配置了,还是必须手动配置?

不,我认为你没有忘记配置任何东西。事实上,关于 PHP 和 Microsoft ODBC 驱动程序之间长期存在的 "issues",您的解释是迄今为止我发现的最好的解释。鉴于 PDO_ODBC page 说:

,这些问题尤其令人费解

On Windows, PDO_ODBC ... is the recommended driver for connecting to Microsoft SQL Server databases.

但是,在 Windows 上,他们还提供 PDO_SQLSRV,这实际上似乎可以正常工作。

所以看起来是PDO_ODBC "doesn't have any notion of NVARCHAR",而不是整个PDO。

(如果涉及 Unicode 字符,尝试将 PHP 与 Microsoft Access ODBC 一起使用时会出现类似问题)

结论:PHP对 ODBC 的支持仍然有点混乱,至少在 Microsoft 数据库方面是这样。

我整天都在苦苦挣扎,试图弄清楚 ODBC/PHP

的可能性

我的服务器是Windows2012R2服务器,没有安装任何office程序。只有 MS 64 位访问驱动程序(AccessDatabaseEngine_X64.exe 文件版本 16.0.4519.1000)

ODBC 驱动程序是 16.00.4513.1000(ACEODBC.DLL 从 07-03-2017)

我的服务器是 运行 PHP Ver 7.4 和 Apache 2.4

我已将此添加到我的 PHP.INI:extension=php_com_dotnet.dll

我已经尝试了 PHP/ODBC Classic 和 PHP/ODBC/PDO - 不成功

为了测试,我总是使用丹麦岛 Ærø 和波兰城市 Łódź

我在笔记本电脑 (Office 2016) 上创建了 Excel 和 Access 文件,并将文件复制到服务器。如前所述...我的服务器上没有安装 Office!

我也试过ADODB.Connection。我不会产生正确的输出。

<?PHP
$file_location = "C:\TestData\AccessPHP.accdb";
$connStr = "Driver={Microsoft Access Driver (*.mdb, *.accdb)};charset=UTF-8;Dbq=$file_location;";
$con = new COM("ADODB.Connection", NULL, CP_UTF8);  // specify UTF-8 code page
$con->Open($connStr);
$rst = new COM("ADODB.Recordset");
$sql = "SELECT * FROM demo1";
$rst->Open($sql, $con, 3, 3);  // adOpenStatic, adLockOptimistic
echo 'Łódź ' . bin2hex('Łódź') . ' Length: ' . mb_strlen('Łódź') . '<br>';
while (!$rst->EOF) {
    $p = $rst->Fields("Place");
    echo 'H: ' . bin2hex($p) . ' P: ' . $p . ' - P:(1252) ' . utf8_encode($p) . ': ' . $rst->Fields(2) . '<br>';
    $rst->MoveNext;
}
$rst->Close();
$con->Close();

主要灵感来自这里:

此解决方案中的 "problem" 是使用旧的 .MDB 文件格式。 我认为应该尽量避免。让我们瞄准最新的驱动程序。 (我希望@Gord Thompson 对此发表评论。)

所以...最大的问题是:谁有问题?微软或 PHP ?

我转向了 PowerScript。它看起来像这样:

$query='select * from demo1' 
$conn = New-Object System.Data.Odbc.OdbcConnection
$conn.ConnectionString = "DSN=DemoDSN"
$conn.open()
$cmd = New-object System.Data.Odbc.OdbcCommand($query,$conn)
$ds = New-Object system.Data.DataSet
$numrows = (New-Object system.Data.odbc.odbcDataAdapter($cmd)).fill($ds) 
$conn.close()
foreach ($row in $ds.Tables[0].Rows ) {
    write-host "$($row[0]) `t $($row.Place)"
}
Write-host "Number of rows returned: $numrows"
$ds.tables

结果符合预期:

1    London
2    Ærø
3    Łódź
Number of rows returned: 3
Key Place  RowNameWithæøå
--- -----  --------------
  1 London          1,456
  2 Ærø                 8
  3 Łódź         -12,3456

好的。这表明 PHP 是有问题的那个。

我安装了这个http://querytool.com的试用版 这是一个非常好的软件,使用 MS 64 位驱动程序。

它就像一个魅力。 Excel 和 Access 100% ok

(我个人用的是DBeaver的免费版,省了180$)

OK...'Advanced Query Tool'也证明可以让MS Drivers通过ODBC传送双字节UTF-8抛光字符。

令人难过的是,强大的 PHP 7.4 无法正确处理 ODBC :-(

作为最后的奖励信息,我可以告诉你,当使用 Excel 驱动程序(顺便说一下,它是同一个 DLL)时,你需要知道,如果工作簿文件包含一个名为 Sheet1 的 sheet,那么您必须在查询中将其命名为 [Sheet1$]select * from [Sheet1$]

好吧,这不是 100% 的解决方案,但也许它会启发某些人,弄清楚如何解决这个 PHP 问题:-)