66 * phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDOStatement
77 *
88 * PDO uses camel case naming, enable non-snake:
9+ * phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
910 * phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1011 *
1112 * PDO uses $class as a variable name, enable it:
1213 * phpcs:disable Universal.NamingConventions.NoReservedKeywordParameterNames.classFound
14+ *
15+ * We use traits to support different PHP versions with incompatible PDO statement
16+ * method signatures. For that, enable multiple object structures in one file:
17+ * phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
1318 */
1419
20+ if ( PHP_VERSION_ID < 80000 ) {
21+ trait WP_PDO_Synthetic_Statement_PHP_Compat {
22+
23+ public function setFetchMode ( $ mode , $ params = null ): bool {
24+ $ this ->fetch_mode = $ mode ;
25+ return true ;
26+ }
27+
28+ public function fetchAll ( $ mode = null , $ class_name = null , $ constructor_args = null ): array {
29+ return $ this ->fetchAllRows ( $ mode , $ class_name , $ constructor_args );
30+ }
31+ }
32+ } else {
33+ trait WP_PDO_Synthetic_Statement_PHP_Compat {
34+
35+ #[ReturnTypeWillChange]
36+ public function setFetchMode ( $ mode , ...$ args ): bool {
37+ $ this ->fetch_mode = $ mode ;
38+ return true ;
39+ }
40+
41+ public function fetchAll ( $ mode = PDO ::FETCH_DEFAULT , ...$ args ): array {
42+ return $ this ->fetchAllRows ( $ mode , ...$ args );
43+ }
44+ }
45+ }
46+
1547class WP_PDO_Synthetic_Statement extends PDOStatement {
48+ use WP_PDO_Synthetic_Statement_PHP_Compat;
49+
1650 /**
17- * Column names of the columns in the result set.
51+ * The PDO connection.
52+ *
53+ * @var PDO
54+ */
55+ private $ pdo ;
56+
57+ /**
58+ * Column metadata as reported by SQLite.
1859 *
1960 * @var array<string>
2061 */
21- private $ columns = array ();
62+ private $ sqlite_column_meta = array ();
2263
2364 /**
2465 * Rows of the result set.
@@ -44,9 +85,18 @@ class WP_PDO_Synthetic_Statement extends PDOStatement {
4485 /**
4586 * The current fetch mode.
4687 *
88+ * The PDO::FETCH_DEFAULT constant is available from PHP 8.0.
89+ *
4790 * @var int
4891 */
49- private $ fetch_mode = PDO ::FETCH_DEFAULT ;
92+ private $ fetch_mode = 0 ; // PDO::FETCH_DEFAULT
93+
94+ /**
95+ * The PDO attributes of the statement.
96+ *
97+ * @var array<int, mixed>
98+ */
99+ private $ attributes = array ();
50100
51101 /**
52102 * Additional arguments for the current fetch mode.
@@ -56,50 +106,82 @@ class WP_PDO_Synthetic_Statement extends PDOStatement {
56106 private $ fetch_mode_args = array ();
57107
58108 public function __construct (
109+ PDO $ pdo ,
110+ array $ sqlite_column_metadata ,
59111 array $ rows ,
60- array $ columns ,
61112 int $ affected_rows = 0
62113 ) {
63- $ this ->rows = $ rows ;
64- $ this ->columns = $ columns ;
65- $ this ->affected_rows = $ affected_rows ;
114+ $ this ->pdo = $ pdo ;
115+ $ this ->sqlite_column_meta = $ sqlite_column_metadata ;
116+ $ this ->rows = $ rows ;
117+ $ this ->affected_rows = $ affected_rows ;
66118 }
67119
68- public function execute ( ? array $ params = null ): bool {
120+ public function execute ( $ params = null ): bool {
69121 return true ;
70122 }
71123
72124 public function columnCount (): int {
73- return count ( $ this ->columns );
125+ return count ( $ this ->sqlite_column_meta );
74126 }
75127
76128 public function rowCount (): int {
77129 return $ this ->affected_rows ;
78130 }
79131
132+ /**
133+ * Fetch a row from the result set.
134+ *
135+ * @param int|null $mode The fetch mode.
136+ * Default: PDO::FETCH_DEFAULT (null for PHP < 8.0)
137+ * @param int|null $cursorOrientation The cursor orientation.
138+ * Default: PDO::FETCH_ORI_NEXT (null for PHP < 8.0)
139+ * @param int|null $cursorOffset The cursor offset.
140+ * Default: 0 (null for PHP < 8.0)
141+ * @return array|false The row or false if there are no more rows.
142+ */
80143 #[ReturnTypeWillChange]
81144 public function fetch (
82- int $ mode = PDO :: FETCH_DEFAULT ,
83- int $ cursorOrientation = PDO ::FETCH_ORI_NEXT ,
84- int $ cursorOffset = 0
145+ $ mode = 0 ,
146+ $ cursorOrientation = PDO ::FETCH_ORI_NEXT ,
147+ $ cursorOffset = 0
85148 ) {
149+ if ( 0 === $ mode || null === $ mode ) {
150+ $ mode = $ this ->fetch_mode ;
151+ }
152+ if ( null === $ cursorOrientation ) {
153+ $ cursorOrientation = 0 ; // PDO::FETCH_ORI_NEXT (avaliable from PHP 8.0)
154+ }
155+ if ( null === $ cursorOffset ) {
156+ $ cursorOffset = 0 ;
157+ }
158+
86159 if ( ! array_key_exists ( $ this ->row_offset , $ this ->rows ) ) {
87160 return false ;
88161 }
89162 // TODO: $cursorOffset
90163
91- if ( PDO ::FETCH_DEFAULT === $ mode ) {
92- $ mode = $ this ->fetch_mode ;
93- }
94-
95164 $ row = $ this ->rows [ $ this ->row_offset ];
96165 $ this ->row_offset += 1 ;
97166
167+ $ column_names = array_column ( $ this ->sqlite_column_meta , 'name ' );
168+
169+ if ( PHP_VERSION_ID < 80100 && ! $ this ->getAttribute ( PDO ::ATTR_STRINGIFY_FETCHES ) ) {
170+ foreach ( $ row as $ i => $ value ) {
171+ $ type = $ this ->sqlite_column_meta [ $ i ]['native_type ' ];
172+ if ( 'integer ' === $ type ) {
173+ $ row [ $ i ] = (int ) $ value ;
174+ } elseif ( 'float ' === $ type ) {
175+ $ row [ $ i ] = (float ) $ value ;
176+ }
177+ }
178+ }
179+
98180 switch ( $ mode ) {
99181 case PDO ::FETCH_BOTH :
100182 $ values = array ();
101183 foreach ( $ row as $ i => $ value ) {
102- $ name = $ this -> columns [ $ i ];
184+ $ name = $ column_names [ $ i ];
103185 $ values [ $ name ] = $ value ;
104186 if ( ! array_key_exists ( $ i , $ values ) ) {
105187 $ values [ $ i ] = $ value ;
@@ -109,11 +191,11 @@ public function fetch(
109191 case PDO ::FETCH_NUM :
110192 return $ row ;
111193 case PDO ::FETCH_ASSOC :
112- return array_combine ( $ this -> columns , $ row );
194+ return array_combine ( $ column_names , $ row );
113195 case PDO ::FETCH_NAMED :
114196 $ values = array ();
115197 foreach ( $ row as $ i => $ value ) {
116- $ name = $ this -> columns [ $ i ];
198+ $ name = $ column_names [ $ i ];
117199 if ( is_array ( $ values [ $ name ] ?? null ) ) {
118200 $ values [ $ name ][] = $ value ;
119201 } elseif ( array_key_exists ( $ name , $ values ) ) {
@@ -124,32 +206,24 @@ public function fetch(
124206 }
125207 return $ values ;
126208 case PDO ::FETCH_OBJ :
127- $ assoc = array_combine ( $ this -> columns , $ row );
209+ $ assoc = array_combine ( $ column_names , $ row );
128210 return (object ) $ assoc ;
129211 default :
130212 throw new ValueError ( sprintf ( 'Unsupported fetch mode: %d ' , $ mode ) );
131213 }
132214 }
133215
134- public function fetchAll ( int $ mode = PDO ::FETCH_DEFAULT , ...$ args ): array {
135- $ rows = array ();
136- while ( $ row = $ this ->fetch ( $ mode , ...$ args ) ) {
137- $ rows [] = $ row ;
138- }
139- return $ rows ;
140- }
141-
142216 #[ReturnTypeWillChange]
143- public function fetchColumn ( int $ column = 0 ) {
217+ public function fetchColumn ( $ column = 0 ) {
144218 return $ this ->rows [ $ this ->row_offset ][ $ column ] ?? null ;
145219 }
146220
147221 #[ReturnTypeWillChange]
148- public function fetchObject ( ? string $ class = 'stdClass ' , array $ constructorArgs = array () ) {
222+ public function fetchObject ( $ class = 'stdClass ' , $ constructorArgs = array () ) {
149223 return new $ class ( $ this ->rows [ $ this ->row_offset ], $ constructorArgs );
150224 }
151225
152- public function getColumnMeta ( int $ column ): array {
226+ public function getColumnMeta ( $ column ): array {
153227 throw new RuntimeException ( 'Not implemented ' );
154228 }
155229
@@ -162,11 +236,27 @@ public function errorInfo(): array {
162236 }
163237
164238 #[ReturnTypeWillChange]
165- public function setFetchMode ( int $ mode , ...$ args ): bool {
166- $ this ->fetch_mode = $ mode ;
239+ public function getAttribute ( $ attribute ) {
240+ return $ this ->attributes [ $ attribute ] ?? $ this ->pdo ->getAttribute ( $ attribute );
241+ }
242+
243+ public function setAttribute ( $ attribute , $ value ): bool {
244+ $ this ->attributes [ $ attribute ] = $ value ;
167245 return true ;
168246 }
169247
248+ private function fetchAllRows ( $ mode = null , ...$ args ): array {
249+ if ( null === $ mode || 0 === $ mode ) {
250+ $ mode = $ this ->fetch_mode ;
251+ }
252+
253+ $ rows = array ();
254+ while ( $ row = $ this ->fetch ( $ mode , ...$ args ) ) {
255+ $ rows [] = $ row ;
256+ }
257+ return $ rows ;
258+ }
259+
170260 // TODO:
171261 // public function bindColumn()
172262 // public function bindParam()
0 commit comments