1use std::{convert::TryFrom, ffi::CString, mem, ptr};
2
3use super::{ClassBuilder, FunctionBuilder, class::first_duplicate};
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, ModuleAllocations, ModuleEntry, ModuleGlobal, ModuleGlobals},
13};
14#[cfg(feature = "enum")]
15use crate::{builders::enum_builder::EnumBuilder, enum_::RegisteredEnum};
16
17#[must_use]
48#[derive(Debug)]
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 globals_size: usize,
65 #[cfg(php_zts)]
66 globals_id_ptr: *mut i32,
67 #[cfg(not(php_zts))]
68 globals_ptr: *mut std::ffi::c_void,
69 globals_ctor: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
70 globals_dtor: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
71}
72
73impl Default for ModuleBuilder<'_> {
74 fn default() -> Self {
75 Self {
76 name: String::new(),
77 version: String::new(),
78 functions: vec![],
79 constants: vec![],
80 classes: vec![],
81 interfaces: vec![],
82 #[cfg(feature = "enum")]
83 enums: vec![],
84 startup_func: None,
85 shutdown_func: None,
86 request_startup_func: None,
87 request_shutdown_func: None,
88 post_deactivate_func: None,
89 info_func: None,
90 globals_size: 0,
91 #[cfg(php_zts)]
92 globals_id_ptr: ptr::null_mut(),
93 #[cfg(not(php_zts))]
94 globals_ptr: ptr::null_mut(),
95 globals_ctor: None,
96 globals_dtor: None,
97 }
98 }
99}
100
101impl ModuleBuilder<'_> {
102 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
109 Self {
110 name: name.into(),
111 version: version.into(),
112 ..Default::default()
113 }
114 }
115
116 pub fn name(mut self, name: impl Into<String>) -> Self {
122 self.name = name.into();
123 self
124 }
125
126 pub fn version(mut self, version: impl Into<String>) -> Self {
132 self.version = version.into();
133 self
134 }
135
136 pub fn startup_function(mut self, func: StartupShutdownFunc) -> Self {
142 self.startup_func = Some(func);
143 self
144 }
145
146 pub fn shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
152 self.shutdown_func = Some(func);
153 self
154 }
155
156 pub fn request_startup_function(mut self, func: StartupShutdownFunc) -> Self {
162 self.request_startup_func = Some(func);
163 self
164 }
165
166 pub fn request_shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
172 self.request_shutdown_func = Some(func);
173 self
174 }
175
176 pub fn post_deactivate_function(mut self, func: unsafe extern "C" fn() -> i32) -> Self {
186 self.post_deactivate_func = Some(func);
187 self
188 }
189
190 pub fn info_function(mut self, func: InfoFunc) -> Self {
197 self.info_func = Some(func);
198 self
199 }
200
201 pub fn globals<T: ModuleGlobal>(mut self, handle: &'static ModuleGlobals<T>) -> Self {
231 use crate::zend::module_globals::{ginit_callback, gshutdown_callback};
232
233 self.globals_size = std::mem::size_of::<T>();
234 #[cfg(php_zts)]
235 {
236 self.globals_id_ptr = handle.id_ptr();
237 }
238 #[cfg(not(php_zts))]
239 {
240 self.globals_ptr = handle.data_ptr();
241 }
242 self.globals_ctor = Some(ginit_callback::<T>);
243 self.globals_dtor = Some(gshutdown_callback::<T>);
244 self
245 }
246
247 #[cfg(feature = "observer")]
287 pub fn fcall_observer<F, O>(self, factory: F) -> Self
288 where
289 F: Fn() -> O + Send + Sync + 'static,
290 O: crate::zend::FcallObserver + Send + Sync,
291 {
292 let boxed_factory: Box<
293 dyn Fn() -> Box<dyn crate::zend::FcallObserver + Send + Sync> + Send + Sync,
294 > = Box::new(move || Box::new(factory()));
295 crate::zend::observer::register_fcall_observer_factory(boxed_factory);
296 self
297 }
298
299 #[cfg(feature = "observer")]
342 pub fn error_observer<F, O>(self, factory: F) -> Self
343 where
344 F: Fn() -> O + Send + Sync + 'static,
345 O: crate::zend::ErrorObserver + Send + Sync,
346 {
347 let boxed_factory: Box<
348 dyn Fn() -> Box<dyn crate::zend::ErrorObserver + Send + Sync> + Send + Sync,
349 > = Box::new(move || Box::new(factory()));
350 crate::zend::error_observer::register_error_observer_factory(boxed_factory);
351 self
352 }
353
354 #[cfg(feature = "observer")]
397 pub fn exception_observer<F, O>(self, factory: F) -> Self
398 where
399 F: Fn() -> O + Send + Sync + 'static,
400 O: crate::zend::ExceptionObserver + Send + Sync,
401 {
402 let boxed_factory: Box<
403 dyn Fn() -> Box<dyn crate::zend::ExceptionObserver + Send + Sync> + Send + Sync,
404 > = Box::new(move || Box::new(factory()));
405 crate::zend::exception_observer::register_exception_observer_factory(boxed_factory);
406 self
407 }
408
409 pub fn function(mut self, func: FunctionBuilder<'static>) -> Self {
415 self.functions.push(func);
416 self
417 }
418
419 pub fn constant(
428 mut self,
429 r#const: (&str, impl IntoConst + Send + 'static, DocComments),
430 ) -> Self {
431 let (name, val, docs) = r#const;
432 self.constants.push((
433 name.into(),
434 Box::new(val) as Box<dyn IntoConst + Send>,
435 docs,
436 ));
437 self
438 }
439
440 #[expect(
446 clippy::expect_used,
447 reason = "registration runs in MINIT, where a failure is an extension bug the engine cannot recover from"
448 )]
449 pub fn interface<T: RegisteredClass>(mut self) -> Self {
450 self.interfaces.push(|| {
451 let mut builder = ClassBuilder::new(T::CLASS_NAME);
452 for (method, flags) in T::method_builders() {
453 builder = builder.method(method, flags);
454 }
455 for interface in T::IMPLEMENTS {
456 builder = builder.implements(*interface);
457 }
458 for (name, value, docs, flags) in T::constants() {
459 builder = builder
460 .dyn_constant(*name, *value, docs, *flags)
461 .expect("Failed to register constant");
462 }
463
464 if let Some(modifier) = T::BUILDER_MODIFIER {
465 builder = modifier(builder);
466 }
467
468 builder = builder.flags(ClassFlags::Interface);
469 builder
472 .registration(|ce, arg_info| {
473 T::get_metadata().set_ce(ce);
474 T::get_metadata().set_arg_info(arg_info);
475 })
476 .docs(T::DOC_COMMENTS)
477 });
478 self
479 }
480
481 #[expect(
487 clippy::expect_used,
488 reason = "registration runs in MINIT, where a failure is an extension bug the engine cannot recover from"
489 )]
490 pub fn class<T: RegisteredClass>(mut self) -> Self {
491 self.classes.push(|| {
492 let mut builder = ClassBuilder::new(T::CLASS_NAME);
493 for (method, flags) in T::method_builders() {
494 builder = builder.method(method, flags);
495 }
496 for (method, flags) in T::interface_method_implementations() {
499 builder = builder.method(method, flags);
500 }
501 if let Some(parent) = T::EXTENDS {
502 builder = builder.extends(parent);
503 }
504 for interface in T::IMPLEMENTS {
506 builder = builder.implements(*interface);
507 }
508 for interface in T::interface_implementations() {
511 builder = builder.implements(interface);
512 }
513 for (name, value, docs, flags) in T::constants() {
514 builder = builder
515 .dyn_constant(*name, *value, docs, *flags)
516 .expect("Failed to register constant");
517 }
518 for desc in T::get_metadata().all_properties() {
519 let default_stub = if desc.nullable {
520 Some("null".into())
521 } else {
522 None
523 };
524 builder = builder.property(crate::builders::ClassProperty {
525 name: desc.name.into(),
526 flags: desc.flags,
527 default: None,
528 docs: desc.docs,
529 ty: Some(desc.ty),
530 nullable: desc.nullable,
531 readonly: desc.readonly,
532 default_stub,
533 });
534 }
535 for (name, flags, default, docs) in T::static_properties() {
536 let default_stub = default.map(crate::convert::IntoZvalDyn::stub_value);
537 let default_fn = default.map(|v| {
538 Box::new(move || v.as_zval(true))
539 as Box<dyn FnOnce() -> crate::error::Result<crate::types::Zval>>
540 });
541 builder = builder.property(crate::builders::ClassProperty {
542 name: (*name).into(),
543 flags: *flags,
544 default: default_fn,
545 docs,
546 ty: None,
547 nullable: false,
548 readonly: false,
549 default_stub,
550 });
551 }
552 if let Some(modifier) = T::BUILDER_MODIFIER {
553 builder = modifier(builder);
554 }
555
556 builder
557 .flags(T::FLAGS)
558 .object_override::<T>()
559 .registration(|ce, arg_info| {
560 T::get_metadata().set_ce(ce);
561 T::get_metadata().set_arg_info(arg_info);
562 })
563 .docs(T::DOC_COMMENTS)
564 });
565 self
566 }
567
568 #[cfg(feature = "enum")]
570 pub fn enumeration<T>(mut self) -> Self
571 where
572 T: RegisteredClass + RegisteredEnum,
573 {
574 self.enums.push(|| {
575 let mut builder = EnumBuilder::new(T::CLASS_NAME);
576 for case in T::CASES {
577 builder = builder.case(case);
578 }
579 for (method, flags) in T::method_builders() {
580 builder = builder.method(method, flags);
581 }
582
583 builder
584 .registration(|ce, arg_info| {
585 T::get_metadata().set_ce(ce);
586 T::get_metadata().set_arg_info(arg_info);
587 })
588 .docs(T::DOC_COMMENTS)
589 });
590
591 self
592 }
593}
594
595#[cfg(feature = "observer")]
596impl<'a> ModuleBuilder<'a> {
597 pub fn zend_extension<F, H>(
621 self,
622 factory: F,
623 ) -> crate::zend::zend_extension::ZendExtensionBuilder<'a>
624 where
625 F: Fn() -> H + Send + Sync + 'static,
626 H: crate::zend::ZendExtensionHandler,
627 {
628 crate::zend::zend_extension::ZendExtensionBuilder::new(self, factory)
629 }
630}
631
632pub struct ModuleStartup {
635 #[cfg(feature = "observer")]
636 name: String,
637 #[cfg(feature = "observer")]
638 version: String,
639 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
640 classes: Vec<fn() -> ClassBuilder>,
641 interfaces: Vec<fn() -> ClassBuilder>,
642 #[cfg(feature = "enum")]
643 enums: Vec<fn() -> EnumBuilder>,
644}
645
646impl ModuleStartup {
647 pub fn startup(&self, _ty: i32, mod_num: i32) -> Result<()> {
658 for (name, val) in &self.constants {
659 val.register_constant(name, mod_num)?;
660 }
661
662 self.interfaces.iter().try_for_each(|c| c().register())?;
665
666 self.classes.iter().try_for_each(|c| c().register())?;
667
668 #[cfg(feature = "enum")]
669 self.enums
670 .iter()
671 .try_for_each(|builder| builder().register())?;
672
673 #[cfg(feature = "observer")]
675 unsafe {
676 crate::zend::observer::observer_startup();
677 crate::zend::error_observer::error_observer_startup();
678 crate::zend::exception_observer::exception_observer_startup();
679 crate::zend::zend_extension::zend_extension_startup(&self.name, &self.version);
680 }
681
682 Ok(())
683 }
684}
685
686pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
688
689pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
691
692impl ModuleBuilder<'_> {
693 fn validate(&self) -> Result<()> {
696 match first_duplicate(self.functions.iter().map(|f| f.name.as_str()), true) {
697 Some(name) => Err(crate::error::Error::DuplicateFunction {
698 function: name.to_owned(),
699 }),
700 None => Ok(()),
701 }
702 }
703}
704
705impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup, ModuleAllocations) {
708 type Error = crate::error::Error;
709
710 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
711 builder.validate()?;
712 let mut arg_info = Vec::with_capacity(builder.functions.len());
713 let mut function_table = Vec::with_capacity(builder.functions.len() + 1);
714 for function in builder.functions {
715 let (entry, args) = function.build()?;
716 function_table.push(entry);
717 arg_info.push(args);
718 }
719 function_table.push(FunctionEntry::end());
720 let function_table = function_table.into_boxed_slice();
721 let functions = function_table.as_ptr();
722
723 #[cfg(feature = "observer")]
724 let ext_name = builder.name.clone();
725 #[cfg(feature = "observer")]
726 let ext_version = builder.version.clone();
727
728 let owned_name = CString::new(builder.name)?;
729 let owned_version = CString::new(builder.version)?;
730 let name = owned_name.as_ptr();
731 let version = owned_version.as_ptr();
732
733 let startup = ModuleStartup {
734 #[cfg(feature = "observer")]
735 name: ext_name,
736 #[cfg(feature = "observer")]
737 version: ext_version,
738 constants: builder
739 .constants
740 .into_iter()
741 .map(|(n, v, _)| (n, v))
742 .collect(),
743 classes: builder.classes,
744 interfaces: builder.interfaces,
745 #[cfg(feature = "enum")]
746 enums: builder.enums,
747 };
748
749 #[cfg(not(php_zts))]
750 let module_entry = ModuleEntry {
751 size: mem::size_of::<ModuleEntry>().try_into()?,
752 zend_api: ZEND_MODULE_API_NO,
753 zend_debug: u8::from(PHP_DEBUG),
754 zts: u8::from(PHP_ZTS),
755 ini_entry: ptr::null(),
756 deps: ptr::null(),
757 name,
758 functions,
759 module_startup_func: builder.startup_func,
760 module_shutdown_func: builder.shutdown_func,
761 request_startup_func: builder.request_startup_func,
762 request_shutdown_func: builder.request_shutdown_func,
763 info_func: builder.info_func,
764 version,
765 globals_size: builder.globals_size,
766 globals_ptr: builder.globals_ptr,
767 globals_ctor: builder.globals_ctor,
768 globals_dtor: builder.globals_dtor,
769 post_deactivate_func: builder.post_deactivate_func,
770 module_started: 0,
771 type_: 0,
772 handle: ptr::null_mut(),
773 module_number: 0,
774 build_id: unsafe { ext_php_rs_php_build_id() },
775 };
776
777 #[cfg(php_zts)]
778 let module_entry = ModuleEntry {
779 size: mem::size_of::<ModuleEntry>().try_into()?,
780 zend_api: ZEND_MODULE_API_NO,
781 zend_debug: u8::from(PHP_DEBUG),
782 zts: u8::from(PHP_ZTS),
783 ini_entry: ptr::null(),
784 deps: ptr::null(),
785 name,
786 functions,
787 module_startup_func: builder.startup_func,
788 module_shutdown_func: builder.shutdown_func,
789 request_startup_func: builder.request_startup_func,
790 request_shutdown_func: builder.request_shutdown_func,
791 info_func: builder.info_func,
792 version,
793 globals_size: builder.globals_size,
794 globals_id_ptr: builder.globals_id_ptr,
795 globals_ctor: builder.globals_ctor,
796 globals_dtor: builder.globals_dtor,
797 post_deactivate_func: builder.post_deactivate_func,
798 module_started: 0,
799 type_: 0,
800 handle: ptr::null_mut(),
801 module_number: 0,
802 build_id: unsafe { ext_php_rs_php_build_id() },
803 };
804
805 Ok((
806 module_entry,
807 startup,
808 ModuleAllocations {
809 functions: function_table,
810 arg_info: arg_info.into_boxed_slice(),
811 name: owned_name,
812 version: owned_version,
813 },
814 ))
815 }
816}
817
818#[cfg(test)]
819mod tests {
820 use crate::test::{
821 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
822 };
823
824 use super::*;
825
826 #[test]
827 fn test_new() {
828 let builder = ModuleBuilder::new("test", "1.0");
829 assert_eq!(builder.name, "test");
830 assert_eq!(builder.version, "1.0");
831 assert!(builder.functions.is_empty());
832 assert!(builder.constants.is_empty());
833 assert_eq!(builder.classes.len(), 0);
834 assert_eq!(builder.interfaces.len(), 0);
835 assert!(builder.startup_func.is_none());
836 assert!(builder.shutdown_func.is_none());
837 assert!(builder.request_startup_func.is_none());
838 assert!(builder.request_shutdown_func.is_none());
839 assert!(builder.post_deactivate_func.is_none());
840 assert!(builder.info_func.is_none());
841 #[cfg(feature = "enum")]
842 assert_eq!(builder.enums.len(), 0);
843 }
844
845 #[test]
846 fn functions_that_differ_only_by_case_fail_the_module_build() {
847 let builder = ModuleBuilder::new("test", "1.0")
848 .function(FunctionBuilder::new("say_hello", test_function))
849 .function(FunctionBuilder::new("Say_Hello", test_function));
850 let built = <(ModuleEntry, ModuleStartup, ModuleAllocations)>::try_from(builder);
851 assert!(matches!(
852 built,
853 Err(crate::error::Error::DuplicateFunction { function }) if function == "Say_Hello"
854 ));
855 }
856
857 #[test]
858 fn test_name() {
859 let builder = ModuleBuilder::new("test", "1.0").name("new_test");
860 assert_eq!(builder.name, "new_test");
861 }
862
863 #[test]
864 fn test_version() {
865 let builder = ModuleBuilder::new("test", "1.0").version("2.0");
866 assert_eq!(builder.version, "2.0");
867 }
868
869 #[test]
870 fn test_startup_function() {
871 let builder =
872 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
873 assert!(builder.startup_func.is_some());
874 }
875
876 #[test]
877 fn test_shutdown_function() {
878 let builder =
879 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
880 assert!(builder.shutdown_func.is_some());
881 }
882
883 #[test]
884 fn test_request_startup_function() {
885 let builder = ModuleBuilder::new("test", "1.0")
886 .request_startup_function(test_startup_shutdown_function);
887 assert!(builder.request_startup_func.is_some());
888 }
889
890 #[test]
891 fn test_request_shutdown_function() {
892 let builder = ModuleBuilder::new("test", "1.0")
893 .request_shutdown_function(test_startup_shutdown_function);
894 assert!(builder.request_shutdown_func.is_some());
895 }
896
897 #[test]
898 fn test_set_post_deactivate_function() {
899 let builder =
900 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
901 assert!(builder.post_deactivate_func.is_some());
902 }
903
904 #[test]
905 fn test_set_info_function() {
906 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
907 assert!(builder.info_func.is_some());
908 }
909
910 #[test]
911 fn test_add_function() {
912 let builder =
913 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
914 assert_eq!(builder.functions.len(), 1);
915 }
916
917 #[test]
918 #[cfg(feature = "embed")]
919 fn test_add_constant() {
920 let builder =
921 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
922 assert_eq!(builder.constants.len(), 1);
923 assert_eq!(builder.constants[0].0, "TEST_CONST");
924 assert_eq!(builder.constants[0].2, DocComments::default());
926 }
927}