Skip to content

Commit 64bd5ed

Browse files
committed
Address differences between PDO on PHP < 8.1 and PDO on PHP >= 8.1
1 parent 01c41de commit 64bd5ed

2 files changed

Lines changed: 111 additions & 43 deletions

File tree

tests/WP_PDO_MySQL_On_SQLite_PDO_API_Tests.php

Lines changed: 103 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
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+
612
/** @var WP_PDO_MySQL_On_SQLite */
713
private $driver;
814

915
public function setUp(): void {
1016
$this->driver = new WP_PDO_MySQL_On_SQLite( 'mysql-on-sqlite:path=:memory:;dbname=wp;' );
11-
12-
// Set "PDO::ATTR_STRINGIFY_FETCHES" to "false" explicitly, so the tests
13-
// are consistent across PHP versions ("false" is the default from 8.1).
14-
$this->driver->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, false );
1517
}
1618

1719
public function test_connection(): void {
@@ -44,14 +46,26 @@ public function test_dsn_parsing(): void {
4446
public function test_query(): void {
4547
$result = $this->driver->query( "SELECT 1, 'abc'" );
4648
$this->assertInstanceOf( PDOStatement::class, $result );
47-
$this->assertSame(
48-
array(
49-
1 => 1,
50-
0 => 1,
51-
'abc' => 'abc',
52-
),
53-
$result->fetch()
54-
);
49+
if ( self::LEGACY_PDO ) {
50+
$this->assertSame(
51+
array(
52+
1 => '1',
53+
2 => '1',
54+
'abc' => 'abc',
55+
3 => 'abc',
56+
),
57+
$result->fetch()
58+
);
59+
} else {
60+
$this->assertSame(
61+
array(
62+
1 => 1,
63+
0 => 1,
64+
'abc' => 'abc',
65+
),
66+
$result->fetch()
67+
);
68+
}
5569
}
5670

5771
/**
@@ -60,9 +74,10 @@ public function test_query(): void {
6074
public function test_query_with_fetch_mode( $query, $mode, $expected ): void {
6175
$stmt = $this->driver->query( $query, $mode );
6276
$result = $stmt->fetch();
77+
6378
if ( is_object( $expected ) ) {
6479
$this->assertInstanceOf( get_class( $expected ), $result );
65-
$this->assertEquals( $expected, $result );
80+
$this->assertSame( (array) $expected, (array) $result );
6681
} elseif ( PDO::FETCH_NAMED === $mode ) {
6782
// PDO::FETCH_NAMED returns all array keys as strings, even numeric
6883
// ones. This is not possible in plain PHP and might be a PDO bug.
@@ -77,13 +92,23 @@ public function test_query_with_fetch_mode( $query, $mode, $expected ): void {
7792

7893
public function test_query_fetch_mode_not_set(): void {
7994
$result = $this->driver->query( 'SELECT 1' );
80-
$this->assertSame(
81-
array(
82-
'1' => 1,
83-
0 => 1,
84-
),
85-
$result->fetch()
86-
);
95+
if ( self::LEGACY_PDO ) {
96+
$this->assertSame(
97+
array(
98+
1 => '1',
99+
2 => '1',
100+
),
101+
$result->fetch()
102+
);
103+
} else {
104+
$this->assertSame(
105+
array(
106+
1 => 1,
107+
0 => 1,
108+
),
109+
$result->fetch()
110+
);
111+
}
87112
$this->assertFalse( $result->fetch() );
88113
}
89114

@@ -94,6 +119,16 @@ public function test_query_fetch_mode_invalid_arg_count(): void {
94119
}
95120

96121
public function test_query_fetch_default_mode_allow_any_args(): void {
122+
if ( self::LEGACY_PDO ) {
123+
// On PHP < 8.1, fetch mode value of NULL is not allowed.
124+
$result = @$this->driver->query( 'SELECT 1', null, 1, 2, 'abc', array(), true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
125+
$this->assertFalse( $result );
126+
$this->assertSame( 'PDO::query(): SQLSTATE[HY000]: General error: mode must be an integer', error_get_last()['message'] );
127+
return;
128+
}
129+
130+
// On PHP >= 8.1, NULL fetch mode is allowed to use the default fetch mode.
131+
// In such cases, any additional arguments are ignored and not validated.
97132
$expected_result = array(
98133
array(
99134
1 => 1,
@@ -234,15 +269,28 @@ public function test_rollback_no_active_transaction(): void {
234269
public function test_fetch_default(): void {
235270
// Default fetch mode is PDO::FETCH_BOTH.
236271
$result = $this->driver->query( "SELECT 1, 'abc', 2" );
237-
$this->assertSame(
238-
array(
239-
1 => 1,
240-
0 => 1,
241-
'abc' => 'abc',
242-
'2' => 2,
243-
),
244-
$result->fetch()
245-
);
272+
if ( self::LEGACY_PDO ) {
273+
$this->assertSame(
274+
array(
275+
1 => '1',
276+
2 => '2',
277+
'abc' => 'abc',
278+
3 => 'abc',
279+
4 => '2',
280+
),
281+
$result->fetch()
282+
);
283+
} else {
284+
$this->assertSame(
285+
array(
286+
1 => 1,
287+
0 => 1,
288+
'abc' => 'abc',
289+
'2' => 2,
290+
),
291+
$result->fetch()
292+
);
293+
}
246294
}
247295

248296
/**
@@ -251,6 +299,7 @@ public function test_fetch_default(): void {
251299
public function test_fetch( $query, $mode, $expected ): void {
252300
$stmt = $this->driver->query( $query );
253301
$result = $stmt->fetch( $mode );
302+
254303
if ( is_object( $expected ) ) {
255304
$this->assertInstanceOf( get_class( $expected ), $result );
256305
$this->assertEquals( $expected, $result );
@@ -269,30 +318,41 @@ public function data_pdo_fetch_methods(): Generator {
269318
yield 'PDO::FETCH_BOTH' => array(
270319
"SELECT 1, 'abc', 2, 'two' as `2`",
271320
PDO::FETCH_BOTH,
272-
array(
273-
1 => 1,
274-
0 => 1,
275-
'abc' => 'abc',
276-
'2' => 'two',
277-
'3' => 'two',
278-
),
321+
self::LEGACY_PDO
322+
? array(
323+
1 => '1',
324+
2 => 'two',
325+
'abc' => 'abc',
326+
3 => 'abc',
327+
4 => '2',
328+
5 => 'two',
329+
)
330+
: array(
331+
1 => 1,
332+
0 => 1,
333+
'abc' => 'abc',
334+
2 => 'two',
335+
3 => 'two',
336+
),
279337
);
280338

281339
// PDO::FETCH_NUM
282340
yield 'PDO::FETCH_NUM' => array(
283341
"SELECT 1, 'abc', 2, 'two' as `2`",
284342
PDO::FETCH_NUM,
285-
array( 1, 'abc', 2, 'two' ),
343+
self::LEGACY_PDO
344+
? array( '1', 'abc', '2', 'two' )
345+
: array( 1, 'abc', 2, 'two' ),
286346
);
287347

288348
// PDO::FETCH_ASSOC
289349
yield 'PDO::FETCH_ASSOC' => array(
290350
"SELECT 1, 'abc', 2, 'two' as `2`",
291351
PDO::FETCH_ASSOC,
292352
array(
293-
'1' => 1,
353+
1 => self::LEGACY_PDO ? '1' : 1,
294354
'abc' => 'abc',
295-
'2' => 'two',
355+
2 => 'two',
296356
),
297357
);
298358

@@ -301,9 +361,9 @@ public function data_pdo_fetch_methods(): Generator {
301361
"SELECT 1, 'abc', 2, 'two' as `2`",
302362
PDO::FETCH_NAMED,
303363
array(
304-
'1' => 1,
364+
1 => self::LEGACY_PDO ? '1' : 1,
305365
'abc' => 'abc',
306-
'2' => array( 2, 'two' ),
366+
2 => array( self::LEGACY_PDO ? '2' : 2, 'two' ),
307367
),
308368
);
309369

@@ -312,9 +372,9 @@ public function data_pdo_fetch_methods(): Generator {
312372
"SELECT 1, 'abc', 2, 'two' as `2`",
313373
PDO::FETCH_OBJ,
314374
(object) array(
315-
'1' => 1,
375+
1 => self::LEGACY_PDO ? '1' : 1,
316376
'abc' => 'abc',
317-
'2' => 'two',
377+
2 => 'two',
318378
),
319379
);
320380
}

wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,14 @@ public function query( string $query, ?int $fetch_mode = null, ...$fetch_mode_ar
777777
};
778778

779779
if ( null === $fetch_mode ) {
780+
if ( PHP_VERSION_ID < 80100 && func_num_args() > 1 ) {
781+
trigger_error(
782+
'PDO::query(): SQLSTATE[HY000]: General error: mode must be an integer',
783+
E_USER_WARNING
784+
);
785+
return false;
786+
}
787+
780788
// When the default FETCH_BOTH is not set explicitly, additional
781789
// arguments are ignored, and the argument count is not validated.
782790
$fetch_mode = PDO::FETCH_BOTH;

0 commit comments

Comments
 (0)