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}