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
1414namespace 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