ext_php_rs/types/object.rs
1//! Represents an object in PHP. Allows for overriding the internal object used
2//! by classes, allowing users to store Rust data inside a PHP object.
3//!
4//! # Lazy Objects (PHP 8.4+)
5//!
6//! PHP 8.4 introduced lazy objects, which defer their initialization until
7//! their properties are first accessed. This module provides introspection APIs
8//! for lazy objects:
9//!
10//! - [`ZendObject::is_lazy()`] - Check if an object is lazy (ghost or proxy)
11//! - [`ZendObject::is_lazy_ghost()`] - Check if an object is a lazy ghost
12//! - [`ZendObject::is_lazy_proxy()`] - Check if an object is a lazy proxy
13//! - [`ZendObject::is_lazy_initialized()`] - Check if a lazy object has been initialized
14//! - [`ZendObject::lazy_init()`] - Trigger initialization of a lazy object
15//!
16//! ## Lazy Ghosts vs Lazy Proxies
17//!
18//! - **Lazy Ghosts**: The ghost object itself becomes the real instance when
19//! initialized. After initialization, the ghost is indistinguishable from a
20//! regular object (the `is_lazy()` flag is cleared).
21//!
22//! - **Lazy Proxies**: A proxy wraps a real instance that is created when first
23//! accessed. The proxy and real instance have different identities. After
24//! initialization, the proxy still reports as lazy (`is_lazy()` returns true).
25//!
26//! ## Creating Lazy Objects
27//!
28//! Lazy objects should be created using PHP's `ReflectionClass` API:
29//!
30//! ```php
31//! <?php
32//! // Create a lazy ghost
33//! $reflector = new ReflectionClass(MyClass::class);
34//! $ghost = $reflector->newLazyGhost(function ($obj) {
35//! $obj->__construct('initialized');
36//! });
37//!
38//! // Create a lazy proxy
39//! $proxy = $reflector->newLazyProxy(function ($obj) {
40//! return new MyClass('initialized');
41//! });
42//! ```
43//!
44//! **Note**: PHP 8.4 lazy objects only work with user-defined PHP classes, not
45//! internal classes. Since Rust-defined classes (using `#[php_class]`) are
46//! registered as internal classes, they cannot be made lazy using PHP's
47//! Reflection API.
48
49use std::{convert::TryInto, fmt::Debug, os::raw::c_char, ptr};
50
51use crate::{
52 boxed::{ZBox, ZBoxable},
53 class::RegisteredClass,
54 convert::{FromZendObject, FromZval, FromZvalMut, IntoZval, IntoZvalDyn},
55 error::{Error, Result},
56 ffi::{
57 HashTable, ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET,
58 ext_php_rs_zend_object_release, object_properties_init, zend_call_known_function,
59 zend_function, zend_hash_str_find_ptr_lc, zend_object, zend_objects_new,
60 },
61 flags::DataType,
62 rc::PhpRc,
63 types::{ZendClassObject, ZendStr, Zval},
64 zend::{ClassEntry, ExecutorGlobals, ZendObjectHandlers, ce},
65};
66
67#[cfg(php84)]
68use crate::ffi::{zend_lazy_object_init, zend_lazy_object_mark_as_initialized};
69
70#[cfg(all(feature = "closure", php84))]
71use crate::{
72 closure::Closure,
73 ffi::{
74 _zend_fcall_info_cache, ZEND_LAZY_OBJECT_STRATEGY_GHOST, ZEND_LAZY_OBJECT_STRATEGY_PROXY,
75 zend_is_callable_ex, zend_object_make_lazy,
76 },
77};
78
79/// A PHP object.
80///
81/// This type does not maintain any information about its type, for example,
82/// classes with have associated Rust structs cannot be accessed through this
83/// type. [`ZendClassObject`] is used for this purpose, and you can convert
84/// between the two.
85pub type ZendObject = zend_object;
86
87impl ZendObject {
88 /// Creates a new [`ZendObject`], returned inside an [`ZBox<ZendObject>`]
89 /// wrapper.
90 ///
91 /// # Parameters
92 ///
93 /// * `ce` - The type of class the new object should be an instance of.
94 ///
95 /// # Panics
96 ///
97 /// Panics when allocating memory for the new object fails.
98 #[must_use]
99 pub fn new(ce: &ClassEntry) -> ZBox<Self> {
100 // SAFETY: Using emalloc to allocate memory inside Zend arena. Casting `ce` to
101 // `*mut` is valid as the function will not mutate `ce`.
102 unsafe {
103 let ptr = match ce.__bindgen_anon_2.create_object {
104 None => {
105 let ptr = zend_objects_new(ptr::from_ref(ce).cast_mut());
106 assert!(!ptr.is_null(), "Failed to allocate memory for Zend object");
107
108 object_properties_init(ptr, ptr::from_ref(ce).cast_mut());
109 ptr
110 }
111 Some(v) => v(ptr::from_ref(ce).cast_mut()),
112 };
113
114 ZBox::from_raw(
115 ptr.as_mut()
116 .expect("Failed to allocate memory for Zend object"),
117 )
118 }
119 }
120
121 /// Creates a new `stdClass` instance, returned inside an
122 /// [`ZBox<ZendObject>`] wrapper.
123 ///
124 /// # Panics
125 ///
126 /// Panics if allocating memory for the object fails, or if the `stdClass`
127 /// class entry has not been registered with PHP yet.
128 ///
129 /// # Example
130 ///
131 /// ```no_run
132 /// use ext_php_rs::types::ZendObject;
133 ///
134 /// let mut obj = ZendObject::new_stdclass();
135 ///
136 /// obj.set_property("hello", "world");
137 /// ```
138 #[must_use]
139 pub fn new_stdclass() -> ZBox<Self> {
140 // SAFETY: This will be `NULL` until it is initialized. `as_ref()` checks for
141 // null, so we can panic if it's null.
142 Self::new(ce::stdclass())
143 }
144
145 /// Converts a class object into an owned [`ZendObject`]. This removes any
146 /// possibility of accessing the underlying attached Rust struct.
147 #[must_use]
148 pub fn from_class_object<T: RegisteredClass>(obj: ZBox<ZendClassObject<T>>) -> ZBox<Self> {
149 let this = obj.into_raw();
150 // SAFETY: Consumed box must produce a well-aligned non-null pointer.
151 unsafe { ZBox::from_raw(this.get_mut_zend_obj()) }
152 }
153
154 /// Returns the [`ClassEntry`] associated with this object.
155 ///
156 /// # Panics
157 ///
158 /// Panics if the class entry is invalid.
159 #[must_use]
160 pub fn get_class_entry(&self) -> &'static ClassEntry {
161 // SAFETY: it is OK to panic here since PHP would segfault anyway
162 // when encountering an object with no class entry.
163 unsafe { self.ce.as_ref() }.expect("Could not retrieve class entry.")
164 }
165
166 /// Attempts to retrieve the class name of the object.
167 ///
168 /// # Errors
169 ///
170 /// * `Error::InvalidScope` - If the object handlers or the class name
171 /// cannot be retrieved.
172 pub fn get_class_name(&self) -> Result<String> {
173 unsafe {
174 self.handlers()?
175 .get_class_name
176 .and_then(|f| f(self).as_ref())
177 .ok_or(Error::InvalidScope)
178 .and_then(TryInto::try_into)
179 }
180 }
181
182 /// Returns whether this object is an instance of the given [`ClassEntry`].
183 ///
184 /// This method checks the class and interface inheritance chain.
185 ///
186 /// # Panics
187 ///
188 /// Panics if the class entry is invalid.
189 #[must_use]
190 pub fn instance_of(&self, ce: &ClassEntry) -> bool {
191 self.get_class_entry().instance_of(ce)
192 }
193
194 /// Checks if the given object is an instance of a registered class with
195 /// Rust type `T`.
196 ///
197 /// This method doesn't check the class and interface inheritance chain.
198 #[must_use]
199 pub fn is_instance<T: RegisteredClass>(&self) -> bool {
200 (self.ce.cast_const()).eq(&ptr::from_ref(T::get_metadata().ce()))
201 }
202
203 /// Returns whether this object is an instance of \Traversable
204 ///
205 /// # Panics
206 ///
207 /// Panics if the class entry is invalid.
208 #[must_use]
209 pub fn is_traversable(&self) -> bool {
210 self.instance_of(ce::traversable())
211 }
212
213 /// Tries to call a method on the object.
214 ///
215 /// # Returns
216 ///
217 /// Returns the return value of the method, or an error if the method
218 /// could not be found or called.
219 ///
220 /// # Errors
221 ///
222 /// * `Error::Callable` - If the method could not be found.
223 /// * If a parameter could not be converted to a zval.
224 /// * If the parameter count is bigger than `u32::MAX`.
225 // TODO: Measure this
226 #[allow(clippy::inline_always)]
227 #[inline(always)]
228 pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
229 let mut retval = Zval::new();
230 let len = params.len();
231 let params = params
232 .into_iter()
233 .map(|val| val.as_zval(false))
234 .collect::<Result<Vec<_>>>()?;
235 let packed = params.into_boxed_slice();
236
237 unsafe {
238 let res = zend_hash_str_find_ptr_lc(
239 &raw const (*self.ce).function_table,
240 name.as_ptr().cast::<c_char>(),
241 name.len(),
242 )
243 .cast::<zend_function>();
244
245 if res.is_null() {
246 return Err(Error::Callable);
247 }
248
249 zend_call_known_function(
250 res,
251 ptr::from_ref(self).cast_mut(),
252 self.ce,
253 &raw mut retval,
254 len.try_into()?,
255 packed.as_ptr().cast_mut(),
256 std::ptr::null_mut(),
257 );
258 };
259
260 Ok(retval)
261 }
262
263 /// Attempts to read a property from the Object. Returns a result containing
264 /// the value of the property if it exists and can be read, and an
265 /// [`Error`] otherwise.
266 ///
267 /// # Parameters
268 ///
269 /// * `name` - The name of the property.
270 /// * `query` - The type of query to use when attempting to get a property.
271 ///
272 /// # Errors
273 ///
274 /// * `Error::InvalidScope` - If the object handlers or the properties
275 /// cannot be retrieved.
276 pub fn get_property<'a, T>(&'a self, name: &str) -> Result<T>
277 where
278 T: FromZval<'a>,
279 {
280 if !self.has_property(name, PropertyQuery::Exists)? {
281 return Err(Error::InvalidProperty);
282 }
283
284 let mut name = ZendStr::new(name, false);
285 let mut rv = Zval::new();
286
287 let zv = unsafe {
288 self.handlers()?.read_property.ok_or(Error::InvalidScope)?(
289 self.mut_ptr(),
290 &raw mut *name,
291 1,
292 ptr::null_mut(),
293 &raw mut rv,
294 )
295 .as_ref()
296 }
297 .ok_or(Error::InvalidScope)?;
298
299 T::from_zval(zv).ok_or_else(|| Error::ZvalConversion(zv.get_type()))
300 }
301
302 /// Attempts to set a property on the object.
303 ///
304 /// # Parameters
305 ///
306 /// * `name` - The name of the property.
307 /// * `value` - The value to set the property to.
308 ///
309 /// # Errors
310 ///
311 /// * `Error::InvalidScope` - If the object handlers or the properties
312 /// cannot be retrieved.
313 pub fn set_property(&mut self, name: &str, value: impl IntoZval) -> Result<()> {
314 let mut name = ZendStr::new(name, false);
315 let mut value = value.into_zval(false)?;
316
317 unsafe {
318 self.handlers()?.write_property.ok_or(Error::InvalidScope)?(
319 self,
320 &raw mut *name,
321 &raw mut value,
322 ptr::null_mut(),
323 )
324 .as_ref()
325 }
326 .ok_or(Error::InvalidScope)?;
327 Ok(())
328 }
329
330 /// Checks if a property exists on an object. Takes a property name and
331 /// query parameter, which defines what classifies if a property exists
332 /// or not. See [`PropertyQuery`] for more information.
333 ///
334 /// # Parameters
335 ///
336 /// * `name` - The name of the property.
337 /// * `query` - The 'query' to classify if a property exists.
338 ///
339 /// # Errors
340 ///
341 /// * `Error::InvalidScope` - If the object handlers or the properties
342 /// cannot be retrieved.
343 pub fn has_property(&self, name: &str, query: PropertyQuery) -> Result<bool> {
344 let mut name = ZendStr::new(name, false);
345
346 Ok(unsafe {
347 self.handlers()?.has_property.ok_or(Error::InvalidScope)?(
348 self.mut_ptr(),
349 &raw mut *name,
350 query as _,
351 std::ptr::null_mut(),
352 )
353 } > 0)
354 }
355
356 /// Attempts to retrieve the properties of the object. Returned inside a
357 /// Zend Hashtable.
358 ///
359 /// # Errors
360 ///
361 /// * `Error::InvalidScope` - If the object handlers or the properties
362 /// cannot be retrieved.
363 pub fn get_properties(&self) -> Result<&HashTable> {
364 unsafe {
365 self.handlers()?
366 .get_properties
367 .and_then(|props| props(self.mut_ptr()).as_ref())
368 .ok_or(Error::InvalidScope)
369 }
370 }
371
372 /// Extracts some type from a Zend object.
373 ///
374 /// This is a wrapper function around `FromZendObject::extract()`.
375 ///
376 /// # Errors
377 ///
378 /// Returns an error if the conversion fails.
379 pub fn extract<'a, T>(&'a self) -> Result<T>
380 where
381 T: FromZendObject<'a>,
382 {
383 T::from_zend_object(self)
384 }
385
386 /// Returns an unique identifier for the object.
387 ///
388 /// The id is guaranteed to be unique for the lifetime of the object.
389 /// Once the object is destroyed, it may be reused for other objects.
390 /// This is equivalent to calling the [`spl_object_id`] PHP function.
391 ///
392 /// [`spl_object_id`]: https://www.php.net/manual/function.spl-object-id
393 #[inline]
394 #[must_use]
395 pub fn get_id(&self) -> u32 {
396 self.handle
397 }
398
399 /// Computes an unique hash for the object.
400 ///
401 /// The hash is guaranteed to be unique for the lifetime of the object.
402 /// Once the object is destroyed, it may be reused for other objects.
403 /// This is equivalent to calling the [`spl_object_hash`] PHP function.
404 ///
405 /// [`spl_object_hash`]: https://www.php.net/manual/function.spl-object-hash.php
406 #[must_use]
407 pub fn hash(&self) -> String {
408 format!("{:016x}0000000000000000", self.handle)
409 }
410
411 // Object extra_flags constants for lazy object detection.
412 // PHP 8.4+ lazy object constants
413 // These are checked on zend_object.extra_flags before calling zend_lazy_object_get_flags.
414 // IS_OBJ_LAZY_UNINITIALIZED = (1U<<31) - Virtual proxy or uninitialized Ghost
415 #[cfg(php84)]
416 const IS_OBJ_LAZY_UNINITIALIZED: u32 = 1 << 31;
417 // IS_OBJ_LAZY_PROXY = (1U<<30) - Virtual proxy (may be initialized)
418 #[cfg(php84)]
419 const IS_OBJ_LAZY_PROXY: u32 = 1 << 30;
420
421 /// Returns whether this object is a lazy object (ghost or proxy).
422 ///
423 /// Lazy objects are objects whose initialization is deferred until
424 /// one of their properties is accessed.
425 ///
426 /// This is a PHP 8.4+ feature.
427 #[cfg(php84)]
428 #[must_use]
429 pub fn is_lazy(&self) -> bool {
430 // Check extra_flags directly - safe for all objects
431 (self.extra_flags & (Self::IS_OBJ_LAZY_UNINITIALIZED | Self::IS_OBJ_LAZY_PROXY)) != 0
432 }
433
434 /// Returns whether this object is a lazy proxy.
435 ///
436 /// Lazy proxies wrap a real instance that is created when the proxy
437 /// is first accessed. The proxy and real instance have different identities.
438 ///
439 /// This is a PHP 8.4+ feature.
440 #[cfg(php84)]
441 #[must_use]
442 pub fn is_lazy_proxy(&self) -> bool {
443 // Check extra_flags directly - safe for all objects
444 (self.extra_flags & Self::IS_OBJ_LAZY_PROXY) != 0
445 }
446
447 /// Returns whether this object is a lazy ghost.
448 ///
449 /// Lazy ghosts are indistinguishable from non-lazy objects once initialized.
450 /// The ghost object itself becomes the real instance.
451 ///
452 /// This is a PHP 8.4+ feature.
453 #[cfg(php84)]
454 #[must_use]
455 pub fn is_lazy_ghost(&self) -> bool {
456 // A lazy ghost has IS_OBJ_LAZY_UNINITIALIZED set but NOT IS_OBJ_LAZY_PROXY
457 (self.extra_flags & Self::IS_OBJ_LAZY_UNINITIALIZED) != 0
458 && (self.extra_flags & Self::IS_OBJ_LAZY_PROXY) == 0
459 }
460
461 /// Returns whether this lazy object has been initialized.
462 ///
463 /// Returns `false` for non-lazy objects.
464 ///
465 /// This is a PHP 8.4+ feature.
466 #[cfg(php84)]
467 #[must_use]
468 pub fn is_lazy_initialized(&self) -> bool {
469 if !self.is_lazy() {
470 return false;
471 }
472 // A lazy object is initialized when IS_OBJ_LAZY_UNINITIALIZED is NOT set.
473 // For ghosts: both flags clear when initialized
474 // For proxies: IS_OBJ_LAZY_PROXY stays but IS_OBJ_LAZY_UNINITIALIZED clears
475 (self.extra_flags & Self::IS_OBJ_LAZY_UNINITIALIZED) == 0
476 }
477
478 /// Triggers initialization of a lazy object.
479 ///
480 /// If the object is a lazy ghost, this populates the object in place.
481 /// If the object is a lazy proxy, this creates the real instance.
482 ///
483 /// Returns `None` if the object is not lazy or initialization fails.
484 ///
485 /// This is a PHP 8.4+ feature.
486 #[cfg(php84)]
487 #[must_use]
488 pub fn lazy_init(&mut self) -> Option<&mut Self> {
489 if !self.is_lazy() {
490 return None;
491 }
492 unsafe { zend_lazy_object_init(self).as_mut() }
493 }
494
495 /// Marks a lazy object as initialized without calling the initializer.
496 ///
497 /// This can be used to manually initialize a lazy object's properties
498 /// and then mark it as initialized.
499 ///
500 /// Returns `None` if the object is not lazy.
501 ///
502 /// This is a PHP 8.4+ feature.
503 #[cfg(php84)]
504 #[must_use]
505 pub fn mark_lazy_initialized(&mut self) -> Option<&mut Self> {
506 if !self.is_lazy() {
507 return None;
508 }
509 unsafe { zend_lazy_object_mark_as_initialized(self).as_mut() }
510 }
511
512 /// For lazy proxies, returns the real instance after initialization.
513 ///
514 /// Returns `None` if this is not a lazy proxy or if not initialized.
515 ///
516 /// This is a PHP 8.4+ feature.
517 #[cfg(php84)]
518 #[must_use]
519 pub fn lazy_get_instance(&mut self) -> Option<&mut Self> {
520 if !self.is_lazy_proxy() || !self.is_lazy_initialized() {
521 return None;
522 }
523 // Note: We use zend_lazy_object_init here because zend_lazy_object_get_instance
524 // is not exported (no ZEND_API) in PHP and cannot be linked on Windows.
525 // zend_lazy_object_init returns the real instance for already-initialized proxies.
526 unsafe { zend_lazy_object_init(self).as_mut() }
527 }
528
529 /// Converts this object into a lazy ghost with the given initializer.
530 ///
531 /// The initializer closure will be called when the object's properties are
532 /// first accessed. The closure should perform initialization logic.
533 ///
534 /// # Parameters
535 ///
536 /// * `initializer` - A closure that performs initialization. The closure
537 /// returns `()`. Any state needed for initialization should be captured
538 /// in the closure.
539 ///
540 /// # Returns
541 ///
542 /// Returns `Ok(())` if the object was successfully made lazy, or an error
543 /// if the operation failed.
544 ///
545 /// # Example
546 ///
547 /// ```rust,no_run
548 /// use ext_php_rs::types::ZendObject;
549 ///
550 /// fn make_lazy_example(obj: &mut ZendObject) -> ext_php_rs::error::Result<()> {
551 /// let init_value = "initialized".to_string();
552 /// obj.make_lazy_ghost(Box::new(move || {
553 /// // Use captured state for initialization
554 /// println!("Initializing with: {}", init_value);
555 /// }) as Box<dyn Fn()>)?;
556 /// Ok(())
557 /// }
558 /// ```
559 ///
560 /// # Errors
561 ///
562 /// Returns an error if the initializer closure cannot be converted to a
563 /// PHP callable or if the object cannot be made lazy.
564 ///
565 /// # Safety
566 ///
567 /// This is a PHP 8.4+ feature. The closure must be `'static` as it may be
568 /// called at any time during the object's lifetime.
569 ///
570 /// **Note**: PHP 8.4 lazy objects only work with user-defined PHP classes,
571 /// not internal classes. Rust-defined classes cannot be made lazy.
572 #[cfg(all(feature = "closure", php84))]
573 #[cfg_attr(docs, doc(cfg(all(feature = "closure", php84))))]
574 #[allow(clippy::cast_possible_truncation)]
575 pub fn make_lazy_ghost<F>(&mut self, initializer: F) -> Result<()>
576 where
577 F: Fn() + 'static,
578 {
579 self.make_lazy_internal(initializer, ZEND_LAZY_OBJECT_STRATEGY_GHOST as u8)
580 }
581
582 /// Converts this object into a lazy proxy with the given initializer.
583 ///
584 /// The initializer closure will be called when the object's properties are
585 /// first accessed. The closure should return the real instance that the
586 /// proxy will forward to.
587 ///
588 /// # Parameters
589 ///
590 /// * `initializer` - A closure that returns `Option<ZBox<ZendObject>>`,
591 /// the real instance. Any state needed should be captured in the closure.
592 ///
593 /// # Returns
594 ///
595 /// Returns `Ok(())` if the object was successfully made lazy, or an error
596 /// if the operation failed.
597 ///
598 /// # Example
599 ///
600 /// ```rust,no_run
601 /// use ext_php_rs::types::ZendObject;
602 /// use ext_php_rs::boxed::ZBox;
603 ///
604 /// fn make_proxy_example(obj: &mut ZendObject) -> ext_php_rs::error::Result<()> {
605 /// obj.make_lazy_proxy(Box::new(|| {
606 /// // Create and return the real instance
607 /// Some(ZendObject::new_stdclass())
608 /// }) as Box<dyn Fn() -> Option<ZBox<ZendObject>>>)?;
609 /// Ok(())
610 /// }
611 /// ```
612 ///
613 /// # Errors
614 ///
615 /// Returns an error if the initializer closure cannot be converted to a
616 /// PHP callable or if the object cannot be made lazy.
617 ///
618 /// # Safety
619 ///
620 /// This is a PHP 8.4+ feature. The closure must be `'static` as it may be
621 /// called at any time during the object's lifetime.
622 ///
623 /// **Note**: PHP 8.4 lazy objects only work with user-defined PHP classes,
624 /// not internal classes. Rust-defined classes cannot be made lazy.
625 #[cfg(all(feature = "closure", php84))]
626 #[cfg_attr(docs, doc(cfg(all(feature = "closure", php84))))]
627 #[allow(clippy::cast_possible_truncation)]
628 pub fn make_lazy_proxy<F>(&mut self, initializer: F) -> Result<()>
629 where
630 F: Fn() -> Option<ZBox<ZendObject>> + 'static,
631 {
632 self.make_lazy_internal(initializer, ZEND_LAZY_OBJECT_STRATEGY_PROXY as u8)
633 }
634
635 /// Internal implementation for making an object lazy.
636 #[cfg(all(feature = "closure", php84))]
637 fn make_lazy_internal<F, R>(&mut self, initializer: F, strategy: u8) -> Result<()>
638 where
639 F: Fn() -> R + 'static,
640 R: IntoZval + 'static,
641 {
642 // Check if the class can be made lazy
643 let ce = unsafe { self.ce.as_ref() }.ok_or(Error::InvalidPointer)?;
644 if !ce.can_be_lazy() {
645 return Err(Error::LazyObjectFailed);
646 }
647
648 // Cannot make an already-lazy uninitialized object lazy again
649 if self.is_lazy() && !self.is_lazy_initialized() {
650 return Err(Error::LazyObjectFailed);
651 }
652
653 // Wrap the Rust closure in a PHP-callable Closure
654 let closure = Closure::wrap(Box::new(initializer) as Box<dyn Fn() -> R>);
655
656 // Convert the closure to a zval
657 let mut initializer_zv = Zval::new();
658 closure.set_zval(&mut initializer_zv, false)?;
659
660 // Initialize the fcc structure
661 let mut fcc: _zend_fcall_info_cache = unsafe { std::mem::zeroed() };
662
663 // Populate the fcc using zend_is_callable_ex
664 let is_callable = unsafe {
665 zend_is_callable_ex(
666 &raw mut initializer_zv,
667 ptr::null_mut(),
668 0,
669 ptr::null_mut(),
670 &raw mut fcc,
671 ptr::null_mut(),
672 )
673 };
674
675 if !is_callable {
676 return Err(Error::Callable);
677 }
678
679 // Get the class entry
680 let ce = self.ce;
681
682 // Make the object lazy
683 let result = unsafe {
684 zend_object_make_lazy(self, ce, &raw mut initializer_zv, &raw mut fcc, strategy)
685 };
686
687 if result.is_null() {
688 Err(Error::LazyObjectFailed)
689 } else {
690 Ok(())
691 }
692 }
693
694 /// Attempts to retrieve a reference to the object handlers.
695 #[inline]
696 unsafe fn handlers(&self) -> Result<&ZendObjectHandlers> {
697 unsafe { self.handlers.as_ref() }.ok_or(Error::InvalidScope)
698 }
699
700 /// Returns a mutable pointer to `self`, regardless of the type of
701 /// reference. Only to be used in situations where a C function requires
702 /// a mutable pointer but does not modify the underlying data.
703 #[inline]
704 fn mut_ptr(&self) -> *mut Self {
705 ptr::from_ref(self).cast_mut()
706 }
707}
708
709unsafe impl ZBoxable for ZendObject {
710 fn free(&mut self) {
711 unsafe { ext_php_rs_zend_object_release(self) }
712 }
713}
714
715impl Debug for ZendObject {
716 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
717 let mut dbg = f.debug_struct(
718 self.get_class_name()
719 .unwrap_or_else(|_| "ZendObject".to_string())
720 .as_str(),
721 );
722
723 if let Ok(props) = self.get_properties() {
724 for (key, val) in props {
725 dbg.field(key.to_string().as_str(), val);
726 }
727 }
728
729 dbg.finish()
730 }
731}
732
733impl<'a> FromZval<'a> for &'a ZendObject {
734 const TYPE: DataType = DataType::Object(None);
735
736 fn from_zval(zval: &'a Zval) -> Option<Self> {
737 zval.object()
738 }
739}
740
741impl<'a> FromZvalMut<'a> for &'a mut ZendObject {
742 const TYPE: DataType = DataType::Object(None);
743
744 fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
745 zval.object_mut()
746 }
747}
748
749impl IntoZval for ZBox<ZendObject> {
750 const TYPE: DataType = DataType::Object(None);
751 const NULLABLE: bool = false;
752
753 #[inline]
754 fn set_zval(mut self, zv: &mut Zval, _: bool) -> Result<()> {
755 // We must decrement the refcounter on the object before inserting into the
756 // zval, as the reference counter will be incremented on add.
757 // NOTE(david): again is this needed, we increment in `set_object`.
758 self.dec_count();
759 zv.set_object(self.into_raw());
760 Ok(())
761 }
762}
763
764impl IntoZval for &mut ZendObject {
765 const TYPE: DataType = DataType::Object(None);
766 const NULLABLE: bool = false;
767
768 #[inline]
769 fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
770 zv.set_object(self);
771 Ok(())
772 }
773}
774
775impl FromZendObject<'_> for String {
776 fn from_zend_object(obj: &ZendObject) -> Result<Self> {
777 let mut ret = Zval::new();
778 unsafe {
779 zend_call_known_function(
780 (*obj.ce).__tostring,
781 ptr::from_ref(obj).cast_mut(),
782 obj.ce,
783 &raw mut ret,
784 0,
785 ptr::null_mut(),
786 ptr::null_mut(),
787 );
788 }
789
790 if let Some(err) = ExecutorGlobals::take_exception() {
791 // TODO: become an error
792 let class_name = obj.get_class_name();
793 panic!(
794 "Uncaught exception during call to {}::__toString(): {:?}",
795 class_name.expect("unable to determine class name"),
796 err
797 );
798 } else if let Some(output) = ret.extract() {
799 Ok(output)
800 } else {
801 // TODO: become an error
802 let class_name = obj.get_class_name();
803 panic!(
804 "{}::__toString() must return a string",
805 class_name.expect("unable to determine class name"),
806 );
807 }
808 }
809}
810
811impl<T: RegisteredClass> From<ZBox<ZendClassObject<T>>> for ZBox<ZendObject> {
812 #[inline]
813 fn from(obj: ZBox<ZendClassObject<T>>) -> Self {
814 ZendObject::from_class_object(obj)
815 }
816}
817
818/// Different ways to query if a property exists.
819#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
820#[repr(u32)]
821pub enum PropertyQuery {
822 /// Property exists and is not NULL.
823 Isset = ZEND_PROPERTY_ISSET,
824 /// Property is not empty.
825 NotEmpty = ZEND_ISEMPTY,
826 /// Property exists.
827 Exists = ZEND_PROPERTY_EXISTS,
828}