use crate::ffi::{zend_execute_data, ZEND_MM_ALIGNMENT, ZEND_MM_ALIGNMENT_MASK};
use crate::{
args::ArgParser,
class::RegisteredClass,
types::{ZendClassObject, ZendObject, Zval},
};
/// Execute data passed when a function is called from PHP.
///
/// This generally contains things related to the call, including but not
/// limited to:
///
/// * Arguments
/// * `$this` object reference
/// * Reference to return value
/// * Previous execute data
pub type ExecuteData = zend_execute_data;
impl ExecuteData {
/// Returns an [`ArgParser`] pre-loaded with the arguments contained inside
/// `self`.
///
/// # Example
///
/// ```no_run
/// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType};
///
/// #[no_mangle]
/// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
/// let mut a = Arg::new("a", DataType::Long);
///
/// // The `parse_args!()` macro can be used for this.
/// let parser = ex.parser()
/// .arg(&mut a)
/// .parse();
///
/// if parser.is_err() {
/// return;
/// }
///
/// dbg!(a);
/// }
/// ```
pub fn parser(&mut self) -> ArgParser<'_, '_> {
self.parser_object().0
}
/// Returns an [`ArgParser`] pre-loaded with the arguments contained inside
/// `self`.
///
/// A reference to `$this` is also returned in an [`Option`], which resolves
/// to [`None`] if this function is not called inside a method.
///
/// # Example
///
/// ```no_run
/// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType};
///
/// #[no_mangle]
/// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
/// let mut a = Arg::new("a", DataType::Long);
///
/// let (parser, this) = ex.parser_object();
/// let parser = parser
/// .arg(&mut a)
/// .parse();
///
/// if parser.is_err() {
/// return;
/// }
///
/// dbg!(a, this);
/// }
/// ```
pub fn parser_object(&mut self) -> (ArgParser<'_, '_>, Option<&mut ZendObject>) {
// SAFETY: All fields of the `u2` union are the same type.
let n_args = unsafe { self.This.u2.num_args };
let mut args = vec![];
for i in 0..n_args {
// SAFETY: Function definition ensures arg lifetime doesn't exceed execution
// data lifetime.
let arg = unsafe { self.zend_call_arg(i as usize) };
args.push(arg);
}
let obj = self.This.object_mut();
(ArgParser::new(args), obj)
}
/// Returns an [`ArgParser`] pre-loaded with the arguments contained inside
/// `self`.
///
/// A reference to `$this` is also returned in an [`Option`], which resolves
/// to [`None`] if this function is not called inside a method.
///
/// This function differs from [`parse_object`] in the fact that it returns
/// a reference to a [`ZendClassObject`], which is an object that
/// contains an arbitrary Rust type at the start of the object. The
/// object will also resolve to [`None`] if the function is called
/// inside a method that does not belong to an object with type `T`.
///
/// # Example
///
/// ```no_run
/// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType, prelude::*};
///
/// #[php_class]
/// #[derive(Debug)]
/// struct Example;
///
/// #[no_mangle]
/// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
/// let mut a = Arg::new("a", DataType::Long);
///
/// let (parser, this) = ex.parser_method::<Example>();
/// let parser = parser
/// .arg(&mut a)
/// .parse();
///
/// if parser.is_err() {
/// return;
/// }
///
/// dbg!(a, this);
/// }
///
/// #[php_module]
/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
/// module
/// }
/// ```
///
/// [`parse_object`]: #method.parse_object
pub fn parser_method<T: RegisteredClass>(
&mut self,
) -> (ArgParser<'_, '_>, Option<&mut ZendClassObject<T>>) {
let (parser, obj) = self.parser_object();
(
parser,
obj.and_then(|obj| ZendClassObject::from_zend_obj_mut(obj)),
)
}
/// Attempts to retrieve a reference to the underlying class object of the
/// Zend object.
///
/// Returns a [`ZendClassObject`] if the execution data contained a valid
/// object of type `T`, otherwise returns [`None`].
///
/// # Example
///
/// ```no_run
/// use ext_php_rs::{types::Zval, zend::ExecuteData, prelude::*};
///
/// #[php_class]
/// #[derive(Debug)]
/// struct Example;
///
/// #[no_mangle]
/// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
/// let this = ex.get_object::<Example>();
/// dbg!(this);
/// }
///
/// #[php_module]
/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
/// module
/// }
/// ```
pub fn get_object<T: RegisteredClass>(&mut self) -> Option<&mut ZendClassObject<T>> {
ZendClassObject::from_zend_obj_mut(self.get_self()?)
}
/// Attempts to retrieve the 'this' object, which can be used in class
/// methods to retrieve the underlying Zend object.
///
/// # Example
///
/// ```no_run
/// use ext_php_rs::{types::Zval, zend::ExecuteData};
///
/// #[no_mangle]
/// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) {
/// let this = ex.get_self();
/// dbg!(this);
/// }
/// ```
pub fn get_self(&mut self) -> Option<&mut ZendObject> {
// TODO(david): This should be a `&mut self` function but we need to fix arg
// parser first.
self.This.object_mut()
}
/// Translation of macro `ZEND_CALL_ARG(call, n)`
/// zend_compile.h:578
///
/// The resultant zval reference has a lifetime equal to the lifetime of
/// `self`. This isn't specified because when you attempt to get a
/// reference to args and the `$this` object, Rust doesn't let you.
/// Since this is a private method it's up to the caller to ensure the
/// lifetime isn't exceeded.
#[doc(hidden)]
unsafe fn zend_call_arg<'a>(&self, n: usize) -> Option<&'a mut Zval> {
let ptr = self.zend_call_var_num(n as isize);
ptr.as_mut()
}
/// Translation of macro `ZEND_CALL_VAR_NUM(call, n)`
/// zend_compile.h: 575
#[doc(hidden)]
unsafe fn zend_call_var_num(&self, n: isize) -> *mut Zval {
let ptr = self as *const Self as *mut Zval;
ptr.offset(Self::zend_call_frame_slot() + n)
}
/// Translation of macro `ZEND_CALL_FRAME_SLOT`
/// zend_compile:573
#[doc(hidden)]
fn zend_call_frame_slot() -> isize {
(Self::zend_mm_aligned_size::<Self>() + Self::zend_mm_aligned_size::<Zval>() - 1)
/ Self::zend_mm_aligned_size::<Zval>()
}
/// Translation of macro `ZEND_MM_ALIGNED_SIZE(size)`
/// zend_alloc.h:41
#[doc(hidden)]
fn zend_mm_aligned_size<T>() -> isize {
let size = std::mem::size_of::<T>();
((size as isize) + ZEND_MM_ALIGNMENT as isize - 1) & ZEND_MM_ALIGNMENT_MASK as isize
}
}
#[cfg(test)]
mod tests {
use super::ExecuteData;
#[test]
fn test_zend_call_frame_slot() {
// PHP 8.0.2 (cli) (built: Feb 21 2021 11:51:33) ( NTS )
// Copyright (c) The PHP Group
// Zend Engine v4.0.2, Copyright (c) Zend Technologies
assert_eq!(ExecuteData::zend_call_frame_slot(), 5);
}
}