1use std::{ffi::CString, mem::MaybeUninit, ptr, rc::Rc};
2
3use crate::{
4 builders::FunctionBuilder,
5 class::{ClassEntryInfo, ConstructorMeta, ConstructorResult, RegisteredClass},
6 convert::{IntoZval, IntoZvalDyn},
7 describe::DocComments,
8 error::{Error, Result},
9 exception::PhpException,
10 ffi::{
11 zend_declare_class_constant, zend_declare_property, zend_do_implement_interface,
12 zend_register_internal_class_ex,
13 },
14 flags::{ClassFlags, MethodFlags, PropertyFlags},
15 types::{ZendClassObject, ZendObject, ZendStr, Zval},
16 zend::{ClassEntry, ExecuteData, FunctionEntry},
17 zend_fastcall,
18};
19
20type ConstantEntry = (String, Box<dyn FnOnce() -> Result<Zval>>, DocComments);
21
22#[must_use]
24pub struct ClassBuilder {
25 pub(crate) name: String,
26 ce: ClassEntry,
27 pub(crate) extends: Option<ClassEntryInfo>,
28 pub(crate) interfaces: Vec<ClassEntryInfo>,
29 pub(crate) methods: Vec<(FunctionBuilder<'static>, MethodFlags)>,
30 object_override: Option<unsafe extern "C" fn(class_type: *mut ClassEntry) -> *mut ZendObject>,
31 pub(crate) properties: Vec<(String, PropertyFlags, DocComments)>,
32 pub(crate) constants: Vec<ConstantEntry>,
33 register: Option<fn(&'static mut ClassEntry)>,
34 pub(crate) docs: DocComments,
35}
36
37impl ClassBuilder {
38 pub fn new<T: Into<String>>(name: T) -> Self {
45 Self {
46 name: name.into(),
47 ce: unsafe { MaybeUninit::zeroed().assume_init() },
50 extends: None,
51 interfaces: vec![],
52 methods: vec![],
53 object_override: None,
54 properties: vec![],
55 constants: vec![],
56 register: None,
57 docs: &[],
58 }
59 }
60
61 pub fn extends(mut self, parent: ClassEntryInfo) -> Self {
67 self.extends = Some(parent);
68 self
69 }
70
71 pub fn implements(mut self, interface: ClassEntryInfo) -> Self {
81 self.interfaces.push(interface);
82 self
83 }
84
85 pub fn method(mut self, func: FunctionBuilder<'static>, flags: MethodFlags) -> Self {
92 self.methods.push((func, flags));
93 self
94 }
95
96 pub fn property<T: Into<String>>(
112 mut self,
113 name: T,
114 flags: PropertyFlags,
115 docs: DocComments,
116 ) -> Self {
117 self.properties.push((name.into(), flags, docs));
118 self
119 }
120
121 pub fn constant<T: Into<String>>(
137 mut self,
138 name: T,
139 value: impl IntoZval + 'static,
140 docs: DocComments,
141 ) -> Result<Self> {
142 self.constants
143 .push((name.into(), Box::new(|| value.into_zval(true)), docs));
144 Ok(self)
145 }
146
147 pub fn dyn_constant<T: Into<String>>(
163 mut self,
164 name: T,
165 value: &'static dyn IntoZvalDyn,
166 docs: DocComments,
167 ) -> Result<Self> {
168 let value = Rc::new(value);
169 self.constants
170 .push((name.into(), Box::new(move || value.as_zval(true)), docs));
171 Ok(self)
172 }
173
174 pub fn flags(mut self, flags: ClassFlags) -> Self {
180 self.ce.ce_flags = flags.bits();
181 self
182 }
183
184 pub fn object_override<T: RegisteredClass>(mut self) -> Self {
198 extern "C" fn create_object<T: RegisteredClass>(ce: *mut ClassEntry) -> *mut ZendObject {
199 let obj = unsafe { ZendClassObject::<T>::new_uninit(ce.as_ref()) };
202 obj.into_raw().get_mut_zend_obj()
203 }
204
205 zend_fastcall! {
206 extern fn constructor<T: RegisteredClass>(ex: &mut ExecuteData, _: &mut Zval) {
207 let Some(ConstructorMeta { constructor, .. }) = T::constructor() else {
208 PhpException::default("You cannot instantiate this class from PHP.".into())
209 .throw()
210 .expect("Failed to throw exception when constructing class");
211 return;
212 };
213
214 let this = match constructor(ex) {
215 ConstructorResult::Ok(this) => this,
216 ConstructorResult::Exception(e) => {
217 e.throw()
218 .expect("Failed to throw exception while constructing class");
219 return;
220 }
221 ConstructorResult::ArgError => return,
222 };
223
224 let Some(this_obj) = ex.get_object::<T>() else {
225 PhpException::default("Failed to retrieve reference to `this` object.".into())
226 .throw()
227 .expect("Failed to throw exception while constructing class");
228 return;
229 };
230
231 this_obj.initialize(this);
232 }
233 }
234
235 debug_assert_eq!(
236 self.name.as_str(),
237 T::CLASS_NAME,
238 "Class name in builder does not match class name in `impl RegisteredClass`."
239 );
240 self.object_override = Some(create_object::<T>);
241 self.method(
242 {
243 let mut func = FunctionBuilder::new("__construct", constructor::<T>);
244 if let Some(ConstructorMeta { build_fn, .. }) = T::constructor() {
245 func = build_fn(func);
246 }
247 func
248 },
249 MethodFlags::Public,
250 )
251 }
252
253 pub fn registration(mut self, register: fn(&'static mut ClassEntry)) -> Self {
260 self.register = Some(register);
261 self
262 }
263
264 pub fn docs(mut self, docs: DocComments) -> Self {
270 self.docs = docs;
271 self
272 }
273
274 pub fn register(mut self) -> Result<()> {
287 self.ce.name = ZendStr::new_interned(&self.name, true).into_raw();
288
289 let mut methods = self
290 .methods
291 .into_iter()
292 .map(|(method, flags)| {
293 method.build().map(|mut method| {
294 method.flags |= flags.bits();
295 method
296 })
297 })
298 .collect::<Result<Vec<_>>>()?;
299
300 methods.push(FunctionEntry::end());
301 let func = Box::into_raw(methods.into_boxed_slice()) as *const FunctionEntry;
302 self.ce.info.internal.builtin_functions = func;
303
304 let class = unsafe {
305 zend_register_internal_class_ex(
306 &raw mut self.ce,
307 match self.extends {
308 Some((ptr, _)) => ptr::from_ref(ptr()).cast_mut(),
309 None => std::ptr::null_mut(),
310 },
311 )
312 .as_mut()
313 .ok_or(Error::InvalidPointer)?
314 };
315
316 if self.object_override.is_some() {
318 cfg_if::cfg_if! {
319 if #[cfg(php81)] {
320 class.ce_flags |= ClassFlags::NotSerializable.bits();
321 } else {
322 class.serialize = Some(crate::ffi::zend_class_serialize_deny);
323 class.unserialize = Some(crate::ffi::zend_class_unserialize_deny);
324 }
325 }
326 }
327
328 for (iface, _) in self.interfaces {
329 let interface = iface();
330 assert!(
331 interface.is_interface(),
332 "Given class entry was not an interface."
333 );
334
335 unsafe { zend_do_implement_interface(class, ptr::from_ref(interface).cast_mut()) };
336 }
337
338 for (name, flags, _) in self.properties {
339 unsafe {
340 zend_declare_property(
341 class,
342 CString::new(name.as_str())?.as_ptr(),
343 name.len() as _,
344 &mut Zval::new(),
345 flags.bits().try_into()?,
346 );
347 }
348 }
349
350 for (name, value, _) in self.constants {
351 let value = Box::into_raw(Box::new(value()?));
352 unsafe {
353 zend_declare_class_constant(
354 class,
355 CString::new(name.as_str())?.as_ptr(),
356 name.len(),
357 value,
358 );
359 };
360 }
361
362 if let Some(object_override) = self.object_override {
363 class.__bindgen_anon_2.create_object = Some(object_override);
364 }
365
366 if let Some(register) = self.register {
367 register(class);
368 } else {
369 panic!("Class {} was not registered.", self.name);
370 }
371
372 Ok(())
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use crate::test::test_function;
379
380 use super::*;
381
382 #[test]
383 fn test_new() {
384 let class = ClassBuilder::new("Foo");
385 assert_eq!(class.name, "Foo");
386 assert_eq!(class.extends, None);
387 assert_eq!(class.interfaces, vec![]);
388 assert_eq!(class.methods.len(), 0);
389 assert_eq!(class.object_override, None);
390 assert_eq!(class.properties, vec![]);
391 assert_eq!(class.constants.len(), 0);
392 assert_eq!(class.register, None);
393 assert_eq!(class.docs, &[] as DocComments);
394 }
395
396 #[test]
397 fn test_extends() {
398 let extends: ClassEntryInfo = (|| todo!(), "Bar");
399 let class = ClassBuilder::new("Foo").extends(extends);
400 assert_eq!(class.extends, Some(extends));
401 }
402
403 #[test]
404 fn test_implements() {
405 let implements: ClassEntryInfo = (|| todo!(), "Bar");
406 let class = ClassBuilder::new("Foo").implements(implements);
407 assert_eq!(class.interfaces, vec![implements]);
408 }
409
410 #[test]
411 fn test_method() {
412 let method = FunctionBuilder::new("foo", test_function);
413 let class = ClassBuilder::new("Foo").method(method, MethodFlags::Public);
414 assert_eq!(class.methods.len(), 1);
415 }
416
417 #[test]
418 fn test_property() {
419 let class = ClassBuilder::new("Foo").property("bar", PropertyFlags::Public, &["Doc 1"]);
420 assert_eq!(
421 class.properties,
422 vec![(
423 "bar".to_string(),
424 PropertyFlags::Public,
425 &["Doc 1"] as DocComments
426 )]
427 );
428 }
429
430 #[test]
431 #[cfg(feature = "embed")]
432 fn test_constant() {
433 let class = ClassBuilder::new("Foo")
434 .constant("bar", 42, &["Doc 1"])
435 .expect("Failed to create constant");
436 assert_eq!(class.constants.len(), 1);
437 assert_eq!(class.constants[0].0, "bar");
438 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
439 }
440
441 #[test]
442 #[cfg(feature = "embed")]
443 fn test_dyn_constant() {
444 let class = ClassBuilder::new("Foo")
445 .dyn_constant("bar", &42, &["Doc 1"])
446 .expect("Failed to create constant");
447 assert_eq!(class.constants.len(), 1);
448 assert_eq!(class.constants[0].0, "bar");
449 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
450 }
451
452 #[test]
453 fn test_flags() {
454 let class = ClassBuilder::new("Foo").flags(ClassFlags::Abstract);
455 assert_eq!(class.ce.ce_flags, ClassFlags::Abstract.bits());
456 }
457
458 #[test]
459 fn test_registration() {
460 let class = ClassBuilder::new("Foo").registration(|_| {});
461 assert!(class.register.is_some());
462 }
463
464 #[test]
465 fn test_docs() {
466 let class = ClassBuilder::new("Foo").docs(&["Doc 1"]);
467 assert_eq!(class.docs, &["Doc 1"] as DocComments);
468 }
469
470 }