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, zend_register_internal_interface,
13 },
14 flags::{ClassFlags, MethodFlags, PropertyFlags},
15 types::{ZendClassObject, ZendObject, ZendStr, Zval},
16 zend::{ClassEntry, ExecuteData, FunctionEntry},
17 zend_fastcall,
18};
19
20type ConstantEntry = (
22 String,
23 Box<dyn FnOnce() -> Result<Zval>>,
24 DocComments,
25 String,
26);
27type PropertyDefault = Option<Box<dyn FnOnce() -> Result<Zval>>>;
28
29#[must_use]
31pub struct ClassBuilder {
32 pub(crate) name: String,
33 ce: ClassEntry,
34 pub(crate) extends: Option<ClassEntryInfo>,
35 pub(crate) interfaces: Vec<ClassEntryInfo>,
36 pub(crate) methods: Vec<(FunctionBuilder<'static>, MethodFlags)>,
37 object_override: Option<unsafe extern "C" fn(class_type: *mut ClassEntry) -> *mut ZendObject>,
38 pub(crate) properties: Vec<(String, PropertyFlags, PropertyDefault, DocComments)>,
39 pub(crate) constants: Vec<ConstantEntry>,
40 register: Option<fn(&'static mut ClassEntry)>,
41 pub(crate) docs: DocComments,
42}
43
44impl ClassBuilder {
45 pub fn new<T: Into<String>>(name: T) -> Self {
52 Self {
53 name: name.into(),
54 ce: unsafe { MaybeUninit::zeroed().assume_init() },
57 extends: None,
58 interfaces: vec![],
59 methods: vec![],
60 object_override: None,
61 properties: vec![],
62 constants: vec![],
63 register: None,
64 docs: &[],
65 }
66 }
67
68 #[must_use]
70 pub fn get_flags(&self) -> u32 {
71 self.ce.ce_flags
72 }
73
74 pub fn extends(mut self, parent: ClassEntryInfo) -> Self {
80 self.extends = Some(parent);
81 self
82 }
83
84 pub fn implements(mut self, interface: ClassEntryInfo) -> Self {
94 self.interfaces.push(interface);
95 self
96 }
97
98 pub fn method(mut self, func: FunctionBuilder<'static>, flags: MethodFlags) -> Self {
105 self.methods.push((func, flags));
106 self
107 }
108
109 pub fn property<T: Into<String>>(
118 mut self,
119 name: T,
120 flags: PropertyFlags,
121 default: PropertyDefault,
122 docs: DocComments,
123 ) -> Self {
124 self.properties.push((name.into(), flags, default, docs));
125 self
126 }
127
128 pub fn constant<T: Into<String>>(
144 mut self,
145 name: T,
146 value: impl IntoZval + 'static,
147 docs: DocComments,
148 ) -> Result<Self> {
149 let zval = value.into_zval(true)?;
151 let stub = crate::convert::zval_to_stub(&zval);
152 self.constants
153 .push((name.into(), Box::new(|| Ok(zval)), docs, stub));
154 Ok(self)
155 }
156
157 pub fn dyn_constant<T: Into<String>>(
173 mut self,
174 name: T,
175 value: &'static dyn IntoZvalDyn,
176 docs: DocComments,
177 ) -> Result<Self> {
178 let stub = value.stub_value();
179 let value = Rc::new(value);
180 self.constants.push((
181 name.into(),
182 Box::new(move || value.as_zval(true)),
183 docs,
184 stub,
185 ));
186 Ok(self)
187 }
188
189 pub fn flags(mut self, flags: ClassFlags) -> Self {
195 self.ce.ce_flags = flags.bits();
196 self
197 }
198
199 pub fn object_override<T: RegisteredClass>(mut self) -> Self {
213 extern "C" fn create_object<T: RegisteredClass>(ce: *mut ClassEntry) -> *mut ZendObject {
214 if let Some(instance) = T::default_init() {
221 let obj = ZendClassObject::<T>::new(instance);
222 return obj.into_raw().get_mut_zend_obj();
223 }
224
225 let obj = unsafe { ZendClassObject::<T>::new_uninit(ce.as_ref()) };
228 obj.into_raw().get_mut_zend_obj()
229 }
230
231 zend_fastcall! {
232 extern fn constructor<T: RegisteredClass>(ex: &mut ExecuteData, _: &mut Zval) {
233 use crate::zend::try_catch;
234 use std::panic::AssertUnwindSafe;
235
236 let catch_result = try_catch(AssertUnwindSafe(|| {
239 let Some(ConstructorMeta { constructor, .. }) = T::constructor() else {
240 PhpException::default("You cannot instantiate this class from PHP.".into())
241 .throw()
242 .expect("Failed to throw exception when constructing class");
243 return;
244 };
245
246 let this = match constructor(ex) {
247 ConstructorResult::Ok(this) => this,
248 ConstructorResult::Exception(e) => {
249 e.throw()
250 .expect("Failed to throw exception while constructing class");
251 return;
252 }
253 ConstructorResult::ArgError => return,
254 };
255
256 let Some(this_obj) = ex.get_object_uninit::<T>() else {
259 PhpException::default("Failed to retrieve reference to `this` object.".into())
260 .throw()
261 .expect("Failed to throw exception while constructing class");
262 return;
263 };
264
265 this_obj.initialize(this);
266 }));
267
268 if catch_result.is_err() {
270 unsafe { crate::zend::bailout(); }
271 }
272 }
273 }
274
275 debug_assert_eq!(
276 self.name.as_str(),
277 T::CLASS_NAME,
278 "Class name in builder does not match class name in `impl RegisteredClass`."
279 );
280 self.object_override = Some(create_object::<T>);
281 let is_interface = T::FLAGS.contains(ClassFlags::Interface);
282
283 let (func, visibility) = if let Some(ConstructorMeta {
284 build_fn, flags, ..
285 }) = T::constructor()
286 {
287 let func = if is_interface {
288 FunctionBuilder::new_abstract("__construct")
289 } else {
290 FunctionBuilder::new("__construct", constructor::<T>)
291 };
292
293 (build_fn(func), flags.unwrap_or(MethodFlags::Public))
294 } else {
295 (
296 if is_interface {
297 FunctionBuilder::new_abstract("__construct")
298 } else {
299 FunctionBuilder::new("__construct", constructor::<T>)
300 },
301 MethodFlags::Public,
302 )
303 };
304
305 self.method(func, visibility)
306 }
307
308 pub fn registration(mut self, register: fn(&'static mut ClassEntry)) -> Self {
315 self.register = Some(register);
316 self
317 }
318
319 pub fn docs(mut self, docs: DocComments) -> Self {
325 self.docs = docs;
326 self
327 }
328
329 pub fn register(mut self) -> Result<()> {
342 self.ce.name = ZendStr::new_interned(&self.name, true).into_raw();
343
344 let mut methods = self
345 .methods
346 .into_iter()
347 .map(|(method, flags)| {
348 method.build().map(|mut method| {
349 method.flags |= flags.bits();
350 method
351 })
352 })
353 .collect::<Result<Vec<_>>>()?;
354
355 methods.push(FunctionEntry::end());
356 let func = Box::into_raw(methods.into_boxed_slice()) as *const FunctionEntry;
357 self.ce.info.internal.builtin_functions = func;
358
359 let class = if self.ce.flags().contains(ClassFlags::Interface) {
360 unsafe {
361 zend_register_internal_interface(&raw mut self.ce)
362 .as_mut()
363 .ok_or(Error::InvalidPointer)?
364 }
365 } else {
366 unsafe {
367 zend_register_internal_class_ex(
368 &raw mut self.ce,
369 match self.extends {
370 Some((ptr, _)) => ptr::from_ref(ptr()).cast_mut(),
371 None => std::ptr::null_mut(),
372 },
373 )
374 .as_mut()
375 .ok_or(Error::InvalidPointer)?
376 }
377 };
378
379 if self.object_override.is_some() {
381 cfg_if::cfg_if! {
382 if #[cfg(php81)] {
383 class.ce_flags |= ClassFlags::NotSerializable.bits();
384 } else {
385 class.serialize = Some(crate::ffi::zend_class_serialize_deny);
386 class.unserialize = Some(crate::ffi::zend_class_unserialize_deny);
387 }
388 }
389 }
390
391 for (iface, _) in self.interfaces {
392 let interface = iface();
393 assert!(
394 interface.is_interface(),
395 "Given class entry was not an interface."
396 );
397
398 unsafe { zend_do_implement_interface(class, ptr::from_ref(interface).cast_mut()) };
399 }
400
401 for (name, flags, default, _) in self.properties {
402 let mut default_zval = match default {
403 Some(f) => f()?,
404 None => Zval::new(),
405 };
406 unsafe {
407 zend_declare_property(
408 class,
409 CString::new(name.as_str())?.as_ptr(),
410 name.len() as _,
411 &raw mut default_zval,
412 flags.bits().try_into()?,
413 );
414 }
415 }
416
417 for (name, value, _, _) in self.constants {
418 let value = Box::into_raw(Box::new(value()?));
419 unsafe {
420 zend_declare_class_constant(
421 class,
422 CString::new(name.as_str())?.as_ptr(),
423 name.len(),
424 value,
425 );
426 };
427 }
428
429 if let Some(object_override) = self.object_override {
430 class.__bindgen_anon_2.create_object = Some(object_override);
431 }
432
433 if let Some(register) = self.register {
434 register(class);
435 } else {
436 panic!("Class {} was not registered.", self.name);
437 }
438
439 Ok(())
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use crate::test::test_function;
446
447 use super::*;
448
449 #[test]
450 #[allow(unpredictable_function_pointer_comparisons)]
451 fn test_new() {
452 let class = ClassBuilder::new("Foo");
453 assert_eq!(class.name, "Foo");
454 assert_eq!(class.extends, None);
455 assert_eq!(class.interfaces, vec![]);
456 assert_eq!(class.methods.len(), 0);
457 assert_eq!(class.object_override, None);
458 assert!(class.properties.is_empty());
459 assert_eq!(class.constants.len(), 0);
460 assert_eq!(class.register, None);
461 assert_eq!(class.docs, &[] as DocComments);
462 }
463
464 #[test]
465 fn test_extends() {
466 let extends: ClassEntryInfo = (|| todo!(), "Bar");
467 let class = ClassBuilder::new("Foo").extends(extends);
468 assert_eq!(class.extends, Some(extends));
469 }
470
471 #[test]
472 fn test_implements() {
473 let implements: ClassEntryInfo = (|| todo!(), "Bar");
474 let class = ClassBuilder::new("Foo").implements(implements);
475 assert_eq!(class.interfaces, vec![implements]);
476 }
477
478 #[test]
479 fn test_method() {
480 let method = FunctionBuilder::new("foo", test_function);
481 let class = ClassBuilder::new("Foo").method(method, MethodFlags::Public);
482 assert_eq!(class.methods.len(), 1);
483 }
484
485 #[test]
486 fn test_property() {
487 let class =
488 ClassBuilder::new("Foo").property("bar", PropertyFlags::Public, None, &["Doc 1"]);
489 assert_eq!(class.properties.len(), 1);
490 assert_eq!(class.properties[0].0, "bar");
491 assert_eq!(class.properties[0].1, PropertyFlags::Public);
492 assert!(class.properties[0].2.is_none());
493 assert_eq!(class.properties[0].3, &["Doc 1"] as DocComments);
494 }
495
496 #[test]
497 #[cfg(feature = "embed")]
498 fn test_constant() {
499 let class = ClassBuilder::new("Foo")
500 .constant("bar", 42, &["Doc 1"])
501 .expect("Failed to create constant");
502 assert_eq!(class.constants.len(), 1);
503 assert_eq!(class.constants[0].0, "bar");
504 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
505 }
506
507 #[test]
508 #[cfg(feature = "embed")]
509 fn test_dyn_constant() {
510 let class = ClassBuilder::new("Foo")
511 .dyn_constant("bar", &42, &["Doc 1"])
512 .expect("Failed to create constant");
513 assert_eq!(class.constants.len(), 1);
514 assert_eq!(class.constants[0].0, "bar");
515 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
516 }
517
518 #[test]
519 fn test_flags() {
520 let class = ClassBuilder::new("Foo").flags(ClassFlags::Abstract);
521 assert_eq!(class.ce.ce_flags, ClassFlags::Abstract.bits());
522 }
523
524 #[test]
525 fn test_registration() {
526 let class = ClassBuilder::new("Foo").registration(|_| {});
527 assert!(class.register.is_some());
528 }
529
530 #[test]
531 fn test_registration_interface() {
532 let class = ClassBuilder::new("Foo")
533 .flags(ClassFlags::Interface)
534 .registration(|_| {});
535 assert!(class.register.is_some());
536 }
537
538 #[test]
539 fn test_docs() {
540 let class = ClassBuilder::new("Foo").docs(&["Doc 1"]);
541 assert_eq!(class.docs, &["Doc 1"] as DocComments);
542 }
543
544 }