ext_php_rs/builders/
function.rs

1use crate::{
2    args::{Arg, ArgInfo},
3    error::{Error, Result},
4    flags::{DataType, MethodFlags},
5    types::Zval,
6    zend::{ExecuteData, FunctionEntry, ZendType},
7};
8use std::{ffi::CString, mem, ptr};
9
10/// Function representation in Rust.
11#[cfg(not(windows))]
12pub type FunctionHandler = extern "C" fn(execute_data: &mut ExecuteData, retval: &mut Zval);
13#[cfg(windows)]
14pub type FunctionHandler =
15    extern "vectorcall" fn(execute_data: &mut ExecuteData, retval: &mut Zval);
16
17/// Function representation in Rust using pointers.
18#[cfg(not(windows))]
19type FunctionPointerHandler = extern "C" fn(execute_data: *mut ExecuteData, retval: *mut Zval);
20#[cfg(windows)]
21type FunctionPointerHandler =
22    extern "vectorcall" fn(execute_data: *mut ExecuteData, retval: *mut Zval);
23
24/// Builder for registering a function in PHP.
25#[derive(Debug)]
26pub struct FunctionBuilder<'a> {
27    name: String,
28    function: FunctionEntry,
29    args: Vec<Arg<'a>>,
30    n_req: Option<usize>,
31    retval: Option<DataType>,
32    ret_as_ref: bool,
33    ret_as_null: bool,
34}
35
36impl<'a> FunctionBuilder<'a> {
37    /// Creates a new function builder, used to build functions
38    /// to be exported to PHP.
39    ///
40    /// # Parameters
41    ///
42    /// * `name` - The name of the function.
43    /// * `handler` - The handler to be called when the function is invoked from
44    ///   PHP.
45    pub fn new<T: Into<String>>(name: T, handler: FunctionHandler) -> Self {
46        Self {
47            name: name.into(),
48            function: FunctionEntry {
49                fname: ptr::null(),
50                // SAFETY: `*mut T` and `&mut T` have the same ABI as long as `*mut T` is non-null,
51                // aligned and pointing to a `T`. PHP guarantees that these conditions will be met.
52                handler: Some(unsafe {
53                    mem::transmute::<FunctionHandler, FunctionPointerHandler>(handler)
54                }),
55                arg_info: ptr::null(),
56                num_args: 0,
57                flags: 0, // TBD?
58                #[cfg(php84)]
59                doc_comment: ptr::null(),
60                #[cfg(php84)]
61                frameless_function_infos: ptr::null(),
62            },
63            args: vec![],
64            n_req: None,
65            retval: None,
66            ret_as_ref: false,
67            ret_as_null: false,
68        }
69    }
70
71    /// Create a new function builder for an abstract function that can be used
72    /// on an abstract class or an interface.
73    ///
74    /// # Parameters
75    ///
76    /// * `name` - The name of the function.
77    pub fn new_abstract<T: Into<String>>(name: T) -> Self {
78        Self {
79            name: name.into(),
80            function: FunctionEntry {
81                fname: ptr::null(),
82                handler: None,
83                arg_info: ptr::null(),
84                num_args: 0,
85                flags: MethodFlags::Abstract.bits(),
86                #[cfg(php84)]
87                doc_comment: ptr::null(),
88                #[cfg(php84)]
89                frameless_function_infos: ptr::null(),
90            },
91            args: vec![],
92            n_req: None,
93            retval: None,
94            ret_as_ref: false,
95            ret_as_null: false,
96        }
97    }
98
99    /// Creates a constructor builder, used to build the constructor
100    /// for classes.
101    ///
102    /// # Parameters
103    ///
104    /// * `handler` - The handler to be called when the function is invoked from
105    ///   PHP.
106    pub fn constructor(handler: FunctionHandler) -> Self {
107        Self::new("__construct", handler)
108    }
109
110    /// Adds an argument to the function.
111    ///
112    /// # Parameters
113    ///
114    /// * `arg` - The argument to add to the function.
115    pub fn arg(mut self, arg: Arg<'a>) -> Self {
116        self.args.push(arg);
117        self
118    }
119
120    /// Sets the rest of the given arguments as not required.
121    pub fn not_required(mut self) -> Self {
122        self.n_req = Some(self.args.len());
123        self
124    }
125
126    pub fn variadic(mut self) -> Self {
127        self.function.flags |= MethodFlags::Variadic.bits();
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;
142        self
143    }
144
145    /// Builds the function converting it into a Zend function entry.
146    ///
147    /// Returns a result containing the function entry if successful.
148    pub fn build(mut self) -> Result<FunctionEntry> {
149        let mut args = Vec::with_capacity(self.args.len() + 1);
150
151        // argument header, retval etc
152        args.push(ArgInfo {
153            name: self.n_req.unwrap_or(self.args.len()) as *const _,
154            type_: match self.retval {
155                Some(retval) => {
156                    ZendType::empty_from_type(retval, self.ret_as_ref, false, self.ret_as_null)
157                        .ok_or(Error::InvalidCString)?
158                }
159                None => ZendType::empty(false, false),
160            },
161            default_value: ptr::null(),
162        });
163
164        // arguments
165        args.extend(
166            self.args
167                .iter()
168                .map(|arg| arg.as_arg_info())
169                .collect::<Result<Vec<_>>>()?,
170        );
171
172        self.function.fname = CString::new(self.name)?.into_raw();
173        self.function.num_args = (args.len() - 1) as u32;
174        self.function.arg_info = Box::into_raw(args.into_boxed_slice()) as *const ArgInfo;
175
176        Ok(self.function)
177    }
178}