ext_php_rs/types/
callable.rs

1//! Types related to callables in PHP (anonymous functions, functions, etc).
2
3use std::{convert::TryFrom, ops::Deref};
4
5use crate::{
6    convert::{FromZval, IntoZvalDyn},
7    error::{Error, Result},
8    ffi::_call_user_function_impl,
9    flags::DataType,
10    zend::ExecutorGlobals,
11};
12
13use super::Zval;
14
15/// Acts as a wrapper around a callable [`Zval`]. Allows the owner to call the
16/// [`Zval`] as if it was a PHP function through the [`try_call`] method.
17///
18/// [`try_call`]: #method.try_call
19#[derive(Debug)]
20pub struct ZendCallable<'a>(OwnedZval<'a>);
21
22impl<'a> ZendCallable<'a> {
23    /// Attempts to create a new [`ZendCallable`] from a zval.
24    ///
25    /// # Parameters
26    ///
27    /// * `callable` - The underlying [`Zval`] that is callable.
28    ///
29    /// # Errors
30    ///
31    /// Returns an error if the [`Zval`] was not callable.
32    pub fn new(callable: &'a Zval) -> Result<Self> {
33        if callable.is_callable() {
34            Ok(Self(OwnedZval::Reference(callable)))
35        } else {
36            Err(Error::Callable)
37        }
38    }
39
40    /// Attempts to create a new [`ZendCallable`] by taking ownership of a Zval.
41    /// Returns a result containing the callable if the zval was callable.
42    ///
43    /// # Parameters
44    ///
45    /// * `callable` - The underlying [`Zval`] that is callable.
46    pub fn new_owned(callable: Zval) -> Result<Self> {
47        if callable.is_callable() {
48            Ok(Self(OwnedZval::Owned(callable)))
49        } else {
50            Err(Error::Callable)
51        }
52    }
53
54    /// Attempts to create a new [`ZendCallable`] from a function name. Returns
55    /// a result containing the callable if the function existed and was
56    /// callable.
57    ///
58    /// # Parameters
59    ///
60    /// * `name` - Name of the callable function.
61    ///
62    /// # Example
63    ///
64    /// ```no_run
65    /// use ext_php_rs::types::ZendCallable;
66    ///
67    /// let strpos = ZendCallable::try_from_name("strpos").unwrap();
68    /// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap();
69    /// assert_eq!(result.long(), Some(1));
70    /// ```
71    pub fn try_from_name(name: &str) -> Result<Self> {
72        let mut callable = Zval::new();
73        callable.set_string(name, false)?;
74
75        Self::new_owned(callable)
76    }
77
78    /// Attempts to call the callable with a list of arguments to pass to the
79    /// function.
80    ///
81    /// You should not call this function directly, rather through the
82    /// [`call_user_func`] macro.
83    ///
84    /// # Parameters
85    ///
86    /// * `params` - A list of parameters to call the function with.
87    ///
88    /// # Returns
89    ///
90    /// Returns the result wrapped in [`Ok`] upon success. If calling the
91    /// callable fails, or an exception is thrown, an [`Err`] is returned.
92    ///
93    /// # Example
94    ///
95    /// ```no_run
96    /// use ext_php_rs::types::ZendCallable;
97    ///
98    /// let strpos = ZendCallable::try_from_name("strpos").unwrap();
99    /// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap();
100    /// assert_eq!(result.long(), Some(1));
101    /// ```
102    #[inline(always)]
103    pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
104        if !self.0.is_callable() {
105            return Err(Error::Callable);
106        }
107
108        let mut retval = Zval::new();
109        let len = params.len();
110        let params = params
111            .into_iter()
112            .map(|val| val.as_zval(false))
113            .collect::<Result<Vec<_>>>()?;
114        let packed = params.into_boxed_slice();
115
116        let result = unsafe {
117            _call_user_function_impl(
118                std::ptr::null_mut(),
119                self.0.as_ref() as *const crate::ffi::_zval_struct as *mut crate::ffi::_zval_struct,
120                &mut retval,
121                len as _,
122                packed.as_ptr() as *mut _,
123                std::ptr::null_mut(),
124            )
125        };
126
127        if result < 0 {
128            Err(Error::Callable)
129        } else if let Some(e) = ExecutorGlobals::take_exception() {
130            Err(Error::Exception(e))
131        } else {
132            Ok(retval)
133        }
134    }
135}
136
137impl<'a> FromZval<'a> for ZendCallable<'a> {
138    const TYPE: DataType = DataType::Callable;
139
140    fn from_zval(zval: &'a Zval) -> Option<Self> {
141        ZendCallable::new(zval).ok()
142    }
143}
144
145impl TryFrom<Zval> for ZendCallable<'_> {
146    type Error = Error;
147
148    fn try_from(value: Zval) -> Result<Self> {
149        ZendCallable::new_owned(value)
150    }
151}
152
153/// A container for a zval. Either contains a reference to a zval or an owned
154/// zval.
155#[derive(Debug)]
156enum OwnedZval<'a> {
157    Reference(&'a Zval),
158    Owned(Zval),
159}
160
161impl OwnedZval<'_> {
162    fn as_ref(&self) -> &Zval {
163        match self {
164            OwnedZval::Reference(zv) => zv,
165            OwnedZval::Owned(zv) => zv,
166        }
167    }
168}
169
170impl Deref for OwnedZval<'_> {
171    type Target = Zval;
172
173    fn deref(&self) -> &Self::Target {
174        self.as_ref()
175    }
176}