Description
The v11.0.0 M3 migration converted most fbird_* functions to return opaque Firebird\* objects instead of raw resources. However, fbird_prepare() and fbird_prepare_ex() were not migrated — they still return raw resource handles.
This is the last remaining resource/object duality in the extension. Completing the migration would:
- Eliminate all
is_resource() / get_resource_type() checks in consuming code
- Make the type system consistent (every handle-returning function returns an object)
- Align with PHP's long-term resource-to-object migration trajectory
Current State
Functions that return objects (M3 complete) ✅
| Function |
Returns |
fbird_connect() / fbird_pconnect() / fbird_create_database() |
Firebird\Connection |
fbird_trans() / fbird_trans_start() |
Firebird\Transaction |
fbird_execute() |
Firebird\ResultSet (SELECT) |
fbird_blob_create() / fbird_blob_open() / fbird_blob_create_seekable() / fbird_blob_open_seekable() |
Firebird\Blob |
fbird_service_attach() |
Firebird\Service |
fbird_set_event_handler() |
Firebird\Event |
Functions that still return resources (M3 incomplete) ❌
| Function |
Returns |
Stub |
C code |
fbird_prepare() |
resource|false |
: mixed (@return resource|false) |
fbird_query_exec.c:1319 — RETVAL_RES(ib_query->res) |
fbird_prepare_ex() |
resource|false |
: mixed (@return resource|false) |
fbird_query_exec.c:1377 — RETVAL_RES(ib_query->res) |
Related: functions that return resources for SELECT results (see issue #296)
| Function |
Returns |
C code |
fbird_query() |
resource (stub says \Firebird\ResultSet) |
fbird_query_exec.c:509,677,877 — RETVAL_RES() |
fbird_execute_query() |
resource|false |
Same pattern |
fbird_execute_auto() |
resource|int|false |
Same pattern |
fbird_query_params_tx() |
resource|int|false |
Same pattern |
The Firebird\Statement Class Already Exists
The Firebird\Statement class was registered as a skeleton class entry in v8.0.0 (M3 Phase A+B). It has a private constructor and is intended to be returned by fbird_prepare() — the same pattern used for Firebird\Transaction (returned by fbird_trans() since M3 Phase D).
The infrastructure is already in place:
fbird_statement_ce class entry (registered in fbird_register_classes(), fbird_classes.c)
fbird_resultset_ce class entry (same)
fbird_setup_resultset_object() helper (already used by fbird_execute())
- A
fbird_setup_statement_object() helper would follow the same pattern
Suggested Implementation
1. fbird_prepare() and fbird_prepare_ex()
Replace RETVAL_RES(ib_query->res) with an object-wrapping call, following the same pattern as fbird_setup_resultset_object():
// New helper in fbird_classes.c (same pattern as fbird_setup_resultset_object):
void fbird_setup_statement_object(zval *return_value, zend_resource *res)
{
zval_ptr_dtor(return_value);
object_init_ex(return_value, fbird_statement_ce);
fbird_statement_obj *intern = fbird_statement_from_obj(Z_OBJ_P(return_value));
GC_ADDREF(res);
intern->query_res = res;
}
// In PHP_FUNCTION(fbird_prepare) and PHP_FUNCTION(fbird_prepare_ex):
// Replace:
// RETVAL_RES(ib_query->res);
// Z_TRY_ADDREF_P(return_value);
// With:
// zend_resource *_res = ib_query->res;
// Z_TRY_ADDREF_P(&_res); // keep alive for object
// fbird_setup_statement_object(return_value, _res);
2. fbird_query(), fbird_execute_query(), fbird_query_params_tx(), fbird_execute_auto()
Add the fbird_setup_resultset_object() wrapping block (already used by fbird_execute()) at the end of each function. See issue #296 for the exact code block and insertion points.
3. Update stubs
// Current (stubs/firebird-stubs.php)
function fbird_prepare(...): mixed {} // @return resource|false
function fbird_prepare_ex(...): mixed {} // @return resource|false
// After migration
function fbird_prepare(...): \Firebird\Statement|false {}
function fbird_prepare_ex(...): \Firebird\Statement|false {}
4. Dual-accept bridge
All consuming functions (fbird_execute(), fbird_free_query(), fbird_fetch_row(), etc.) already accept both resources and Firebird\ResultSet/Firebird\Statement objects via the dual-accept bridge (M3 Phase E/F). No changes needed on the consuming side.
Impact on doctrine-firebird-driver
Completing the migration would allow the driver to:
| Current code |
After migration |
Statement::isStatementValid() — !== null && !== false (handles resource+object+false) |
instanceof \Firebird\Statement |
Result::isResultValid() — is_object() (broad, can't use instanceof) |
instanceof \Firebird\ResultSet |
All remaining is_resource() calls |
Eliminated entirely |
@param resource|\Firebird\Transaction|false|null |
@param \Firebird\Statement|\Firebird\Transaction|false|null |
Net effect: the driver would have zero resource-type checks — a fully object-oriented API surface.
Environment
- php-firebird v11.0.1
Firebird\Statement class registered since v8.0.0
- Dual-accept bridge in place since v11.0.0 (Phase E/F)
Description
The v11.0.0 M3 migration converted most
fbird_*functions to return opaqueFirebird\*objects instead of raw resources. However,fbird_prepare()andfbird_prepare_ex()were not migrated — they still return rawresourcehandles.This is the last remaining resource/object duality in the extension. Completing the migration would:
is_resource()/get_resource_type()checks in consuming codeCurrent State
Functions that return objects (M3 complete) ✅
fbird_connect()/fbird_pconnect()/fbird_create_database()Firebird\Connectionfbird_trans()/fbird_trans_start()Firebird\Transactionfbird_execute()Firebird\ResultSet(SELECT)fbird_blob_create()/fbird_blob_open()/fbird_blob_create_seekable()/fbird_blob_open_seekable()Firebird\Blobfbird_service_attach()Firebird\Servicefbird_set_event_handler()Firebird\EventFunctions that still return resources (M3 incomplete) ❌
fbird_prepare()resource|false: mixed(@return resource|false)fbird_query_exec.c:1319—RETVAL_RES(ib_query->res)fbird_prepare_ex()resource|false: mixed(@return resource|false)fbird_query_exec.c:1377—RETVAL_RES(ib_query->res)Related: functions that return resources for SELECT results (see issue #296)
fbird_query()resource(stub says\Firebird\ResultSet)fbird_query_exec.c:509,677,877—RETVAL_RES()fbird_execute_query()resource|falsefbird_execute_auto()resource|int|falsefbird_query_params_tx()resource|int|falseThe
Firebird\StatementClass Already ExistsThe
Firebird\Statementclass was registered as a skeleton class entry in v8.0.0 (M3 Phase A+B). It has a private constructor and is intended to be returned byfbird_prepare()— the same pattern used forFirebird\Transaction(returned byfbird_trans()since M3 Phase D).The infrastructure is already in place:
fbird_statement_ceclass entry (registered infbird_register_classes(),fbird_classes.c)fbird_resultset_ceclass entry (same)fbird_setup_resultset_object()helper (already used byfbird_execute())fbird_setup_statement_object()helper would follow the same patternSuggested Implementation
1.
fbird_prepare()andfbird_prepare_ex()Replace
RETVAL_RES(ib_query->res)with an object-wrapping call, following the same pattern asfbird_setup_resultset_object():2.
fbird_query(),fbird_execute_query(),fbird_query_params_tx(),fbird_execute_auto()Add the
fbird_setup_resultset_object()wrapping block (already used byfbird_execute()) at the end of each function. See issue #296 for the exact code block and insertion points.3. Update stubs
4. Dual-accept bridge
All consuming functions (
fbird_execute(),fbird_free_query(),fbird_fetch_row(), etc.) already accept both resources andFirebird\ResultSet/Firebird\Statementobjects via the dual-accept bridge (M3 Phase E/F). No changes needed on the consuming side.Impact on doctrine-firebird-driver
Completing the migration would allow the driver to:
Statement::isStatementValid()—!== null && !== false(handles resource+object+false)instanceof \Firebird\StatementResult::isResultValid()—is_object()(broad, can't use instanceof)instanceof \Firebird\ResultSetis_resource()calls@param resource|\Firebird\Transaction|false|null@param \Firebird\Statement|\Firebird\Transaction|false|nullNet effect: the driver would have zero resource-type checks — a fully object-oriented API surface.
Environment
Firebird\Statementclass registered since v8.0.0