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
242 let (func, visibility) = if let Some(ConstructorMeta {
243 build_fn, flags, ..
244 }) = T::constructor()
245 {
246 let func = FunctionBuilder::new("__construct", constructor::<T>);
247 (build_fn(func), flags.unwrap_or(MethodFlags::Public))
248 } else {
249 (
250 FunctionBuilder::new("__construct", constructor::<T>),
251 MethodFlags::Public,
252 )
253 };
254
255 self.method(func, visibility)
256 }
257
258 pub fn registration(mut self, register: fn(&'static mut ClassEntry)) -> Self {
265 self.register = Some(register);
266 self
267 }
268
269 pub fn docs(mut self, docs: DocComments) -> Self {
275 self.docs = docs;
276 self
277 }
278
279 pub fn register(mut self) -> Result<()> {
292 self.ce.name = ZendStr::new_interned(&self.name, true).into_raw();
293
294 let mut methods = self
295 .methods
296 .into_iter()
297 .map(|(method, flags)| {
298 method.build().map(|mut method| {
299 method.flags |= flags.bits();
300 method
301 })
302 })
303 .collect::<Result<Vec<_>>>()?;
304
305 methods.push(FunctionEntry::end());
306 let func = Box::into_raw(methods.into_boxed_slice()) as *const FunctionEntry;
307 self.ce.info.internal.builtin_functions = func;
308
309 let class = unsafe {
310 zend_register_internal_class_ex(
311 &raw mut self.ce,
312 match self.extends {
313 Some((ptr, _)) => ptr::from_ref(ptr()).cast_mut(),
314 None => std::ptr::null_mut(),
315 },
316 )
317 .as_mut()
318 .ok_or(Error::InvalidPointer)?
319 };
320
321 if self.object_override.is_some() {
323 cfg_if::cfg_if! {
324 if #[cfg(php81)] {
325 class.ce_flags |= ClassFlags::NotSerializable.bits();
326 } else {
327 class.serialize = Some(crate::ffi::zend_class_serialize_deny);
328 class.unserialize = Some(crate::ffi::zend_class_unserialize_deny);
329 }
330 }
331 }
332
333 for (iface, _) in self.interfaces {
334 let interface = iface();
335 assert!(
336 interface.is_interface(),
337 "Given class entry was not an interface."
338 );
339
340 unsafe { zend_do_implement_interface(class, ptr::from_ref(interface).cast_mut()) };
341 }
342
343 for (name, flags, _) in self.properties {
344 unsafe {
345 zend_declare_property(
346 class,
347 CString::new(name.as_str())?.as_ptr(),
348 name.len() as _,
349 &mut Zval::new(),
350 flags.bits().try_into()?,
351 );
352 }
353 }
354
355 for (name, value, _) in self.constants {
356 let value = Box::into_raw(Box::new(value()?));
357 unsafe {
358 zend_declare_class_constant(
359 class,
360 CString::new(name.as_str())?.as_ptr(),
361 name.len(),
362 value,
363 );
364 };
365 }
366
367 if let Some(object_override) = self.object_override {
368 class.__bindgen_anon_2.create_object = Some(object_override);
369 }
370
371 if let Some(register) = self.register {
372 register(class);
373 } else {
374 panic!("Class {} was not registered.", self.name);
375 }
376
377 Ok(())
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use crate::test::test_function;
384
385 use super::*;
386
387 #[test]
388 #[allow(unpredictable_function_pointer_comparisons)]
389 fn test_new() {
390 let class = ClassBuilder::new("Foo");
391 assert_eq!(class.name, "Foo");
392 assert_eq!(class.extends, None);
393 assert_eq!(class.interfaces, vec![]);
394 assert_eq!(class.methods.len(), 0);
395 assert_eq!(class.object_override, None);
396 assert_eq!(class.properties, vec![]);
397 assert_eq!(class.constants.len(), 0);
398 assert_eq!(class.register, None);
399 assert_eq!(class.docs, &[] as DocComments);
400 }
401
402 #[test]
403 fn test_extends() {
404 let extends: ClassEntryInfo = (|| todo!(), "Bar");
405 let class = ClassBuilder::new("Foo").extends(extends);
406 assert_eq!(class.extends, Some(extends));
407 }
408
409 #[test]
410 fn test_implements() {
411 let implements: ClassEntryInfo = (|| todo!(), "Bar");
412 let class = ClassBuilder::new("Foo").implements(implements);
413 assert_eq!(class.interfaces, vec![implements]);
414 }
415
416 #[test]
417 fn test_method() {
418 let method = FunctionBuilder::new("foo", test_function);
419 let class = ClassBuilder::new("Foo").method(method, MethodFlags::Public);
420 assert_eq!(class.methods.len(), 1);
421 }
422
423 #[test]
424 fn test_property() {
425 let class = ClassBuilder::new("Foo").property("bar", PropertyFlags::Public, &["Doc 1"]);
426 assert_eq!(
427 class.properties,
428 vec![(
429 "bar".to_string(),
430 PropertyFlags::Public,
431 &["Doc 1"] as DocComments
432 )]
433 );
434 }
435
436 #[test]
437 #[cfg(feature = "embed")]
438 fn test_constant() {
439 let class = ClassBuilder::new("Foo")
440 .constant("bar", 42, &["Doc 1"])
441 .expect("Failed to create constant");
442 assert_eq!(class.constants.len(), 1);
443 assert_eq!(class.constants[0].0, "bar");
444 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
445 }
446
447 #[test]
448 #[cfg(feature = "embed")]
449 fn test_dyn_constant() {
450 let class = ClassBuilder::new("Foo")
451 .dyn_constant("bar", &42, &["Doc 1"])
452 .expect("Failed to create constant");
453 assert_eq!(class.constants.len(), 1);
454 assert_eq!(class.constants[0].0, "bar");
455 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
456 }
457
458 #[test]
459 fn test_flags() {
460 let class = ClassBuilder::new("Foo").flags(ClassFlags::Abstract);
461 assert_eq!(class.ce.ce_flags, ClassFlags::Abstract.bits());
462 }
463
464 #[test]
465 fn test_registration() {
466 let class = ClassBuilder::new("Foo").registration(|_| {});
467 assert!(class.register.is_some());
468 }
469
470 #[test]
471 fn test_docs() {
472 let class = ClassBuilder::new("Foo").docs(&["Doc 1"]);
473 assert_eq!(class.docs, &["Doc 1"] as DocComments);
474 }
475
476 }