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}