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, DataType, 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
29pub struct ClassProperty {
31 pub name: String,
33 pub flags: PropertyFlags,
35 pub default: PropertyDefault,
37 pub docs: DocComments,
39 pub ty: Option<DataType>,
41 pub nullable: bool,
43 pub readonly: bool,
45 pub default_stub: Option<String>,
47}
48
49#[must_use]
51pub struct ClassBuilder {
52 pub(crate) name: String,
53 ce: ClassEntry,
54 pub(crate) extends: Option<ClassEntryInfo>,
55 pub(crate) interfaces: Vec<ClassEntryInfo>,
56 pub(crate) methods: Vec<(FunctionBuilder<'static>, MethodFlags)>,
57 object_override: Option<unsafe extern "C" fn(class_type: *mut ClassEntry) -> *mut ZendObject>,
58 pub(crate) properties: Vec<ClassProperty>,
59 pub(crate) constants: Vec<ConstantEntry>,
60 register: Option<fn(&'static mut ClassEntry)>,
61 pub(crate) docs: DocComments,
62}
63
64impl ClassBuilder {
65 pub fn new<T: Into<String>>(name: T) -> Self {
72 Self {
73 name: name.into(),
74 ce: unsafe { MaybeUninit::zeroed().assume_init() },
77 extends: None,
78 interfaces: vec![],
79 methods: vec![],
80 object_override: None,
81 properties: vec![],
82 constants: vec![],
83 register: None,
84 docs: &[],
85 }
86 }
87
88 #[must_use]
90 pub fn get_flags(&self) -> u32 {
91 self.ce.ce_flags
92 }
93
94 pub fn extends(mut self, parent: ClassEntryInfo) -> Self {
100 self.extends = Some(parent);
101 self
102 }
103
104 pub fn implements(mut self, interface: ClassEntryInfo) -> Self {
114 self.interfaces.push(interface);
115 self
116 }
117
118 pub fn method(mut self, func: FunctionBuilder<'static>, flags: MethodFlags) -> Self {
125 self.methods.push((func, flags));
126 self
127 }
128
129 pub fn property(mut self, prop: ClassProperty) -> Self {
135 self.properties.push(prop);
136 self
137 }
138
139 pub fn constant<T: Into<String>>(
155 mut self,
156 name: T,
157 value: impl IntoZval + 'static,
158 docs: DocComments,
159 ) -> Result<Self> {
160 let zval = value.into_zval(true)?;
162 let stub = crate::convert::zval_to_stub(&zval);
163 self.constants
164 .push((name.into(), Box::new(|| Ok(zval)), docs, stub));
165 Ok(self)
166 }
167
168 pub fn dyn_constant<T: Into<String>>(
184 mut self,
185 name: T,
186 value: &'static dyn IntoZvalDyn,
187 docs: DocComments,
188 ) -> Result<Self> {
189 let stub = value.stub_value();
190 let value = Rc::new(value);
191 self.constants.push((
192 name.into(),
193 Box::new(move || value.as_zval(true)),
194 docs,
195 stub,
196 ));
197 Ok(self)
198 }
199
200 pub fn flags(mut self, flags: ClassFlags) -> Self {
206 self.ce.ce_flags = flags.bits();
207 self
208 }
209
210 pub fn object_override<T: RegisteredClass>(mut self) -> Self {
224 extern "C" fn create_object<T: RegisteredClass>(ce: *mut ClassEntry) -> *mut ZendObject {
225 if let Some(instance) = T::default_init() {
232 let obj = ZendClassObject::<T>::new(instance);
233 return obj.into_raw().get_mut_zend_obj();
234 }
235
236 let obj = unsafe { ZendClassObject::<T>::new_uninit(ce.as_ref()) };
239 obj.into_raw().get_mut_zend_obj()
240 }
241
242 zend_fastcall! {
243 extern fn constructor<T: RegisteredClass>(ex: &mut ExecuteData, _: &mut Zval) {
244 use crate::zend::try_catch;
245 use std::panic::AssertUnwindSafe;
246
247 let catch_result = try_catch(AssertUnwindSafe(|| {
250 let Some(ConstructorMeta { constructor, .. }) = T::constructor() else {
251 PhpException::default("You cannot instantiate this class from PHP.".into())
252 .throw()
253 .expect("Failed to throw exception when constructing class");
254 return;
255 };
256
257 let this = match constructor(ex) {
258 ConstructorResult::Ok(this) => this,
259 ConstructorResult::Exception(e) => {
260 e.throw()
261 .expect("Failed to throw exception while constructing class");
262 return;
263 }
264 ConstructorResult::ArgError => return,
265 };
266
267 let Some(this_obj) = ex.get_object_uninit::<T>() else {
270 PhpException::default("Failed to retrieve reference to `this` object.".into())
271 .throw()
272 .expect("Failed to throw exception while constructing class");
273 return;
274 };
275
276 this_obj.initialize(this);
277 }));
278
279 if catch_result.is_err() {
281 unsafe { crate::zend::bailout(); }
282 }
283 }
284 }
285
286 debug_assert_eq!(
287 self.name.as_str(),
288 T::CLASS_NAME,
289 "Class name in builder does not match class name in `impl RegisteredClass`."
290 );
291 self.object_override = Some(create_object::<T>);
292 let is_interface = T::FLAGS.contains(ClassFlags::Interface);
293
294 if let Some(ConstructorMeta {
297 build_fn, flags, ..
298 }) = T::constructor()
299 {
300 let func = if is_interface {
301 FunctionBuilder::new_abstract("__construct")
302 } else {
303 FunctionBuilder::new("__construct", constructor::<T>)
304 };
305 let visibility = flags.unwrap_or(MethodFlags::Public);
306 self.method(build_fn(func), visibility)
307 } else if is_interface {
308 self
310 } else {
311 let func = FunctionBuilder::new("__construct", constructor::<T>);
313 self.method(func, MethodFlags::Public)
314 }
315 }
316
317 pub fn registration(mut self, register: fn(&'static mut ClassEntry)) -> Self {
324 self.register = Some(register);
325 self
326 }
327
328 pub fn docs(mut self, docs: DocComments) -> Self {
334 self.docs = docs;
335 self
336 }
337
338 pub fn register(mut self) -> Result<()> {
351 self.ce.name = ZendStr::new_interned(&self.name, true).into_raw();
352
353 let mut methods = self
354 .methods
355 .into_iter()
356 .map(|(method, flags)| {
357 method.build().map(|mut method| {
358 method.flags |= flags.bits();
359 method
360 })
361 })
362 .collect::<Result<Vec<_>>>()?;
363
364 methods.push(FunctionEntry::end());
365 let func = Box::into_raw(methods.into_boxed_slice()) as *const FunctionEntry;
366 self.ce.info.internal.builtin_functions = func;
367
368 let class = if self.ce.flags().contains(ClassFlags::Interface) {
369 unsafe {
370 zend_register_internal_interface(&raw mut self.ce)
371 .as_mut()
372 .ok_or(Error::InvalidPointer)?
373 }
374 } else {
375 unsafe {
376 zend_register_internal_class_ex(
377 &raw mut self.ce,
378 match self.extends {
379 Some((ptr, _)) => ptr::from_ref(ptr()).cast_mut(),
380 None => std::ptr::null_mut(),
381 },
382 )
383 .as_mut()
384 .ok_or(Error::InvalidPointer)?
385 }
386 };
387
388 if self.object_override.is_some() {
390 cfg_if::cfg_if! {
391 if #[cfg(php81)] {
392 class.ce_flags |= ClassFlags::NotSerializable.bits();
393 } else {
394 class.serialize = Some(crate::ffi::zend_class_serialize_deny);
395 class.unserialize = Some(crate::ffi::zend_class_unserialize_deny);
396 }
397 }
398 }
399
400 for (iface, _) in self.interfaces {
401 let interface = iface();
402 assert!(
403 interface.is_interface(),
404 "Given class entry was not an interface."
405 );
406
407 unsafe { zend_do_implement_interface(class, ptr::from_ref(interface).cast_mut()) };
408 }
409
410 for prop in self.properties {
411 let mut default_zval = match prop.default {
412 Some(f) => f()?,
413 None => Zval::new(),
414 };
415 unsafe {
416 zend_declare_property(
417 class,
418 CString::new(prop.name.as_str())?.as_ptr(),
419 prop.name.len() as _,
420 &raw mut default_zval,
421 prop.flags.bits().try_into()?,
422 );
423 }
424 }
425
426 for (name, value, _, _) in self.constants {
427 let value = Box::into_raw(Box::new(value()?));
428 unsafe {
429 zend_declare_class_constant(
430 class,
431 CString::new(name.as_str())?.as_ptr(),
432 name.len(),
433 value,
434 );
435 };
436 }
437
438 if let Some(object_override) = self.object_override {
439 class.__bindgen_anon_2.create_object = Some(object_override);
440 }
441
442 if let Some(register) = self.register {
443 register(class);
444 } else {
445 panic!("Class {} was not registered.", self.name);
446 }
447
448 Ok(())
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use crate::test::test_function;
455
456 use super::*;
457
458 #[test]
459 #[allow(unpredictable_function_pointer_comparisons)]
460 fn test_new() {
461 let class = ClassBuilder::new("Foo");
462 assert_eq!(class.name, "Foo");
463 assert_eq!(class.extends, None);
464 assert_eq!(class.interfaces, vec![]);
465 assert_eq!(class.methods.len(), 0);
466 assert_eq!(class.object_override, None);
467 assert!(class.properties.is_empty());
468 assert_eq!(class.constants.len(), 0);
469 assert_eq!(class.register, None);
470 assert_eq!(class.docs, &[] as DocComments);
471 }
472
473 #[test]
474 fn test_extends() {
475 let extends: ClassEntryInfo = (|| todo!(), "Bar");
476 let class = ClassBuilder::new("Foo").extends(extends);
477 assert_eq!(class.extends, Some(extends));
478 }
479
480 #[test]
481 fn test_implements() {
482 let implements: ClassEntryInfo = (|| todo!(), "Bar");
483 let class = ClassBuilder::new("Foo").implements(implements);
484 assert_eq!(class.interfaces, vec![implements]);
485 }
486
487 #[test]
488 fn test_method() {
489 let method = FunctionBuilder::new("foo", test_function);
490 let class = ClassBuilder::new("Foo").method(method, MethodFlags::Public);
491 assert_eq!(class.methods.len(), 1);
492 }
493
494 #[test]
495 fn test_property() {
496 let class = ClassBuilder::new("Foo").property(ClassProperty {
497 name: "bar".into(),
498 flags: PropertyFlags::Public,
499 default: None,
500 docs: &["Doc 1"],
501 ty: Some(DataType::String),
502 nullable: false,
503 readonly: false,
504 default_stub: None,
505 });
506 assert_eq!(class.properties.len(), 1);
507 assert_eq!(class.properties[0].name, "bar");
508 assert_eq!(class.properties[0].flags, PropertyFlags::Public);
509 assert!(class.properties[0].default.is_none());
510 assert_eq!(class.properties[0].docs, &["Doc 1"] as DocComments);
511 assert_eq!(class.properties[0].ty, Some(DataType::String));
512 }
513
514 #[test]
515 #[cfg(feature = "embed")]
516 fn test_constant() {
517 let class = ClassBuilder::new("Foo")
518 .constant("bar", 42, &["Doc 1"])
519 .expect("Failed to create constant");
520 assert_eq!(class.constants.len(), 1);
521 assert_eq!(class.constants[0].0, "bar");
522 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
523 }
524
525 #[test]
526 #[cfg(feature = "embed")]
527 fn test_dyn_constant() {
528 let class = ClassBuilder::new("Foo")
529 .dyn_constant("bar", &42, &["Doc 1"])
530 .expect("Failed to create constant");
531 assert_eq!(class.constants.len(), 1);
532 assert_eq!(class.constants[0].0, "bar");
533 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
534 }
535
536 #[test]
537 fn test_flags() {
538 let class = ClassBuilder::new("Foo").flags(ClassFlags::Abstract);
539 assert_eq!(class.ce.ce_flags, ClassFlags::Abstract.bits());
540 }
541
542 #[test]
543 fn test_registration() {
544 let class = ClassBuilder::new("Foo").registration(|_| {});
545 assert!(class.register.is_some());
546 }
547
548 #[test]
549 fn test_registration_interface() {
550 let class = ClassBuilder::new("Foo")
551 .flags(ClassFlags::Interface)
552 .registration(|_| {});
553 assert!(class.register.is_some());
554 }
555
556 #[test]
557 fn test_docs() {
558 let class = ClassBuilder::new("Foo").docs(&["Doc 1"]);
559 assert_eq!(class.docs, &["Doc 1"] as DocComments);
560 }
561
562 }