1use std::{convert::TryFrom, ffi::CString, mem, ptr};
2
3use super::{ClassBuilder, FunctionBuilder};
4use crate::{
5 PHP_DEBUG, PHP_ZTS,
6 class::RegisteredClass,
7 constant::IntoConst,
8 describe::DocComments,
9 error::Result,
10 ffi::{ZEND_MODULE_API_NO, ext_php_rs_php_build_id},
11 flags::ClassFlags,
12 zend::{FunctionEntry, ModuleEntry},
13};
14#[cfg(feature = "enum")]
15use crate::{builders::enum_builder::EnumBuilder, enum_::RegisteredEnum};
16
17#[must_use]
48#[derive(Debug, Default)]
49pub struct ModuleBuilder<'a> {
50 pub(crate) name: String,
51 pub(crate) version: String,
52 pub(crate) functions: Vec<FunctionBuilder<'a>>,
53 pub(crate) constants: Vec<(String, Box<dyn IntoConst + Send>, DocComments)>,
54 pub(crate) classes: Vec<fn() -> ClassBuilder>,
55 pub(crate) interfaces: Vec<fn() -> ClassBuilder>,
56 #[cfg(feature = "enum")]
57 pub(crate) enums: Vec<fn() -> EnumBuilder>,
58 startup_func: Option<StartupShutdownFunc>,
59 shutdown_func: Option<StartupShutdownFunc>,
60 request_startup_func: Option<StartupShutdownFunc>,
61 request_shutdown_func: Option<StartupShutdownFunc>,
62 post_deactivate_func: Option<unsafe extern "C" fn() -> i32>,
63 info_func: Option<InfoFunc>,
64}
65
66impl ModuleBuilder<'_> {
67 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
74 Self {
75 name: name.into(),
76 version: version.into(),
77 functions: vec![],
78 constants: vec![],
79 classes: vec![],
80 ..Default::default()
81 }
82 }
83
84 pub fn name(mut self, name: impl Into<String>) -> Self {
90 self.name = name.into();
91 self
92 }
93
94 pub fn version(mut self, version: impl Into<String>) -> Self {
100 self.version = version.into();
101 self
102 }
103
104 pub fn startup_function(mut self, func: StartupShutdownFunc) -> Self {
110 self.startup_func = Some(func);
111 self
112 }
113
114 pub fn shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
120 self.shutdown_func = Some(func);
121 self
122 }
123
124 pub fn request_startup_function(mut self, func: StartupShutdownFunc) -> Self {
130 self.request_startup_func = Some(func);
131 self
132 }
133
134 pub fn request_shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
140 self.request_shutdown_func = Some(func);
141 self
142 }
143
144 pub fn post_deactivate_function(mut self, func: unsafe extern "C" fn() -> i32) -> Self {
154 self.post_deactivate_func = Some(func);
155 self
156 }
157
158 pub fn info_function(mut self, func: InfoFunc) -> Self {
165 self.info_func = Some(func);
166 self
167 }
168
169 #[cfg(feature = "observer")]
207 pub fn fcall_observer<F, O>(self, factory: F) -> Self
208 where
209 F: Fn() -> O + Send + Sync + 'static,
210 O: crate::zend::FcallObserver + Send + Sync,
211 {
212 let boxed_factory: Box<
213 dyn Fn() -> Box<dyn crate::zend::FcallObserver + Send + Sync> + Send + Sync,
214 > = Box::new(move || Box::new(factory()));
215 crate::zend::observer::register_fcall_observer_factory(boxed_factory);
216 self
217 }
218
219 #[cfg(feature = "observer")]
260 pub fn error_observer<F, O>(self, factory: F) -> Self
261 where
262 F: Fn() -> O + Send + Sync + 'static,
263 O: crate::zend::ErrorObserver + Send + Sync,
264 {
265 let boxed_factory: Box<
266 dyn Fn() -> Box<dyn crate::zend::ErrorObserver + Send + Sync> + Send + Sync,
267 > = Box::new(move || Box::new(factory()));
268 crate::zend::error_observer::register_error_observer_factory(boxed_factory);
269 self
270 }
271
272 #[cfg(feature = "observer")]
313 pub fn exception_observer<F, O>(self, factory: F) -> Self
314 where
315 F: Fn() -> O + Send + Sync + 'static,
316 O: crate::zend::ExceptionObserver + Send + Sync,
317 {
318 let boxed_factory: Box<
319 dyn Fn() -> Box<dyn crate::zend::ExceptionObserver + Send + Sync> + Send + Sync,
320 > = Box::new(move || Box::new(factory()));
321 crate::zend::exception_observer::register_exception_observer_factory(boxed_factory);
322 self
323 }
324
325 pub fn function(mut self, func: FunctionBuilder<'static>) -> Self {
331 self.functions.push(func);
332 self
333 }
334
335 pub fn constant(
344 mut self,
345 r#const: (&str, impl IntoConst + Send + 'static, DocComments),
346 ) -> Self {
347 let (name, val, docs) = r#const;
348 self.constants.push((
349 name.into(),
350 Box::new(val) as Box<dyn IntoConst + Send>,
351 docs,
352 ));
353 self
354 }
355
356 pub fn interface<T: RegisteredClass>(mut self) -> Self {
362 self.interfaces.push(|| {
363 let mut builder = ClassBuilder::new(T::CLASS_NAME);
364 for (method, flags) in T::method_builders() {
365 builder = builder.method(method, flags);
366 }
367 for interface in T::IMPLEMENTS {
368 builder = builder.implements(*interface);
369 }
370 for (name, value, docs) in T::constants() {
371 builder = builder
372 .dyn_constant(*name, *value, docs)
373 .expect("Failed to register constant");
374 }
375
376 if let Some(modifier) = T::BUILDER_MODIFIER {
377 builder = modifier(builder);
378 }
379
380 builder = builder.flags(ClassFlags::Interface);
381 builder
384 .registration(|ce| {
385 T::get_metadata().set_ce(ce);
386 })
387 .docs(T::DOC_COMMENTS)
388 });
389 self
390 }
391
392 pub fn class<T: RegisteredClass>(mut self) -> Self {
398 self.classes.push(|| {
399 let mut builder = ClassBuilder::new(T::CLASS_NAME);
400 for (method, flags) in T::method_builders() {
401 builder = builder.method(method, flags);
402 }
403 for (method, flags) in T::interface_method_implementations() {
406 builder = builder.method(method, flags);
407 }
408 if let Some(parent) = T::EXTENDS {
409 builder = builder.extends(parent);
410 }
411 for interface in T::IMPLEMENTS {
413 builder = builder.implements(*interface);
414 }
415 for interface in T::interface_implementations() {
418 builder = builder.implements(interface);
419 }
420 for (name, value, docs) in T::constants() {
421 builder = builder
422 .dyn_constant(*name, *value, docs)
423 .expect("Failed to register constant");
424 }
425 for (name, prop_info) in T::get_properties() {
426 let default_stub = if prop_info.nullable {
427 Some("null".into())
428 } else {
429 None
430 };
431 builder = builder.property(crate::builders::ClassProperty {
432 name: name.into(),
433 flags: prop_info.flags,
434 default: None,
435 docs: prop_info.docs,
436 ty: Some(prop_info.ty),
437 nullable: prop_info.nullable,
438 readonly: prop_info.readonly,
439 default_stub,
440 });
441 }
442 for (name, flags, default, docs) in T::static_properties() {
443 let default_stub = default.map(crate::convert::IntoZvalDyn::stub_value);
444 let default_fn = default.map(|v| {
445 Box::new(move || v.as_zval(true))
446 as Box<dyn FnOnce() -> crate::error::Result<crate::types::Zval>>
447 });
448 builder = builder.property(crate::builders::ClassProperty {
449 name: (*name).into(),
450 flags: *flags,
451 default: default_fn,
452 docs,
453 ty: None,
454 nullable: false,
455 readonly: false,
456 default_stub,
457 });
458 }
459 if let Some(modifier) = T::BUILDER_MODIFIER {
460 builder = modifier(builder);
461 }
462
463 builder
464 .flags(T::FLAGS)
465 .object_override::<T>()
466 .registration(|ce| {
467 T::get_metadata().set_ce(ce);
468 })
469 .docs(T::DOC_COMMENTS)
470 });
471 self
472 }
473
474 #[cfg(feature = "enum")]
476 pub fn enumeration<T>(mut self) -> Self
477 where
478 T: RegisteredClass + RegisteredEnum,
479 {
480 self.enums.push(|| {
481 let mut builder = EnumBuilder::new(T::CLASS_NAME);
482 for case in T::CASES {
483 builder = builder.case(case);
484 }
485 for (method, flags) in T::method_builders() {
486 builder = builder.method(method, flags);
487 }
488
489 builder
490 .registration(|ce| {
491 T::get_metadata().set_ce(ce);
492 })
493 .docs(T::DOC_COMMENTS)
494 });
495
496 self
497 }
498}
499
500pub struct ModuleStartup {
503 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
504 classes: Vec<fn() -> ClassBuilder>,
505 interfaces: Vec<fn() -> ClassBuilder>,
506 #[cfg(feature = "enum")]
507 enums: Vec<fn() -> EnumBuilder>,
508}
509
510impl ModuleStartup {
511 pub fn startup(self, _ty: i32, mod_num: i32) -> Result<()> {
522 for (name, val) in self.constants {
523 val.register_constant(&name, mod_num)?;
524 }
525
526 self.interfaces.into_iter().map(|c| c()).for_each(|c| {
529 c.register().expect("Failed to build interface");
530 });
531
532 self.classes.into_iter().map(|c| c()).for_each(|c| {
533 c.register().expect("Failed to build class");
534 });
535
536 #[cfg(feature = "enum")]
537 self.enums
538 .into_iter()
539 .map(|builder| builder())
540 .for_each(|e| {
541 e.register().expect("Failed to build enum");
542 });
543
544 #[cfg(feature = "observer")]
546 unsafe {
547 crate::zend::observer::observer_startup();
548 crate::zend::error_observer::error_observer_startup();
549 crate::zend::exception_observer::exception_observer_startup();
550 }
551
552 Ok(())
553 }
554}
555
556pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
558
559pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
561
562impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup) {
565 type Error = crate::error::Error;
566
567 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
568 let mut functions = builder
569 .functions
570 .into_iter()
571 .map(FunctionBuilder::build)
572 .collect::<Result<Vec<_>>>()?;
573 functions.push(FunctionEntry::end());
574 let functions = Box::into_raw(functions.into_boxed_slice()) as *const FunctionEntry;
575
576 let name = CString::new(builder.name)?.into_raw();
577 let version = CString::new(builder.version)?.into_raw();
578
579 let startup = ModuleStartup {
580 constants: builder
581 .constants
582 .into_iter()
583 .map(|(n, v, _)| (n, v))
584 .collect(),
585 classes: builder.classes,
586 interfaces: builder.interfaces,
587 #[cfg(feature = "enum")]
588 enums: builder.enums,
589 };
590
591 #[cfg(not(php_zts))]
592 let module_entry = ModuleEntry {
593 size: mem::size_of::<ModuleEntry>().try_into()?,
594 zend_api: ZEND_MODULE_API_NO,
595 zend_debug: u8::from(PHP_DEBUG),
596 zts: u8::from(PHP_ZTS),
597 ini_entry: ptr::null(),
598 deps: ptr::null(),
599 name,
600 functions,
601 module_startup_func: builder.startup_func,
602 module_shutdown_func: builder.shutdown_func,
603 request_startup_func: builder.request_startup_func,
604 request_shutdown_func: builder.request_shutdown_func,
605 info_func: builder.info_func,
606 version,
607 globals_size: 0,
608 globals_ptr: ptr::null_mut(),
609 globals_ctor: None,
610 globals_dtor: None,
611 post_deactivate_func: builder.post_deactivate_func,
612 module_started: 0,
613 type_: 0,
614 handle: ptr::null_mut(),
615 module_number: 0,
616 build_id: unsafe { ext_php_rs_php_build_id() },
617 };
618
619 #[cfg(php_zts)]
620 let module_entry = ModuleEntry {
621 size: mem::size_of::<ModuleEntry>().try_into()?,
622 zend_api: ZEND_MODULE_API_NO,
623 zend_debug: u8::from(PHP_DEBUG),
624 zts: u8::from(PHP_ZTS),
625 ini_entry: ptr::null(),
626 deps: ptr::null(),
627 name,
628 functions,
629 module_startup_func: builder.startup_func,
630 module_shutdown_func: builder.shutdown_func,
631 request_startup_func: builder.request_startup_func,
632 request_shutdown_func: builder.request_shutdown_func,
633 info_func: builder.info_func,
634 version,
635 globals_size: 0,
636 globals_id_ptr: ptr::null_mut(),
637 globals_ctor: None,
638 globals_dtor: None,
639 post_deactivate_func: builder.post_deactivate_func,
640 module_started: 0,
641 type_: 0,
642 handle: ptr::null_mut(),
643 module_number: 0,
644 build_id: unsafe { ext_php_rs_php_build_id() },
645 };
646
647 Ok((module_entry, startup))
648 }
649}
650
651#[cfg(test)]
652mod tests {
653 use crate::test::{
654 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
655 };
656
657 use super::*;
658
659 #[test]
660 fn test_new() {
661 let builder = ModuleBuilder::new("test", "1.0");
662 assert_eq!(builder.name, "test");
663 assert_eq!(builder.version, "1.0");
664 assert!(builder.functions.is_empty());
665 assert!(builder.constants.is_empty());
666 assert!(builder.classes.is_empty());
667 assert!(builder.interfaces.is_empty());
668 assert!(builder.startup_func.is_none());
669 assert!(builder.shutdown_func.is_none());
670 assert!(builder.request_startup_func.is_none());
671 assert!(builder.request_shutdown_func.is_none());
672 assert!(builder.post_deactivate_func.is_none());
673 assert!(builder.info_func.is_none());
674 #[cfg(feature = "enum")]
675 assert!(builder.enums.is_empty());
676 }
677
678 #[test]
679 fn test_name() {
680 let builder = ModuleBuilder::new("test", "1.0").name("new_test");
681 assert_eq!(builder.name, "new_test");
682 }
683
684 #[test]
685 fn test_version() {
686 let builder = ModuleBuilder::new("test", "1.0").version("2.0");
687 assert_eq!(builder.version, "2.0");
688 }
689
690 #[test]
691 fn test_startup_function() {
692 let builder =
693 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
694 assert!(builder.startup_func.is_some());
695 }
696
697 #[test]
698 fn test_shutdown_function() {
699 let builder =
700 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
701 assert!(builder.shutdown_func.is_some());
702 }
703
704 #[test]
705 fn test_request_startup_function() {
706 let builder = ModuleBuilder::new("test", "1.0")
707 .request_startup_function(test_startup_shutdown_function);
708 assert!(builder.request_startup_func.is_some());
709 }
710
711 #[test]
712 fn test_request_shutdown_function() {
713 let builder = ModuleBuilder::new("test", "1.0")
714 .request_shutdown_function(test_startup_shutdown_function);
715 assert!(builder.request_shutdown_func.is_some());
716 }
717
718 #[test]
719 fn test_set_post_deactivate_function() {
720 let builder =
721 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
722 assert!(builder.post_deactivate_func.is_some());
723 }
724
725 #[test]
726 fn test_set_info_function() {
727 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
728 assert!(builder.info_func.is_some());
729 }
730
731 #[test]
732 fn test_add_function() {
733 let builder =
734 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
735 assert_eq!(builder.functions.len(), 1);
736 }
737
738 #[test]
739 #[cfg(feature = "embed")]
740 fn test_add_constant() {
741 let builder =
742 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
743 assert_eq!(builder.constants.len(), 1);
744 assert_eq!(builder.constants[0].0, "TEST_CONST");
745 assert_eq!(builder.constants[0].2, DocComments::default());
747 }
748}