Adds PDO::disconnect() to disconnect from an established database connection and PDO::isConnected() to query connection status.
In invoking PDO::disconnect(), the intended effect is to immediately close connection to the database. PDO::isConnected() will reflect whether a connection has been closed via call to disconnect() or otherwise, by the remote end. Connection state is tracked through a pre-existing sense variable.
<?php class PDO { public function disconnect(): bool {} public function isConnected(): bool {} } ?>
Subsequent interactions with a disconnected PDO object that would interact with the database are expected to fail. This failure state is integrated with PDO error mode and the error interfaces errorCode() and errorInfo() using the standard SQLSTATE error code “01002 - Disconnect error”. Silent or warning error execution modes will prompt the pre-existing FALSE-y return value of these same PDO interfaces.
Some interfaces may still be successfully invoked on a disconnected PDO object, namely disconnect(), isConnected(), getAttribute(), setAttribute(), errorCode(), and errorInfo(). Any prior error state will be cleared upon invocation of disconnect(), allowing for error reporting of disconnect() itself.
Due to implementation constraint, the attribute interfaces getAttribute() and setAttribute() are degraded for disconnected PDO objects, permitting interaction with PDO native attributes only and yielding an implementation error (or warning, depending on error mode) if interacting with driver-specific attributes. The error text for this is as follows: “driver attributes not initialized, possibly due to disconnect”.
Upon persistent PDO object creation, if a connection was previously closed by a call to disconnect(), it will be re-established. This is aligned with current behavior, which replaces the persistent database connection if the connection incurs a “liveness” check failure during instantiation. Note that this connection replacement only occurs for the newly-created PDO object and does not extend to other PDO objects referencing the same persistent connection. Persistent PDO objects which share a database connection will all be disconnected when disconnect() is invoked on any individual PDO object, albeit maintaining multiple PDO objects which reference the same database connection has long been discouraged.
Disconnect error example:
<?php $pdo = new PDO($dsn); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); $pdo->disconnect(); // produces "Warning: PDO::beginTransaction(): SQLSTATE[01002]: Disconnect error in %s on line %d" $pdo->query('SELECT * FROM DB'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // produces PDOException with message "SQLSTATE[01002]: Disconnect error" $pdo->query('SELECT * FROM DB'); ?>
Persistent connect example:
<?php // Request 1 $pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_PERSISTENT => true]); // connected $pdo->disconnect(); // Request 2 $pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_PERSISTENT => true]); // connected ?>
It is assumed that a user has reasonable need to close a database connection prior to and possibly outside script termination. Some possible rationale for doing so are given below.
Currently, a database connection can only be closed by the client through PDO object destruct or, in the case of persistent connections, it is closed at the end of script execution. In order to effectively disconnect and/or replace a database connection, PDO implementation has thus imposed the need to manage PDO object references, as all must be released in order to invoke the object destructor.
Furthermore, there can be no verification of database connection closure, given all PDO references have necessarily been released to achieve the closed state. A user must trust in their implementation without means to verify.
Given the above constraints, a common integration pattern is to decorate the PDO class in order to better ensure that only a single reference is held by the application. It is the community solution to the pursuant complexity problem of managing PDO object references, as evidenced by this related php-internals exchange.
PHP makes the implementation of a decorator simple enough through magic methods like __call and __get, but this is complicated in PDO by the PDO statement object, which holds reference to a PDO object, and so must also be reference-managed. A leak-proof decorator should thus override PDO::prepare() and PDO::query() to also prevent the leak of the PDO statement object, likely requiring additional interfaces to otherwise interact with prepared PDO statements, to e.g. bind parameters and execute them, and queried PDO statements, to e.g. fetch one row, N rows, one column, etc. This prescribed implementation pattern is burdensome.
For persistent PDO objects, the user has no native capacity to close a connection, given that the database handle is inaccessible and registered as a PHP persistent resource, which remains until the PHP process terminates. To ensure database connectivity across subsequent PHP requests, each new PDO object includes a liveness check on the persistent connection, possibly prompting a new connection. This has meant the burden of defining a viable database connection resides within the database driver's liveness check, and that an outcome differing with actual viability as it concerns a given application results in a non-viable database connection for all subsequent PHP requests. The proposed disconnect() thus grants the user the capacity to declare a connection as no-longer viable.
This proposal aims for backwards compatibility, and likely no changes will be needed upon upgrade.
There is no requirement that callers adopt disconnect() to close a connection, nor need callers query isConnected(), and the current destructor-based disconnect pathway remains supported.
Although there is novel introduction of the SQLSTATE error “01002 - Disconnect error” to various PDO interfaces, and which can be encountered even if disconnect() has not been explicitly invoked, i.e. through database driver detection of remote connection closure, the added error conforms to pre-existing PDO error mechanics. PDO implementations presumably already by-and-large include error handling, at least where continued database connectivity has not been guaranteed.
The reviewer should bear in mind that this proposal does not introduce the disconnected state to an established PDO connection, which has necessarily always accompanied PDO, but rather the means for the user to achieve it through a held PDO object.
next PHP 8.x
No known impact.
This proposal targets the common PDO extension and does not require rework to dependent PDO driver extensions.
A minor unit test change is necessary within the MySQL PDO extension, due to the additive change to the PHP PDO interface.
No known impact.
None
The capability to reconnect a disconnected PDO may be desirable to have in the future, but there are several hindrances to providing such functionality: the capture of database credentials and DSN to persist beyond the PDO constructor, the separation of connectivity implementation from the constructor, and the separation of driver implementation for connection closure from the freeing of allocated memory structures, presently combined. Rework along the latter lines would also permit continued interaction with the underlying driver implementation past the point of disconnect().
A wrinkle in PDO implementation referred to above is the PDO statement object and its held reference to the PDO object that created it. When freeing a PDO object, an associated PDO statement object is also necessarily freed, notwithstanding that the underlying database connection may remain open, i.e. is persistent. A worthwhile refactoring effort could seek to remove both the PDO object's held reference to the last-created PDO statement object as well as the PDO statement's held reference to the PDO object which created it.
In the case of PDO, the reference to a PDO statement appears to exist for error reporting purposes, such that a path to removal may exist in copying error state from the statement to the database handle at the time of error occurrence. In the case of PDO statement, the reference to PDO appears to exist for GC purposes, preventing the freeing of an associated PDO object and subsequent disconnect of a database handle while the statement remains, the necessity of which is reduced by this proposal separating PDO destruct implementation from database disconnect, though some additional work around PDO and PDO statement lifecycle would be required to ensure proper handling of the database handle.
Pending
Pending
https://bugs.php.net/bug.php?id=40681
https://bugs.php.net/bug.php?id=62065
Pending
Pending