ext_php_rs/builders/
function.rs

1use crate::{
2    args::{Arg, ArgInfo},
3    describe::DocComments,
4    error::{Error, Result},
5    flags::{DataType, MethodFlags},
6    types::Zval,
7    zend::{ExecuteData, FunctionEntry, ZendType},
8};
9use std::{ffi::CString, mem, ptr};
10
11/// Function representation in Rust.
12#[cfg(not(windows))]
13pub type FunctionHandler = extern "C" fn(execute_data: &mut ExecuteData, retval: &mut Zval);
14#[cfg(windows)]
15pub type FunctionHandler =
16    extern "vectorcall" fn(execute_data: &mut ExecuteData, retval: &mut Zval);
17
18/// Function representation in Rust using pointers.
19#[cfg(not(windows))]
20type FunctionPointerHandler = extern "C" fn(execute_data: *mut ExecuteData, retval: *mut Zval);
21#[cfg(windows)]
22type FunctionPointerHandler =
23    extern "vectorcall" fn(execute_data: *mut ExecuteData, retval: *mut Zval);
24
25/// Builder for registering a function in PHP.
26#[must_use]
27#[derive(Debug)]
28pub struct FunctionBuilder<'a> {
29    pub(crate) name: String,
30    function: FunctionEntry,
31    pub(crate) args: Vec<Arg<'a>>,
32    n_req: Option<usize>,
33    pub(crate) retval: Option<DataType>,
34    ret_as_ref: bool,
35    pub(crate) ret_as_null: bool,
36    pub(crate) docs: DocComments,
37}
38
39impl<'a> FunctionBuilder<'a> {
40    /// Creates a new function builder, used to build functions
41    /// to be exported to PHP.
42    ///
43    /// # Parameters
44    ///
45    /// * `name` - The name of the function.
46    /// * `handler` - The handler to be called when the function is invoked from
47    ///   PHP.
48    pub fn new<T: Into<String>>(name: T, handler: FunctionHandler) -> Self {
49        Self {
50            name: name.into(),
51            function: FunctionEntry {
52                fname: ptr::null(),
53                // SAFETY: `*mut T` and `&mut T` have the same ABI as long as `*mut T` is non-null,
54                // aligned and pointing to a `T`. PHP guarantees that these conditions will be met.
55                handler: Some(unsafe {
56                    mem::transmute::<FunctionHandler, FunctionPointerHandler>(handler)
57                }),
58                arg_info: ptr::null(),
59                num_args: 0,
60                flags: 0, // TBD?
61                #[cfg(php84)]
62                doc_comment: ptr::null(),
63                #[cfg(php84)]
64                frameless_function_infos: ptr::null(),
65            },
66            args: vec![],
67            n_req: None,
68            retval: None,
69            ret_as_ref: false,
70            ret_as_null: false,
71            docs: &[],
72        }
73    }
74
75    /// Create a new function builder for an abstract function that can be used
76    /// on an abstract class or an interface.
77    ///
78    /// # Parameters
79    ///
80    /// * `name` - The name of the function.
81    pub fn new_abstract<T: Into<String>>(name: T) -> Self {
82        Self {
83            name: name.into(),
84            function: FunctionEntry {
85                fname: ptr::null(),
86                handler: None,
87                arg_info: ptr::null(),
88                num_args: 0,
89                flags: MethodFlags::Abstract.bits(),
90                #[cfg(php84)]
91                doc_comment: ptr::null(),
92                #[cfg(php84)]
93                frameless_function_infos: ptr::null(),
94            },
95            args: vec![],
96            n_req: None,
97            retval: None,
98            ret_as_ref: false,
99            ret_as_null: false,
100            docs: &[],
101        }
102    }
103
104    /// Creates a constructor builder, used to build the constructor
105    /// for classes.
106    ///
107    /// # Parameters
108    ///
109    /// * `handler` - The handler to be called when the function is invoked from
110    ///   PHP.
111    pub fn constructor(handler: FunctionHandler) -> Self {
112        Self::new("__construct", handler)
113    }
114
115    /// Adds an argument to the function.
116    ///
117    /// # Parameters
118    ///
119    /// * `arg` - The argument to add to the function.
120    pub fn arg(mut self, arg: Arg<'a>) -> Self {
121        self.args.push(arg);
122        self
123    }
124
125    /// Sets the rest of the given arguments as not required.
126    pub fn not_required(mut self) -> Self {
127        self.n_req = Some(self.args.len());
128        self
129    }
130
131    /// Sets the return value of the function.
132    ///
133    /// # Parameters
134    ///
135    /// * `type_` - The return type of the function.
136    /// * `as_ref` - Whether the function returns a reference.
137    /// * `allow_null` - Whether the function return value is nullable.
138    pub fn returns(mut self, type_: DataType, as_ref: bool, allow_null: bool) -> Self {
139        self.retval = Some(type_);
140        self.ret_as_ref = as_ref;
141        self.ret_as_null = allow_null && type_ != DataType::Void && type_ != DataType::Mixed;
142        self
143    }
144
145    /// Sets the documentation for the function.
146    /// This is used to generate the PHP stubs for the function.
147    ///
148    /// # Parameters
149    ///
150    /// * `docs` - The documentation for the function.
151    pub fn docs(mut self, docs: DocComments) -> Self {
152        self.docs = docs;
153        self
154    }
155
156    /// Builds the function converting it into a Zend function entry.
157    ///
158    /// Returns a result containing the function entry if successful.
159    ///
160    /// # Errors
161    ///
162    /// * `Error::InvalidCString` - If the function name is not a valid C
163    ///   string.
164    /// * `Error::IntegerOverflow` - If the number of arguments is too large.
165    /// * If arg info for an argument could not be created.
166    /// * If the function name contains NUL bytes.
167    pub fn build(mut self) -> Result<FunctionEntry> {
168        let mut args = Vec::with_capacity(self.args.len() + 1);
169        let mut n_req = self.n_req.unwrap_or(self.args.len());
170        let variadic = self.args.last().is_some_and(|arg| arg.variadic);
171
172        if variadic {
173            self.function.flags |= MethodFlags::Variadic.bits();
174            n_req = n_req.saturating_sub(1);
175        }
176
177        // argument header, retval etc
178        // The first argument is used as `zend_internal_function_info` for the function.
179        // That struct shares the same memory as `zend_internal_arg_info` which is used
180        // for the arguments.
181        args.push(ArgInfo {
182            // required_num_args
183            name: n_req as *const _,
184            type_: match self.retval {
185                Some(retval) => {
186                    ZendType::empty_from_type(retval, self.ret_as_ref, false, self.ret_as_null)
187                        .ok_or(Error::InvalidCString)?
188                }
189                None => ZendType::empty(false, false),
190            },
191            default_value: ptr::null(),
192        });
193
194        // arguments
195        args.extend(
196            self.args
197                .iter()
198                .map(Arg::as_arg_info)
199                .collect::<Result<Vec<_>>>()?,
200        );
201
202        self.function.fname = CString::new(self.name)?.into_raw();
203        self.function.num_args = (args.len() - 1).try_into()?;
204        self.function.arg_info = Box::into_raw(args.into_boxed_slice()) as *const ArgInfo;
205
206        Ok(self.function)
207    }
208}