Skip to content

[RFC] Change default PDO error mode to exceptions #5388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ PHP 8.0 UPGRADE NOTES
now ignored.

- PDO:
. The default error handling mode has been changed from "silent" to
"exceptions". See https://www.php.net/manual/en/pdo.error-handling.php
for details of behavior changes and how to explicitly set this attribute.
RFC: https://wiki.php.net/rfc/pdo_default_errmode
. The method PDOStatement::setFetchMode() now accepts the following signature:

PDOStatement::setFetchMode($mode, $classname, $params)
Expand Down
1 change: 1 addition & 0 deletions ext/pdo/pdo_dbh.c
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ static PHP_METHOD(PDO, dbh_constructor)
}

dbh->auto_commit = pdo_attr_lval(options, PDO_ATTR_AUTOCOMMIT, 1);
dbh->error_mode = pdo_attr_lval(options, PDO_ATTR_ERRMODE, PDO_ERRMODE_EXCEPTION);

if (!dbh->data_source || (username && !dbh->username) || (password && !dbh->password)) {
php_error_docref(NULL, E_ERROR, "Out of memory");
Expand Down
9 changes: 9 additions & 0 deletions ext/pdo/tests/bug_44159.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ try {
--FILE--
<?php
$pdo = new PDO("sqlite:".__DIR__."/foo.db");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);

$attrs = array(PDO::ATTR_STATEMENT_CLASS, PDO::ATTR_STRINGIFY_FETCHES, PDO::NULL_TO_STRING);

Expand All @@ -26,15 +27,23 @@ foreach ($attrs as $attr) {
?>
--EXPECTF--
Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); the classname must be a string specifying an existing class in %s on line %d

Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)

Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); the classname must be a string specifying an existing class in %s on line %d

Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)

Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); the classname must be a string specifying an existing class in %s on line %d

Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)

Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: attribute value must be an integer in %s on line %d

Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)
bool(true)
bool(true)
Expand Down
2 changes: 2 additions & 0 deletions ext/pdo/tests/pdo_test.inc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class PDOTest {
if (!$db) {
die("Could not create PDO object (DSN=$dsn, user=$user)\n");
}
// Ignore errors about non-existant tables
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);

// clean up any crufty test tables we might have left behind
// on a previous run
Expand Down
8 changes: 8 additions & 0 deletions ext/pdo_mysql/tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ PDO_MYSQL_TEST_CHARSET
NOTE: if any of `PDO_MYSQL_TEST_[HOST|DB|SOCKET|ENGINE|CHARSET]` is part of
`PDO_MYSQL_TEST_DSN`, the values must match. That is, for example, for
`PDO_MYSQL_TEST_DSN = mysql:dbname=test` you MUST set `PDO_MYSQL_TEST_DB=test`.

## MySQL User Permissions

The MySQL user used to run the tests must have full permissions on the test
database, plus the following additional permissions:

* SUPER: Required to [create functions if binary logging is enabled](https://dev.mysql.com/doc/refman/8.0/en/stored-programs-logging.html#sa38412929)
* SELECT permissions on performance_schema.session_connect_attrs
3 changes: 2 additions & 1 deletion ext/pdo_mysql/tests/pdo_mysql___construct.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ MySQLPDOTest::skip();
$dsn = MySQLPDOTest::getDSN(array('dbname' => $db), 'dbname=' . $invalid_db);
try { $db = @new PDO($dsn, $user, $pass); assert(false); printf("%s\n", $dsn); } catch (PDOException $e) {
$tmp = $e->getMessage();
if (!stristr($tmp, '42000') && !stristr($tmp, '1049'))
// 1044 may occur here if running tests using a custom user that does not have access to all databases
if (!stristr($tmp, '42000') && !stristr($tmp, '1049') && !stristr($tmp, '1044'))
printf("[022] Cannot find proper error codes: %s\n", $tmp);
}

Expand Down
7 changes: 5 additions & 2 deletions ext/pdo_mysql/tests/pdo_mysql___construct_options.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ MySQLPDOTest::skip();

try {
$db = new PDO($dsn, $user, $pass, array($option => $value));
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
if (!is_object($db) || ($value !== ($tmp = @$db->getAttribute($option))))
printf("[%03d] Expecting '%s'/%s got '%s'/%s' for options '%s'\n",
$offset,
Expand Down Expand Up @@ -81,6 +82,7 @@ MySQLPDOTest::skip();
printf("[003] [TODO][CHANGEREQUEST] Please, lets not ignore invalid options and bail out!\n");

$db = new PDO($dsn, $user, $pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
foreach ($valid_options as $option => $name) {
/* TODO getAttribute() is pretty poor in supporting the options, suppress errors */
$tmp = @$db->getAttribute($option);
Expand Down Expand Up @@ -155,10 +157,11 @@ MySQLPDOTest::skip();
set_option_and_check(34, PDO::MYSQL_ATTR_DIRECT_QUERY, 0, 'PDO::MYSQL_ATTR_DIRECT_QUERY');

} catch (PDOException $e) {
printf("[001] %s, [%s] %s\n",
printf("[001] %s, [%s] %s Line: %s\n",
$e->getMessage(),
(is_object($db)) ? $db->errorCode() : 'n/a',
(is_object($db)) ? implode(' ', $db->errorInfo()) : 'n/a');
(is_object($db)) ? implode(' ', $db->errorInfo()) : 'n/a',
$e->getLine());
}

print "done!";
Expand Down
6 changes: 5 additions & 1 deletion ext/pdo_mysql/tests/pdo_mysql_attr_multi_statements.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ error_reporting=E_ALL

$table = sprintf("test_%s", md5(mt_rand(0, PHP_INT_MAX)));
$db = new PDO($dsn, $user, $pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$db->exec(sprintf('DROP TABLE IF EXISTS %s', $table));
$create = sprintf('CREATE TABLE %s(id INT)', $table);
$db->exec($create);
Expand All @@ -35,6 +36,7 @@ error_reporting=E_ALL

// New connection, does not allow multiple statements.
$db = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_MULTI_STATEMENTS => false));
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$stmt = $db->query(sprintf('SELECT * FROM %s; INSERT INTO %s(id) VALUES (3)', $table, $table));
var_dump($stmt);
$info = $db->errorInfo();
Expand All @@ -49,7 +51,7 @@ error_reporting=E_ALL
$db->exec(sprintf('DROP TABLE IF EXISTS %s', $table));
print "done!";
?>
--EXPECT--
--EXPECTF--
string(5) "00000"
array(2) {
[0]=>
Expand All @@ -70,6 +72,8 @@ array(1) {
string(1) "1"
}
}

Warning: PDO::query(): SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INSERT INTO %s(id) VALUES (3)' at line 1 in %s on line %d
bool(false)
string(5) "42000"
array(2) {
Expand Down
1 change: 1 addition & 0 deletions ext/pdo_mysql/tests/pdo_mysql_exec.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ MySQLPDOTest::skip();
<?php
require __DIR__ . '/mysql_pdo_test.inc';
$db = MySQLPDOTest::factory();
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
@$db->exec('DROP TABLE IF EXISTS test');
?>
--EXPECTF--
Expand Down
6 changes: 5 additions & 1 deletion ext/pdo_mysql/tests/pdo_mysql_multi_stmt_nextrowset.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ if (!MySQLPDOTest::isPDOMySQLnd())
$user = PDO_MYSQL_TEST_USER;
$pass = PDO_MYSQL_TEST_PASS;
$db = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_MULTI_STATEMENTS => $multi));
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
test_proc($db);

$db = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_MULTI_STATEMENTS => $multi));
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, 0);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
Expand Down Expand Up @@ -86,7 +88,7 @@ if (!MySQLPDOTest::isPDOMySQLnd())
require __DIR__ . '/mysql_pdo_test.inc';
MySQLPDOTest::dropTestTable();
?>
--EXPECT--
--EXPECTF--
Native PS...

Testing with PDO::MYSQL_ATTR_MULTI_STATEMENTS set to false
Expand Down Expand Up @@ -172,6 +174,8 @@ array(3) {
}
}
bool(false)

Warning: PDO::query(): SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INSERT INTO test (id, label) VALUES (99, 'x')' at line 1 in %s on line %d
string(5) "42000"

Testing with PDO::MYSQL_ATTR_MULTI_STATEMENTS set to true
Expand Down
5 changes: 5 additions & 0 deletions ext/pdo_mysql/tests/pdo_mysql_pconnect.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ MySQLPDOTest::skip();

$db1 = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
$db2 = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
$db1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$db2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$db1->exec('SET @pdo_persistent_connection=1');
$stmt = $db2->query('SELECT @pdo_persistent_connection as _pers');
$tmp = $stmt->fetch(PDO::FETCH_ASSOC);
Expand All @@ -37,6 +39,7 @@ MySQLPDOTest::skip();

$db1 = NULL; /* should be equal to closing to my understanding */
$db1 = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
$db1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$stmt = $db1->query('SELECT CONNECTION_ID() as _con1');
$tmp = $stmt->fetch(PDO::FETCH_ASSOC);
$con1 = $tmp['_con1'];
Expand All @@ -60,11 +63,13 @@ MySQLPDOTest::skip();
}

$db1 = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => false));
$db1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$stmt = $db1->query('SELECT CONNECTION_ID() as _con1');
$tmp = $stmt->fetch(PDO::FETCH_ASSOC);
$con1 = $tmp['_con1'];

@$db2 = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
$db2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$stmt = $db2->query('SELECT CONNECTION_ID() as _con2');
$tmp = $stmt->fetch(PDO::FETCH_ASSOC);
$con2 = $tmp['_con2'];
Expand Down
18 changes: 18 additions & 0 deletions ext/pdo_sqlite/tests/pdo_fetch_func_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ if (!extension_loaded('pdo_sqlite')) print 'skip not loaded';
<?php

$db = new PDO('sqlite::memory:');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);

$db->exec('CREATE TABLE testing (id INTEGER , name VARCHAR)');
$db->exec('INSERT INTO testing VALUES(1, "php")');
$db->exec('INSERT INTO testing VALUES(2, "")');
Expand Down Expand Up @@ -91,18 +93,28 @@ array(2) {
}

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: function 'nothing' not found or invalid function name in %s on line %d

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: function '' not found or invalid function name in %s on line %d

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: no array or string given in %s on line %d

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: no array or string given in %s on line %d

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: class 'PDOStatement' does not have a method 'foo' in %s on line %d

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)
array(2) {
[0]=>
Expand All @@ -118,10 +130,16 @@ array(2) {
}

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: non-static method bar::test2() cannot be called statically in %s on line %d

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: non-static method bar::test3() cannot be called statically in %s on line %d

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: class 'bar' does not have a method 'inexistent' in %s on line %d

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d
bool(false)
2 changes: 2 additions & 0 deletions ext/pdo_sqlite/tests/pdo_sqlite_extendederror_attr.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ PDO_sqlite: Testing PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES

echo "Creating new PDO" . PHP_EOL;
$db = new PDO('sqlite::memory:');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);

$db->exec("CREATE TABLE dog ( id INTEGER PRIMARY KEY, name TEXT, annoying INTEGER )");

Expand All @@ -23,6 +24,7 @@ echo sprintf("Second Error Info: SQLSTATE Error Code: (%s), Driver Specific Erro

echo "Creating new PDO with Extended Result Codes turned on" . PHP_EOL;
$db = new PDO('sqlite::memory:', '', '', [PDO::SQLITE_ATTR_EXTENDED_RESULT_CODES => TRUE]);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);

$db->exec("CREATE TABLE dog ( id INTEGER PRIMARY KEY, name TEXT, annoying INTEGER )");

Expand Down
5 changes: 4 additions & 1 deletion ext/pdo_sqlite/tests/pdo_sqlite_transaction.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ PDO_sqlite: Testing transaction
<?php

$db = new PDO('sqlite::memory:');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);

$db->beginTransaction();

Expand All @@ -24,5 +25,7 @@ var_dump($r->rowCount());
$db->query('DROP TABLE foobar');

?>
--EXPECT--
--EXPECTF--
int(0)

Warning: PDO::query(): SQLSTATE[HY000]: General error: 6 database table is locked in %s on line %d