Skip to content

Commit 08e50ef

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 0d90b6c commit 08e50ef

File tree

92 files changed

+3238
-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.

92 files changed

+3238
-68
lines changed

ext/pdo/pdo_dbh.c

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,82 @@ 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+
zend_result 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+
return FAILURE;
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+
return SUCCESS;
240+
}
241+
242+
static bool create_driver_specific_pdo_object(const char *driver_name, zend_class_entry *called_scope, zval *new_object)
243+
{
244+
zend_class_entry *ce_based_on_driver_name = NULL, *ce_based_on_called_object = NULL;
245+
246+
for (int i = 0; i < number_of_pdo_driver_class_entries && (ce_based_on_driver_name == NULL || ce_based_on_called_object == NULL); i++) {
247+
pdo_driver_class_entry *driver_class_entry = pdo_driver_class_entries[i];
248+
249+
if (strcmp(driver_class_entry->driver_name, driver_name) == 0) {
250+
ce_based_on_driver_name = driver_class_entry->driver_ce;
251+
}
252+
253+
if (called_scope != pdo_dbh_ce && instanceof_function(called_scope, driver_class_entry->driver_ce)) {
254+
ce_based_on_called_object = called_scope;
255+
}
256+
}
257+
258+
if (ce_based_on_called_object) {
259+
if (ce_based_on_driver_name) {
260+
if (instanceof_function(ce_based_on_called_object, ce_based_on_driver_name) == false) {
261+
zend_throw_exception_ex(php_pdo_get_exception(), 0,
262+
"%s::connect() cannot be called when connecting to the \"%s\" driver, "
263+
"you must call either %s::connect() or PDO::connect() instead",
264+
ZSTR_VAL(called_scope->name), driver_name, ZSTR_VAL(ce_based_on_driver_name->name));
265+
return false;
266+
}
267+
268+
object_init_ex(new_object, ce_based_on_called_object);
269+
return true;
270+
} else {
271+
zend_throw_exception_ex(php_pdo_get_exception(), 0,
272+
"%s::connect() cannot be called when connecting to an unknown driver, "
273+
"you must call PDO::connect() instead",
274+
ZSTR_VAL(called_scope->name));
275+
return false;
276+
}
277+
}
278+
279+
if (ce_based_on_driver_name) {
280+
if (called_scope != pdo_dbh_ce) {
281+
zend_throw_exception_ex(php_pdo_get_exception(), 0,
282+
"%s::connect() cannot be called when connecting to the \"%s\" driver, "
283+
"you must call either %s::connect() or PDO::connect() instead",
284+
ZSTR_VAL(called_scope->name), driver_name, ZSTR_VAL(ce_based_on_driver_name->name));
285+
return false;
286+
}
287+
288+
// A specific driver implementation was called on PDO
289+
object_init_ex(new_object, ce_based_on_driver_name);
290+
} else {
291+
// No specific DB implementation found
292+
object_init_ex(new_object, called_scope);
293+
}
294+
295+
return true;
296+
}
297+
298+
static void internal_construct(INTERNAL_FUNCTION_PARAMETERS, zend_object *object, zend_class_entry *current_scope, zval *new_zval_object)
226299
{
227-
zval *object = ZEND_THIS;
228300
pdo_dbh_t *dbh = NULL;
229301
bool is_persistent = 0;
230302
char *data_source;
@@ -291,7 +363,17 @@ PHP_METHOD(PDO, __construct)
291363
RETURN_THROWS();
292364
}
293365

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

296378
/* is this supposed to be a persistent connection ? */
297379
if (options) {
@@ -352,7 +434,7 @@ PHP_METHOD(PDO, __construct)
352434
if (pdbh) {
353435
efree(dbh);
354436
/* switch over to the persistent one */
355-
Z_PDO_OBJECT_P(object)->inner = pdbh;
437+
php_pdo_dbh_fetch_object(object)->inner = pdbh;
356438
pdbh->refcount++;
357439
dbh = pdbh;
358440
}
@@ -432,6 +514,19 @@ PHP_METHOD(PDO, __construct)
432514
zend_throw_exception(pdo_exception_ce, "Constructor failed", 0);
433515
}
434516
}
517+
518+
/* {{{ */
519+
PHP_METHOD(PDO, __construct)
520+
{
521+
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, Z_OBJ(EX(This)), EX(This).value.ce, NULL);
522+
}
523+
/* }}} */
524+
525+
/* {{{ */
526+
PHP_METHOD(PDO, connect)
527+
{
528+
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, Z_OBJ(EX(This)), EX(This).value.ce, return_value);
529+
}
435530
/* }}} */
436531

437532
static zval *pdo_stmt_instantiate(pdo_dbh_t *dbh, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args) /* {{{ */
@@ -1334,6 +1429,8 @@ static HashTable *dbh_get_gc(zend_object *object, zval **gc_data, int *gc_count)
13341429
}
13351430

13361431
static zend_object_handlers pdo_dbh_object_handlers;
1432+
static zend_object_handlers pdosqlite_dbh_object_handlers;
1433+
13371434
static void pdo_dbh_free_storage(zend_object *std);
13381435

13391436
void pdo_dbh_init(int module_number)
@@ -1349,6 +1446,13 @@ void pdo_dbh_init(int module_number)
13491446
pdo_dbh_object_handlers.get_method = dbh_method_get;
13501447
pdo_dbh_object_handlers.compare = zend_objects_not_comparable;
13511448
pdo_dbh_object_handlers.get_gc = dbh_get_gc;
1449+
1450+
memcpy(&pdosqlite_dbh_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
1451+
pdosqlite_dbh_object_handlers.offset = XtOffsetOf(pdo_dbh_object_t, std);
1452+
pdosqlite_dbh_object_handlers.free_obj = pdo_dbh_free_storage;
1453+
pdosqlite_dbh_object_handlers.get_method = dbh_method_get;
1454+
pdosqlite_dbh_object_handlers.compare = zend_objects_not_comparable;
1455+
pdosqlite_dbh_object_handlers.get_gc = dbh_get_gc;
13521456
}
13531457

13541458
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+
PHPAPI zend_result 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)