ext_php_rs/zend/
ex.rs

1use crate::ffi::{zend_execute_data, ZEND_MM_ALIGNMENT, ZEND_MM_ALIGNMENT_MASK};
2
3use crate::{
4    args::ArgParser,
5    class::RegisteredClass,
6    types::{ZendClassObject, ZendObject, Zval},
7};
8
9use super::function::Function;
10
11/// Execute data passed when a function is called from PHP.
12///
13/// This generally contains things related to the call, including but not
14/// limited to:
15///
16/// * Arguments
17/// * `$this` object reference
18/// * Reference to return value
19/// * Previous execute data
20pub type ExecuteData = zend_execute_data;
21
22impl ExecuteData {
23    /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside
24    /// `self`.
25    ///
26    /// # Example
27    ///
28    /// ```no_run
29    /// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType};
30    ///
31    /// #[no_mangle]
32    /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
33    ///     let mut a = Arg::new("a", DataType::Long);
34    ///
35    ///     // The `parse_args!()` macro can be used for this.
36    ///     let parser = ex.parser()
37    ///         .arg(&mut a)
38    ///         .parse();
39    ///
40    ///     if parser.is_err() {
41    ///         return;
42    ///     }
43    ///
44    ///     dbg!(a);
45    /// }
46    /// ```
47    pub fn parser(&mut self) -> ArgParser<'_, '_> {
48        self.parser_object().0
49    }
50
51    /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside
52    /// `self`.
53    ///
54    /// A reference to `$this` is also returned in an [`Option`], which resolves
55    /// to [`None`] if this function is not called inside a method.
56    ///
57    /// # Example
58    ///
59    /// ```no_run
60    /// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType};
61    ///
62    /// #[no_mangle]
63    /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
64    ///     let mut a = Arg::new("a", DataType::Long);
65    ///
66    ///     let (parser, this) = ex.parser_object();
67    ///     let parser = parser
68    ///         .arg(&mut a)
69    ///         .parse();
70    ///
71    ///     if parser.is_err() {
72    ///         return;
73    ///     }
74    ///
75    ///     dbg!(a, this);
76    /// }
77    /// ```
78    pub fn parser_object(&mut self) -> (ArgParser<'_, '_>, Option<&mut ZendObject>) {
79        // SAFETY: All fields of the `u2` union are the same type.
80        let n_args = unsafe { self.This.u2.num_args };
81        let mut args = vec![];
82
83        for i in 0..n_args {
84            // SAFETY: Function definition ensures arg lifetime doesn't exceed execution
85            // data lifetime.
86            let arg = unsafe { self.zend_call_arg(i as usize) };
87            args.push(arg);
88        }
89
90        let obj = self.This.object_mut();
91
92        (ArgParser::new(args), obj)
93    }
94
95    /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside
96    /// `self`.
97    ///
98    /// A reference to `$this` is also returned in an [`Option`], which resolves
99    /// to [`None`] if this function is not called inside a method.
100    ///
101    /// This function differs from [`parse_object`] in the fact that it returns
102    /// a reference to a [`ZendClassObject`], which is an object that
103    /// contains an arbitrary Rust type at the start of the object. The
104    /// object will also resolve to [`None`] if the function is called
105    /// inside a method that does not belong to an object with type `T`.
106    ///
107    /// # Example
108    ///
109    /// ```no_run
110    /// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType, prelude::*};
111    ///
112    /// #[php_class]
113    /// #[derive(Debug)]
114    /// struct Example;
115    ///
116    /// #[no_mangle]
117    /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
118    ///     let mut a = Arg::new("a", DataType::Long);
119    ///
120    ///     let (parser, this) = ex.parser_method::<Example>();
121    ///     let parser = parser
122    ///         .arg(&mut a)
123    ///         .parse();
124    ///
125    ///     if parser.is_err() {
126    ///         return;
127    ///     }
128    ///
129    ///     dbg!(a, this);
130    /// }
131    ///
132    /// #[php_module]
133    /// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
134    ///     module
135    /// }
136    /// ```
137    ///
138    /// [`parse_object`]: #method.parse_object
139    pub fn parser_method<T: RegisteredClass>(
140        &mut self,
141    ) -> (ArgParser<'_, '_>, Option<&mut ZendClassObject<T>>) {
142        let (parser, obj) = self.parser_object();
143        (
144            parser,
145            obj.and_then(|obj| ZendClassObject::from_zend_obj_mut(obj)),
146        )
147    }
148
149    /// Attempts to retrieve a reference to the underlying class object of the
150    /// Zend object.
151    ///
152    /// Returns a [`ZendClassObject`] if the execution data contained a valid
153    /// object of type `T`, otherwise returns [`None`].
154    ///
155    /// # Example
156    ///
157    /// ```no_run
158    /// use ext_php_rs::{types::Zval, zend::ExecuteData, prelude::*};
159    ///
160    /// #[php_class]
161    /// #[derive(Debug)]
162    /// struct Example;
163    ///
164    /// #[no_mangle]
165    /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
166    ///     let this = ex.get_object::<Example>();
167    ///     dbg!(this);
168    /// }
169    ///
170    /// #[php_module]
171    /// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
172    ///     module
173    /// }
174    /// ```
175    pub fn get_object<T: RegisteredClass>(&mut self) -> Option<&mut ZendClassObject<T>> {
176        ZendClassObject::from_zend_obj_mut(self.get_self()?)
177    }
178
179    /// Attempts to retrieve the 'this' object, which can be used in class
180    /// methods to retrieve the underlying Zend object.
181    ///
182    /// # Example
183    ///
184    /// ```no_run
185    /// use ext_php_rs::{types::Zval, zend::ExecuteData};
186    ///
187    /// #[no_mangle]
188    /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
189    ///     let this = ex.get_self();
190    ///     dbg!(this);
191    /// }
192    /// ```
193    pub fn get_self(&mut self) -> Option<&mut ZendObject> {
194        // TODO(david): This should be a `&mut self` function but we need to fix arg
195        // parser first.
196        self.This.object_mut()
197    }
198
199    /// Attempt to retrieve the function that is being called.
200    pub fn function(&self) -> Option<&Function> {
201        unsafe { self.func.as_ref() }
202    }
203
204    /// Attempt to retrieve the previous execute data on the call stack.
205    pub fn previous(&self) -> Option<&Self> {
206        unsafe { self.prev_execute_data.as_ref() }
207    }
208
209    /// Translation of macro `ZEND_CALL_ARG(call, n)`
210    /// zend_compile.h:578
211    ///
212    /// The resultant zval reference has a lifetime equal to the lifetime of
213    /// `self`. This isn't specified because when you attempt to get a
214    /// reference to args and the `$this` object, Rust doesn't let you.
215    /// Since this is a private method it's up to the caller to ensure the
216    /// lifetime isn't exceeded.
217    #[doc(hidden)]
218    unsafe fn zend_call_arg<'a>(&self, n: usize) -> Option<&'a mut Zval> {
219        let ptr = self.zend_call_var_num(n as isize);
220        ptr.as_mut()
221    }
222
223    /// Translation of macro `ZEND_CALL_VAR_NUM(call, n)`
224    /// zend_compile.h: 575
225    #[doc(hidden)]
226    unsafe fn zend_call_var_num(&self, n: isize) -> *mut Zval {
227        let ptr = self as *const Self as *mut Zval;
228        ptr.offset(Self::zend_call_frame_slot() + n)
229    }
230
231    /// Translation of macro `ZEND_CALL_FRAME_SLOT`
232    /// zend_compile:573
233    #[doc(hidden)]
234    fn zend_call_frame_slot() -> isize {
235        (Self::zend_mm_aligned_size::<Self>() + Self::zend_mm_aligned_size::<Zval>() - 1)
236            / Self::zend_mm_aligned_size::<Zval>()
237    }
238
239    /// Translation of macro `ZEND_MM_ALIGNED_SIZE(size)`
240    /// zend_alloc.h:41
241    #[doc(hidden)]
242    fn zend_mm_aligned_size<T>() -> isize {
243        let size = std::mem::size_of::<T>();
244        ((size as isize) + ZEND_MM_ALIGNMENT as isize - 1) & ZEND_MM_ALIGNMENT_MASK as isize
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::ExecuteData;
251
252    #[test]
253    fn test_zend_call_frame_slot() {
254        // PHP 8.0.2 (cli) (built: Feb 21 2021 11:51:33) ( NTS )
255        // Copyright (c) The PHP Group
256        // Zend Engine v4.0.2, Copyright (c) Zend Technologies
257        assert_eq!(ExecuteData::zend_call_frame_slot(), 5);
258    }
259}