ext_php_rs/builders/
class.rs

1use std::{ffi::CString, mem::MaybeUninit};
2
3use crate::{
4    builders::FunctionBuilder,
5    class::{ConstructorMeta, ConstructorResult, RegisteredClass},
6    convert::IntoZval,
7    error::{Error, Result},
8    exception::PhpException,
9    ffi::{
10        zend_declare_class_constant, zend_declare_property, zend_do_implement_interface,
11        zend_register_internal_class_ex,
12    },
13    flags::{ClassFlags, MethodFlags, PropertyFlags},
14    types::{ZendClassObject, ZendObject, ZendStr, Zval},
15    zend::{ClassEntry, ExecuteData, FunctionEntry},
16    zend_fastcall,
17};
18
19/// Builder for registering a class in PHP.
20pub struct ClassBuilder {
21    name: String,
22    ce: ClassEntry,
23    extends: Option<&'static ClassEntry>,
24    interfaces: Vec<&'static ClassEntry>,
25    methods: Vec<FunctionEntry>,
26    object_override: Option<unsafe extern "C" fn(class_type: *mut ClassEntry) -> *mut ZendObject>,
27    properties: Vec<(String, Zval, PropertyFlags)>,
28    constants: Vec<(String, Zval)>,
29}
30
31impl ClassBuilder {
32    /// Creates a new class builder, used to build classes
33    /// to be exported to PHP.
34    ///
35    /// # Parameters
36    ///
37    /// * `name` - The name of the class.
38    pub fn new<T: Into<String>>(name: T) -> Self {
39        Self {
40            name: name.into(),
41            // SAFETY: A zeroed class entry is in an initialized state, as it is a raw C type
42            // whose fields do not have a drop implementation.
43            ce: unsafe { MaybeUninit::zeroed().assume_init() },
44            extends: None,
45            interfaces: vec![],
46            methods: vec![],
47            object_override: None,
48            properties: vec![],
49            constants: vec![],
50        }
51    }
52
53    /// Sets the class builder to extend another class.
54    ///
55    /// # Parameters
56    ///
57    /// * `parent` - The parent class to extend.
58    pub fn extends(mut self, parent: &'static ClassEntry) -> Self {
59        self.extends = Some(parent);
60        self
61    }
62
63    /// Implements an interface on the class.
64    ///
65    /// # Parameters
66    ///
67    /// * `interface` - Interface to implement on the class.
68    ///
69    /// # Panics
70    ///
71    /// Panics when the given class entry `interface` is not an interface.
72    pub fn implements(mut self, interface: &'static ClassEntry) -> Self {
73        assert!(
74            interface.is_interface(),
75            "Given class entry was not an interface."
76        );
77        self.interfaces.push(interface);
78        self
79    }
80
81    /// Adds a method to the class.
82    ///
83    /// # Parameters
84    ///
85    /// * `func` - The function entry to add to the class.
86    /// * `flags` - Flags relating to the function. See [`MethodFlags`].
87    pub fn method(mut self, mut func: FunctionEntry, flags: MethodFlags) -> Self {
88        func.flags |= flags.bits();
89        self.methods.push(func);
90        self
91    }
92
93    /// Adds a property to the class. The initial type of the property is given
94    /// by the type of the given default. Note that the user can change the
95    /// type.
96    ///
97    /// # Parameters
98    ///
99    /// * `name` - The name of the property to add to the class.
100    /// * `default` - The default value of the property.
101    /// * `flags` - Flags relating to the property. See [`PropertyFlags`].
102    ///
103    /// # Panics
104    ///
105    /// Function will panic if the given `default` cannot be converted into a
106    /// [`Zval`].
107    pub fn property<T: Into<String>>(
108        mut self,
109        name: T,
110        default: impl IntoZval,
111        flags: PropertyFlags,
112    ) -> Self {
113        let default = match default.into_zval(true) {
114            Ok(default) => default,
115            Err(_) => panic!("Invalid default value for property `{}`.", name.into()),
116        };
117
118        self.properties.push((name.into(), default, flags));
119        self
120    }
121
122    /// Adds a constant to the class. The type of the constant is defined by the
123    /// type of the given default.
124    ///
125    /// Returns a result containing the class builder if the constant was
126    /// successfully added.
127    ///
128    /// # Parameters
129    ///
130    /// * `name` - The name of the constant to add to the class.
131    /// * `value` - The value of the constant.
132    pub fn constant<T: Into<String>>(mut self, name: T, value: impl IntoZval) -> Result<Self> {
133        let value = value.into_zval(true)?;
134
135        self.constants.push((name.into(), value));
136        Ok(self)
137    }
138
139    /// Sets the flags for the class.
140    ///
141    /// # Parameters
142    ///
143    /// * `flags` - Flags relating to the class. See [`ClassFlags`].
144    pub fn flags(mut self, flags: ClassFlags) -> Self {
145        self.ce.ce_flags = flags.bits();
146        self
147    }
148
149    /// Overrides the creation of the Zend object which will represent an
150    /// instance of this class.
151    ///
152    /// # Parameters
153    ///
154    /// * `T` - The type which will override the Zend object. Must implement
155    ///   [`RegisteredClass`]
156    ///   which can be derived using the [`php_class`](crate::php_class) attribute
157    ///   macro.
158    ///
159    /// # Panics
160    ///
161    /// Panics if the class name associated with `T` is not the same as the
162    /// class name specified when creating the builder.
163    pub fn object_override<T: RegisteredClass>(mut self) -> Self {
164        extern "C" fn create_object<T: RegisteredClass>(ce: *mut ClassEntry) -> *mut ZendObject {
165            // SAFETY: After calling this function, PHP will always call the constructor
166            // defined below, which assumes that the object is uninitialized.
167            let obj = unsafe { ZendClassObject::<T>::new_uninit(ce.as_ref()) };
168            obj.into_raw().get_mut_zend_obj()
169        }
170
171        zend_fastcall! {
172            extern fn constructor<T: RegisteredClass>(ex: &mut ExecuteData, _: &mut Zval) {
173                let ConstructorMeta { constructor, .. } = match T::CONSTRUCTOR {
174                    Some(c) => c,
175                    None => {
176                        PhpException::default("You cannot instantiate this class from PHP.".into())
177                            .throw()
178                            .expect("Failed to throw exception when constructing class");
179                        return;
180                    }
181                };
182
183                let this = match constructor(ex) {
184                    ConstructorResult::Ok(this) => this,
185                    ConstructorResult::Exception(e) => {
186                        e.throw()
187                            .expect("Failed to throw exception while constructing class");
188                        return;
189                    }
190                    ConstructorResult::ArgError => return,
191                };
192                let this_obj = match ex.get_object::<T>() {
193                    Some(obj) => obj,
194                    None => {
195                        PhpException::default("Failed to retrieve reference to `this` object.".into())
196                            .throw()
197                            .expect("Failed to throw exception while constructing class");
198                        return;
199                    }
200                };
201                this_obj.initialize(this);
202            }
203        }
204
205        debug_assert_eq!(
206            self.name.as_str(),
207            T::CLASS_NAME,
208            "Class name in builder does not match class name in `impl RegisteredClass`."
209        );
210        self.object_override = Some(create_object::<T>);
211        self.method(
212            {
213                let mut func = FunctionBuilder::new("__construct", constructor::<T>);
214                if let Some(ConstructorMeta { build_fn, .. }) = T::CONSTRUCTOR {
215                    func = build_fn(func);
216                }
217                func.build().expect("Failed to build constructor function")
218            },
219            MethodFlags::Public,
220        )
221    }
222
223    /// Builds the class, returning a reference to the class entry.
224    ///
225    /// # Errors
226    ///
227    /// Returns an [`Error`] variant if the class could not be registered.
228    pub fn build(mut self) -> Result<&'static mut ClassEntry> {
229        self.ce.name = ZendStr::new_interned(&self.name, true).into_raw();
230
231        self.methods.push(FunctionEntry::end());
232        let func = Box::into_raw(self.methods.into_boxed_slice()) as *const FunctionEntry;
233        self.ce.info.internal.builtin_functions = func;
234
235        let class = unsafe {
236            zend_register_internal_class_ex(
237                &mut self.ce,
238                match self.extends {
239                    Some(ptr) => (ptr as *const _) as *mut _,
240                    None => std::ptr::null_mut(),
241                },
242            )
243            .as_mut()
244            .ok_or(Error::InvalidPointer)?
245        };
246
247        // disable serialization if the class has an associated object
248        if self.object_override.is_some() {
249            cfg_if::cfg_if! {
250                if #[cfg(php81)] {
251                    class.ce_flags |= ClassFlags::NotSerializable.bits();
252                } else {
253                    class.serialize = Some(crate::ffi::zend_class_serialize_deny);
254                    class.unserialize = Some(crate::ffi::zend_class_unserialize_deny);
255                }
256            }
257        }
258
259        for iface in self.interfaces {
260            unsafe {
261                zend_do_implement_interface(
262                    class,
263                    iface as *const crate::ffi::_zend_class_entry
264                        as *mut crate::ffi::_zend_class_entry,
265                )
266            };
267        }
268
269        for (name, mut default, flags) in self.properties {
270            unsafe {
271                zend_declare_property(
272                    class,
273                    CString::new(name.as_str())?.as_ptr(),
274                    name.len() as _,
275                    &mut default,
276                    flags.bits() as _,
277                );
278            }
279        }
280
281        for (name, value) in self.constants {
282            let value = Box::into_raw(Box::new(value));
283            unsafe {
284                zend_declare_class_constant(
285                    class,
286                    CString::new(name.as_str())?.as_ptr(),
287                    name.len(),
288                    value,
289                )
290            };
291        }
292
293        if let Some(object_override) = self.object_override {
294            class.__bindgen_anon_2.create_object = Some(object_override);
295        }
296
297        Ok(class)
298    }
299}