Skip to content

Commit

Permalink
Implement PDO driver-specific subclasses
Browse files Browse the repository at this point in the history
  • Loading branch information
kocsismate and Danack committed Jan 11, 2024
1 parent b985a31 commit d6a0b3a
Show file tree
Hide file tree
Showing 132 changed files with 3,178 additions and 198 deletions.
11 changes: 11 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,33 @@ OpenSSL:

PDO:
. Fixed setAttribute and getAttribute (SakiTakamachi)
. Implement PDO driver-specific subclasses RFC (danack, kocsismate)

PDO_DBLIB:
. Fixed setAttribute and getAttribute (SakiTakamachi)
. Added class PdoDbLib (danack, kocsismate)

PDO_FIREBIRD:
. Fixed setAttribute and getAttribute (SakiTakamachi)
. Feature: Add transaction isolation level and mode settings to pdo_firebird
(SakiTakamachi)
. Added class PdoFirebird. (danack, kocsismate)

PDO_MYSQL:
. Fixed setAttribute and getAttribute (SakiTakamachi)
. Added class PdoMysql. (danack, kocsismate)

PDO_ODBC:
. Added class PdoOdbc. (danack, kocsismate)

PDO_PGSQL:
. Fixed GH-12423, DSN credentials being prioritized over the user/password
PDO constructor arguments. (SakiTakamachi)
. Fixed native float support with pdo_pgsql query results. (Yurunsoft)
. Added class PdoPgsql. (danack, kocsismate)

PDO_SQLITE:
. Added class PdoSqlite. (danack, kocsismate)

PGSQL:
. Added the possibility to have no conditions for pg_select. (OmarEmaraDev)
Expand Down
27 changes: 27 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,33 @@ PHP 8.4 UPGRADE NOTES
- Phar:
. Added support for the unix timestamp extension for zip archives.

- PDO:
. Added support for driver-specific subclasses.
RFC: https://wiki.php.net/rfc/pdo_driver_specific_subclasses
This RFC adds subclasses for PDO in order to better support
database-specific functionalities. The new classes are
instantiatable either via calling the PDO::connect() method
or by invoking their constructor directly.

PDO_DBLIB:
. Fixed setAttribute and getAttribute (SakiTakamachi)
. Added PdoDbLib class (danack, kocsismate)

PDO_FIREBIRD:
. Added class PdoFirebird.

PDO_MYSQL:
. Added class PdoMysql.

PDO_ODBC:
. Added class PdoOdbc.

PDO_PGSQL:
. Added class PdoPgsql.

PDO_SQLITE:
. Added class PdoSqlite.

- POSIX:
. Added constant POSIX_SC_CHILD_MAX
. Added constant POSIX_SC_CLK_TCK
Expand Down
19 changes: 18 additions & 1 deletion ext/pdo/pdo.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ zend_class_entry *pdo_exception_ce;
/* the registry of PDO drivers */
HashTable pdo_driver_hash;

/* the registry of PDO driver specific class entries */
HashTable pdo_driver_specific_ce_hash;

/* we use persistent resources for the driver connection stuff */
static int le_ppdo;

Expand Down Expand Up @@ -115,7 +118,7 @@ PDO_API zend_result php_pdo_register_driver(const pdo_driver_t *driver) /* {{{ *
return FAILURE;
}
if (!zend_hash_str_exists(&module_registry, "pdo", sizeof("pdo") - 1)) {
zend_error_noreturn(E_ERROR, "You MUST load PDO before loading any PDO drivers");
zend_error_noreturn(E_ERROR, "The PDO extension must be loaded first in order to load PDO drivers");
return FAILURE; /* NOTREACHED */
}

Expand All @@ -129,10 +132,22 @@ PDO_API void php_pdo_unregister_driver(const pdo_driver_t *driver) /* {{{ */
return;
}

zend_hash_str_del(&pdo_driver_specific_ce_hash, driver->driver_name, driver->driver_name_len);
zend_hash_str_del(&pdo_driver_hash, driver->driver_name, driver->driver_name_len);
}
/* }}} */

PDO_API zend_result php_pdo_register_driver_specific_ce(const pdo_driver_t *driver, zend_class_entry *ce) /* {{{ */
{
if (!zend_hash_str_exists(&module_registry, "pdo", sizeof("pdo") - 1)) {
zend_error_noreturn(E_ERROR, "The PDO extension must be loaded first in order to load PDO drivers");
return FAILURE; /* NOTREACHED */
}

return zend_hash_str_add_ptr(&pdo_driver_specific_ce_hash, driver->driver_name,
driver->driver_name_len, (void*)ce) != NULL ? SUCCESS : FAILURE;
}

pdo_driver_t *pdo_find_driver(const char *name, int namelen) /* {{{ */
{
return zend_hash_str_find_ptr(&pdo_driver_hash, name, namelen);
Expand Down Expand Up @@ -246,6 +261,7 @@ PHP_MINIT_FUNCTION(pdo)
pdo_sqlstate_init_error_table();

zend_hash_init(&pdo_driver_hash, 0, NULL, NULL, 1);
zend_hash_init(&pdo_driver_specific_ce_hash, 0, NULL, NULL, 1);

le_ppdo = zend_register_list_destructors_ex(NULL, php_pdo_pdbh_dtor,
"PDO persistent database", module_number);
Expand All @@ -263,6 +279,7 @@ PHP_MINIT_FUNCTION(pdo)
PHP_MSHUTDOWN_FUNCTION(pdo)
{
zend_hash_destroy(&pdo_driver_hash);
zend_hash_destroy(&pdo_driver_specific_ce_hash);
pdo_sqlstate_fini_error_table();
return SUCCESS;
}
Expand Down
87 changes: 82 additions & 5 deletions ext/pdo/pdo_dbh.c
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,64 @@ static char *dsn_from_uri(char *uri, char *buf, size_t buflen) /* {{{ */
}
/* }}} */

/* {{{ */
PHP_METHOD(PDO, __construct)
static bool create_driver_specific_pdo_object(pdo_driver_t *driver, zend_class_entry *called_scope, zval *new_object)
{
zend_class_entry *ce;
zend_class_entry *ce_based_on_driver_name = NULL, *ce_based_on_called_object = NULL;

ce_based_on_driver_name = zend_hash_str_find_ptr(&pdo_driver_specific_ce_hash, driver->driver_name, driver->driver_name_len);

ZEND_HASH_MAP_FOREACH_PTR(&pdo_driver_specific_ce_hash, ce) {
if (called_scope != pdo_dbh_ce && instanceof_function(called_scope, ce)) {
ce_based_on_called_object = called_scope;
break;
}
} ZEND_HASH_FOREACH_END();

if (ce_based_on_called_object) {
if (ce_based_on_driver_name) {
if (instanceof_function(ce_based_on_called_object, ce_based_on_driver_name) == false) {
zend_throw_exception_ex(pdo_exception_ce, 0,
"%s::connect() cannot be called when connecting to the \"%s\" driver, "
"either %s::connect() or PDO::connect() must be called instead",
ZSTR_VAL(called_scope->name), driver->driver_name, ZSTR_VAL(ce_based_on_driver_name->name));
return false;
}

/* A driver-specific implementation was instantiated via the connect() method of the appropriate driver class */
object_init_ex(new_object, ce_based_on_called_object);
return true;
} else {
zend_throw_exception_ex(pdo_exception_ce, 0,
"%s::connect() cannot be called when connecting to an unknown driver, "
"PDO::connect() must be called instead",
ZSTR_VAL(called_scope->name));
return false;
}
}

if (ce_based_on_driver_name) {
if (called_scope != pdo_dbh_ce) {
/* A driver-specific implementation was instantiated via the connect method of a wrong driver class */
zend_throw_exception_ex(pdo_exception_ce, 0,
"%s::connect() cannot be called when connecting to the \"%s\" driver, "
"either %s::connect() or PDO::connect() must be called instead",
ZSTR_VAL(called_scope->name), driver->driver_name, ZSTR_VAL(ce_based_on_driver_name->name));
return false;
}

/* A driver-specific implementation was instantiated via PDO::__construct() */
object_init_ex(new_object, ce_based_on_driver_name);
} else {
/* No driver-specific implementation found */
object_init_ex(new_object, called_scope);
}

return true;
}

static void internal_construct(INTERNAL_FUNCTION_PARAMETERS, zend_object *object, zend_class_entry *current_scope, zval *new_zval_object)
{
zval *object = ZEND_THIS;
pdo_dbh_t *dbh = NULL;
bool is_persistent = 0;
char *data_source;
Expand Down Expand Up @@ -291,7 +345,16 @@ PHP_METHOD(PDO, __construct)
RETURN_THROWS();
}

dbh = Z_PDO_DBH_P(object);
if (new_zval_object != NULL) {
ZEND_ASSERT((driver->driver_name != NULL) && "PDO driver name is null");
if (!create_driver_specific_pdo_object(driver, current_scope, new_zval_object)) {
RETURN_THROWS();
}

dbh = Z_PDO_DBH_P(new_zval_object);
} else {
dbh = php_pdo_dbh_fetch_inner(object);
}

/* is this supposed to be a persistent connection ? */
if (options) {
Expand Down Expand Up @@ -352,7 +415,7 @@ PHP_METHOD(PDO, __construct)
if (pdbh) {
efree(dbh);
/* switch over to the persistent one */
Z_PDO_OBJECT_P(object)->inner = pdbh;
php_pdo_dbh_fetch_object(object)->inner = pdbh;
pdbh->refcount++;
dbh = pdbh;
}
Expand Down Expand Up @@ -432,6 +495,19 @@ PHP_METHOD(PDO, __construct)
zend_throw_exception(pdo_exception_ce, "Constructor failed", 0);
}
}

/* {{{ */
PHP_METHOD(PDO, __construct)
{
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, Z_OBJ(EX(This)), EX(This).value.ce, NULL);
}
/* }}} */

/* {{{ */
PHP_METHOD(PDO, connect)
{
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, Z_OBJ(EX(This)), EX(This).value.ce, return_value);
}
/* }}} */

static zval *pdo_stmt_instantiate(pdo_dbh_t *dbh, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args) /* {{{ */
Expand Down Expand Up @@ -1334,6 +1410,7 @@ static HashTable *dbh_get_gc(zend_object *object, zval **gc_data, int *gc_count)
}

static zend_object_handlers pdo_dbh_object_handlers;

static void pdo_dbh_free_storage(zend_object *std);

void pdo_dbh_init(int module_number)
Expand Down
7 changes: 7 additions & 0 deletions ext/pdo/pdo_dbh.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ class PDO

public function __construct(string $dsn, ?string $username = null, #[\SensitiveParameter] ?string $password = null, ?array $options = null) {}

public static function connect(
string $dsn,
?string $username = null,
#[\SensitiveParameter] ?string $password = null,
?array $options = null
): static {}

/** @tentative-return-type */
public function beginTransaction(): bool {}

Expand Down
13 changes: 12 additions & 1 deletion ext/pdo/pdo_dbh_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ext/pdo/pdo_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
#define PHP_STMT_GET_OBJ \
pdo_stmt_t *stmt = Z_PDO_STMT_P(ZEND_THIS); \
if (!stmt->dbh) { \
zend_throw_error(NULL, "PDO object is uninitialized"); \
zend_throw_error(NULL, "%s object is uninitialized", ZSTR_VAL(Z_OBJ(EX(This))->ce->name)); \
RETURN_THROWS(); \
} \

Expand Down Expand Up @@ -2231,7 +2231,7 @@ zend_object_iterator *pdo_stmt_iter_get(zend_class_entry *ce, zval *object, int

pdo_stmt_t *stmt = Z_PDO_STMT_P(object);
if (!stmt->dbh) {
zend_throw_error(NULL, "PDO object is uninitialized");
zend_throw_error(NULL, "%s object is uninitialized", ZSTR_VAL(ce->name));
return NULL;
}

Expand Down
10 changes: 5 additions & 5 deletions ext/pdo/php_pdo.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@

#include "zend.h"

extern zend_module_entry pdo_module_entry;
PHPAPI extern zend_module_entry pdo_module_entry;
#define phpext_pdo_ptr &pdo_module_entry

PHPAPI extern zend_class_entry *pdo_dbh_ce;
PHPAPI extern zend_object *pdo_dbh_new(zend_class_entry *ce);

#include "php_version.h"
#define PHP_PDO_VERSION PHP_VERSION

Expand Down Expand Up @@ -50,14 +53,11 @@ PHP_MINFO_FUNCTION(pdo);
#define REGISTER_PDO_CLASS_CONST_LONG(const_name, value) \
zend_declare_class_constant_long(php_pdo_get_dbh_ce(), const_name, sizeof(const_name)-1, (zend_long)value);

#define REGISTER_PDO_CLASS_CONST_STRING(const_name, value) \
zend_declare_class_constant_stringl(php_pdo_get_dbh_ce(), const_name, sizeof(const_name)-1, value, sizeof(value)-1);

#define LONG_CONST(c) (zend_long) c

#define PDO_CONSTRUCT_CHECK \
if (!dbh->driver) { \
zend_throw_error(NULL, "PDO object is not initialized, constructor was not called"); \
zend_throw_error(NULL, "%s object is uninitialized", ZSTR_VAL(Z_OBJ(EX(This))->ce->name)); \
RETURN_THROWS(); \
} \

Expand Down
5 changes: 5 additions & 0 deletions ext/pdo/php_pdo_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,11 @@ PDO_API zend_result php_pdo_register_driver(const pdo_driver_t *driver);
/* call this in MSHUTDOWN to unregister your PDO driver */
PDO_API void php_pdo_unregister_driver(const pdo_driver_t *driver);

/* Call this in MINIT to register the PDO driver specific class entry.
* Registering the driver specific class entry might fail and should be reported accordingly in MINIT.
* Unregistering the class entry is not necessary, since php_pdo_unregister_driver() takes care of it. */
PDO_API zend_result php_pdo_register_driver_specific_ce(const pdo_driver_t *driver, zend_class_entry *ce);

/* For the convenience of drivers, this function will parse a data source
* string, of the form "name=value; name2=value2" and populate variables
* according to the data you pass in and array of pdo_data_src_parser structures */
Expand Down
3 changes: 1 addition & 2 deletions ext/pdo/php_pdo_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@
#include "php_pdo_error.h"

extern HashTable pdo_driver_hash;
extern HashTable pdo_driver_specific_ce_hash;
extern zend_class_entry *pdo_exception_ce;
int php_pdo_list_entry(void);

void pdo_dbh_init(int module_number);
void pdo_stmt_init(void);

extern zend_object *pdo_dbh_new(zend_class_entry *ce);
extern const zend_function_entry pdo_dbh_functions[];
extern zend_class_entry *pdo_dbh_ce;
extern ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor);

extern zend_object *pdo_dbstmt_new(zend_class_entry *ce);
Expand Down
Loading

0 comments on commit d6a0b3a

Please sign in to comment.