Skip to main content

ext_php_rs/zend/
function.rs

1//! Builder for creating functions and methods in PHP.
2
3use std::{fmt::Debug, os::raw::c_char, ptr};
4
5use crate::{
6    convert::IntoZvalDyn,
7    error::Result,
8    ffi::{
9        zend_call_known_function, zend_fetch_function_str, zend_function, zend_function_entry,
10        zend_hash_str_find_ptr_lc,
11    },
12    flags::{FunctionType, MethodFlags},
13    types::Zval,
14};
15
16use super::ClassEntry;
17
18/// A Zend function entry.
19pub type FunctionEntry = zend_function_entry;
20
21impl Debug for FunctionEntry {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        f.debug_struct("_zend_function_entry")
24            .field("fname", &self.fname)
25            .field("arg_info", &self.arg_info)
26            .field("num_args", &self.num_args)
27            .field("flags", &self.flags)
28            .finish()
29    }
30}
31
32impl FunctionEntry {
33    /// Returns an empty function entry, signifing the end of a function list.
34    #[must_use]
35    pub fn end() -> Self {
36        Self {
37            fname: ptr::null::<c_char>(),
38            handler: None,
39            arg_info: ptr::null(),
40            num_args: 0,
41            flags: 0,
42            #[cfg(php84)]
43            doc_comment: ptr::null(),
44            #[cfg(php84)]
45            frameless_function_infos: ptr::null(),
46        }
47    }
48
49    pub(crate) fn add_flags(&mut self, flags: MethodFlags) {
50        cfg_if::cfg_if! {
51            if #[cfg(php86)] {
52                self.flags |= u64::from(flags.bits());
53            } else {
54                self.flags |= flags.bits();
55            }
56        }
57    }
58}
59
60/// PHP function.
61pub type Function = zend_function;
62
63impl Function {
64    /// Returns the function type.
65    ///
66    /// # Errors
67    ///
68    /// * [`Error::UnknownFunctionType`] - If the engine reports a function type
69    ///   this crate does not model.
70    ///
71    /// [`Error::UnknownFunctionType`]: crate::error::Error::UnknownFunctionType
72    pub fn function_type(&self) -> Result<FunctionType> {
73        FunctionType::try_from(unsafe { self.type_ })
74    }
75
76    /// Attempts to fetch a [`Function`] from the function name.
77    #[inline]
78    #[must_use]
79    pub fn try_from_function(name: &str) -> Option<Self> {
80        unsafe {
81            let res = zend_fetch_function_str(name.as_ptr().cast::<c_char>(), name.len());
82            if res.is_null() {
83                return None;
84            }
85            Some(*res)
86        }
87    }
88
89    /// Attempts to fetch a [`Function`] from the class and method name.
90    #[must_use]
91    pub fn try_from_method(class: &str, name: &str) -> Option<Self> {
92        match ClassEntry::try_find(class) {
93            None => None,
94            Some(ce) => unsafe {
95                let res = zend_hash_str_find_ptr_lc(
96                    &raw const ce.function_table,
97                    name.as_ptr().cast::<c_char>(),
98                    name.len(),
99                )
100                .cast::<zend_function>();
101                if res.is_null() {
102                    return None;
103                }
104                Some(*res)
105            },
106        }
107    }
108
109    /// Attempts to call the callable with a list of arguments to pass to the
110    /// function.
111    ///
112    /// You should not call this function directly, rather through the
113    /// [`call_user_func`] macro.
114    ///
115    /// # Parameters
116    ///
117    /// * `params` - A list of parameters to call the function with.
118    ///
119    /// # Returns
120    ///
121    /// Returns the result wrapped in [`Ok`] upon success.
122    ///
123    /// # Errors
124    ///
125    /// * If the function call fails, an [`Err`] is returned.
126    /// * If the number of parameters is not a valid `u32` value.
127    ///
128    /// # Example
129    ///
130    /// ```no_run
131    /// use ext_php_rs::types::ZendCallable;
132    ///
133    /// let strpos = ZendCallable::try_from_name("strpos").unwrap();
134    /// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap();
135    /// assert_eq!(result.long(), Some(1));
136    /// ```
137    // TODO: Measure this
138    #[allow(clippy::inline_always)]
139    #[inline(always)]
140    pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
141        let mut retval = Zval::new();
142        let len = params.len();
143        let params = params
144            .into_iter()
145            .map(|val| val.as_zval(false))
146            .collect::<Result<Vec<_>>>()?;
147        let packed = params.into_boxed_slice();
148
149        unsafe {
150            zend_call_known_function(
151                ptr::from_ref(self).cast_mut(),
152                ptr::null_mut(),
153                ptr::null_mut(),
154                &raw mut retval,
155                len.try_into()?,
156                packed.as_ptr().cast_mut(),
157                ptr::null_mut(),
158            );
159        };
160
161        Ok(retval)
162    }
163}