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
4use std::{convert::TryInto, fmt::Debug, ops::DerefMut, os::raw::c_char};
5
6use crate::{
7    boxed::{ZBox, ZBoxable},
8    class::RegisteredClass,
9    convert::{FromZendObject, FromZval, FromZvalMut, IntoZval, IntoZvalDyn},
10    error::{Error, Result},
11    ffi::{
12        ext_php_rs_zend_object_release, object_properties_init, zend_call_known_function,
13        zend_function, zend_hash_str_find_ptr_lc, zend_object, zend_objects_new, HashTable,
14        ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET,
15    },
16    flags::DataType,
17    rc::PhpRc,
18    types::{ZendClassObject, ZendStr, Zval},
19    zend::{ce, ClassEntry, ExecutorGlobals, ZendObjectHandlers},
20};
21
22/// A PHP object.
23///
24/// This type does not maintain any information about its type, for example,
25/// classes with have associated Rust structs cannot be accessed through this
26/// type. [`ZendClassObject`] is used for this purpose, and you can convert
27/// between the two.
28pub type ZendObject = zend_object;
29
30impl ZendObject {
31    /// Creates a new [`ZendObject`], returned inside an [`ZBox<ZendObject>`]
32    /// wrapper.
33    ///
34    /// # Parameters
35    ///
36    /// * `ce` - The type of class the new object should be an instance of.
37    ///
38    /// # Panics
39    ///
40    /// Panics when allocating memory for the new object fails.
41    pub fn new(ce: &ClassEntry) -> ZBox<Self> {
42        // SAFETY: Using emalloc to allocate memory inside Zend arena. Casting `ce` to
43        // `*mut` is valid as the function will not mutate `ce`.
44        unsafe {
45            let ptr = match ce.__bindgen_anon_2.create_object {
46                None => {
47                    let ptr = zend_objects_new(ce as *const _ as *mut _);
48                    if ptr.is_null() {
49                        panic!("Failed to allocate memory for Zend object")
50                    }
51                    object_properties_init(ptr, ce as *const _ as *mut _);
52                    ptr
53                }
54                Some(v) => v(ce as *const _ as *mut _),
55            };
56
57            ZBox::from_raw(
58                ptr.as_mut()
59                    .expect("Failed to allocate memory for Zend object"),
60            )
61        }
62    }
63
64    /// Creates a new `stdClass` instance, returned inside an
65    /// [`ZBox<ZendObject>`] wrapper.
66    ///
67    /// # Panics
68    ///
69    /// Panics if allocating memory for the object fails, or if the `stdClass`
70    /// class entry has not been registered with PHP yet.
71    ///
72    /// # Example
73    ///
74    /// ```no_run
75    /// use ext_php_rs::types::ZendObject;
76    ///
77    /// let mut obj = ZendObject::new_stdclass();
78    ///
79    /// obj.set_property("hello", "world");
80    /// ```
81    pub fn new_stdclass() -> ZBox<Self> {
82        // SAFETY: This will be `NULL` until it is initialized. `as_ref()` checks for
83        // null, so we can panic if it's null.
84        Self::new(ce::stdclass())
85    }
86
87    /// Converts a class object into an owned [`ZendObject`]. This removes any
88    /// possibility of accessing the underlying attached Rust struct.
89    pub fn from_class_object<T: RegisteredClass>(obj: ZBox<ZendClassObject<T>>) -> ZBox<Self> {
90        let this = obj.into_raw();
91        // SAFETY: Consumed box must produce a well-aligned non-null pointer.
92        unsafe { ZBox::from_raw(this.get_mut_zend_obj()) }
93    }
94
95    /// Returns the [`ClassEntry`] associated with this object.
96    ///
97    /// # Panics
98    ///
99    /// Panics if the class entry is invalid.
100    pub fn get_class_entry(&self) -> &'static ClassEntry {
101        // SAFETY: it is OK to panic here since PHP would segfault anyway
102        // when encountering an object with no class entry.
103        unsafe { self.ce.as_ref() }.expect("Could not retrieve class entry.")
104    }
105
106    /// Attempts to retrieve the class name of the object.
107    pub fn get_class_name(&self) -> Result<String> {
108        unsafe {
109            self.handlers()?
110                .get_class_name
111                .and_then(|f| f(self).as_ref())
112                .ok_or(Error::InvalidScope)
113                .and_then(|s| s.try_into())
114        }
115    }
116
117    /// Returns whether this object is an instance of the given [`ClassEntry`].
118    ///
119    /// This method checks the class and interface inheritance chain.
120    ///
121    /// # Panics
122    ///
123    /// Panics if the class entry is invalid.
124    pub fn instance_of(&self, ce: &ClassEntry) -> bool {
125        self.get_class_entry().instance_of(ce)
126    }
127
128    /// Checks if the given object is an instance of a registered class with
129    /// Rust type `T`.
130    ///
131    /// This method doesn't check the class and interface inheritance chain.
132    pub fn is_instance<T: RegisteredClass>(&self) -> bool {
133        (self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _))
134    }
135
136    /// Returns whether this object is an instance of \Traversable
137    ///
138    /// # Panics
139    ///
140    /// Panics if the class entry is invalid.
141    pub fn is_traversable(&self) -> bool {
142        self.instance_of(ce::traversable())
143    }
144
145    #[inline(always)]
146    pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
147        let mut retval = Zval::new();
148        let len = params.len();
149        let params = params
150            .into_iter()
151            .map(|val| val.as_zval(false))
152            .collect::<Result<Vec<_>>>()?;
153        let packed = params.into_boxed_slice();
154
155        unsafe {
156            let res = zend_hash_str_find_ptr_lc(
157                &(*self.ce).function_table,
158                name.as_ptr() as *const c_char,
159                name.len(),
160            ) as *mut zend_function;
161            if res.is_null() {
162                return Err(Error::Callable);
163            }
164            zend_call_known_function(
165                res,
166                self as *const _ as *mut _,
167                self.ce,
168                &mut retval,
169                len as _,
170                packed.as_ptr() as *mut _,
171                std::ptr::null_mut(),
172            )
173        };
174
175        Ok(retval)
176    }
177    /// Attempts to read a property from the Object. Returns a result containing
178    /// the value of the property if it exists and can be read, and an
179    /// [`Error`] otherwise.
180    ///
181    /// # Parameters
182    ///
183    /// * `name` - The name of the property.
184    /// * `query` - The type of query to use when attempting to get a property.
185    pub fn get_property<'a, T>(&'a self, name: &str) -> Result<T>
186    where
187        T: FromZval<'a>,
188    {
189        if !self.has_property(name, PropertyQuery::Exists)? {
190            return Err(Error::InvalidProperty);
191        }
192
193        let mut name = ZendStr::new(name, false);
194        let mut rv = Zval::new();
195
196        let zv = unsafe {
197            self.handlers()?.read_property.ok_or(Error::InvalidScope)?(
198                self.mut_ptr(),
199                name.deref_mut(),
200                1,
201                std::ptr::null_mut(),
202                &mut rv,
203            )
204            .as_ref()
205        }
206        .ok_or(Error::InvalidScope)?;
207
208        T::from_zval(zv).ok_or_else(|| Error::ZvalConversion(zv.get_type()))
209    }
210
211    /// Attempts to set a property on the object.
212    ///
213    /// # Parameters
214    ///
215    /// * `name` - The name of the property.
216    /// * `value` - The value to set the property to.
217    pub fn set_property(&mut self, name: &str, value: impl IntoZval) -> Result<()> {
218        let mut name = ZendStr::new(name, false);
219        let mut value = value.into_zval(false)?;
220
221        unsafe {
222            self.handlers()?.write_property.ok_or(Error::InvalidScope)?(
223                self,
224                name.deref_mut(),
225                &mut value,
226                std::ptr::null_mut(),
227            )
228            .as_ref()
229        }
230        .ok_or(Error::InvalidScope)?;
231        Ok(())
232    }
233
234    /// Checks if a property exists on an object. Takes a property name and
235    /// query parameter, which defines what classifies if a property exists
236    /// or not. See [`PropertyQuery`] for more information.
237    ///
238    /// # Parameters
239    ///
240    /// * `name` - The name of the property.
241    /// * `query` - The 'query' to classify if a property exists.
242    pub fn has_property(&self, name: &str, query: PropertyQuery) -> Result<bool> {
243        let mut name = ZendStr::new(name, false);
244
245        Ok(unsafe {
246            self.handlers()?.has_property.ok_or(Error::InvalidScope)?(
247                self.mut_ptr(),
248                name.deref_mut(),
249                query as _,
250                std::ptr::null_mut(),
251            )
252        } > 0)
253    }
254
255    /// Attempts to retrieve the properties of the object. Returned inside a
256    /// Zend Hashtable.
257    pub fn get_properties(&self) -> Result<&HashTable> {
258        unsafe {
259            self.handlers()?
260                .get_properties
261                .and_then(|props| props(self.mut_ptr()).as_ref())
262                .ok_or(Error::InvalidScope)
263        }
264    }
265
266    /// Extracts some type from a Zend object.
267    ///
268    /// This is a wrapper function around `FromZendObject::extract()`.
269    pub fn extract<'a, T>(&'a self) -> Result<T>
270    where
271        T: FromZendObject<'a>,
272    {
273        T::from_zend_object(self)
274    }
275
276    /// Returns an unique identifier for the object.
277    ///
278    /// The id is guaranteed to be unique for the lifetime of the object.
279    /// Once the object is destroyed, it may be reused for other objects.
280    /// This is equivalent to calling the [`spl_object_id`] PHP function.
281    ///
282    /// [`spl_object_id`]: https://www.php.net/manual/function.spl-object-id
283    #[inline]
284    pub fn get_id(&self) -> u32 {
285        self.handle
286    }
287
288    /// Computes an unique hash for the object.
289    ///
290    /// The hash is guaranteed to be unique for the lifetime of the object.
291    /// Once the object is destroyed, it may be reused for other objects.
292    /// This is equivalent to calling the [`spl_object_hash`] PHP function.
293    ///
294    /// [`spl_object_hash`]: https://www.php.net/manual/function.spl-object-hash.php
295    pub fn hash(&self) -> String {
296        format!("{:016x}0000000000000000", self.handle)
297    }
298
299    /// Attempts to retrieve a reference to the object handlers.
300    #[inline]
301    unsafe fn handlers(&self) -> Result<&ZendObjectHandlers> {
302        self.handlers.as_ref().ok_or(Error::InvalidScope)
303    }
304
305    /// Returns a mutable pointer to `self`, regardless of the type of
306    /// reference. Only to be used in situations where a C function requires
307    /// a mutable pointer but does not modify the underlying data.
308    #[inline]
309    fn mut_ptr(&self) -> *mut Self {
310        (self as *const Self) as *mut Self
311    }
312}
313
314unsafe impl ZBoxable for ZendObject {
315    fn free(&mut self) {
316        unsafe { ext_php_rs_zend_object_release(self) }
317    }
318}
319
320impl Debug for ZendObject {
321    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322        let mut dbg = f.debug_struct(
323            self.get_class_name()
324                .unwrap_or_else(|_| "ZendObject".to_string())
325                .as_str(),
326        );
327
328        if let Ok(props) = self.get_properties() {
329            for (key, val) in props.iter() {
330                dbg.field(key.to_string().as_str(), val);
331            }
332        }
333
334        dbg.finish()
335    }
336}
337
338impl<'a> FromZval<'a> for &'a ZendObject {
339    const TYPE: DataType = DataType::Object(None);
340
341    fn from_zval(zval: &'a Zval) -> Option<Self> {
342        zval.object()
343    }
344}
345
346impl<'a> FromZvalMut<'a> for &'a mut ZendObject {
347    const TYPE: DataType = DataType::Object(None);
348
349    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
350        zval.object_mut()
351    }
352}
353
354impl IntoZval for ZBox<ZendObject> {
355    const TYPE: DataType = DataType::Object(None);
356
357    #[inline]
358    fn set_zval(mut self, zv: &mut Zval, _: bool) -> Result<()> {
359        // We must decrement the refcounter on the object before inserting into the
360        // zval, as the reference counter will be incremented on add.
361        // NOTE(david): again is this needed, we increment in `set_object`.
362        self.dec_count();
363        zv.set_object(self.into_raw());
364        Ok(())
365    }
366}
367
368impl IntoZval for &mut ZendObject {
369    const TYPE: DataType = DataType::Object(None);
370
371    #[inline]
372    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
373        zv.set_object(self);
374        Ok(())
375    }
376}
377
378impl FromZendObject<'_> for String {
379    fn from_zend_object(obj: &ZendObject) -> Result<Self> {
380        let mut ret = Zval::new();
381        unsafe {
382            zend_call_known_function(
383                (*obj.ce).__tostring,
384                obj as *const _ as *mut _,
385                obj.ce,
386                &mut ret,
387                0,
388                std::ptr::null_mut(),
389                std::ptr::null_mut(),
390            );
391        }
392
393        if let Some(err) = ExecutorGlobals::take_exception() {
394            // TODO: become an error
395            let class_name = obj.get_class_name();
396            panic!(
397                "Uncaught exception during call to {}::__toString(): {:?}",
398                class_name.expect("unable to determine class name"),
399                err
400            );
401        } else if let Some(output) = ret.extract() {
402            Ok(output)
403        } else {
404            // TODO: become an error
405            let class_name = obj.get_class_name();
406            panic!(
407                "{}::__toString() must return a string",
408                class_name.expect("unable to determine class name"),
409            );
410        }
411    }
412}
413
414impl<T: RegisteredClass> From<ZBox<ZendClassObject<T>>> for ZBox<ZendObject> {
415    #[inline]
416    fn from(obj: ZBox<ZendClassObject<T>>) -> Self {
417        ZendObject::from_class_object(obj)
418    }
419}
420
421/// Different ways to query if a property exists.
422#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
423#[repr(u32)]
424pub enum PropertyQuery {
425    /// Property exists and is not NULL.
426    Isset = ZEND_PROPERTY_ISSET,
427    /// Property is not empty.
428    NotEmpty = ZEND_ISEMPTY,
429    /// Property exists.
430    Exists = ZEND_PROPERTY_EXISTS,
431}