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::{
13 FunctionEntry, ModuleAllocations, ModuleEntry, ModuleGlobal, ModuleGlobals,
14 SapiRequestInfo,
15 sapi_activate::{self, ActivateCallback},
16 },
17};
18#[cfg(feature = "enum")]
19use crate::{builders::enum_builder::EnumBuilder, enum_::RegisteredEnum};
20
21#[must_use]
52#[derive(Debug)]
53pub struct ModuleBuilder<'a> {
54 pub(crate) name: String,
55 pub(crate) version: String,
56 pub(crate) functions: Vec<FunctionBuilder<'a>>,
57 pub(crate) constants: Vec<(String, Box<dyn IntoConst + Send>, DocComments)>,
58 pub(crate) classes: Vec<fn() -> ClassBuilder>,
59 pub(crate) interfaces: Vec<fn() -> ClassBuilder>,
60 #[cfg(feature = "enum")]
61 pub(crate) enums: Vec<fn() -> EnumBuilder>,
62 startup_func: Option<StartupShutdownFunc>,
63 shutdown_func: Option<StartupShutdownFunc>,
64 request_startup_func: Option<StartupShutdownFunc>,
65 request_shutdown_func: Option<StartupShutdownFunc>,
66 post_deactivate_func: Option<unsafe extern "C" fn() -> i32>,
67 sapi_activate: Option<ActivateCallback>,
68 info_func: Option<InfoFunc>,
69 globals_size: usize,
70 #[cfg(php_zts)]
71 globals_id_ptr: *mut i32,
72 #[cfg(not(php_zts))]
73 globals_ptr: *mut std::ffi::c_void,
74 globals_ctor: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
75 globals_dtor: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
76}
77
78impl Default for ModuleBuilder<'_> {
79 fn default() -> Self {
80 Self {
81 name: String::new(),
82 version: String::new(),
83 functions: vec![],
84 constants: vec![],
85 classes: vec![],
86 interfaces: vec![],
87 #[cfg(feature = "enum")]
88 enums: vec![],
89 startup_func: None,
90 shutdown_func: None,
91 request_startup_func: None,
92 request_shutdown_func: None,
93 post_deactivate_func: None,
94 sapi_activate: None,
95 info_func: None,
96 globals_size: 0,
97 #[cfg(php_zts)]
98 globals_id_ptr: ptr::null_mut(),
99 #[cfg(not(php_zts))]
100 globals_ptr: ptr::null_mut(),
101 globals_ctor: None,
102 globals_dtor: None,
103 }
104 }
105}
106
107impl ModuleBuilder<'_> {
108 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
115 Self {
116 name: name.into(),
117 version: version.into(),
118 ..Default::default()
119 }
120 }
121
122 pub fn name(mut self, name: impl Into<String>) -> Self {
128 self.name = name.into();
129 self
130 }
131
132 pub fn version(mut self, version: impl Into<String>) -> Self {
138 self.version = version.into();
139 self
140 }
141
142 pub fn startup_function(mut self, func: StartupShutdownFunc) -> Self {
148 self.startup_func = Some(func);
149 self
150 }
151
152 pub fn shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
158 self.shutdown_func = Some(func);
159 self
160 }
161
162 pub fn request_startup_function(mut self, func: StartupShutdownFunc) -> Self {
168 self.request_startup_func = Some(func);
169 self
170 }
171
172 pub fn request_shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
178 self.request_shutdown_func = Some(func);
179 self
180 }
181
182 pub fn post_deactivate_function(mut self, func: unsafe extern "C" fn() -> i32) -> Self {
192 self.post_deactivate_func = Some(func);
193 self
194 }
195
196 pub fn sapi_activate_function<F>(mut self, callback: F) -> Self
225 where
226 F: Fn(&SapiRequestInfo) + Send + Sync + 'static,
227 {
228 self.sapi_activate = Some(ActivateCallback::new(callback));
229 self
230 }
231
232 pub fn info_function(mut self, func: InfoFunc) -> Self {
239 self.info_func = Some(func);
240 self
241 }
242
243 pub fn globals<T: ModuleGlobal>(mut self, handle: &'static ModuleGlobals<T>) -> Self {
273 use crate::zend::module_globals::{ginit_callback, gshutdown_callback};
274
275 self.globals_size = std::mem::size_of::<T>();
276 #[cfg(php_zts)]
277 {
278 self.globals_id_ptr = handle.id_ptr();
279 }
280 #[cfg(not(php_zts))]
281 {
282 self.globals_ptr = handle.data_ptr();
283 }
284 self.globals_ctor = Some(ginit_callback::<T>);
285 self.globals_dtor = Some(gshutdown_callback::<T>);
286 self
287 }
288
289 #[cfg(feature = "observer")]
329 pub fn fcall_observer<F, O>(self, factory: F) -> Self
330 where
331 F: Fn() -> O + Send + Sync + 'static,
332 O: crate::zend::FcallObserver + Send + Sync,
333 {
334 let boxed_factory: Box<
335 dyn Fn() -> Box<dyn crate::zend::FcallObserver + Send + Sync> + Send + Sync,
336 > = Box::new(move || Box::new(factory()));
337 crate::zend::observer::register_fcall_observer_factory(boxed_factory);
338 self
339 }
340
341 #[cfg(feature = "observer")]
384 pub fn error_observer<F, O>(self, factory: F) -> Self
385 where
386 F: Fn() -> O + Send + Sync + 'static,
387 O: crate::zend::ErrorObserver + Send + Sync,
388 {
389 let boxed_factory: Box<
390 dyn Fn() -> Box<dyn crate::zend::ErrorObserver + Send + Sync> + Send + Sync,
391 > = Box::new(move || Box::new(factory()));
392 crate::zend::error_observer::register_error_observer_factory(boxed_factory);
393 self
394 }
395
396 #[cfg(feature = "observer")]
439 pub fn exception_observer<F, O>(self, factory: F) -> Self
440 where
441 F: Fn() -> O + Send + Sync + 'static,
442 O: crate::zend::ExceptionObserver + Send + Sync,
443 {
444 let boxed_factory: Box<
445 dyn Fn() -> Box<dyn crate::zend::ExceptionObserver + Send + Sync> + Send + Sync,
446 > = Box::new(move || Box::new(factory()));
447 crate::zend::exception_observer::register_exception_observer_factory(boxed_factory);
448 self
449 }
450
451 pub fn function(mut self, func: FunctionBuilder<'static>) -> Self {
457 self.functions.push(func);
458 self
459 }
460
461 pub fn constant(
470 mut self,
471 r#const: (&str, impl IntoConst + Send + 'static, DocComments),
472 ) -> Self {
473 let (name, val, docs) = r#const;
474 self.constants.push((
475 name.into(),
476 Box::new(val) as Box<dyn IntoConst + Send>,
477 docs,
478 ));
479 self
480 }
481
482 #[expect(
488 clippy::expect_used,
489 reason = "registration runs in MINIT, where a failure is an extension bug the engine cannot recover from"
490 )]
491 pub fn interface<T: RegisteredClass>(mut self) -> Self {
492 self.interfaces.push(|| {
493 let mut builder = ClassBuilder::new(T::CLASS_NAME);
494 for (method, flags) in T::method_builders() {
495 builder = builder.method(method, flags);
496 }
497 for interface in T::IMPLEMENTS {
498 builder = builder.implements(*interface);
499 }
500 for (name, value, docs, flags) in T::constants() {
501 builder = builder
502 .dyn_constant(*name, *value, docs, *flags)
503 .expect("Failed to register constant");
504 }
505
506 if let Some(modifier) = T::BUILDER_MODIFIER {
507 builder = modifier(builder);
508 }
509
510 builder = builder.flags(ClassFlags::Interface);
511 builder
514 .registration(|ce, arg_info| {
515 T::get_metadata().set_ce(ce);
516 T::get_metadata().set_arg_info(arg_info);
517 })
518 .docs(T::DOC_COMMENTS)
519 });
520 self
521 }
522
523 #[expect(
529 clippy::expect_used,
530 reason = "registration runs in MINIT, where a failure is an extension bug the engine cannot recover from"
531 )]
532 pub fn class<T: RegisteredClass>(mut self) -> Self {
533 self.classes.push(|| {
534 let mut builder = ClassBuilder::new(T::CLASS_NAME);
535 for (method, flags) in T::method_builders() {
536 builder = builder.method(method, flags);
537 }
538 for (method, flags) in T::interface_method_implementations() {
541 builder = builder.method(method, flags);
542 }
543 if let Some(parent) = T::EXTENDS {
544 builder = builder.extends(parent);
545 }
546 for interface in T::IMPLEMENTS {
548 builder = builder.implements(*interface);
549 }
550 for interface in T::interface_implementations() {
553 builder = builder.implements(interface);
554 }
555 for (name, value, docs, flags) in T::constants() {
556 builder = builder
557 .dyn_constant(*name, *value, docs, *flags)
558 .expect("Failed to register constant");
559 }
560 for desc in T::get_metadata().all_properties() {
561 let default_stub = if desc.nullable {
562 Some("null".into())
563 } else {
564 None
565 };
566 builder = builder.property(crate::builders::ClassProperty {
567 name: desc.name.into(),
568 flags: desc.flags,
569 default: None,
570 docs: desc.docs,
571 ty: Some(desc.ty),
572 nullable: desc.nullable,
573 readonly: desc.readonly,
574 default_stub,
575 });
576 }
577 for (name, flags, default, docs) in T::static_properties() {
578 let default_stub = default.map(crate::convert::IntoZvalDyn::stub_value);
579 let default_fn = default.map(|v| {
580 Box::new(move || v.as_zval(true))
581 as Box<dyn FnOnce() -> crate::error::Result<crate::types::Zval>>
582 });
583 builder = builder.property(crate::builders::ClassProperty {
584 name: (*name).into(),
585 flags: *flags,
586 default: default_fn,
587 docs,
588 ty: None,
589 nullable: false,
590 readonly: false,
591 default_stub,
592 });
593 }
594 if let Some(modifier) = T::BUILDER_MODIFIER {
595 builder = modifier(builder);
596 }
597
598 builder
599 .flags(T::FLAGS)
600 .object_override::<T>()
601 .registration(|ce, arg_info| {
602 T::get_metadata().set_ce(ce);
603 T::get_metadata().set_arg_info(arg_info);
604 })
605 .docs(T::DOC_COMMENTS)
606 });
607 self
608 }
609
610 #[cfg(feature = "enum")]
612 pub fn enumeration<T>(mut self) -> Self
613 where
614 T: RegisteredClass + RegisteredEnum,
615 {
616 self.enums.push(|| {
617 let mut builder = EnumBuilder::new(T::CLASS_NAME);
618 for case in T::CASES {
619 builder = builder.case(case);
620 }
621 for (method, flags) in T::method_builders() {
622 builder = builder.method(method, flags);
623 }
624
625 builder
626 .registration(|ce, arg_info| {
627 T::get_metadata().set_ce(ce);
628 T::get_metadata().set_arg_info(arg_info);
629 })
630 .docs(T::DOC_COMMENTS)
631 });
632
633 self
634 }
635}
636
637#[cfg(feature = "observer")]
638impl<'a> ModuleBuilder<'a> {
639 pub fn zend_extension<F, H>(
663 self,
664 factory: F,
665 ) -> crate::zend::zend_extension::ZendExtensionBuilder<'a>
666 where
667 F: Fn() -> H + Send + Sync + 'static,
668 H: crate::zend::ZendExtensionHandler,
669 {
670 crate::zend::zend_extension::ZendExtensionBuilder::new(self, factory)
671 }
672}
673
674pub struct ModuleStartup {
677 #[cfg(feature = "observer")]
678 name: String,
679 #[cfg(feature = "observer")]
680 version: String,
681 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
682 classes: Vec<fn() -> ClassBuilder>,
683 interfaces: Vec<fn() -> ClassBuilder>,
684 #[cfg(feature = "enum")]
685 enums: Vec<fn() -> EnumBuilder>,
686}
687
688impl ModuleStartup {
689 pub fn startup(&self, ty: i32, mod_num: i32) -> Result<()> {
700 for (name, val) in &self.constants {
701 val.register_constant(name, mod_num)?;
702 }
703
704 self.interfaces.iter().try_for_each(|c| c().register())?;
707
708 self.classes.iter().try_for_each(|c| c().register())?;
709
710 #[cfg(feature = "enum")]
711 self.enums
712 .iter()
713 .try_for_each(|builder| builder().register())?;
714
715 #[cfg(feature = "observer")]
717 unsafe {
718 crate::zend::observer::observer_startup();
719 crate::zend::error_observer::error_observer_startup();
720 crate::zend::exception_observer::exception_observer_startup();
721 crate::zend::zend_extension::zend_extension_startup(&self.name, &self.version);
722 }
723
724 unsafe { sapi_activate::install(ty) }
727 }
728}
729
730pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
732
733pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
735
736impl ModuleBuilder<'_> {
737 fn validate(&self) -> Result<()> {
740 match first_duplicate(self.functions.iter().map(|f| f.name.as_str()), true) {
741 Some(name) => Err(crate::error::Error::DuplicateFunction {
742 function: name.to_owned(),
743 }),
744 None => Ok(()),
745 }
746 }
747}
748
749impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup, ModuleAllocations) {
752 type Error = crate::error::Error;
753
754 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
755 builder.validate()?;
756 let mut arg_info = Vec::with_capacity(builder.functions.len());
757 let mut function_table = Vec::with_capacity(builder.functions.len() + 1);
758 for function in builder.functions {
759 let (entry, args) = function.build()?;
760 function_table.push(entry);
761 arg_info.push(args);
762 }
763 function_table.push(FunctionEntry::end());
764 let function_table = function_table.into_boxed_slice();
765 let functions = function_table.as_ptr();
766
767 #[cfg(feature = "observer")]
768 let ext_name = builder.name.clone();
769 #[cfg(feature = "observer")]
770 let ext_version = builder.version.clone();
771
772 sapi_activate::set_callback(builder.sapi_activate);
773
774 let owned_name = CString::new(builder.name)?;
775 let owned_version = CString::new(builder.version)?;
776 let name = owned_name.as_ptr();
777 let version = owned_version.as_ptr();
778
779 let startup = ModuleStartup {
780 #[cfg(feature = "observer")]
781 name: ext_name,
782 #[cfg(feature = "observer")]
783 version: ext_version,
784 constants: builder
785 .constants
786 .into_iter()
787 .map(|(n, v, _)| (n, v))
788 .collect(),
789 classes: builder.classes,
790 interfaces: builder.interfaces,
791 #[cfg(feature = "enum")]
792 enums: builder.enums,
793 };
794
795 #[cfg(not(php_zts))]
796 let module_entry = ModuleEntry {
797 size: mem::size_of::<ModuleEntry>().try_into()?,
798 zend_api: ZEND_MODULE_API_NO,
799 zend_debug: u8::from(PHP_DEBUG),
800 zts: u8::from(PHP_ZTS),
801 ini_entry: ptr::null(),
802 deps: ptr::null(),
803 name,
804 functions,
805 module_startup_func: builder.startup_func,
806 module_shutdown_func: builder.shutdown_func,
807 request_startup_func: builder.request_startup_func,
808 request_shutdown_func: builder.request_shutdown_func,
809 info_func: builder.info_func,
810 version,
811 globals_size: builder.globals_size,
812 globals_ptr: builder.globals_ptr,
813 globals_ctor: builder.globals_ctor,
814 globals_dtor: builder.globals_dtor,
815 post_deactivate_func: builder.post_deactivate_func,
816 module_started: 0,
817 type_: 0,
818 handle: ptr::null_mut(),
819 module_number: 0,
820 build_id: unsafe { ext_php_rs_php_build_id() },
821 };
822
823 #[cfg(php_zts)]
824 let module_entry = ModuleEntry {
825 size: mem::size_of::<ModuleEntry>().try_into()?,
826 zend_api: ZEND_MODULE_API_NO,
827 zend_debug: u8::from(PHP_DEBUG),
828 zts: u8::from(PHP_ZTS),
829 ini_entry: ptr::null(),
830 deps: ptr::null(),
831 name,
832 functions,
833 module_startup_func: builder.startup_func,
834 module_shutdown_func: builder.shutdown_func,
835 request_startup_func: builder.request_startup_func,
836 request_shutdown_func: builder.request_shutdown_func,
837 info_func: builder.info_func,
838 version,
839 globals_size: builder.globals_size,
840 globals_id_ptr: builder.globals_id_ptr,
841 globals_ctor: builder.globals_ctor,
842 globals_dtor: builder.globals_dtor,
843 post_deactivate_func: builder.post_deactivate_func,
844 module_started: 0,
845 type_: 0,
846 handle: ptr::null_mut(),
847 module_number: 0,
848 build_id: unsafe { ext_php_rs_php_build_id() },
849 };
850
851 Ok((
852 module_entry,
853 startup,
854 ModuleAllocations {
855 functions: function_table,
856 arg_info: arg_info.into_boxed_slice(),
857 name: owned_name,
858 version: owned_version,
859 },
860 ))
861 }
862}
863
864#[cfg(test)]
865mod tests {
866 use crate::test::{
867 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
868 };
869
870 use super::*;
871
872 #[test]
873 fn test_new() {
874 let builder = ModuleBuilder::new("test", "1.0");
875 assert_eq!(builder.name, "test");
876 assert_eq!(builder.version, "1.0");
877 assert!(builder.functions.is_empty());
878 assert!(builder.constants.is_empty());
879 assert_eq!(builder.classes.len(), 0);
880 assert_eq!(builder.interfaces.len(), 0);
881 assert!(builder.startup_func.is_none());
882 assert!(builder.shutdown_func.is_none());
883 assert!(builder.request_startup_func.is_none());
884 assert!(builder.request_shutdown_func.is_none());
885 assert!(builder.post_deactivate_func.is_none());
886 assert!(builder.info_func.is_none());
887 #[cfg(feature = "enum")]
888 assert_eq!(builder.enums.len(), 0);
889 }
890
891 #[test]
892 fn functions_that_differ_only_by_case_fail_the_module_build() {
893 let builder = ModuleBuilder::new("test", "1.0")
894 .function(FunctionBuilder::new("say_hello", test_function))
895 .function(FunctionBuilder::new("Say_Hello", test_function));
896 let built = <(ModuleEntry, ModuleStartup, ModuleAllocations)>::try_from(builder);
897 assert!(matches!(
898 built,
899 Err(crate::error::Error::DuplicateFunction { function }) if function == "Say_Hello"
900 ));
901 }
902
903 #[test]
904 fn test_name() {
905 let builder = ModuleBuilder::new("test", "1.0").name("new_test");
906 assert_eq!(builder.name, "new_test");
907 }
908
909 #[test]
910 fn test_version() {
911 let builder = ModuleBuilder::new("test", "1.0").version("2.0");
912 assert_eq!(builder.version, "2.0");
913 }
914
915 #[test]
916 fn test_startup_function() {
917 let builder =
918 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
919 assert!(builder.startup_func.is_some());
920 }
921
922 #[test]
923 fn test_shutdown_function() {
924 let builder =
925 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
926 assert!(builder.shutdown_func.is_some());
927 }
928
929 #[test]
930 fn test_request_startup_function() {
931 let builder = ModuleBuilder::new("test", "1.0")
932 .request_startup_function(test_startup_shutdown_function);
933 assert!(builder.request_startup_func.is_some());
934 }
935
936 #[test]
937 fn test_request_shutdown_function() {
938 let builder = ModuleBuilder::new("test", "1.0")
939 .request_shutdown_function(test_startup_shutdown_function);
940 assert!(builder.request_shutdown_func.is_some());
941 }
942
943 #[test]
944 fn test_set_post_deactivate_function() {
945 let builder =
946 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
947 assert!(builder.post_deactivate_func.is_some());
948 }
949
950 #[test]
951 fn test_set_info_function() {
952 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
953 assert!(builder.info_func.is_some());
954 }
955
956 #[test]
957 fn test_add_function() {
958 let builder =
959 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
960 assert_eq!(builder.functions.len(), 1);
961 }
962
963 #[test]
964 #[cfg(feature = "embed")]
965 fn test_add_constant() {
966 let builder =
967 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
968 assert_eq!(builder.constants.len(), 1);
969 assert_eq!(builder.constants[0].0, "TEST_CONST");
970 assert_eq!(builder.constants[0].2, DocComments::default());
972 }
973}