Skip to content

Commit 689c455

Browse files
committed
Fix PDO compatibility issues with older PHP versions
1 parent e36797e commit 689c455

File tree

2 files changed

+63
-29
lines changed

2 files changed

+63
-29
lines changed

tests/WP_PDO_MySQL_On_SQLite_PDO_API_Tests.php

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@
33
use PHPUnit\Framework\TestCase;
44

55
class WP_PDO_MySQL_On_SQLite_PDO_API_Tests extends TestCase {
6-
/**
7-
* On PHP < 8.1, some PDO behavior is notably different from PHP >= 8.1.
8-
* To address that, we need to use conditional assertions in some cases.
9-
*/
10-
const LEGACY_PDO = PHP_VERSION_ID < 80100;
11-
126
/** @var WP_PDO_MySQL_On_SQLite */
137
private $driver;
148

159
public function setUp(): void {
1610
$this->driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' );
11+
12+
// Run all tests with stringified fetch mode results, so we can use
13+
// assertions that are consistent across all tested PHP versions.
14+
// The "PDO::ATTR_STRINGIFY_FETCHES" mode is tested in separately.
15+
$this->driver->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true );
1716
}
1817

1918
public function test_connection(): void {
@@ -46,7 +45,7 @@ public function test_dsn_parsing(): void {
4645
public function test_query(): void {
4746
$result = $this->driver->query( "SELECT 1, 'abc'" );
4847
$this->assertInstanceOf( PDOStatement::class, $result );
49-
if ( self::LEGACY_PDO ) {
48+
if ( PHP_VERSION_ID < 80000 ) {
5049
$this->assertSame(
5150
array(
5251
1 => '1',
@@ -59,8 +58,8 @@ public function test_query(): void {
5958
} else {
6059
$this->assertSame(
6160
array(
62-
1 => 1,
63-
0 => 1,
61+
1 => '1',
62+
0 => '1',
6463
'abc' => 'abc',
6564
),
6665
$result->fetch()
@@ -92,7 +91,7 @@ public function test_query_with_fetch_mode( $query, $mode, $expected ): void {
9291

9392
public function test_query_fetch_mode_not_set(): void {
9493
$result = $this->driver->query( 'SELECT 1' );
95-
if ( self::LEGACY_PDO ) {
94+
if ( PHP_VERSION_ID < 80000 ) {
9695
$this->assertSame(
9796
array(
9897
1 => '1',
@@ -103,8 +102,8 @@ public function test_query_fetch_mode_not_set(): void {
103102
} else {
104103
$this->assertSame(
105104
array(
106-
1 => 1,
107-
0 => 1,
105+
1 => '1',
106+
0 => '1',
108107
),
109108
$result->fetch()
110109
);
@@ -119,7 +118,7 @@ public function test_query_fetch_mode_invalid_arg_count(): void {
119118
}
120119

121120
public function test_query_fetch_default_mode_allow_any_args(): void {
122-
if ( self::LEGACY_PDO ) {
121+
if ( PHP_VERSION_ID < 80100 ) {
123122
// On PHP < 8.1, fetch mode value of NULL is not allowed.
124123
$result = @$this->driver->query( 'SELECT 1', null, 1, 2, 'abc', array(), true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
125124
$this->assertFalse( $result );
@@ -131,8 +130,8 @@ public function test_query_fetch_default_mode_allow_any_args(): void {
131130
// In such cases, any additional arguments are ignored and not validated.
132131
$expected_result = array(
133132
array(
134-
1 => 1,
135-
0 => 1,
133+
1 => '1',
134+
0 => '1',
136135
),
137136
);
138137

@@ -269,7 +268,7 @@ public function test_rollback_no_active_transaction(): void {
269268
public function test_fetch_default(): void {
270269
// Default fetch mode is PDO::FETCH_BOTH.
271270
$result = $this->driver->query( "SELECT 1, 'abc', 2" );
272-
if ( self::LEGACY_PDO ) {
271+
if ( PHP_VERSION_ID < 80000 ) {
273272
$this->assertSame(
274273
array(
275274
1 => '1',
@@ -283,10 +282,10 @@ public function test_fetch_default(): void {
283282
} else {
284283
$this->assertSame(
285284
array(
286-
1 => 1,
287-
0 => 1,
285+
1 => '1',
286+
0 => '1',
288287
'abc' => 'abc',
289-
'2' => 2,
288+
'2' => '2',
290289
),
291290
$result->fetch()
292291
);
@@ -333,12 +332,39 @@ public function test_attr_default_fetch_mode(): void {
333332
);
334333
}
335334

335+
public function test_attr_stringify_fetches(): void {
336+
$this->driver->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true );
337+
$result = $this->driver->query( "SELECT 123, 1.23, 'abc', true, false" );
338+
$this->assertSame(
339+
array( '123', '1.23', 'abc', '1', '0' ),
340+
$result->fetch( PDO::FETCH_NUM )
341+
);
342+
343+
$this->driver->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false );
344+
$result = $this->driver->query( "SELECT 123, 1.23, 'abc', true, false" );
345+
$this->assertSame(
346+
/*
347+
* On PHP < 8.1, "PDO::ATTR_STRINGIFY_FETCHES" set to "false" has no
348+
* effect when "PDO::ATTR_EMULATE_PREPARES" is "true" (the default).
349+
*
350+
* TODO: Consider supporting non-string values on PHP < 8.1 when both
351+
* "PDO::ATTR_STRINGIFY_FETCHES" and "PDO::ATTR_EMULATE_PREPARES"
352+
* are set to "false". This would require emulating the behavior,
353+
* as PDO SQLite on PHP < 8.1 seems to always return strings.
354+
*/
355+
PHP_VERSION_ID < 80100
356+
? array( '123', '1.23', 'abc', '1', '0' )
357+
: array( 123, 1.23, 'abc', 1, 0 ),
358+
$result->fetch( PDO::FETCH_NUM )
359+
);
360+
}
361+
336362
public function data_pdo_fetch_methods(): Generator {
337363
// PDO::FETCH_BOTH
338364
yield 'PDO::FETCH_BOTH' => array(
339365
"SELECT 1, 'abc', 2, 'two' as `2`",
340366
PDO::FETCH_BOTH,
341-
self::LEGACY_PDO
367+
PHP_VERSION_ID < 80000
342368
? array(
343369
1 => '1',
344370
2 => 'two',
@@ -348,8 +374,8 @@ public function data_pdo_fetch_methods(): Generator {
348374
5 => 'two',
349375
)
350376
: array(
351-
1 => 1,
352-
0 => 1,
377+
1 => '1',
378+
0 => '1',
353379
'abc' => 'abc',
354380
2 => 'two',
355381
3 => 'two',
@@ -360,17 +386,15 @@ public function data_pdo_fetch_methods(): Generator {
360386
yield 'PDO::FETCH_NUM' => array(
361387
"SELECT 1, 'abc', 2, 'two' as `2`",
362388
PDO::FETCH_NUM,
363-
self::LEGACY_PDO
364-
? array( '1', 'abc', '2', 'two' )
365-
: array( 1, 'abc', 2, 'two' ),
389+
array( '1', 'abc', '2', 'two' ),
366390
);
367391

368392
// PDO::FETCH_ASSOC
369393
yield 'PDO::FETCH_ASSOC' => array(
370394
"SELECT 1, 'abc', 2, 'two' as `2`",
371395
PDO::FETCH_ASSOC,
372396
array(
373-
1 => self::LEGACY_PDO ? '1' : 1,
397+
1 => '1',
374398
'abc' => 'abc',
375399
2 => 'two',
376400
),
@@ -381,9 +405,9 @@ public function data_pdo_fetch_methods(): Generator {
381405
"SELECT 1, 'abc', 2, 'two' as `2`",
382406
PDO::FETCH_NAMED,
383407
array(
384-
1 => self::LEGACY_PDO ? '1' : 1,
408+
1 => '1',
385409
'abc' => 'abc',
386-
2 => array( self::LEGACY_PDO ? '2' : 2, 'two' ),
410+
2 => array( '2', 'two' ),
387411
),
388412
);
389413

@@ -392,7 +416,7 @@ public function data_pdo_fetch_methods(): Generator {
392416
"SELECT 1, 'abc', 2, 'two' as `2`",
393417
PDO::FETCH_OBJ,
394418
(object) array(
395-
1 => self::LEGACY_PDO ? '1' : 1,
419+
1 => '1',
396420
'abc' => 'abc',
397421
2 => 'two',
398422
),

wp-includes/sqlite-ast/class-wp-pdo-proxy-statement.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ trait WP_PDO_Proxy_Statement_PHP_Compat {
3535
* @return bool True on success, false on failure.
3636
*/
3737
public function setFetchMode( $mode, $params = null ): bool {
38+
// Do not pass additional arguments when they are NULL to prevent
39+
// "fetch mode doesn't allow any extra arguments" error.
40+
if ( null === $params ) {
41+
return $this->setDefaultFetchMode( $mode );
42+
}
3843
return $this->setDefaultFetchMode( $mode, $params );
3944
}
4045

@@ -47,6 +52,11 @@ public function setFetchMode( $mode, $params = null ): bool {
4752
* @return array The result set as an array of rows.
4853
*/
4954
public function fetchAll( $mode = null, $class_name = null, $constructor_args = null ): array {
55+
// Do not pass additional arguments when they are NULL to prevent
56+
// "Extraneous additional parameters" error.
57+
if ( null === $class_name && null === $constructor_args ) {
58+
return $this->fetchAllRows( $mode );
59+
}
5060
return $this->fetchAllRows( $mode, $class_name, $constructor_args );
5161
}
5262
}

0 commit comments

Comments
 (0)