Skip to content

Commit 40769bc

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 02bca09 commit 40769bc

Some content is hidden

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

70 files changed

+3005
-37
lines changed

ext/pdo/pdo_dbh.c

+73-4
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,42 @@ static char *dsn_from_uri(char *uri, char *buf, size_t buflen) /* {{{ */
221221
}
222222
/* }}} */
223223

224-
/* {{{ */
225-
PHP_METHOD(PDO, __construct)
224+
225+
#define MAX_PDO_SUB_CLASSES 64
226+
static unsigned int number_of_pdo_driver_class_entries = 0;
227+
static pdo_driver_class_entry *pdo_driver_class_entries[MAX_PDO_SUB_CLASSES];
228+
229+
// It would be possible remove this and roll it into the standard driver class entries
230+
// I chose not to do it at this time, as that would break existing PDO extensions
231+
void pdo_register_driver_specific_class(pdo_driver_class_entry *driver_class_entry)
232+
{
233+
if (number_of_pdo_driver_class_entries >= MAX_PDO_SUB_CLASSES) {
234+
// TODO - return false
235+
}
236+
237+
pdo_driver_class_entries[number_of_pdo_driver_class_entries] = driver_class_entry;
238+
number_of_pdo_driver_class_entries += 1;
239+
}
240+
241+
242+
static
243+
void create_specific_pdo_object(zval *new_object, const char *driver_name)
244+
{
245+
for (int i=0; i < number_of_pdo_driver_class_entries; i += 1) {
246+
pdo_driver_class_entry *driver_class_entry = pdo_driver_class_entries[i];
247+
if (strcmp(driver_class_entry->driver_name, driver_name) == 0) {
248+
object_init_ex(new_object, driver_class_entry->driver_ce);
249+
return;
250+
}
251+
}
252+
253+
// No specific DB implementation found
254+
object_init_ex(new_object, pdo_dbh_ce);
255+
}
256+
257+
static
258+
void internal_construct(INTERNAL_FUNCTION_PARAMETERS, zval *object, zval *new_zval_object)
226259
{
227-
zval *object = ZEND_THIS;
228260
pdo_dbh_t *dbh = NULL;
229261
bool is_persistent = 0;
230262
char *data_source;
@@ -291,7 +323,19 @@ PHP_METHOD(PDO, __construct)
291323
RETURN_THROWS();
292324
}
293325

294-
dbh = Z_PDO_DBH_P(object);
326+
if (object == NULL) {
327+
ZEND_ASSERT((driver->driver_name != NULL) && "PDO driver name is null");
328+
create_specific_pdo_object(new_zval_object, driver->driver_name);
329+
330+
if (new_zval_object == NULL) {
331+
zend_throw_exception_ex(php_pdo_get_exception(), 0, "Failed to create specific PDO class");
332+
RETURN_THROWS();
333+
}
334+
335+
dbh = Z_PDO_DBH_P(new_zval_object);
336+
} else {
337+
dbh = Z_PDO_DBH_P(object);
338+
}
295339

296340
/* is this supposed to be a persistent connection ? */
297341
if (options) {
@@ -432,8 +476,24 @@ PHP_METHOD(PDO, __construct)
432476
zend_throw_exception(pdo_exception_ce, "Constructor failed", 0);
433477
}
434478
}
479+
480+
/* {{{ */
481+
PHP_METHOD(PDO, __construct)
482+
{
483+
zval *object = ZEND_THIS;
484+
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, object, NULL);
485+
}
435486
/* }}} */
436487

488+
489+
/* {{{ */
490+
PHP_METHOD(PDO, connect)
491+
{
492+
internal_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, NULL, return_value);
493+
}
494+
/* }}} */
495+
496+
437497
static zval *pdo_stmt_instantiate(pdo_dbh_t *dbh, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args) /* {{{ */
438498
{
439499
if (!Z_ISUNDEF_P(ctor_args)) {
@@ -1329,6 +1389,8 @@ static HashTable *dbh_get_gc(zend_object *object, zval **gc_data, int *gc_count)
13291389
}
13301390

13311391
static zend_object_handlers pdo_dbh_object_handlers;
1392+
static zend_object_handlers pdosqlite_dbh_object_handlers;
1393+
13321394
static void pdo_dbh_free_storage(zend_object *std);
13331395

13341396
void pdo_dbh_init(int module_number)
@@ -1344,6 +1406,13 @@ void pdo_dbh_init(int module_number)
13441406
pdo_dbh_object_handlers.get_method = dbh_method_get;
13451407
pdo_dbh_object_handlers.compare = zend_objects_not_comparable;
13461408
pdo_dbh_object_handlers.get_gc = dbh_get_gc;
1409+
1410+
memcpy(&pdosqlite_dbh_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
1411+
pdosqlite_dbh_object_handlers.offset = XtOffsetOf(pdo_dbh_object_t, std);
1412+
pdosqlite_dbh_object_handlers.free_obj = pdo_dbh_free_storage;
1413+
pdosqlite_dbh_object_handlers.get_method = dbh_method_get;
1414+
pdosqlite_dbh_object_handlers.compare = zend_objects_not_comparable;
1415+
pdosqlite_dbh_object_handlers.get_gc = dbh_get_gc;
13471416
}
13481417

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

ext/pdo/pdo_dbh.stub.php

+7
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+
?string $password = null,
173+
?array $options = null
174+
): PDO|PDOSqlite {}
175+
169176
/** @tentative-return-type */
170177
public function beginTransaction(): bool {}
171178

ext/pdo/pdo_dbh_arginfo.h

+10-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/pdo/php_pdo.h

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
extern zend_module_entry pdo_module_entry;
2323
#define phpext_pdo_ptr &pdo_module_entry
2424

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

ext/pdo/php_pdo_driver.h

+10
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

-2
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_dblib/pdo_dblib.c

+11
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

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
/** @generate-class-entries */
4+
5+
/** @not-serializable */
6+
class PdoDblib extends PDO
7+
{
8+
9+
}

ext/pdo_dblib/pdo_dblib_arginfo.h

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
function getDsnUserAndPassword()
4+
{
5+
6+
if (false !== getenv('PDO_DBLIB_TEST_DSN')) {
7+
$dsn = getenv('PDO_DBLIB_TEST_DSN');
8+
} else {
9+
$dsn = 'dblib:host=localhost;dbname=test';
10+
}
11+
12+
if (false !== getenv('PDO_DBLIB_TEST_USER')) {
13+
$user = getenv('PDO_DBLIB_TEST_USER');
14+
} else {
15+
$user = 'php';
16+
}
17+
18+
if (false !== getenv('PDO_DBLIB_TEST_PASS')) {
19+
$pass = getenv('PDO_DBLIB_TEST_PASS');
20+
} else {
21+
$pass = 'password';
22+
}
23+
return [$dsn, $user, $pass];
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
--TEST--
2+
PdoDblib basic
3+
--EXTENSIONS--
4+
pdo
5+
--FILE--
6+
<?php
7+
8+
require_once __DIR__ . "/../config_functions.inc";
9+
10+
if (class_exists(PdoDblib::class) === false) {
11+
echo "PdoDblib class does not exist.\n";
12+
exit(-1);
13+
}
14+
echo "PdoDblib class exists.\n";
15+
16+
17+
[$dsn, $user, $pass] = getDsnUserAndPassword();
18+
19+
$db = new PdoDblib($dsn, $user, $pass);
20+
21+
$db->query('drop table if exists #foobar;');
22+
23+
$db->query("create table #foobar(name varchar(32)); ");
24+
$db->query("insert into #foobar values('PHP');");
25+
$db->query("insert into #foobar values('PHP6');");
26+
27+
foreach ($db->query('SELECT name FROM #foobar') as $row) {
28+
var_dump($row);
29+
}
30+
31+
$db->query('drop table #foobar;');
32+
33+
echo "Fin.";
34+
?>
35+
--EXPECT--
36+
PdoDblib class exists.
37+
array(2) {
38+
["name"]=>
39+
string(3) "PHP"
40+
[0]=>
41+
string(3) "PHP"
42+
}
43+
array(2) {
44+
["name"]=>
45+
string(4) "PHP6"
46+
[0]=>
47+
string(4) "PHP6"
48+
}
49+
Fin.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
--TEST--
2+
PdoDblib create through PDO::connect
3+
--EXTENSIONS--
4+
pdo
5+
--FILE--
6+
<?php
7+
8+
require_once __DIR__ . "/../config_functions.inc";
9+
10+
if (class_exists(PdoDblib::class) === false) {
11+
echo "PdoDblib class does not exist.\n";
12+
exit(-1);
13+
}
14+
15+
echo "PdoDblib class exists.\n";
16+
17+
[$dsn, $user, $pass] = getDsnUserAndPassword();
18+
19+
$db = Pdo::connect($dsn, $user, $pass);
20+
21+
if (!$db instanceof PdoDblib) {
22+
echo "Wrong class type. Should be PdoDblib but is [" . get_class($db) . "\n";
23+
}
24+
25+
$db->query('drop table if exists #test;');
26+
27+
$db->query("create table #test(name varchar(32)); ");
28+
$db->query("insert into #test values('PHP');");
29+
$db->query("insert into #test values('PHP6');");
30+
31+
foreach ($db->query('SELECT name FROM #test') as $row) {
32+
var_dump($row);
33+
}
34+
35+
$db->query('drop table #test;');
36+
37+
echo "Fin.";
38+
?>
39+
--EXPECT--
40+
PdoDblib class exists.
41+
array(2) {
42+
["name"]=>
43+
string(3) "PHP"
44+
[0]=>
45+
string(3) "PHP"
46+
}
47+
array(2) {
48+
["name"]=>
49+
string(4) "PHP6"
50+
[0]=>
51+
string(4) "PHP6"
52+
}
53+
Fin.

0 commit comments

Comments
 (0)