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}