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 if let Some(ConstructorMeta {
286 build_fn, flags, ..
287 }) = T::constructor()
288 {
289 let func = if is_interface {
290 FunctionBuilder::new_abstract("__construct")
291 } else {
292 FunctionBuilder::new("__construct", constructor::<T>)
293 };
294 let visibility = flags.unwrap_or(MethodFlags::Public);
295 self.method(build_fn(func), visibility)
296 } else if is_interface {
297 self
299 } else {
300 let func = FunctionBuilder::new("__construct", constructor::<T>);
302 self.method(func, MethodFlags::Public)
303 }
304 }
305
306 pub fn registration(mut self, register: fn(&'static mut ClassEntry)) -> Self {
313 self.register = Some(register);
314 self
315 }
316
317 pub fn docs(mut self, docs: DocComments) -> Self {
323 self.docs = docs;
324 self
325 }
326
327 pub fn register(mut self) -> Result<()> {
340 self.ce.name = ZendStr::new_interned(&self.name, true).into_raw();
341
342 let mut methods = self
343 .methods
344 .into_iter()
345 .map(|(method, flags)| {
346 method.build().map(|mut method| {
347 method.flags |= flags.bits();
348 method
349 })
350 })
351 .collect::<Result<Vec<_>>>()?;
352
353 methods.push(FunctionEntry::end());
354 let func = Box::into_raw(methods.into_boxed_slice()) as *const FunctionEntry;
355 self.ce.info.internal.builtin_functions = func;
356
357 let class = if self.ce.flags().contains(ClassFlags::Interface) {
358 unsafe {
359 zend_register_internal_interface(&raw mut self.ce)
360 .as_mut()
361 .ok_or(Error::InvalidPointer)?
362 }
363 } else {
364 unsafe {
365 zend_register_internal_class_ex(
366 &raw mut self.ce,
367 match self.extends {
368 Some((ptr, _)) => ptr::from_ref(ptr()).cast_mut(),
369 None => std::ptr::null_mut(),
370 },
371 )
372 .as_mut()
373 .ok_or(Error::InvalidPointer)?
374 }
375 };
376
377 if self.object_override.is_some() {
379 cfg_if::cfg_if! {
380 if #[cfg(php81)] {
381 class.ce_flags |= ClassFlags::NotSerializable.bits();
382 } else {
383 class.serialize = Some(crate::ffi::zend_class_serialize_deny);
384 class.unserialize = Some(crate::ffi::zend_class_unserialize_deny);
385 }
386 }
387 }
388
389 for (iface, _) in self.interfaces {
390 let interface = iface();
391 assert!(
392 interface.is_interface(),
393 "Given class entry was not an interface."
394 );
395
396 unsafe { zend_do_implement_interface(class, ptr::from_ref(interface).cast_mut()) };
397 }
398
399 for (name, flags, default, _) in self.properties {
400 let mut default_zval = match default {
401 Some(f) => f()?,
402 None => Zval::new(),
403 };
404 unsafe {
405 zend_declare_property(
406 class,
407 CString::new(name.as_str())?.as_ptr(),
408 name.len() as _,
409 &raw mut default_zval,
410 flags.bits().try_into()?,
411 );
412 }
413 }
414
415 for (name, value, _, _) in self.constants {
416 let value = Box::into_raw(Box::new(value()?));
417 unsafe {
418 zend_declare_class_constant(
419 class,
420 CString::new(name.as_str())?.as_ptr(),
421 name.len(),
422 value,
423 );
424 };
425 }
426
427 if let Some(object_override) = self.object_override {
428 class.__bindgen_anon_2.create_object = Some(object_override);
429 }
430
431 if let Some(register) = self.register {
432 register(class);
433 } else {
434 panic!("Class {} was not registered.", self.name);
435 }
436
437 Ok(())
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 use crate::test::test_function;
444
445 use super::*;
446
447 #[test]
448 #[allow(unpredictable_function_pointer_comparisons)]
449 fn test_new() {
450 let class = ClassBuilder::new("Foo");
451 assert_eq!(class.name, "Foo");
452 assert_eq!(class.extends, None);
453 assert_eq!(class.interfaces, vec![]);
454 assert_eq!(class.methods.len(), 0);
455 assert_eq!(class.object_override, None);
456 assert!(class.properties.is_empty());
457 assert_eq!(class.constants.len(), 0);
458 assert_eq!(class.register, None);
459 assert_eq!(class.docs, &[] as DocComments);
460 }
461
462 #[test]
463 fn test_extends() {
464 let extends: ClassEntryInfo = (|| todo!(), "Bar");
465 let class = ClassBuilder::new("Foo").extends(extends);
466 assert_eq!(class.extends, Some(extends));
467 }
468
469 #[test]
470 fn test_implements() {
471 let implements: ClassEntryInfo = (|| todo!(), "Bar");
472 let class = ClassBuilder::new("Foo").implements(implements);
473 assert_eq!(class.interfaces, vec![implements]);
474 }
475
476 #[test]
477 fn test_method() {
478 let method = FunctionBuilder::new("foo", test_function);
479 let class = ClassBuilder::new("Foo").method(method, MethodFlags::Public);
480 assert_eq!(class.methods.len(), 1);
481 }
482
483 #[test]
484 fn test_property() {
485 let class =
486 ClassBuilder::new("Foo").property("bar", PropertyFlags::Public, None, &["Doc 1"]);
487 assert_eq!(class.properties.len(), 1);
488 assert_eq!(class.properties[0].0, "bar");
489 assert_eq!(class.properties[0].1, PropertyFlags::Public);
490 assert!(class.properties[0].2.is_none());
491 assert_eq!(class.properties[0].3, &["Doc 1"] as DocComments);
492 }
493
494 #[test]
495 #[cfg(feature = "embed")]
496 fn test_constant() {
497 let class = ClassBuilder::new("Foo")
498 .constant("bar", 42, &["Doc 1"])
499 .expect("Failed to create constant");
500 assert_eq!(class.constants.len(), 1);
501 assert_eq!(class.constants[0].0, "bar");
502 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
503 }
504
505 #[test]
506 #[cfg(feature = "embed")]
507 fn test_dyn_constant() {
508 let class = ClassBuilder::new("Foo")
509 .dyn_constant("bar", &42, &["Doc 1"])
510 .expect("Failed to create constant");
511 assert_eq!(class.constants.len(), 1);
512 assert_eq!(class.constants[0].0, "bar");
513 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
514 }
515
516 #[test]
517 fn test_flags() {
518 let class = ClassBuilder::new("Foo").flags(ClassFlags::Abstract);
519 assert_eq!(class.ce.ce_flags, ClassFlags::Abstract.bits());
520 }
521
522 #[test]
523 fn test_registration() {
524 let class = ClassBuilder::new("Foo").registration(|_| {});
525 assert!(class.register.is_some());
526 }
527
528 #[test]
529 fn test_registration_interface() {
530 let class = ClassBuilder::new("Foo")
531 .flags(ClassFlags::Interface)
532 .registration(|_| {});
533 assert!(class.register.is_some());
534 }
535
536 #[test]
537 fn test_docs() {
538 let class = ClassBuilder::new("Foo").docs(&["Doc 1"]);
539 assert_eq!(class.docs, &["Doc 1"] as DocComments);
540 }
541
542 }