88 * @author Tran Ngoc Duc <ductn@diepxuan.com>
99 * @author Tran Ngoc Duc <caothu91@gmail.com>
1010 *
11- * @lastupdate 2026-04-07 13:04:50
11+ * @lastupdate 2026-04-07 13:49:23
1212 */
1313
1414namespace Diepxuan \Simba \StoredProcedures ;
@@ -36,12 +36,15 @@ class ProcedureCaller
3636 * @param null|string $connection Optional connection name
3737 *
3838 * @return mixed kết quả trả về từ procedure (tùy thuộc vào procedure)
39+ *
40+ * @note UTF-8 Support: Build trực tiếp Unicode literals (N'...') vào SQL
41+ * Tránh lỗi partial match khi replace placeholders
42+ * Tham khảo: docs/SQLSRV-UTF8-ROOT-CAUSE.md
3943 */
4044 public static function call (string $ name , array $ params = [], ?string $ connection = null )
4145 {
4246 $ declareSql = [];
4347 $ execParts = [];
44- $ bindings = [];
4548 $ selectOut = [];
4649 $ hasOutput = false ;
4750 $ outputTypes = [];
@@ -59,8 +62,9 @@ public static function call(string $name, array $params = [], ?string $connectio
5962 $ execParts [] = "@ {$ key } = @ {$ key } OUTPUT " ;
6063 $ selectOut [] = "@ {$ key } as {$ key }" ;
6164 } else {
62- $ execParts [] = "@ {$ key } = : {$ key }" ;
63- $ bindings [$ key ] = $ value ;
65+ // INPUT param - Build trực tiếp Unicode literal vào SQL (không dùng placeholder)
66+ $ literal = self ::toUnicodeLiteral ($ value );
67+ $ execParts [] = "@ {$ key } = {$ literal }" ;
6468 }
6569 }
6670
@@ -83,14 +87,8 @@ public static function call(string $name, array $params = [], ?string $connectio
8387
8488 $ conn = $ connection ? DB ::connection ($ connection ) : DB ::connection ();
8589
86- // Dùng select() để execute toàn bộ batch và fetch kết quả
87- if (empty ($ bindings )) {
88- $ rows = $ conn ->select ($ sql );
89- } else {
90- // Replace :placeholder với Unicode literal (fix UTF-8 cho SQL Server)
91- $ processedQuery = self ::replacePlaceholders ($ sql , $ bindings );
92- $ rows = $ conn ->select ($ processedQuery );
93- }
90+ // Execute trực tiếp (không cần bindings vì đã build Unicode literal vào SQL)
91+ $ rows = $ conn ->select ($ sql );
9492
9593 // Tự động ép kiểu các giá trị output trả về dựa trên type đã khai báo
9694 if ($ hasOutput && !empty ($ rows )) {
@@ -122,7 +120,11 @@ public static function escape(string $value): string
122120
123121 /**
124122 * Chuyển giá trị sang dạng Unicode literal (N'...')
125- * Dùng cho parameter binding bị lỗi UTF-8.
123+ * Build trực tiếp vào SQL, không qua parameter binding.
124+ *
125+ * @param mixed $value Giá trị cần convert
126+ *
127+ * @return string Unicode literal (N'...' cho strings, NULL cho null, v.v.)
126128 */
127129 public static function toUnicodeLiteral (mixed $ value ): string
128130 {
@@ -144,22 +146,4 @@ public static function toUnicodeLiteral(mixed $value): string
144146
145147 return "N' " . self ::escape ((string ) $ value ) . "' " ;
146148 }
147-
148- /**
149- * Replace :placeholder với Unicode literals (N'...')
150- * Fix UTF-8 cho SQL Server parameter binding trên Linux.
151- *
152- * @param string $query SQL query với :named placeholders
153- * @param array $bindings Associative array [paramName => value]
154- */
155- private static function replacePlaceholders (string $ query , array $ bindings ): string
156- {
157- foreach ($ bindings as $ key => $ value ) {
158- $ literal = self ::toUnicodeLiteral ($ value );
159- $ pattern = '/: ' . preg_quote ($ key , '/ ' ) . '/ ' ;
160- $ query = preg_replace ($ pattern , $ literal , $ query );
161- }
162-
163- return $ query ;
164- }
165149}
0 commit comments