Skip to main content

phper/
objects.rs

1// Copyright (c) 2022 PHPER Framework Team
2// PHPER is licensed under Mulan PSL v2.
3// You can use this software according to the terms and conditions of the Mulan
4// PSL v2. You may obtain a copy of Mulan PSL v2 at:
5//          http://license.coscl.org.cn/MulanPSL2
6// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
7// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
8// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
9// See the Mulan PSL v2 for more details.
10
11//! Apis relate to [zend_object].
12
13use crate::{
14    alloc::EBox,
15    classes::ClassEntry,
16    functions::{ZFunc, call_internal, call_raw_common},
17    sys::*,
18    values::ZVal,
19};
20use phper_alloc::{RefClone, ToRefOwned};
21use std::{
22    any::Any,
23    ffi::c_void,
24    fmt::{self, Debug},
25    marker::PhantomData,
26    mem::{ManuallyDrop, replace, size_of},
27    ops::{Deref, DerefMut},
28    ptr::null_mut,
29};
30
31/// Wrapper of [zend_object].
32#[repr(transparent)]
33pub struct ZObj {
34    inner: zend_object,
35    _p: PhantomData<*mut ()>,
36}
37
38impl ZObj {
39    /// Wraps a raw pointer.
40    ///
41    /// # Safety
42    ///
43    /// Create from raw pointer.
44    ///
45    /// # Panics
46    ///
47    /// Panics if pointer is null.
48    #[inline]
49    pub unsafe fn from_ptr<'a>(ptr: *const zend_object) -> &'a Self {
50        unsafe { (ptr as *const Self).as_ref().expect("ptr should't be null") }
51    }
52
53    /// Wraps a raw pointer, return None if pointer is null.
54    ///
55    /// # Safety
56    ///
57    /// Create from raw pointer.
58    #[inline]
59    pub unsafe fn try_from_ptr<'a>(ptr: *const zend_object) -> Option<&'a Self> {
60        unsafe { (ptr as *const Self).as_ref() }
61    }
62
63    /// Wraps a raw pointer.
64    ///
65    /// # Safety
66    ///
67    /// Create from raw pointer.
68    ///
69    /// # Panics
70    ///
71    /// Panics if pointer is null.
72    #[inline]
73    pub unsafe fn from_mut_ptr<'a>(ptr: *mut zend_object) -> &'a mut Self {
74        unsafe { (ptr as *mut Self).as_mut().expect("ptr should't be null") }
75    }
76
77    /// Wraps a raw pointer, return None if pointer is null.
78    ///
79    /// # Safety
80    ///
81    /// Create from raw pointer.
82    #[inline]
83    pub unsafe fn try_from_mut_ptr<'a>(ptr: *mut zend_object) -> Option<&'a mut Self> {
84        unsafe { (ptr as *mut Self).as_mut() }
85    }
86
87    /// Returns a raw pointer wrapped.
88    pub const fn as_ptr(&self) -> *const zend_object {
89        &self.inner
90    }
91
92    /// Returns a raw pointer wrapped.
93    #[inline]
94    pub fn as_mut_ptr(&mut self) -> *mut zend_object {
95        &mut self.inner
96    }
97
98    /// Upgrade to state obj.
99    ///
100    /// # Safety
101    ///
102    /// Should only call this method for the class of object defined by the
103    /// extension created by `phper`, otherwise, memory problems will caused.
104    pub unsafe fn as_state_obj<T>(&self) -> &StateObj<T> {
105        unsafe { StateObj::from_object_ptr(self.as_ptr()) }
106    }
107
108    /// Upgrade to mutable state obj.
109    ///
110    /// # Safety
111    ///
112    /// Should only call this method for the class of object defined by the
113    /// extension created by `phper`, otherwise, memory problems will caused.
114    pub unsafe fn as_mut_state_obj<T>(&mut self) -> &mut StateObj<T> {
115        unsafe { StateObj::from_mut_object_ptr(self.as_mut_ptr()) }
116    }
117
118    /// Get the inner handle of object.
119    #[inline]
120    pub fn handle(&self) -> u32 {
121        self.inner.handle
122    }
123
124    /// Get the class reference of object.
125    pub fn get_class(&self) -> &ClassEntry {
126        unsafe { ClassEntry::from_ptr(self.inner.ce) }
127    }
128
129    /// Get the mutable class reference of object.
130    pub fn get_mut_class(&mut self) -> &mut ClassEntry {
131        unsafe { ClassEntry::from_mut_ptr(self.inner.ce) }
132    }
133
134    /// Get the property by name of object.
135    pub fn get_property(&self, name: impl AsRef<str>) -> &ZVal {
136        let object = self.as_ptr() as *mut _;
137        let prop = Self::inner_get_property(self.inner.ce, object, name);
138        unsafe { ZVal::from_ptr(prop) }
139    }
140
141    /// Get the mutable property by name of object.
142    pub fn get_mut_property(&mut self, name: impl AsRef<str>) -> &mut ZVal {
143        let object = self.as_mut_ptr();
144        let prop = Self::inner_get_property(self.inner.ce, object, name);
145        unsafe { ZVal::from_mut_ptr(prop) }
146    }
147
148    #[allow(clippy::useless_conversion)]
149    fn inner_get_property(
150        scope: *mut zend_class_entry, object: *mut zend_object, name: impl AsRef<str>,
151    ) -> *mut zval {
152        let name = name.as_ref();
153
154        unsafe {
155            #[cfg(phper_major_version = "8")]
156            {
157                zend_read_property(
158                    scope,
159                    object,
160                    name.as_ptr().cast(),
161                    name.len().try_into().unwrap(),
162                    true.into(),
163                    null_mut(),
164                )
165            }
166            #[cfg(phper_major_version = "7")]
167            {
168                let mut zv = std::mem::zeroed::<zval>();
169                phper_zval_obj(&mut zv, object);
170                zend_read_property(
171                    scope,
172                    &mut zv,
173                    name.as_ptr().cast(),
174                    name.len().try_into().unwrap(),
175                    true.into(),
176                    null_mut(),
177                )
178            }
179        }
180    }
181
182    /// Set the property by name of object.
183    #[allow(clippy::useless_conversion)]
184    pub fn set_property(&mut self, name: impl AsRef<str>, val: impl Into<ZVal>) {
185        let name = name.as_ref();
186        let mut val = val.into();
187        unsafe {
188            #[cfg(phper_major_version = "8")]
189            {
190                zend_update_property(
191                    self.inner.ce,
192                    &mut self.inner,
193                    name.as_ptr().cast(),
194                    name.len().try_into().unwrap(),
195                    val.as_mut_ptr(),
196                )
197            }
198            #[cfg(phper_major_version = "7")]
199            {
200                let mut zv = std::mem::zeroed::<zval>();
201                phper_zval_obj(&mut zv, self.as_mut_ptr());
202                zend_update_property(
203                    self.inner.ce,
204                    &mut zv,
205                    name.as_ptr().cast(),
206                    name.len().try_into().unwrap(),
207                    val.as_mut_ptr(),
208                )
209            }
210        }
211    }
212
213    /// Call the object method by name.
214    ///
215    /// # Examples
216    ///
217    /// ```no_run
218    /// use phper::{alloc::EBox, classes::ClassEntry, values::ZVal};
219    ///
220    /// fn example() -> phper::Result<ZVal> {
221    ///     let mut memcached = ClassEntry::from_globals("Memcached")?.new_object(&mut [])?;
222    ///     memcached.call(
223    ///         "addServer",
224    ///         &mut [ZVal::from("127.0.0.1"), ZVal::from(11211)],
225    ///     )?;
226    ///     let r = memcached.call("get", &mut [ZVal::from("hello")])?;
227    ///     Ok(r)
228    /// }
229    /// ```
230    pub fn call(
231        &mut self, method_name: &str, arguments: impl AsMut<[ZVal]>,
232    ) -> crate::Result<ZVal> {
233        let mut method = method_name.into();
234        call_internal(&mut method, Some(self), arguments)
235    }
236
237    pub(crate) fn call_construct(&mut self, arguments: impl AsMut<[ZVal]>) -> crate::Result<()> {
238        unsafe {
239            let Some(get_constructor) = (*self.inner.handlers).get_constructor else {
240                return Ok(());
241            };
242
243            // The `get_constructor` is possible to throw PHP Error, so call it inside
244            // `call_raw_common`.
245            let mut val = call_raw_common(|val| {
246                let f = get_constructor(self.as_mut_ptr());
247                if !f.is_null() {
248                    phper_zval_func(val.as_mut_ptr(), f);
249                }
250            })?;
251
252            if val.get_type_info().is_null() {
253                return Ok(());
254            }
255
256            let f = phper_z_func_p(val.as_mut_ptr());
257            let zend_fn = ZFunc::from_mut_ptr(f);
258            zend_fn.call(Some(self), arguments)?;
259
260            Ok(())
261        }
262    }
263
264    pub(crate) unsafe fn gc_refcount(&self) -> u32 {
265        unsafe { phper_zend_object_gc_refcount(self.as_ptr()) }
266    }
267}
268
269impl Drop for ZObj {
270    fn drop(&mut self) {
271        unsafe {
272            phper_zend_object_release(self.as_mut_ptr());
273        }
274    }
275}
276
277impl ToRefOwned for ZObj {
278    type Owned = ZObject;
279
280    fn to_ref_owned(&mut self) -> Self::Owned {
281        let mut val = ManuallyDrop::new(ZVal::default());
282        unsafe {
283            phper_zval_obj(val.as_mut_ptr(), self.as_mut_ptr());
284            phper_z_addref_p(val.as_mut_ptr());
285            ZObject::from_raw_cast(val.as_mut_z_obj().unwrap().as_mut_ptr())
286        }
287    }
288}
289
290impl Debug for ZObj {
291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292        common_fmt(self, f, "ZObj")
293    }
294}
295
296/// An owned PHP object value.
297///
298/// `ZObject` represents an owned PHP object allocated in the Zend Engine
299/// memory. It provides safe access to PHP object operations and automatically
300/// manages memory cleanup.
301pub type ZObject = EBox<ZObj>;
302
303impl ZObject {
304    /// Another way to new object like [crate::classes::ClassEntry::new_object].
305    pub fn new(class_entry: &ClassEntry, arguments: impl AsMut<[ZVal]>) -> crate::Result<Self> {
306        class_entry.new_object(arguments)
307    }
308
309    /// New object, like `new`, but get class by [`ClassEntry::from_globals`].
310    pub fn new_by_class_name(
311        class_name: impl AsRef<str>, arguments: &mut [ZVal],
312    ) -> crate::Result<Self> {
313        let class_entry = ClassEntry::from_globals(class_name)?;
314        Self::new(class_entry, arguments)
315    }
316
317    /// New object with class `stdClass`.
318    pub fn new_by_std_class() -> Self {
319        Self::new_by_class_name("stdclass", &mut []).unwrap()
320    }
321}
322
323impl RefClone for ZObject {
324    #[inline]
325    fn ref_clone(&mut self) -> Self {
326        self.to_ref_owned()
327    }
328}
329
330pub(crate) type AnyState = *mut dyn Any;
331
332/// The object owned state, usually as the parameter of method handler.
333#[repr(C)]
334pub struct StateObj<T> {
335    any_state: AnyState,
336    object: ZObj,
337    _p: PhantomData<T>,
338}
339
340impl<T> StateObj<T> {
341    /// The `zend_object_alloc` often allocate more memory to hold the state
342    /// (usually is a pointer), and place it before `zend_object`.
343    pub(crate) const fn offset() -> usize {
344        size_of::<AnyState>()
345    }
346
347    #[inline]
348    pub(crate) unsafe fn from_mut_ptr<'a>(ptr: *mut c_void) -> &'a mut Self {
349        unsafe { (ptr as *mut Self).as_mut().expect("ptr should't be null") }
350    }
351
352    pub(crate) unsafe fn from_object_ptr<'a>(ptr: *const zend_object) -> &'a Self {
353        unsafe {
354            ((ptr as usize - Self::offset()) as *const Self)
355                .as_ref()
356                .unwrap()
357        }
358    }
359
360    pub(crate) unsafe fn from_mut_object_ptr<'a>(ptr: *mut zend_object) -> &'a mut Self {
361        unsafe {
362            ((ptr as usize - Self::offset()) as *mut Self)
363                .as_mut()
364                .unwrap()
365        }
366    }
367
368    pub(crate) unsafe fn drop_state(&mut self) {
369        unsafe {
370            drop(Box::from_raw(self.any_state));
371        }
372    }
373
374    #[inline]
375    pub(crate) fn as_mut_any_state(&mut self) -> &mut AnyState {
376        &mut self.any_state
377    }
378
379    /// Gets object.
380    #[inline]
381    pub fn as_object(&self) -> &ZObj {
382        &self.object
383    }
384
385    /// Gets mutable object.
386    #[inline]
387    pub fn as_mut_object(&mut self) -> &mut ZObj {
388        &mut self.object
389    }
390}
391
392impl<T: 'static> StateObj<T> {
393    /// Gets inner state.
394    pub fn as_state(&self) -> &T {
395        unsafe {
396            let any_state = self.any_state.as_ref().unwrap();
397            any_state.downcast_ref().unwrap()
398        }
399    }
400
401    /// Gets inner mutable state.
402    pub fn as_mut_state(&mut self) -> &mut T {
403        unsafe {
404            let any_state = self.any_state.as_mut().unwrap();
405            any_state.downcast_mut().unwrap()
406        }
407    }
408}
409
410impl<T> Deref for StateObj<T> {
411    type Target = ZObj;
412
413    fn deref(&self) -> &Self::Target {
414        &self.object
415    }
416}
417
418impl<T> DerefMut for StateObj<T> {
419    fn deref_mut(&mut self) -> &mut Self::Target {
420        &mut self.object
421    }
422}
423
424impl<T> Debug for StateObj<T> {
425    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426        common_fmt(self, f, "StateObj")
427    }
428}
429
430/// An owned PHP object with associated Rust state.
431///
432/// `StateObject<T>` represents an owned PHP object that contains additional
433/// Rust state of type `T`. This allows embedding custom Rust data structures
434/// within PHP objects while maintaining proper memory management and cleanup.
435pub type StateObject<T> = EBox<StateObj<T>>;
436
437impl<T> StateObject<T> {
438    #[inline]
439    pub(crate) fn from_raw_object(object: *mut zend_object) -> Self {
440        unsafe { Self::from_raw(StateObj::from_mut_object_ptr(object)) }
441    }
442
443    #[inline]
444    pub(crate) fn into_raw_object(self) -> *mut zend_object {
445        ManuallyDrop::new(self).as_mut_ptr()
446    }
447
448    /// Converts into [ZObject].
449    pub fn into_z_object(self) -> ZObject {
450        unsafe { ZObject::from_raw_cast(self.into_raw_object()) }
451    }
452}
453
454impl<T: 'static> StateObject<T> {
455    /// Converts into state.
456    ///
457    /// Because the [zend_object] is refcounted type,
458    /// therefore, you can only obtain state ownership when the refcount of the
459    /// [zend_object] is `1`, otherwise, it will return
460    /// `None`.
461    pub fn into_state(mut self) -> Option<T> {
462        unsafe {
463            if self.gc_refcount() != 1 {
464                return None;
465            }
466            let null: AnyState = Box::into_raw(Box::new(()));
467            let ptr = replace(self.as_mut_any_state(), null);
468            Some(*Box::from_raw(ptr).downcast().unwrap())
469        }
470    }
471}
472
473fn common_fmt(this: &ZObj, f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
474    let mut d = f.debug_struct(name);
475    match this.get_class().get_name().to_c_str() {
476        Ok(class_name) => {
477            d.field("class", &class_name);
478        }
479        Err(e) => {
480            d.field("class", &e);
481        }
482    }
483    d.field("handle", &this.handle());
484    d.finish()
485}