Skip to content

Implement PDO driver specific sub-classes #12804

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 11 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
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make static perhaps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is exposed as it's needed by pdo_dbh.c.


/* 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
6 changes: 3 additions & 3 deletions ext/pdo/tests/pdo_test.inc
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ if (getenv('PDOTEST_DSN') === false) {
class PDOTest {
// create an instance of the PDO driver, based on
// the current environment
static function factory($classname = 'PDO') {
static function factory($classname = PDO::class) {
$dsn = getenv('PDOTEST_DSN');
$user = getenv('PDOTEST_USER');
$pass = getenv('PDOTEST_PASS');
Expand Down Expand Up @@ -54,12 +54,12 @@ class PDOTest {
}
}

static function test_factory($file) {
static function test_factory($file, $classname = PDO::class) {
$config = self::get_config($file);
foreach ($config['ENV'] as $k => $v) {
putenv("$k=$v");
}
return self::factory();
return self::factory($classname);
}

static function get_config($file) {
Expand Down
6 changes: 3 additions & 3 deletions ext/pdo/tests/pdo_uninitialized.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ try {

?>
--EXPECT--
PDO object is not initialized, constructor was not called
PDO object is uninitialized
PDO object is uninitialized
MyPDO object is uninitialized
MyPDOStatement object is uninitialized
MyPDOStatement object is uninitialized
8 changes: 7 additions & 1 deletion ext/pdo_dblib/pdo_dblib.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@
#include "php_pdo_dblib.h"
#include "php_pdo_dblib_int.h"
#include "zend_exceptions.h"
#include "pdo_dblib_arginfo.h"

ZEND_DECLARE_MODULE_GLOBALS(dblib)
static PHP_GINIT_FUNCTION(dblib);

static zend_class_entry *PdoDblib_ce;

static const zend_module_dep pdo_dblib_deps[] = {
ZEND_MOD_REQUIRED("pdo")
ZEND_MOD_END
Expand Down Expand Up @@ -201,6 +204,9 @@ PHP_MINIT_FUNCTION(pdo_dblib)
return FAILURE;
}

PdoDblib_ce = register_class_PdoDblib(pdo_dbh_ce);
PdoDblib_ce->create_object = pdo_dbh_new;

if (FAILURE == php_pdo_register_driver(&pdo_dblib_driver)) {
return FAILURE;
}
Expand All @@ -210,7 +216,7 @@ PHP_MINIT_FUNCTION(pdo_dblib)
dbmsghandle((MHANDLEFUNC) pdo_dblib_msg_handler);
#endif

return SUCCESS;
return php_pdo_register_driver_specific_ce(&pdo_dblib_driver, PdoDblib_ce);
}

PHP_MSHUTDOWN_FUNCTION(pdo_dblib)
Expand Down
Loading