Skip to content

Commit e740462

Browse files
Danackkocsismate
andcommitted
Implement PDO driver specific sub-classes
RFC: https://wiki.php.net/rfc/pdo_driver_specific_subclasses Co-authored-by: Máté Kocsis <[email protected]>
1 parent a2ea452 commit e740462

File tree

93 files changed

+3228
-68
lines changed

Some content is hidden

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

93 files changed

+3228
-68
lines changed

.github/actions/configure-x64/action.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ runs:
7070
--with-tcadb \
7171
--with-lmdb \
7272
--with-qdbm \
73+
--enable-pdo \
7374
${{ inputs.skipSlow == 'false' && '--with-snmp' || '' }} \
7475
${{ inputs.skipSlow == 'false' && '--with-unixODBC' || '' }} \
7576
${{ inputs.skipSlow == 'false' && '--with-imap' || '' }} \

ext/pdo/pdo_dbh.c

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

224-
/* {{{ */
225-
PHP_METHOD(PDO, __construct)
224+
#define MAX_PDO_SUB_CLASSES 64
225+
static unsigned int number_of_pdo_driver_class_entries = 0;
226+
static pdo_driver_class_entry *pdo_driver_class_entries[MAX_PDO_SUB_CLASSES];
227+
228+
// It would be possible remove this and roll it into the standard driver class entries
229+
// I chose not to do it at this time, as that would break existing PDO extensions
230+
void pdo_register_driver_specific_class(pdo_driver_class_entry *driver_class_entry)
231+
{
232+
if (number_of_pdo_driver_class_entries >= MAX_PDO_SUB_CLASSES) {
233+
// TODO - return false
234+
}
235+
236+
pdo_driver_class_entries[number_of_pdo_driver_class_entries] = driver_class_entry;
237+
number_of_pdo_driver_class_entries += 1;
238+
}
239+
240+
static bool create_driver_specific_pdo_object(const char *driver_name, zend_class_entry *called_scope, zval *new_object)
241+
{
242+
zend_class_entry *ce_based_on_driver_name = NULL, *ce_based_on_called_object = NULL;
243+
244+
for (int i = 0; i < number_of_pdo_driver_class_entries && (ce_based_on_driver_name == NULL || ce_based_on_called_object == NULL); i++) {
245+
pdo_driver_class_entry *driver_class_entry = pdo_driver_class_entries[i];
246+
247+
if (strcmp(driver_class_entry->driver_name, driver_name) == 0) {
248+
ce_based_on_driver_name = driver_class_entry->driver_ce;
249+
}
250+
251+
if (instanceof_function(called_scope, driver_class_entry->driver_ce)) {
252+
ce_based_on_called_object = called_scope;
253+
}
254+
}
255+
256+
if (ce_based_on_called_object) {
257+
if (ce_based_on_driver_name) {
258+
if (instanceof_function(ce_based_on_called_object, ce_based_on_driver_name) == false) {
259+
zend_throw_exception_ex(php_pdo_get_exception(), 0,
260+
"%s::connect() cannot be called when connecting to the \"%s\" driver, "
261+
"you must call either %s::connect() or PDO::connect() instead",
262+
ZSTR_VAL(called_scope->name), driver_name, ZSTR_VAL(ce_based_on_driver_name->name));
263+
return false;
264+
}
265+
266+
object_init_ex(new_object, ce_based_on_called_object);
267+
return true;
268+
} else {
269+
zend_throw_exception_ex(php_pdo_get_exception(), 0,
270+
"%s::connect() cannot be called when connecting to an unknown driver, "
271+
"you must call PDO::connect() instead",
272+
ZSTR_VAL(called_scope->name));
273+
return false;
274+
}
275+
}
276+
277+
if (ce_based_on_driver_name) {
278+
if (called_scope != pdo_dbh_ce) {
279+
zend_throw_exception_ex(php_pdo_get_exception(), 0,
280+
"%s::connect() cannot be called when connecting to the \"%s\" driver, "
281+
"you must call either %s::connect() or PDO::connect() instead",
282+
ZSTR_VAL(called_scope->name), driver_name, ZSTR_VAL(ce_based_on_driver_name->name));
283+
return false;
284+
}
285+
286+
// A specific driver implementation was called on PDO
287+
object_init_ex(new_object, ce_based_on_driver_name);
288+
} else {
289+
// No specific DB implementation found
290+
object_init_ex(new_object, called_scope);
291+
}
292+
293+
return true;
294+
}
295+
296+
static void internal_construct(INTERNAL_FUNCTION_PARAMETERS, zend_object *object, zend_class_entry *current_scope, zval *new_zval_object)
226297
{
227-
zval *object = ZEND_THIS;
228298
pdo_dbh_t *dbh = NULL;
229299
bool is_persistent = 0;
230300
char *data_source;
@@ -291,7 +361,17 @@ PHP_METHOD(PDO, __construct)
291361
RETURN_THROWS();
292362
}
293363

294-
dbh = Z_PDO_DBH_P(object);
364+
if (new_zval_object != NULL) {
365+
ZEND_ASSERT((driver->driver_name != NULL) && "PDO driver name is null");
366+
bool result = create_driver_specific_pdo_object(driver->driver_name, current_scope, new_zval_object);
367+
if (!result) {
368+
RETURN_THROWS();
369+
}
370+
371+
dbh = Z_PDO_DBH_P(new_zval_object);
372+
} else {
373+
dbh = php_pdo_dbh_fetch_inner(object);
374+
}
295375

296376
/* is this supposed to be a persistent connection ? */
297377
if (options) {
@@ -352,7 +432,7 @@ PHP_METHOD(PDO, __construct)
352432
if (pdbh) {
353433
efree(dbh);
354434
/* switch over to the persistent one */
355-
Z_PDO_OBJECT_P(object)->inner = pdbh;
435+
php_pdo_dbh_fetch_object(object)->inner = pdbh;
356436
pdbh->refcount++;
357437
dbh = pdbh;
358438
}
@@ -432,6 +512,19 @@ PHP_METHOD(PDO, __construct)
432512
zend_throw_exception(pdo_exception_ce, "Constructor failed", 0);
433513
}
434514
}
515+
516+
/* {{{ */
517+
PHP_METHOD(PDO, __construct)
518+
{
519+
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, Z_OBJ(EX(This)), EX(This).value.ce, NULL);
520+
}
521+
/* }}} */
522+
523+
/* {{{ */
524+
PHP_METHOD(PDO, connect)
525+
{
526+
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, Z_OBJ(EX(This)), EX(This).value.ce, return_value);
527+
}
435528
/* }}} */
436529

437530
static zval *pdo_stmt_instantiate(pdo_dbh_t *dbh, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args) /* {{{ */
@@ -1334,6 +1427,8 @@ static HashTable *dbh_get_gc(zend_object *object, zval **gc_data, int *gc_count)
13341427
}
13351428

13361429
static zend_object_handlers pdo_dbh_object_handlers;
1430+
static zend_object_handlers pdosqlite_dbh_object_handlers;
1431+
13371432
static void pdo_dbh_free_storage(zend_object *std);
13381433

13391434
void pdo_dbh_init(int module_number)
@@ -1349,6 +1444,13 @@ void pdo_dbh_init(int module_number)
13491444
pdo_dbh_object_handlers.get_method = dbh_method_get;
13501445
pdo_dbh_object_handlers.compare = zend_objects_not_comparable;
13511446
pdo_dbh_object_handlers.get_gc = dbh_get_gc;
1447+
1448+
memcpy(&pdosqlite_dbh_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
1449+
pdosqlite_dbh_object_handlers.offset = XtOffsetOf(pdo_dbh_object_t, std);
1450+
pdosqlite_dbh_object_handlers.free_obj = pdo_dbh_free_storage;
1451+
pdosqlite_dbh_object_handlers.get_method = dbh_method_get;
1452+
pdosqlite_dbh_object_handlers.compare = zend_objects_not_comparable;
1453+
pdosqlite_dbh_object_handlers.get_gc = dbh_get_gc;
13521454
}
13531455

13541456
static void dbh_free(pdo_dbh_t *dbh, bool free_persistent)

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/php_pdo.h

Lines changed: 4 additions & 4 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,9 +53,6 @@ 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 \

ext/pdo/php_pdo_driver.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,16 @@ typedef struct {
215215

216216
} pdo_driver_t;
217217

218+
// NOTE - This separate struct, could be rolled it into pdo_driver_t
219+
// I chose not to, as that would cause BC break and require a lot of
220+
// downstream work.
221+
typedef struct {
222+
char *driver_name;
223+
zend_class_entry *driver_ce;
224+
} pdo_driver_class_entry;
225+
226+
void pdo_register_driver_specific_class(pdo_driver_class_entry *driver_class_entry);
227+
218228
/* {{{ methods for a database handle */
219229

220230
/* close or otherwise disconnect the database */

ext/pdo/php_pdo_int.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ int php_pdo_list_entry(void);
2828
void pdo_dbh_init(int module_number);
2929
void pdo_stmt_init(void);
3030

31-
extern zend_object *pdo_dbh_new(zend_class_entry *ce);
3231
extern const zend_function_entry pdo_dbh_functions[];
33-
extern zend_class_entry *pdo_dbh_ce;
3432
extern ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor);
3533

3634
extern zend_object *pdo_dbstmt_new(zend_class_entry *ce);

ext/pdo/tests/pdo_test.inc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ class PDOTest {
5454
}
5555
}
5656

57-
static function test_factory($file) {
57+
static function test_factory($file, $classname = 'PDO') {
5858
$config = self::get_config($file);
5959
foreach ($config['ENV'] as $k => $v) {
6060
putenv("$k=$v");
6161
}
62-
return self::factory();
62+
return self::factory($classname);
6363
}
6464

6565
static function get_config($file) {

ext/pdo_dblib/pdo_dblib.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@
2727
#include "php_pdo_dblib.h"
2828
#include "php_pdo_dblib_int.h"
2929
#include "zend_exceptions.h"
30+
#include "pdo_dblib_arginfo.h"
3031

3132
ZEND_DECLARE_MODULE_GLOBALS(dblib)
3233
static PHP_GINIT_FUNCTION(dblib);
3334

35+
zend_class_entry *PdoDblib_ce;
36+
static pdo_driver_class_entry PdoDblib_pdo_driver_class_entry;
37+
3438
static const zend_module_dep pdo_dblib_deps[] = {
3539
ZEND_MOD_REQUIRED("pdo")
3640
ZEND_MOD_END
@@ -201,6 +205,13 @@ PHP_MINIT_FUNCTION(pdo_dblib)
201205
return FAILURE;
202206
}
203207

208+
PdoDblib_ce = register_class_PdoDblib(pdo_dbh_ce);
209+
PdoDblib_ce->create_object = pdo_dbh_new;
210+
211+
PdoDblib_pdo_driver_class_entry.driver_name = "dblib";
212+
PdoDblib_pdo_driver_class_entry.driver_ce = PdoDblib_ce;
213+
pdo_register_driver_specific_class(&PdoDblib_pdo_driver_class_entry);
214+
204215
if (FAILURE == php_pdo_register_driver(&pdo_dblib_driver)) {
205216
return FAILURE;
206217
}

ext/pdo_dblib/pdo_dblib.stub.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/** @generate-class-entries */
4+
5+
/**
6+
* @strict-properties
7+
* @not-serializable
8+
*/
9+
class PdoDblib extends PDO
10+
{
11+
/** @cvalue PDO_DBLIB_ATTR_CONNECTION_TIMEOUT */
12+
public const int ATTR_CONNECTION_TIMEOUT = UNKNOWN;
13+
14+
/** @cvalue PDO_DBLIB_ATTR_QUERY_TIMEOUT */
15+
public const int ATTR_QUERY_TIMEOUT = UNKNOWN;
16+
17+
/** @cvalue PDO_DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER */
18+
public const int ATTR_STRINGIFY_UNIQUEIDENTIFIER = UNKNOWN;
19+
20+
/** @cvalue PDO_DBLIB_ATTR_VERSION */
21+
public const int ATTR_VERSION = UNKNOWN;
22+
23+
/** @cvalue PDO_DBLIB_ATTR_TDS_VERSION */
24+
public const int ATTR_TDS_VERSION = UNKNOWN;
25+
26+
/** @cvalue PDO_DBLIB_ATTR_SKIP_EMPTY_ROWSETS */
27+
public const int ATTR_SKIP_EMPTY_ROWSETS = UNKNOWN;
28+
29+
/** @cvalue PDO_DBLIB_ATTR_DATETIME_CONVERT */
30+
public const int ATTR_DATETIME_CONVERT = UNKNOWN;
31+
}

0 commit comments

Comments
 (0)