Skip to content
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
41 changes: 27 additions & 14 deletions ext/pgsql/pgsql.c
Original file line number Diff line number Diff line change
Expand Up @@ -2129,37 +2129,50 @@ PHP_FUNCTION(pg_fetch_object)
ce = zend_standard_class_def;
}

if (!ce->constructor && ctor_params && zend_hash_num_elements(ctor_params) > 0) {
zend_argument_value_error(3,
"must be empty when the specified class (%s) does not have a constructor",
ZSTR_VAL(ce->name)
);
if (UNEXPECTED(object_init_ex(return_value, ce) == FAILURE)) {
RETURN_THROWS();
}

zval dataset;
if (UNEXPECTED(!php_pgsql_fetch_hash(&dataset, result, row, row_is_null, PGSQL_ASSOC))) {
/* Either an exception is thrown, or we return false */
zval_ptr_dtor(return_value);
RETURN_FALSE;
}

// TODO: Check CE is an instantiable class earlier?
zend_result obj_initialized = object_init_ex(return_value, ce);
if (UNEXPECTED(obj_initialized == FAILURE)) {
zval_ptr_dtor(&dataset);
RETURN_THROWS();
}
if (!ce->default_properties_count && !ce->__set) {
Z_OBJ_P(return_value)->properties = Z_ARR(dataset);
} else {
zend_merge_properties(return_value, Z_ARRVAL(dataset));
zval_ptr_dtor(&dataset);
}

// TODO: Need to grab constructor via object handler as this allows instantiating internal objects with overridden get_constructor
if (ce->constructor) {
zend_call_known_function(ce->constructor, Z_OBJ_P(return_value), Z_OBJCE_P(return_value),
zend_object *obj = Z_OBJ_P(return_value);
const zend_class_entry *old = EG(fake_scope);
EG(fake_scope) = ce;
zend_function *constructor = obj->handlers->get_constructor(obj);
EG(fake_scope) = old;

if (UNEXPECTED(EG(exception))) {
/* visibility error or override refused - VM dtors return_value */
return;
}

if (UNEXPECTED(!constructor && ctor_params && zend_hash_num_elements(ctor_params) > 0)) {
zend_argument_value_error(4,
"must be empty when the specified class (%s) does not have a constructor",
ZSTR_VAL(ce->name)
);
RETURN_THROWS();
}

if (constructor) {
zend_call_known_function(constructor, obj, ce,
/* retval */ NULL, /* argc */ 0, /* params */ NULL, ctor_params);
if (EG(exception)) {
zend_object_store_ctor_failed(obj);
RETURN_THROWS();
}
Comment on lines +2150 to +2175
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just use object_init_with_constructor()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nice suggestion I ll try it later ;)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks for the suggestion again. however object_init_with_constructor doesn't quite fit here: pg_fetch_object has to merge the fetched row onto $this between init and the constructor
call so user constructors can read $this->num etc., and the helper does both in one shot (its internal _object_and_properties_init would also discard any pre-merged
properties).

The manual code mirrors the helper's structure (object init, get_constructor under EG(fake_scope), zend_object_store_ctor_failed on throw); the only divergence is the
property merge slotted between the two phases. Happy to revisit if I'm missing an angle.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blerg, it does the weird PDO behaviour... I guess ideally it should pass all the row elements as arguments to the constructor, but if we do this we should create new APIs for all the DB extensions.

}
}
/* }}} */
Expand Down
69 changes: 69 additions & 0 deletions ext/pgsql/tests/pg_fetch_object_ctor_paths.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
--TEST--
pg_fetch_object() constructor handling: ctor_params validation, throwing constructor, property visibility
--EXTENSIONS--
pgsql
--SKIPIF--
<?php include("inc/skipif.inc"); ?>
--FILE--
<?php
include 'inc/config.inc';

class NoCtor {}

class ThrowingCtor {
public function __construct() {
throw new RuntimeException('boom');
}
public function __destruct() {
echo "ThrowingCtor::__destruct called (BUG)\n";
}
}

class SeesProps {
public function __construct() {
echo "ctor sees: num={$this->num}, str={$this->str}\n";
}
public function __destruct() {
echo "SeesProps::__destruct called\n";
}
}

$table_name = "pg_fetch_object_ctor_paths";
$db = pg_connect($conn_str);
pg_query($db, "CREATE TABLE {$table_name} (num int, str text)");
pg_query($db, "INSERT INTO {$table_name} VALUES(1, 'hello')");

$sql = "SELECT * FROM {$table_name} WHERE num = 1";

// 1) ctor_params on a class with no constructor must throw ValueError
try {
pg_fetch_object(pg_query($db, $sql), null, 'NoCtor', [1, 2]);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}

// 2) Constructor that throws: __destruct must NOT run on the partially constructed object
try {
pg_fetch_object(pg_query($db, $sql), null, 'ThrowingCtor');
} catch (RuntimeException $e) {
echo "caught: ", $e->getMessage(), "\n";
}

// 3) Constructor sees row properties already merged onto $this
$obj = pg_fetch_object(pg_query($db, $sql), null, 'SeesProps');
unset($obj);

echo "Ok\n";
?>
--CLEAN--
<?php
include('inc/config.inc');
$db = pg_connect($conn_str);
pg_query($db, "DROP TABLE IF EXISTS pg_fetch_object_ctor_paths");
?>
--EXPECT--
pg_fetch_object(): Argument #4 ($constructor_args) must be empty when the specified class (NoCtor) does not have a constructor
caught: boom
ctor sees: num=1, str=hello
SeesProps::__destruct called
Ok
Loading