Skip to content

Commit d6a0b3a

Browse files
kocsismateDanack
andcommitted
Implement PDO driver-specific subclasses
RFC: https://wiki.php.net/rfc/pdo_driver_specific_subclasses Closes GH-12804 Co-Authored-By: Danack <[email protected]>
1 parent b985a31 commit d6a0b3a

File tree

132 files changed

+3178
-198
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

132 files changed

+3178
-198
lines changed

NEWS

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,33 @@ OpenSSL:
6060

6161
PDO:
6262
. Fixed setAttribute and getAttribute (SakiTakamachi)
63+
. Implement PDO driver-specific subclasses RFC (danack, kocsismate)
6364

6465
PDO_DBLIB:
6566
. Fixed setAttribute and getAttribute (SakiTakamachi)
67+
. Added class PdoDbLib (danack, kocsismate)
6668

6769
PDO_FIREBIRD:
6870
. Fixed setAttribute and getAttribute (SakiTakamachi)
6971
. Feature: Add transaction isolation level and mode settings to pdo_firebird
7072
(SakiTakamachi)
73+
. Added class PdoFirebird. (danack, kocsismate)
7174

7275
PDO_MYSQL:
7376
. Fixed setAttribute and getAttribute (SakiTakamachi)
77+
. Added class PdoMysql. (danack, kocsismate)
78+
79+
PDO_ODBC:
80+
. Added class PdoOdbc. (danack, kocsismate)
7481

7582
PDO_PGSQL:
7683
. Fixed GH-12423, DSN credentials being prioritized over the user/password
7784
PDO constructor arguments. (SakiTakamachi)
7885
. Fixed native float support with pdo_pgsql query results. (Yurunsoft)
86+
. Added class PdoPgsql. (danack, kocsismate)
87+
88+
PDO_SQLITE:
89+
. Added class PdoSqlite. (danack, kocsismate)
7990

8091
PGSQL:
8192
. Added the possibility to have no conditions for pg_select. (OmarEmaraDev)

UPGRADING

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,33 @@ PHP 8.4 UPGRADE NOTES
152152
- Phar:
153153
. Added support for the unix timestamp extension for zip archives.
154154

155+
- PDO:
156+
. Added support for driver-specific subclasses.
157+
RFC: https://wiki.php.net/rfc/pdo_driver_specific_subclasses
158+
This RFC adds subclasses for PDO in order to better support
159+
database-specific functionalities. The new classes are
160+
instantiatable either via calling the PDO::connect() method
161+
or by invoking their constructor directly.
162+
163+
PDO_DBLIB:
164+
. Fixed setAttribute and getAttribute (SakiTakamachi)
165+
. Added PdoDbLib class (danack, kocsismate)
166+
167+
PDO_FIREBIRD:
168+
. Added class PdoFirebird.
169+
170+
PDO_MYSQL:
171+
. Added class PdoMysql.
172+
173+
PDO_ODBC:
174+
. Added class PdoOdbc.
175+
176+
PDO_PGSQL:
177+
. Added class PdoPgsql.
178+
179+
PDO_SQLITE:
180+
. Added class PdoSqlite.
181+
155182
- POSIX:
156183
. Added constant POSIX_SC_CHILD_MAX
157184
. Added constant POSIX_SC_CLK_TCK

ext/pdo/pdo.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ zend_class_entry *pdo_exception_ce;
4141
/* the registry of PDO drivers */
4242
HashTable pdo_driver_hash;
4343

44+
/* the registry of PDO driver specific class entries */
45+
HashTable pdo_driver_specific_ce_hash;
46+
4447
/* we use persistent resources for the driver connection stuff */
4548
static int le_ppdo;
4649

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

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

135+
zend_hash_str_del(&pdo_driver_specific_ce_hash, driver->driver_name, driver->driver_name_len);
132136
zend_hash_str_del(&pdo_driver_hash, driver->driver_name, driver->driver_name_len);
133137
}
134138
/* }}} */
135139

140+
PDO_API zend_result php_pdo_register_driver_specific_ce(const pdo_driver_t *driver, zend_class_entry *ce) /* {{{ */
141+
{
142+
if (!zend_hash_str_exists(&module_registry, "pdo", sizeof("pdo") - 1)) {
143+
zend_error_noreturn(E_ERROR, "The PDO extension must be loaded first in order to load PDO drivers");
144+
return FAILURE; /* NOTREACHED */
145+
}
146+
147+
return zend_hash_str_add_ptr(&pdo_driver_specific_ce_hash, driver->driver_name,
148+
driver->driver_name_len, (void*)ce) != NULL ? SUCCESS : FAILURE;
149+
}
150+
136151
pdo_driver_t *pdo_find_driver(const char *name, int namelen) /* {{{ */
137152
{
138153
return zend_hash_str_find_ptr(&pdo_driver_hash, name, namelen);
@@ -246,6 +261,7 @@ PHP_MINIT_FUNCTION(pdo)
246261
pdo_sqlstate_init_error_table();
247262

248263
zend_hash_init(&pdo_driver_hash, 0, NULL, NULL, 1);
264+
zend_hash_init(&pdo_driver_specific_ce_hash, 0, NULL, NULL, 1);
249265

250266
le_ppdo = zend_register_list_destructors_ex(NULL, php_pdo_pdbh_dtor,
251267
"PDO persistent database", module_number);
@@ -263,6 +279,7 @@ PHP_MINIT_FUNCTION(pdo)
263279
PHP_MSHUTDOWN_FUNCTION(pdo)
264280
{
265281
zend_hash_destroy(&pdo_driver_hash);
282+
zend_hash_destroy(&pdo_driver_specific_ce_hash);
266283
pdo_sqlstate_fini_error_table();
267284
return SUCCESS;
268285
}

ext/pdo/pdo_dbh.c

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,64 @@ static char *dsn_from_uri(char *uri, char *buf, size_t buflen) /* {{{ */
221221
}
222222
/* }}} */
223223

224-
/* {{{ */
225-
PHP_METHOD(PDO, __construct)
224+
static bool create_driver_specific_pdo_object(pdo_driver_t *driver, zend_class_entry *called_scope, zval *new_object)
225+
{
226+
zend_class_entry *ce;
227+
zend_class_entry *ce_based_on_driver_name = NULL, *ce_based_on_called_object = NULL;
228+
229+
ce_based_on_driver_name = zend_hash_str_find_ptr(&pdo_driver_specific_ce_hash, driver->driver_name, driver->driver_name_len);
230+
231+
ZEND_HASH_MAP_FOREACH_PTR(&pdo_driver_specific_ce_hash, ce) {
232+
if (called_scope != pdo_dbh_ce && instanceof_function(called_scope, ce)) {
233+
ce_based_on_called_object = called_scope;
234+
break;
235+
}
236+
} ZEND_HASH_FOREACH_END();
237+
238+
if (ce_based_on_called_object) {
239+
if (ce_based_on_driver_name) {
240+
if (instanceof_function(ce_based_on_called_object, ce_based_on_driver_name) == false) {
241+
zend_throw_exception_ex(pdo_exception_ce, 0,
242+
"%s::connect() cannot be called when connecting to the \"%s\" driver, "
243+
"either %s::connect() or PDO::connect() must be called instead",
244+
ZSTR_VAL(called_scope->name), driver->driver_name, ZSTR_VAL(ce_based_on_driver_name->name));
245+
return false;
246+
}
247+
248+
/* A driver-specific implementation was instantiated via the connect() method of the appropriate driver class */
249+
object_init_ex(new_object, ce_based_on_called_object);
250+
return true;
251+
} else {
252+
zend_throw_exception_ex(pdo_exception_ce, 0,
253+
"%s::connect() cannot be called when connecting to an unknown driver, "
254+
"PDO::connect() must be called instead",
255+
ZSTR_VAL(called_scope->name));
256+
return false;
257+
}
258+
}
259+
260+
if (ce_based_on_driver_name) {
261+
if (called_scope != pdo_dbh_ce) {
262+
/* A driver-specific implementation was instantiated via the connect method of a wrong driver class */
263+
zend_throw_exception_ex(pdo_exception_ce, 0,
264+
"%s::connect() cannot be called when connecting to the \"%s\" driver, "
265+
"either %s::connect() or PDO::connect() must be called instead",
266+
ZSTR_VAL(called_scope->name), driver->driver_name, ZSTR_VAL(ce_based_on_driver_name->name));
267+
return false;
268+
}
269+
270+
/* A driver-specific implementation was instantiated via PDO::__construct() */
271+
object_init_ex(new_object, ce_based_on_driver_name);
272+
} else {
273+
/* No driver-specific implementation found */
274+
object_init_ex(new_object, called_scope);
275+
}
276+
277+
return true;
278+
}
279+
280+
static void internal_construct(INTERNAL_FUNCTION_PARAMETERS, zend_object *object, zend_class_entry *current_scope, zval *new_zval_object)
226281
{
227-
zval *object = ZEND_THIS;
228282
pdo_dbh_t *dbh = NULL;
229283
bool is_persistent = 0;
230284
char *data_source;
@@ -291,7 +345,16 @@ PHP_METHOD(PDO, __construct)
291345
RETURN_THROWS();
292346
}
293347

294-
dbh = Z_PDO_DBH_P(object);
348+
if (new_zval_object != NULL) {
349+
ZEND_ASSERT((driver->driver_name != NULL) && "PDO driver name is null");
350+
if (!create_driver_specific_pdo_object(driver, current_scope, new_zval_object)) {
351+
RETURN_THROWS();
352+
}
353+
354+
dbh = Z_PDO_DBH_P(new_zval_object);
355+
} else {
356+
dbh = php_pdo_dbh_fetch_inner(object);
357+
}
295358

296359
/* is this supposed to be a persistent connection ? */
297360
if (options) {
@@ -352,7 +415,7 @@ PHP_METHOD(PDO, __construct)
352415
if (pdbh) {
353416
efree(dbh);
354417
/* switch over to the persistent one */
355-
Z_PDO_OBJECT_P(object)->inner = pdbh;
418+
php_pdo_dbh_fetch_object(object)->inner = pdbh;
356419
pdbh->refcount++;
357420
dbh = pdbh;
358421
}
@@ -432,6 +495,19 @@ PHP_METHOD(PDO, __construct)
432495
zend_throw_exception(pdo_exception_ce, "Constructor failed", 0);
433496
}
434497
}
498+
499+
/* {{{ */
500+
PHP_METHOD(PDO, __construct)
501+
{
502+
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, Z_OBJ(EX(This)), EX(This).value.ce, NULL);
503+
}
504+
/* }}} */
505+
506+
/* {{{ */
507+
PHP_METHOD(PDO, connect)
508+
{
509+
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, Z_OBJ(EX(This)), EX(This).value.ce, return_value);
510+
}
435511
/* }}} */
436512

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

13361412
static zend_object_handlers pdo_dbh_object_handlers;
1413+
13371414
static void pdo_dbh_free_storage(zend_object *std);
13381415

13391416
void pdo_dbh_init(int module_number)

ext/pdo/pdo_dbh.stub.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ class PDO
166166

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

169+
public static function connect(
170+
string $dsn,
171+
?string $username = null,
172+
#[\SensitiveParameter] ?string $password = null,
173+
?array $options = null
174+
): static {}
175+
169176
/** @tentative-return-type */
170177
public function beginTransaction(): bool {}
171178

ext/pdo/pdo_dbh_arginfo.h

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/pdo/pdo_stmt.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
#define PHP_STMT_GET_OBJ \
3838
pdo_stmt_t *stmt = Z_PDO_STMT_P(ZEND_THIS); \
3939
if (!stmt->dbh) { \
40-
zend_throw_error(NULL, "PDO object is uninitialized"); \
40+
zend_throw_error(NULL, "%s object is uninitialized", ZSTR_VAL(Z_OBJ(EX(This))->ce->name)); \
4141
RETURN_THROWS(); \
4242
} \
4343

@@ -2231,7 +2231,7 @@ zend_object_iterator *pdo_stmt_iter_get(zend_class_entry *ce, zval *object, int
22312231

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

ext/pdo/php_pdo.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919

2020
#include "zend.h"
2121

22-
extern zend_module_entry pdo_module_entry;
22+
PHPAPI extern zend_module_entry pdo_module_entry;
2323
#define phpext_pdo_ptr &pdo_module_entry
2424

25+
PHPAPI extern zend_class_entry *pdo_dbh_ce;
26+
PHPAPI extern zend_object *pdo_dbh_new(zend_class_entry *ce);
27+
2528
#include "php_version.h"
2629
#define PHP_PDO_VERSION PHP_VERSION
2730

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

53-
#define REGISTER_PDO_CLASS_CONST_STRING(const_name, value) \
54-
zend_declare_class_constant_stringl(php_pdo_get_dbh_ce(), const_name, sizeof(const_name)-1, value, sizeof(value)-1);
55-
5656
#define LONG_CONST(c) (zend_long) c
5757

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

ext/pdo/php_pdo_driver.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,11 @@ PDO_API zend_result php_pdo_register_driver(const pdo_driver_t *driver);
653653
/* call this in MSHUTDOWN to unregister your PDO driver */
654654
PDO_API void php_pdo_unregister_driver(const pdo_driver_t *driver);
655655

656+
/* Call this in MINIT to register the PDO driver specific class entry.
657+
* Registering the driver specific class entry might fail and should be reported accordingly in MINIT.
658+
* Unregistering the class entry is not necessary, since php_pdo_unregister_driver() takes care of it. */
659+
PDO_API zend_result php_pdo_register_driver_specific_ce(const pdo_driver_t *driver, zend_class_entry *ce);
660+
656661
/* For the convenience of drivers, this function will parse a data source
657662
* string, of the form "name=value; name2=value2" and populate variables
658663
* according to the data you pass in and array of pdo_data_src_parser structures */

ext/pdo/php_pdo_int.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,14 @@
2222
#include "php_pdo_error.h"
2323

2424
extern HashTable pdo_driver_hash;
25+
extern HashTable pdo_driver_specific_ce_hash;
2526
extern zend_class_entry *pdo_exception_ce;
2627
int php_pdo_list_entry(void);
2728

2829
void pdo_dbh_init(int module_number);
2930
void pdo_stmt_init(void);
3031

31-
extern zend_object *pdo_dbh_new(zend_class_entry *ce);
3232
extern const zend_function_entry pdo_dbh_functions[];
33-
extern zend_class_entry *pdo_dbh_ce;
3433
extern ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor);
3534

3635
extern zend_object *pdo_dbstmt_new(zend_class_entry *ce);

0 commit comments

Comments
 (0)