ext_php_rs/zend/
handlers.rs

1use std::{ffi::c_void, mem::MaybeUninit, os::raw::c_int, ptr};
2
3use crate::{
4    class::RegisteredClass,
5    exception::PhpResult,
6    ffi::{
7        std_object_handlers, zend_is_true, zend_object_handlers, zend_object_std_dtor,
8        zend_std_get_properties, zend_std_has_property, zend_std_read_property,
9        zend_std_write_property,
10    },
11    flags::ZvalTypeFlags,
12    types::{ZendClassObject, ZendHashTable, ZendObject, ZendStr, Zval},
13};
14
15/// A set of functions associated with a PHP class.
16pub type ZendObjectHandlers = zend_object_handlers;
17
18impl ZendObjectHandlers {
19    /// Creates a new set of object handlers based on the standard object
20    /// handlers.
21    pub fn new<T: RegisteredClass>() -> ZendObjectHandlers {
22        let mut this = MaybeUninit::uninit();
23
24        // SAFETY: `this` is allocated on the stack and is a valid memory location.
25        unsafe { Self::init::<T>(&mut *this.as_mut_ptr()) };
26
27        // SAFETY: We just initialized the handlers in the previous statement, therefore
28        // we are returning a valid object.
29        unsafe { this.assume_init() }
30    }
31
32    /// Initializes a given set of object handlers by copying the standard
33    /// object handlers into the memory location, as well as setting up the
34    /// `T` type destructor.
35    ///
36    /// # Parameters
37    ///
38    /// * `ptr` - Pointer to memory location to copy the standard handlers to.
39    ///
40    /// # Safety
41    ///
42    /// Caller must guarantee that the `ptr` given is a valid memory location.
43    pub unsafe fn init<T: RegisteredClass>(ptr: *mut ZendObjectHandlers) {
44        std::ptr::copy_nonoverlapping(&std_object_handlers, ptr, 1);
45        let offset = ZendClassObject::<T>::std_offset();
46        (*ptr).offset = offset as _;
47        (*ptr).free_obj = Some(Self::free_obj::<T>);
48        (*ptr).read_property = Some(Self::read_property::<T>);
49        (*ptr).write_property = Some(Self::write_property::<T>);
50        (*ptr).get_properties = Some(Self::get_properties::<T>);
51        (*ptr).has_property = Some(Self::has_property::<T>);
52    }
53
54    unsafe extern "C" fn free_obj<T: RegisteredClass>(object: *mut ZendObject) {
55        let obj = object
56            .as_mut()
57            .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
58            .expect("Invalid object pointer given for `free_obj`");
59
60        // Manually drop the object as we don't want to free the underlying memory.
61        ptr::drop_in_place(&mut obj.obj);
62
63        zend_object_std_dtor(object)
64    }
65
66    unsafe extern "C" fn read_property<T: RegisteredClass>(
67        object: *mut ZendObject,
68        member: *mut ZendStr,
69        type_: c_int,
70        cache_slot: *mut *mut c_void,
71        rv: *mut Zval,
72    ) -> *mut Zval {
73        #[inline(always)]
74        unsafe fn internal<T: RegisteredClass>(
75            object: *mut ZendObject,
76            member: *mut ZendStr,
77            type_: c_int,
78            cache_slot: *mut *mut c_void,
79            rv: *mut Zval,
80        ) -> PhpResult<*mut Zval> {
81            let obj = object
82                .as_mut()
83                .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
84                .ok_or("Invalid object pointer given")?;
85            let prop_name = member
86                .as_ref()
87                .ok_or("Invalid property name pointer given")?;
88            let self_ = &mut **obj;
89            let props = T::get_metadata().get_properties();
90            let prop = props.get(prop_name.as_str()?);
91
92            // retval needs to be treated as initialized, so we set the type to null
93            let rv_mut = rv.as_mut().ok_or("Invalid return zval given")?;
94            rv_mut.u1.type_info = ZvalTypeFlags::Null.bits();
95
96            Ok(match prop {
97                Some(prop) => {
98                    prop.get(self_, rv_mut)?;
99                    rv
100                }
101                None => zend_std_read_property(object, member, type_, cache_slot, rv),
102            })
103        }
104
105        match internal::<T>(object, member, type_, cache_slot, rv) {
106            Ok(rv) => rv,
107            Err(e) => {
108                let _ = e.throw();
109                (*rv).set_null();
110                rv
111            }
112        }
113    }
114
115    unsafe extern "C" fn write_property<T: RegisteredClass>(
116        object: *mut ZendObject,
117        member: *mut ZendStr,
118        value: *mut Zval,
119        cache_slot: *mut *mut c_void,
120    ) -> *mut Zval {
121        #[inline(always)]
122        unsafe fn internal<T: RegisteredClass>(
123            object: *mut ZendObject,
124            member: *mut ZendStr,
125            value: *mut Zval,
126            cache_slot: *mut *mut c_void,
127        ) -> PhpResult<*mut Zval> {
128            let obj = object
129                .as_mut()
130                .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
131                .ok_or("Invalid object pointer given")?;
132            let prop_name = member
133                .as_ref()
134                .ok_or("Invalid property name pointer given")?;
135            let self_ = &mut **obj;
136            let props = T::get_metadata().get_properties();
137            let prop = props.get(prop_name.as_str()?);
138            let value_mut = value.as_mut().ok_or("Invalid return zval given")?;
139
140            Ok(match prop {
141                Some(prop) => {
142                    prop.set(self_, value_mut)?;
143                    value
144                }
145                None => zend_std_write_property(object, member, value, cache_slot),
146            })
147        }
148
149        match internal::<T>(object, member, value, cache_slot) {
150            Ok(rv) => rv,
151            Err(e) => {
152                let _ = e.throw();
153                value
154            }
155        }
156    }
157
158    unsafe extern "C" fn get_properties<T: RegisteredClass>(
159        object: *mut ZendObject,
160    ) -> *mut ZendHashTable {
161        #[inline(always)]
162        unsafe fn internal<T: RegisteredClass>(
163            object: *mut ZendObject,
164            props: &mut ZendHashTable,
165        ) -> PhpResult {
166            let obj = object
167                .as_mut()
168                .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
169                .ok_or("Invalid object pointer given")?;
170            let self_ = &mut **obj;
171            let struct_props = T::get_metadata().get_properties();
172
173            for (name, val) in struct_props {
174                let mut zv = Zval::new();
175                if val.get(self_, &mut zv).is_err() {
176                    continue;
177                }
178                props.insert(name, zv).map_err(|e| {
179                    format!("Failed to insert value into properties hashtable: {e:?}")
180                })?;
181            }
182
183            Ok(())
184        }
185
186        let props = zend_std_get_properties(object)
187            .as_mut()
188            .or_else(|| Some(ZendHashTable::new().into_raw()))
189            .expect("Failed to get property hashtable");
190
191        if let Err(e) = internal::<T>(object, props) {
192            let _ = e.throw();
193        }
194
195        props
196    }
197
198    unsafe extern "C" fn has_property<T: RegisteredClass>(
199        object: *mut ZendObject,
200        member: *mut ZendStr,
201        has_set_exists: c_int,
202        cache_slot: *mut *mut c_void,
203    ) -> c_int {
204        #[inline(always)]
205        unsafe fn internal<T: RegisteredClass>(
206            object: *mut ZendObject,
207            member: *mut ZendStr,
208            has_set_exists: c_int,
209            cache_slot: *mut *mut c_void,
210        ) -> PhpResult<c_int> {
211            let obj = object
212                .as_mut()
213                .and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
214                .ok_or("Invalid object pointer given")?;
215            let prop_name = member
216                .as_ref()
217                .ok_or("Invalid property name pointer given")?;
218            let props = T::get_metadata().get_properties();
219            let prop = props.get(prop_name.as_str()?);
220            let self_ = &mut **obj;
221
222            match has_set_exists {
223                //
224                // * 0 (has) whether property exists and is not NULL
225                0 => {
226                    if let Some(val) = prop {
227                        let mut zv = Zval::new();
228                        val.get(self_, &mut zv)?;
229                        if !zv.is_null() {
230                            return Ok(1);
231                        }
232                    }
233                }
234                //
235                // * 1 (set) whether property exists and is true
236                1 => {
237                    if let Some(val) = prop {
238                        let mut zv = Zval::new();
239                        val.get(self_, &mut zv)?;
240
241                        cfg_if::cfg_if! {
242                            if #[cfg(php84)] {
243                                #[allow(clippy::unnecessary_mut_passed)]
244                                if zend_is_true(&mut zv) {
245                                    return Ok(1);
246                                }
247                            } else {
248                                #[allow(clippy::unnecessary_mut_passed)]
249                                if zend_is_true(&mut zv) == 1 {
250                                    return Ok(1);
251                                }
252                            }
253                        }
254                    }
255                }
256                //
257                // * 2 (exists) whether property exists
258                2 => {
259                    if prop.is_some() {
260                        return Ok(1);
261                    }
262                }
263                _ => return Err(
264                    "Invalid value given for `has_set_exists` in struct `has_property` function."
265                        .into(),
266                ),
267            };
268
269            Ok(zend_std_has_property(
270                object,
271                member,
272                has_set_exists,
273                cache_slot,
274            ))
275        }
276
277        match internal::<T>(object, member, has_set_exists, cache_slot) {
278            Ok(rv) => rv,
279            Err(e) => {
280                let _ = e.throw();
281                0
282            }
283        }
284    }
285}