impl {{ wrapper }} {
/// Create a new bridge wrapping a PHP object.
///
/// Validates that the PHP object provides all required methods.
pub fn new(php_obj: &mut ext_php_rs::types::ZendObject) -> Self {
// Validation of required methods is done in the registration function below.
// Skipping debug_assert in constructor to avoid type issues with get_property.
// Extract and cache name
let cached_name = php_obj
.try_call_method("name", vec![])
.ok()
.and_then(|v| v.string())
.unwrap_or("unknown".into())
.to_string();
// SAFETY: Increment PHP reference count to keep the object alive.
// The ZendObject is passed to us as &mut, but we need to store a pointer
// that may outlive this function call (when registered in global registry).
// Incrementing the refcount ensures the object won't be garbage-collected
// while we hold a reference to it. We directly manipulate the gc refcount.
unsafe {
(*php_obj).gc.refcount = (*php_obj).gc.refcount.wrapping_add(1);
}
Self {
inner: php_obj as *mut _,
cached_name,
}
}
}
// SAFETY: PHP is single-threaded within a request context.
// The raw pointer to ZendObject is only used within a single PHP request
// and is never accessed concurrently from multiple threads.
unsafe impl Send for {{ wrapper }} {}
unsafe impl Sync for {{ wrapper }} {}
impl Drop for {{ wrapper }} {
fn drop(&mut self) {
// SAFETY: Decrement the refcount we incremented in new().
// This allows PHP to garbage-collect the object when the bridge is dropped.
unsafe {
(*self.inner).gc.refcount = (*self.inner).gc.refcount.wrapping_sub(1);
}
}
}