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,
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    /// Converts the function entry into a raw and pointer, releasing it to the
50    /// C world.
51    #[must_use]
52    pub fn into_raw(self) -> *mut Self {
53        Box::into_raw(Box::new(self))
54    }
55}
56
57/// PHP function.
58pub type Function = zend_function;
59
60impl Function {
61    /// Returns the function type.
62    #[must_use]
63    pub fn function_type(&self) -> FunctionType {
64        FunctionType::from(unsafe { self.type_ })
65    }
66
67    /// Attempts to fetch a [`Function`] from the function name.
68    #[inline]
69    #[must_use]
70    pub fn try_from_function(name: &str) -> Option<Self> {
71        unsafe {
72            let res = zend_fetch_function_str(name.as_ptr().cast::<c_char>(), name.len());
73            if res.is_null() {
74                return None;
75            }
76            Some(*res)
77        }
78    }
79
80    /// Attempts to fetch a [`Function`] from the class and method name.
81    #[must_use]
82    pub fn try_from_method(class: &str, name: &str) -> Option<Self> {
83        match ClassEntry::try_find(class) {
84            None => None,
85            Some(ce) => unsafe {
86                let res = zend_hash_str_find_ptr_lc(
87                    &raw const ce.function_table,
88                    name.as_ptr().cast::<c_char>(),
89                    name.len(),
90                )
91                .cast::<zend_function>();
92                if res.is_null() {
93                    return None;
94                }
95                Some(*res)
96            },
97        }
98    }
99
100    /// Attempts to call the callable with a list of arguments to pass to the
101    /// function.
102    ///
103    /// You should not call this function directly, rather through the
104    /// [`call_user_func`] macro.
105    ///
106    /// # Parameters
107    ///
108    /// * `params` - A list of parameters to call the function with.
109    ///
110    /// # Returns
111    ///
112    /// Returns the result wrapped in [`Ok`] upon success.
113    ///
114    /// # Errors
115    ///
116    /// * If the function call fails, an [`Err`] is returned.
117    /// * If the number of parameters is not a valid `u32` value.
118    ///
119    /// # Example
120    ///
121    /// ```no_run
122    /// use ext_php_rs::types::ZendCallable;
123    ///
124    /// let strpos = ZendCallable::try_from_name("strpos").unwrap();
125    /// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap();
126    /// assert_eq!(result.long(), Some(1));
127    /// ```
128    // TODO: Measure this
129    #[allow(clippy::inline_always)]
130    #[inline(always)]
131    pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
132        let mut retval = Zval::new();
133        let len = params.len();
134        let params = params
135            .into_iter()
136            .map(|val| val.as_zval(false))
137            .collect::<Result<Vec<_>>>()?;
138        let packed = params.into_boxed_slice();
139
140        unsafe {
141            zend_call_known_function(
142                ptr::from_ref(self).cast_mut(),
143                ptr::null_mut(),
144                ptr::null_mut(),
145                &raw mut retval,
146                len.try_into()?,
147                packed.as_ptr().cast_mut(),
148                ptr::null_mut(),
149            );
150        };
151
152        Ok(retval)
153    }
154}