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
19pub 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 pub fn new<T: Into<String>>(name: T) -> Self {
39 Self {
40 name: name.into(),
41 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 pub fn extends(mut self, parent: &'static ClassEntry) -> Self {
59 self.extends = Some(parent);
60 self
61 }
62
63 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 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 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 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 pub fn flags(mut self, flags: ClassFlags) -> Self {
145 self.ce.ce_flags = flags.bits();
146 self
147 }
148
149 pub fn object_override<T: RegisteredClass>(mut self) -> Self {
164 extern "C" fn create_object<T: RegisteredClass>(ce: *mut ClassEntry) -> *mut ZendObject {
165 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 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 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}