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