PHP 8.0: PDO: Default error mode set to exceptions

Version8.0
TypeChange

PHP's PDO extension provides a consistent interface to access databases. it supports various database software, including MySQL, PostgreSQL and SQLite.

For each connection PDO makes to a database, it supports an attribute to specify how PDO should behave when an error occurs with the query, connection, or the database.

PDO::ATTR_ERRMODE attribute controls how PDO should behave on errors.

  • PDO::ERRMODE_SILENT (default): PDO will not raise warnings or throw exceptions in case of an error. It is up to the caller to inspect PDO::errorCode() and PDO::errorInfo() methods on both statement and PDO objects to determine errors.
  • PDO::ERRMODE_WARNING: Emits a warning (E_WARNING) in addition to setting the error code and information.
  • PDO::ERRMODE_EXCEPTION: Throws a PDOException in case of an error.

The error mode attribute can be set when instantiating the PDO object, or after the connection is established.

$pdo = new PDO($dsn, 'root', '', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$pdo = new PDO($dsn, 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Prior to PHP 8.0, the default error mode was to be silent (PDO::ERRMODE_SILENT); PDO did not emit a warning or throw an exception if the database returned an error. Most libraries that deals with database often explicitly set the error mode to PDO::ERRMODE_EXCEPTION because throwing an exception is desired as it minimizes the chances of overlooking database errors.

$pdo = new PDO('mysql:host=loc...', 'root', '');
$pdo->exec("DELETE FROM probably_missing_table WHERE id=42");

The snippet above, for example, tries to delete a record from a table that does not exist. It is desired that that the database error SQLSTATE[42S02]: Base table or view not found: 1146 Table 'phpwatch.myguests' doesn't exist is brought as a PDOException.

In PHP 8.0, the default PDO::ATTR_ERRMODE attribute is set to PDO::ERRMODE_EXCEPTION.

It is still allowed to explicitly set the attribute to PDO::ERRMODE_EXCEPTION. Applications that already used the PDO::ERRMODE_EXCEPTION error mode will not need to make any changes.

Backwards Compatibility Impact

Although not recommended, applications that relied on the PDO's silent behavior on errors can set PDO::ATTR_ERRMODE attribute to PDO::ERRMODE_SILENT. This will make PDO silently ignore all errors throughout the lifespan of that database connection.

$pdo = new PDO($dsn, 'root', '', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT
]);

To selectively ignore database errors, it is more appropriate to catch that error instead of setting the PDO::ATTR_ERRMODE attribute throughout the connection.

- $pdo->exec("DELETE FROM probably_missing_table WHERE id=42");
+ try {
+   $pdo->exec("DELETE FROM probably_missing_table WHERE id=42");
+ }
+ catch (PDOException) {}

This snippet uses the PHP 8.0 feature to catch exceptions only by type.

Both approaches above are not ideal, as they ignore errors returned from the database, and ideally should be corrected in the query or the database structure.

Related Changes

In PHP 8.1, the default error handling behavior of the MySQLi extension has changed from silencing errors to throw an Exception on errors.

Prior to PHP 8.1, the default error reporting mode in MySQLi was to silent the errors, and often lead to code that did not follow proper Exception/Error handling. From PHP 8.1 and later, the default error reporting mode is to throw an Exception on errors.

MySQLi's default error mode is changed from MYSQLI_REPORT_OFF to MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT

- MYSQLI_REPORT_OFF
+ MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT

This change is similar to PDO extension changing its error reporting mode to Exceptions in PHP 8.0.


Prior to PHP 8.1, an error in the extension, database, query, or the database connection returned false and emitted a PHP warning.

$mysqli = new mysqli("localhost", "non-existing-user", "", "");
Warning: mysqli::__construct(): (HY000/2002): No connection could be made because the target machine actively
refused it in ... on line ...

From PHP 8.1 and later, the default error mode is set to MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT.

$mysqli = new mysqli("localhost", "non-existing-user", "", "");
Fatal error: Uncaught mysqli_sql_exception: Connection refused in ...:...

Backwards Compatibility Impact

This is a breaking change because the default value is different in PHP 8.1.

For compatibility across PHP versions prior to PHP 8.1, it is possible to explicitly set the error handling mode using mysqli_report function before the first MySQLi connection is made. It is also possible to instantiate a mysqli_driver instance, and set the error reporting value.

Note that mysqli_driver class has a mono-state pattern, which means once the error reporting value is set, it affects all MySQLi connections throughout the request unless the value is later overwritten in another call. The same applies for the mysqli_report function as well.


Configure PHP 8.1 behavior in all PHP versions:

 mysqli_report(MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT);

or

$driver = new mysqli_driver();  
$driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT;

Configure PHP < 8.1 behavior in all PHP versions:

mysqli_report(MYSQLI_REPORT_OFF);

or

$driver = new mysqli_driver();
$driver->report_mode = MYSQLI_REPORT_OFF;

Using Exceptions (default behavior in PHP 8.1) is recommended, because it brings errors that could have gone unnoticed. To prevent sensitive data leakage, configure the display_errors INI value to off. With MYSQLI_REPORT_OFF, the caller must handle the errors by checking the return value of mysqli_* function calls, or by checking the $mysqli->error property.

Related Changes


RFC Discussion Implementation


RFC Discussion Implementation