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 = (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 #[must_use]
63 pub fn get_flags(&self) -> u32 {
64 self.ce.ce_flags
65 }
66
67 pub fn extends(mut self, parent: ClassEntryInfo) -> Self {
73 self.extends = Some(parent);
74 self
75 }
76
77 pub fn implements(mut self, interface: ClassEntryInfo) -> Self {
87 self.interfaces.push(interface);
88 self
89 }
90
91 pub fn method(mut self, func: FunctionBuilder<'static>, flags: MethodFlags) -> Self {
98 self.methods.push((func, flags));
99 self
100 }
101
102 pub fn property<T: Into<String>>(
118 mut self,
119 name: T,
120 flags: PropertyFlags,
121 docs: DocComments,
122 ) -> Self {
123 self.properties.push((name.into(), flags, docs));
124 self
125 }
126
127 pub fn constant<T: Into<String>>(
143 mut self,
144 name: T,
145 value: impl IntoZval + 'static,
146 docs: DocComments,
147 ) -> Result<Self> {
148 self.constants
149 .push((name.into(), Box::new(|| value.into_zval(true)), docs));
150 Ok(self)
151 }
152
153 pub fn dyn_constant<T: Into<String>>(
169 mut self,
170 name: T,
171 value: &'static dyn IntoZvalDyn,
172 docs: DocComments,
173 ) -> Result<Self> {
174 let value = Rc::new(value);
175 self.constants
176 .push((name.into(), Box::new(move || value.as_zval(true)), docs));
177 Ok(self)
178 }
179
180 pub fn flags(mut self, flags: ClassFlags) -> Self {
186 self.ce.ce_flags = flags.bits();
187 self
188 }
189
190 pub fn object_override<T: RegisteredClass>(mut self) -> Self {
204 extern "C" fn create_object<T: RegisteredClass>(ce: *mut ClassEntry) -> *mut ZendObject {
205 let obj = unsafe { ZendClassObject::<T>::new_uninit(ce.as_ref()) };
208 obj.into_raw().get_mut_zend_obj()
209 }
210
211 zend_fastcall! {
212 extern fn constructor<T: RegisteredClass>(ex: &mut ExecuteData, _: &mut Zval) {
213 let Some(ConstructorMeta { constructor, .. }) = T::constructor() else {
214 PhpException::default("You cannot instantiate this class from PHP.".into())
215 .throw()
216 .expect("Failed to throw exception when constructing class");
217 return;
218 };
219
220 let this = match constructor(ex) {
221 ConstructorResult::Ok(this) => this,
222 ConstructorResult::Exception(e) => {
223 e.throw()
224 .expect("Failed to throw exception while constructing class");
225 return;
226 }
227 ConstructorResult::ArgError => return,
228 };
229
230 let Some(this_obj) = ex.get_object::<T>() else {
231 PhpException::default("Failed to retrieve reference to `this` object.".into())
232 .throw()
233 .expect("Failed to throw exception while constructing class");
234 return;
235 };
236
237 this_obj.initialize(this);
238 }
239 }
240
241 debug_assert_eq!(
242 self.name.as_str(),
243 T::CLASS_NAME,
244 "Class name in builder does not match class name in `impl RegisteredClass`."
245 );
246 self.object_override = Some(create_object::<T>);
247 let is_interface = T::FLAGS.contains(ClassFlags::Interface);
248
249 let (func, visibility) = if let Some(ConstructorMeta {
250 build_fn, flags, ..
251 }) = T::constructor()
252 {
253 let func = if is_interface {
254 FunctionBuilder::new_abstract("__construct")
255 } else {
256 FunctionBuilder::new("__construct", constructor::<T>)
257 };
258
259 (build_fn(func), flags.unwrap_or(MethodFlags::Public))
260 } else {
261 (
262 if is_interface {
263 FunctionBuilder::new_abstract("__construct")
264 } else {
265 FunctionBuilder::new("__construct", constructor::<T>)
266 },
267 MethodFlags::Public,
268 )
269 };
270
271 self.method(func, visibility)
272 }
273
274 pub fn registration(mut self, register: fn(&'static mut ClassEntry)) -> Self {
281 self.register = Some(register);
282 self
283 }
284
285 pub fn docs(mut self, docs: DocComments) -> Self {
291 self.docs = docs;
292 self
293 }
294
295 pub fn register(mut self) -> Result<()> {
308 self.ce.name = ZendStr::new_interned(&self.name, true).into_raw();
309
310 let mut methods = self
311 .methods
312 .into_iter()
313 .map(|(method, flags)| {
314 method.build().map(|mut method| {
315 method.flags |= flags.bits();
316 method
317 })
318 })
319 .collect::<Result<Vec<_>>>()?;
320
321 methods.push(FunctionEntry::end());
322 let func = Box::into_raw(methods.into_boxed_slice()) as *const FunctionEntry;
323 self.ce.info.internal.builtin_functions = func;
324
325 let class = if self.ce.flags().contains(ClassFlags::Interface) {
326 unsafe {
327 zend_register_internal_interface(&raw mut self.ce)
328 .as_mut()
329 .ok_or(Error::InvalidPointer)?
330 }
331 } else {
332 unsafe {
333 zend_register_internal_class_ex(
334 &raw mut self.ce,
335 match self.extends {
336 Some((ptr, _)) => ptr::from_ref(ptr()).cast_mut(),
337 None => std::ptr::null_mut(),
338 },
339 )
340 .as_mut()
341 .ok_or(Error::InvalidPointer)?
342 }
343 };
344
345 if self.object_override.is_some() {
347 cfg_if::cfg_if! {
348 if #[cfg(php81)] {
349 class.ce_flags |= ClassFlags::NotSerializable.bits();
350 } else {
351 class.serialize = Some(crate::ffi::zend_class_serialize_deny);
352 class.unserialize = Some(crate::ffi::zend_class_unserialize_deny);
353 }
354 }
355 }
356
357 for (iface, _) in self.interfaces {
358 let interface = iface();
359 assert!(
360 interface.is_interface(),
361 "Given class entry was not an interface."
362 );
363
364 unsafe { zend_do_implement_interface(class, ptr::from_ref(interface).cast_mut()) };
365 }
366
367 for (name, flags, _) in self.properties {
368 unsafe {
369 zend_declare_property(
370 class,
371 CString::new(name.as_str())?.as_ptr(),
372 name.len() as _,
373 &mut Zval::new(),
374 flags.bits().try_into()?,
375 );
376 }
377 }
378
379 for (name, value, _) in self.constants {
380 let value = Box::into_raw(Box::new(value()?));
381 unsafe {
382 zend_declare_class_constant(
383 class,
384 CString::new(name.as_str())?.as_ptr(),
385 name.len(),
386 value,
387 );
388 };
389 }
390
391 if let Some(object_override) = self.object_override {
392 class.__bindgen_anon_2.create_object = Some(object_override);
393 }
394
395 if let Some(register) = self.register {
396 register(class);
397 } else {
398 panic!("Class {} was not registered.", self.name);
399 }
400
401 Ok(())
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use crate::test::test_function;
408
409 use super::*;
410
411 #[test]
412 #[allow(unpredictable_function_pointer_comparisons)]
413 fn test_new() {
414 let class = ClassBuilder::new("Foo");
415 assert_eq!(class.name, "Foo");
416 assert_eq!(class.extends, None);
417 assert_eq!(class.interfaces, vec![]);
418 assert_eq!(class.methods.len(), 0);
419 assert_eq!(class.object_override, None);
420 assert_eq!(class.properties, vec![]);
421 assert_eq!(class.constants.len(), 0);
422 assert_eq!(class.register, None);
423 assert_eq!(class.docs, &[] as DocComments);
424 }
425
426 #[test]
427 fn test_extends() {
428 let extends: ClassEntryInfo = (|| todo!(), "Bar");
429 let class = ClassBuilder::new("Foo").extends(extends);
430 assert_eq!(class.extends, Some(extends));
431 }
432
433 #[test]
434 fn test_implements() {
435 let implements: ClassEntryInfo = (|| todo!(), "Bar");
436 let class = ClassBuilder::new("Foo").implements(implements);
437 assert_eq!(class.interfaces, vec![implements]);
438 }
439
440 #[test]
441 fn test_method() {
442 let method = FunctionBuilder::new("foo", test_function);
443 let class = ClassBuilder::new("Foo").method(method, MethodFlags::Public);
444 assert_eq!(class.methods.len(), 1);
445 }
446
447 #[test]
448 fn test_property() {
449 let class = ClassBuilder::new("Foo").property("bar", PropertyFlags::Public, &["Doc 1"]);
450 assert_eq!(
451 class.properties,
452 vec![(
453 "bar".to_string(),
454 PropertyFlags::Public,
455 &["Doc 1"] as DocComments
456 )]
457 );
458 }
459
460 #[test]
461 #[cfg(feature = "embed")]
462 fn test_constant() {
463 let class = ClassBuilder::new("Foo")
464 .constant("bar", 42, &["Doc 1"])
465 .expect("Failed to create constant");
466 assert_eq!(class.constants.len(), 1);
467 assert_eq!(class.constants[0].0, "bar");
468 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
469 }
470
471 #[test]
472 #[cfg(feature = "embed")]
473 fn test_dyn_constant() {
474 let class = ClassBuilder::new("Foo")
475 .dyn_constant("bar", &42, &["Doc 1"])
476 .expect("Failed to create constant");
477 assert_eq!(class.constants.len(), 1);
478 assert_eq!(class.constants[0].0, "bar");
479 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
480 }
481
482 #[test]
483 fn test_flags() {
484 let class = ClassBuilder::new("Foo").flags(ClassFlags::Abstract);
485 assert_eq!(class.ce.ce_flags, ClassFlags::Abstract.bits());
486 }
487
488 #[test]
489 fn test_registration() {
490 let class = ClassBuilder::new("Foo").registration(|_| {});
491 assert!(class.register.is_some());
492 }
493
494 #[test]
495 fn test_registration_interface() {
496 let class = ClassBuilder::new("Foo")
497 .flags(ClassFlags::Interface)
498 .registration(|_| {});
499 assert!(class.register.is_some());
500 }
501
502 #[test]
503 fn test_docs() {
504 let class = ClassBuilder::new("Foo").docs(&["Doc 1"]);
505 assert_eq!(class.docs, &["Doc 1"] as DocComments);
506 }
507
508 }