ext_php_rs/types/
class_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::{
5    fmt::Debug,
6    mem,
7    ops::{Deref, DerefMut},
8    os::raw::c_char,
9    ptr::{self, NonNull},
10};
11
12use crate::{
13    boxed::{ZBox, ZBoxable},
14    class::RegisteredClass,
15    convert::{FromZendObject, FromZendObjectMut, FromZval, FromZvalMut, IntoZval},
16    error::{Error, Result},
17    ffi::{
18        ext_php_rs_zend_object_alloc, ext_php_rs_zend_object_release, object_properties_init,
19        zend_object, zend_object_std_init, zend_objects_clone_members,
20    },
21    flags::DataType,
22    types::{ZendObject, Zval},
23    zend::ClassEntry,
24};
25
26/// Representation of a Zend class object in memory.
27#[repr(C)]
28#[derive(Debug)]
29pub struct ZendClassObject<T> {
30    /// The object stored inside the class object.
31    pub obj: Option<T>,
32    /// The standard zend object.
33    pub std: ZendObject,
34}
35
36impl<T: RegisteredClass> ZendClassObject<T> {
37    /// Creates a new [`ZendClassObject`] of type `T`, where `T` is a
38    /// [`RegisteredClass`] in PHP, storing the given value `val` inside the
39    /// object.
40    ///
41    /// # Parameters
42    ///
43    /// * `val` - The value to store inside the object.
44    ///
45    /// # Panics
46    ///
47    /// Panics if memory was unable to be allocated for the new object.
48    pub fn new(val: T) -> ZBox<Self> {
49        // SAFETY: We are providing a value to initialize the object with.
50        unsafe { Self::internal_new(Some(val), None) }
51    }
52
53    /// Creates a new [`ZendClassObject`] of type `T`, with an uninitialized
54    /// internal object.
55    ///
56    /// # Safety
57    ///
58    /// As the object is uninitialized, the caller must ensure the following
59    /// until the internal object is initialized:
60    ///
61    /// * The object is never dereferenced to `T`.
62    /// * The [`Clone`] implementation is never called.
63    /// * The [`Debug`] implementation is never called.
64    ///
65    /// If any of these conditions are not met while not initialized, the
66    /// corresponding function will panic. Converting the object into its
67    /// inner pointer with the [`into_raw`] function is valid, however.
68    ///
69    /// [`into_raw`]: #method.into_raw
70    ///
71    /// # Panics
72    ///
73    /// Panics if memory was unable to be allocated for the new object.
74    pub unsafe fn new_uninit(ce: Option<&'static ClassEntry>) -> ZBox<Self> {
75        Self::internal_new(None, ce)
76    }
77
78    /// Creates a new [`ZendObject`] of type `T`, storing the given (and
79    /// potentially uninitialized) `val` inside the object.
80    ///
81    /// # Parameters
82    ///
83    /// * `val` - Value to store inside the object. See safety section.
84    /// * `init` - Whether the given `val` was initialized.
85    ///
86    /// # Safety
87    ///
88    /// Providing an initialized variant of [`MaybeUninit<T>`] is safe.
89    ///
90    /// Providing an uninitialized variant of [`MaybeUninit<T>`] is unsafe. As
91    /// the object is uninitialized, the caller must ensure the following
92    /// until the internal object is initialized:
93    ///
94    /// * The object is never dereferenced to `T`.
95    /// * The [`Clone`] implementation is never called.
96    /// * The [`Debug`] implementation is never called.
97    ///
98    /// If any of these conditions are not met while not initialized, the
99    /// corresponding function will panic. Converting the object into its
100    /// inner with the [`into_raw`] function is valid, however. You can
101    /// initialize the object with the [`initialize`] function.
102    ///
103    /// [`into_raw`]: #method.into_raw
104    /// [`initialize`]: #method.initialize
105    ///
106    /// # Panics
107    ///
108    /// Panics if memory was unable to be allocated for the new object.
109    unsafe fn internal_new(val: Option<T>, ce: Option<&'static ClassEntry>) -> ZBox<Self> {
110        let size = mem::size_of::<ZendClassObject<T>>();
111        let meta = T::get_metadata();
112        let ce = ptr::from_ref(ce.unwrap_or_else(|| meta.ce())).cast_mut();
113        let obj = ext_php_rs_zend_object_alloc(size as _, ce).cast::<ZendClassObject<T>>();
114        let obj = obj
115            .as_mut()
116            .expect("Failed to allocate for new Zend object");
117
118        zend_object_std_init(&raw mut obj.std, ce);
119        object_properties_init(&raw mut obj.std, ce);
120
121        // SAFETY: `obj` is non-null and well aligned as it is a reference.
122        // As the data in `obj.obj` is uninitialized, we don't want to drop
123        // the data, but directly override it.
124        ptr::write(&raw mut obj.obj, val);
125
126        obj.std.handlers = meta.handlers();
127        ZBox::from_raw(obj)
128    }
129
130    /// Initializes the class object with the value `val`.
131    ///
132    /// # Parameters
133    ///
134    /// * `val` - The value to initialize the object with.
135    ///
136    /// # Returns
137    ///
138    /// Returns the old value in an [`Option`] if the object had already been
139    /// initialized, [`None`] otherwise.
140    pub fn initialize(&mut self, val: T) -> Option<T> {
141        self.obj.replace(val)
142    }
143
144    /// Returns a mutable reference to the [`ZendClassObject`] of a given zend
145    /// object `obj`. Returns [`None`] if the given object is not of the
146    /// type `T`.
147    ///
148    /// # Parameters
149    ///
150    /// * `obj` - The zend object to get the [`ZendClassObject`] for.
151    ///
152    /// # Panics
153    ///
154    /// * If the std offset over/underflows `isize`.
155    #[must_use]
156    pub fn from_zend_obj(std: &zend_object) -> Option<&Self> {
157        Some(Self::internal_from_zend_obj(std)?)
158    }
159
160    /// Returns a mutable reference to the [`ZendClassObject`] of a given zend
161    /// object `obj`. Returns [`None`] if the given object is not of the
162    /// type `T`.
163    ///
164    /// # Parameters
165    ///
166    /// * `obj` - The zend object to get the [`ZendClassObject`] for.
167    ///
168    /// # Panics
169    ///
170    /// * If the std offset over/underflows `isize`.
171    #[allow(clippy::needless_pass_by_ref_mut)]
172    pub fn from_zend_obj_mut(std: &mut zend_object) -> Option<&mut Self> {
173        Self::internal_from_zend_obj(std)
174    }
175
176    // TODO: Verify if this is safe to use, as it allows mutating the
177    // hashtable while only having a reference to it. #461
178    #[allow(clippy::mut_from_ref)]
179    fn internal_from_zend_obj(std: &zend_object) -> Option<&mut Self> {
180        let std = ptr::from_ref(std).cast::<c_char>();
181        let ptr = unsafe {
182            let offset = isize::try_from(Self::std_offset()).expect("Offset overflow");
183            let ptr = std.offset(0 - offset).cast::<Self>();
184            ptr.cast_mut().as_mut()?
185        };
186
187        if ptr.std.instance_of(T::get_metadata().ce()) {
188            Some(ptr)
189        } else {
190            None
191        }
192    }
193
194    /// Returns a mutable reference to the underlying Zend object.
195    pub fn get_mut_zend_obj(&mut self) -> &mut zend_object {
196        &mut self.std
197    }
198
199    /// Returns the offset of the `std` property in the class object.
200    pub(crate) fn std_offset() -> usize {
201        unsafe {
202            let null = NonNull::<Self>::dangling();
203            let base = null.as_ref() as *const Self;
204            let std = &raw const null.as_ref().std;
205
206            (std as usize) - (base as usize)
207        }
208    }
209}
210
211impl<'a, T: RegisteredClass> FromZval<'a> for &'a ZendClassObject<T> {
212    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
213
214    fn from_zval(zval: &'a Zval) -> Option<Self> {
215        Self::from_zend_object(zval.object()?).ok()
216    }
217}
218
219impl<'a, T: RegisteredClass> FromZendObject<'a> for &'a ZendClassObject<T> {
220    fn from_zend_object(obj: &'a ZendObject) -> Result<Self> {
221        // TODO(david): replace with better error
222        ZendClassObject::from_zend_obj(obj).ok_or(Error::InvalidScope)
223    }
224}
225
226impl<'a, T: RegisteredClass> FromZvalMut<'a> for &'a mut ZendClassObject<T> {
227    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
228
229    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
230        Self::from_zend_object_mut(zval.object_mut()?).ok()
231    }
232}
233
234impl<'a, T: RegisteredClass> FromZendObjectMut<'a> for &'a mut ZendClassObject<T> {
235    fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result<Self> {
236        ZendClassObject::from_zend_obj_mut(obj).ok_or(Error::InvalidScope)
237    }
238}
239
240unsafe impl<T: RegisteredClass> ZBoxable for ZendClassObject<T> {
241    fn free(&mut self) {
242        // SAFETY: All constructors guarantee that `self` contains a valid pointer.
243        // Further, all constructors guarantee that the `std` field of
244        // `ZendClassObject` will be initialized.
245        unsafe { ext_php_rs_zend_object_release(&raw mut self.std) }
246    }
247}
248
249impl<T> Deref for ZendClassObject<T> {
250    type Target = T;
251
252    fn deref(&self) -> &Self::Target {
253        self.obj
254            .as_ref()
255            .expect("Attempted to access uninitialized class object")
256    }
257}
258
259impl<T> DerefMut for ZendClassObject<T> {
260    fn deref_mut(&mut self) -> &mut Self::Target {
261        self.obj
262            .as_mut()
263            .expect("Attempted to access uninitialized class object")
264    }
265}
266
267impl<T: RegisteredClass + Default> Default for ZBox<ZendClassObject<T>> {
268    #[inline]
269    fn default() -> Self {
270        ZendClassObject::new(T::default())
271    }
272}
273
274impl<T: RegisteredClass + Clone> Clone for ZBox<ZendClassObject<T>> {
275    fn clone(&self) -> Self {
276        // SAFETY: All constructors of `NewClassObject` guarantee that it will contain a
277        // valid pointer. The constructor also guarantees that the internal
278        // `ZendClassObject` pointer will contain a valid, initialized `obj`,
279        // therefore we can dereference both safely.
280        unsafe {
281            let mut new = ZendClassObject::new((***self).clone());
282            zend_objects_clone_members(&raw mut new.std, (&raw const self.std).cast_mut());
283            new
284        }
285    }
286}
287
288impl<T: RegisteredClass> IntoZval for ZBox<ZendClassObject<T>> {
289    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
290    const NULLABLE: bool = false;
291
292    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
293        let obj = self.into_raw();
294        zv.set_object(&mut obj.std);
295        Ok(())
296    }
297}
298
299impl<T: RegisteredClass> IntoZval for &mut ZendClassObject<T> {
300    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
301    const NULLABLE: bool = false;
302
303    #[inline]
304    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
305        zv.set_object(&mut self.std);
306        Ok(())
307    }
308}