如何将 PHP PDO ODBC 与 SQL 服务器和 Unicode 字符一起使用?
How to use PHP PDO ODBC with SQL Server and Unicode Characters?
PHP 如果没有一些手动编码,PDO ODBC 似乎无法存储 NVARCHAR 字符 (UTF-16)。这似乎是整个互联网上的一个非常普遍的 "bug",似乎没有人有明确的解决方案。
如何重现错误
- 尝试使用 PDO 插入以下日文字符:
こんにちは
(表示你好)
- 以下内容将存储在您的数据库中:
ã“ã‚“ã«ã¡ã¯
- 然后通过PDO获取并打印到屏幕上,你会得到:
こんにちは
还不错,但也不好。 PHP 有效,但是当您有其他不在 PHP 中的应用程序从您的数据库访问该信息时,它们将得到错误的字符串:ã“ã‚“ã«ã¡ã¯
.
理想情况下,您希望到处都有 こんにちは
。
症状
PDO 似乎没有任何 NVARCHAR 的概念,也就是使用 16 位编码的字符。事实上,您通过 PDO 传递给 SQL SERVER 或从 SQL SERVER 检索的所有内容都将以 8 位为单位。如何"prove"呢?在这里:
- 您从日文字符串开始
こんにちは
。
您首先必须知道 PHP 将字符串视为二进制,并且(如果这样设置)它将以 UTF-8 格式存储它们。
因此,如果我们查看 こんにちは
的二进制表示,您将得到 E38193E38293E381ABE381A1E381AF
,这也是 SQL 服务器将为您提供 ã“ã‚“ã«ã¡ã¯
的二进制表示. (取决于您的整理)
接下来,让我们把它变成UTF-16,因为那是NVARCHAR的格式。
$utf16_string = mb_convert_encoding('こんりちは', 'UTF-16LE');
以下将こんにちは
的PHP中的二进制表示更改为533093306B3061306F30
,这正是SQL中こんにちは
的二进制表示 SERVER NVARCHAR .
- 接下来尝试通过 PDO 将其保存在 SQL 中,您将得到以下内容:
S0“0k0a0o0
S0“0k0a0o0
在 VARCHAR 中的 SQL SERVER 二进制表示是 533093306B3061306F30
这也是 こんにちは
在 NVARCHAR 中的二进制表示。
一个肮脏的解决方案
您可以使用以下内容通过 PDO ODBC 在 SQL 服务器中保存和检索 unicode 数据,但它很丑陋...
您想将数据转换为与 SQL SERVER NVARCHAR 完全相同的二进制表示
将存储它
mb_convert_encoding('こんニちは', 'UTF-16LE');
您想在 SQL 服务器端以二进制形式接收它,然后将其转换为 NVARCHAR。
@binary VARBINARY(40)
SELECT @string = CONVERT(NVARCHAR(20), @binary);
此时您的数据库中有 こんにちは
。要检索它,您需要将其作为二进制
重新发送到 PHP
一旦你得到了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 问题:-)
PHP 如果没有一些手动编码,PDO ODBC 似乎无法存储 NVARCHAR 字符 (UTF-16)。这似乎是整个互联网上的一个非常普遍的 "bug",似乎没有人有明确的解决方案。
如何重现错误
- 尝试使用 PDO 插入以下日文字符:
こんにちは
(表示你好) - 以下内容将存储在您的数据库中:
ã“ã‚“ã«ã¡ã¯
- 然后通过PDO获取并打印到屏幕上,你会得到:
こんにちは
还不错,但也不好。 PHP 有效,但是当您有其他不在 PHP 中的应用程序从您的数据库访问该信息时,它们将得到错误的字符串:ã“ã‚“ã«ã¡ã¯
.
理想情况下,您希望到处都有 こんにちは
。
症状
PDO 似乎没有任何 NVARCHAR 的概念,也就是使用 16 位编码的字符。事实上,您通过 PDO 传递给 SQL SERVER 或从 SQL SERVER 检索的所有内容都将以 8 位为单位。如何"prove"呢?在这里:
- 您从日文字符串开始
こんにちは
。
您首先必须知道 PHP 将字符串视为二进制,并且(如果这样设置)它将以 UTF-8 格式存储它们。
因此,如果我们查看 こんにちは
的二进制表示,您将得到 E38193E38293E381ABE381A1E381AF
,这也是 SQL 服务器将为您提供 ã“ã‚“ã«ã¡ã¯
的二进制表示. (取决于您的整理)
接下来,让我们把它变成UTF-16,因为那是NVARCHAR的格式。
$utf16_string = mb_convert_encoding('こんりちは', 'UTF-16LE');
以下将こんにちは
的PHP中的二进制表示更改为533093306B3061306F30
,这正是SQL中こんにちは
的二进制表示 SERVER NVARCHAR .
- 接下来尝试通过 PDO 将其保存在 SQL 中,您将得到以下内容:
S0“0k0a0o0
S0“0k0a0o0
在 VARCHAR 中的 SQL SERVER 二进制表示是 533093306B3061306F30
这也是 こんにちは
在 NVARCHAR 中的二进制表示。
一个肮脏的解决方案
您可以使用以下内容通过 PDO ODBC 在 SQL 服务器中保存和检索 unicode 数据,但它很丑陋...
您想将数据转换为与 SQL SERVER NVARCHAR 完全相同的二进制表示 将存储它
mb_convert_encoding('こんニちは', 'UTF-16LE');
您想在 SQL 服务器端以二进制形式接收它,然后将其转换为 NVARCHAR。
@binary VARBINARY(40) SELECT @string = CONVERT(NVARCHAR(20), @binary);
此时您的数据库中有
こんにちは
。要检索它,您需要将其作为二进制 重新发送到 PHP
一旦你得到了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 问题:-)