Skip to content

Commit 40bbb64

Browse files
committed
Merge pull request #146 from diepxuan/fix/sqlsrv-utf8-encoding
fix: UTF-8 encoding support for SQL Server parameter binding
1 parent 8ee3b2d commit 40bbb64

1 file changed

Lines changed: 58 additions & 12 deletions

File tree

src/StoredProcedures/ProcedureCaller.php

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @author Tran Ngoc Duc <ductn@diepxuan.com>
99
* @author Tran Ngoc Duc <caothu91@gmail.com>
1010
*
11-
* @lastupdate 2026-04-06 23:23:01
11+
* @lastupdate 2026-04-07 12:39:39
1212
*/
1313

1414
namespace Diepxuan\Simba\StoredProcedures;
@@ -59,16 +59,8 @@ public static function call(string $name, array $params = [], ?string $connectio
5959
$execParts[] = "@{$key} = @{$key} OUTPUT";
6060
$selectOut[] = "@{$key} as {$key}";
6161
} else {
62-
// FIX UTF-8: Dùng positional parameter (?) với CAST cho string thuần
63-
// Lý do: PDO không hỗ trợ CAST(:named_param AS TYPE), phải dùng CAST(? AS TYPE)
64-
if (\is_string($value) && !empty($value) && !self::isDateOrDatetime($value)) {
65-
// String thuần → CAST thành NVARCHAR để giữ dấu tiếng Việt
66-
$execParts[] = "@{$key} = CAST(? AS NVARCHAR(500))";
67-
} else {
68-
// Date, datetime, int, float, null, empty string → không CAST
69-
$execParts[] = "@{$key} = ?";
70-
}
71-
$bindings[] = $value;
62+
$execParts[] = "@{$key} = :{$key}";
63+
$bindings[$key] = $value;
7264
}
7365
}
7466

@@ -92,7 +84,13 @@ public static function call(string $name, array $params = [], ?string $connectio
9284
$conn = $connection ? DB::connection($connection) : DB::connection();
9385

9486
// Dùng select() để execute toàn bộ batch và fetch kết quả
95-
$rows = $conn->select($sql, $bindings);
87+
if (empty($bindings)) {
88+
$rows = $conn->select($sql);
89+
} else {
90+
// Replace ? với Unicode literal
91+
$processedQuery = self::replacePlaceholders($sql, $bindings);
92+
$rows = $conn->select($processedQuery);
93+
}
9694

9795
// Tự động ép kiểu các giá trị output trả về dựa trên type đã khai báo
9896
if ($hasOutput && !empty($rows)) {
@@ -114,6 +112,54 @@ public static function call(string $name, array $params = [], ?string $connectio
114112
return collect($rows);
115113
}
116114

115+
/**
116+
* Escape single quote cho SQL Server.
117+
*/
118+
public static function escape(string $value): string
119+
{
120+
return str_replace("'", "''", $value);
121+
}
122+
123+
/**
124+
* Chuyển giá trị sang dạng Unicode literal (N'...')
125+
* Dùng cho parameter binding bị lỗi UTF-8.
126+
*/
127+
public static function toUnicodeLiteral(mixed $value): string
128+
{
129+
if (null === $value) {
130+
return 'NULL';
131+
}
132+
133+
if (\is_bool($value)) {
134+
return $value ? '1' : '0';
135+
}
136+
137+
if (\is_int($value) || \is_float($value)) {
138+
return (string) $value;
139+
}
140+
141+
if ($value instanceof \DateTimeInterface) {
142+
return "N'" . self::escape($value->format('Y-m-d H:i:s')) . "'";
143+
}
144+
145+
return "N'" . self::escape((string) $value) . "'";
146+
}
147+
148+
/**
149+
* Replace ? placeholders với Unicode literals.
150+
*/
151+
private static function replacePlaceholders(string $query, array $bindings): string
152+
{
153+
$count = 0;
154+
155+
return preg_replace_callback('/\?/', static function () use ($bindings, &$count) {
156+
$value = $bindings[$count] ?? null;
157+
++$count;
158+
159+
return self::toUnicodeLiteral($value);
160+
}, $query);
161+
}
162+
117163
/**
118164
* Kiểm tra xem value có phải là date/datetime format không.
119165
*/

0 commit comments

Comments
 (0)