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    #[must_use]
201    pub fn function(&self) -> Option<&Function> {
202        unsafe { self.func.as_ref() }
203    }
204
205    /// Attempt to retrieve the previous execute data on the call stack.
206    #[must_use]
207    pub fn previous(&self) -> Option<&Self> {
208        unsafe { self.prev_execute_data.as_ref() }
209    }
210
211    /// Translation of macro `ZEND_CALL_ARG(call, n)`
212    /// zend_compile.h:578
213    ///
214    /// The resultant [`Zval`] reference has a lifetime equal to the lifetime of
215    /// `self`. This isn't specified because when you attempt to get a
216    /// reference to args and the `$this` object, Rust doesn't let you.
217    /// Since this is a private method it's up to the caller to ensure the
218    /// lifetime isn't exceeded.
219    #[doc(hidden)]
220    unsafe fn zend_call_arg<'a>(&self, n: usize) -> Option<&'a mut Zval> {
221        let n = isize::try_from(n).expect("n is too large");
222        let ptr = self.zend_call_var_num(n);
223        ptr.as_mut()
224    }
225
226    /// Translation of macro `ZEND_CALL_VAR_NUM(call, n)`
227    /// zend_compile.h: 575
228    #[doc(hidden)]
229    unsafe fn zend_call_var_num(&self, n: isize) -> *mut Zval {
230        let ptr = std::ptr::from_ref(self) as *mut Zval;
231        ptr.offset(Self::zend_call_frame_slot() + n)
232    }
233
234    /// Translation of macro `ZEND_CALL_FRAME_SLOT`
235    /// zend_compile:573
236    #[doc(hidden)]
237    fn zend_call_frame_slot() -> isize {
238        (Self::zend_mm_aligned_size::<Self>() + Self::zend_mm_aligned_size::<Zval>() - 1)
239            / Self::zend_mm_aligned_size::<Zval>()
240    }
241
242    /// Translation of macro `ZEND_MM_ALIGNED_SIZE(size)`
243    /// zend_alloc.h:41
244    #[doc(hidden)]
245    fn zend_mm_aligned_size<T>() -> isize {
246        let size = isize::try_from(std::mem::size_of::<T>()).expect("size of T is too large");
247        (size + ZEND_MM_ALIGNMENT - 1) & ZEND_MM_ALIGNMENT_MASK
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::ExecuteData;
254
255    #[test]
256    fn test_zend_call_frame_slot() {
257        // PHP 8.0.2 (cli) (built: Feb 21 2021 11:51:33) ( NTS )
258        // Copyright (c) The PHP Group
259        // Zend Engine v4.0.2, Copyright (c) Zend Technologies
260        assert_eq!(ExecuteData::zend_call_frame_slot(), 5);
261    }
262}