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")]
285 pub fn fcall_observer<F, O>(self, factory: F) -> Self
286 where
287 F: Fn() -> O + Send + Sync + 'static,
288 O: crate::zend::FcallObserver + Send + Sync,
289 {
290 let boxed_factory: Box<
291 dyn Fn() -> Box<dyn crate::zend::FcallObserver + Send + Sync> + Send + Sync,
292 > = Box::new(move || Box::new(factory()));
293 crate::zend::observer::register_fcall_observer_factory(boxed_factory);
294 self
295 }
296
297 #[cfg(feature = "observer")]
338 pub fn error_observer<F, O>(self, factory: F) -> Self
339 where
340 F: Fn() -> O + Send + Sync + 'static,
341 O: crate::zend::ErrorObserver + Send + Sync,
342 {
343 let boxed_factory: Box<
344 dyn Fn() -> Box<dyn crate::zend::ErrorObserver + Send + Sync> + Send + Sync,
345 > = Box::new(move || Box::new(factory()));
346 crate::zend::error_observer::register_error_observer_factory(boxed_factory);
347 self
348 }
349
350 #[cfg(feature = "observer")]
391 pub fn exception_observer<F, O>(self, factory: F) -> Self
392 where
393 F: Fn() -> O + Send + Sync + 'static,
394 O: crate::zend::ExceptionObserver + Send + Sync,
395 {
396 let boxed_factory: Box<
397 dyn Fn() -> Box<dyn crate::zend::ExceptionObserver + Send + Sync> + Send + Sync,
398 > = Box::new(move || Box::new(factory()));
399 crate::zend::exception_observer::register_exception_observer_factory(boxed_factory);
400 self
401 }
402
403 pub fn function(mut self, func: FunctionBuilder<'static>) -> Self {
409 self.functions.push(func);
410 self
411 }
412
413 pub fn constant(
422 mut self,
423 r#const: (&str, impl IntoConst + Send + 'static, DocComments),
424 ) -> Self {
425 let (name, val, docs) = r#const;
426 self.constants.push((
427 name.into(),
428 Box::new(val) as Box<dyn IntoConst + Send>,
429 docs,
430 ));
431 self
432 }
433
434 #[expect(
440 clippy::expect_used,
441 reason = "registration runs in MINIT, where a failure is an extension bug the engine cannot recover from"
442 )]
443 pub fn interface<T: RegisteredClass>(mut self) -> Self {
444 self.interfaces.push(|| {
445 let mut builder = ClassBuilder::new(T::CLASS_NAME);
446 for (method, flags) in T::method_builders() {
447 builder = builder.method(method, flags);
448 }
449 for interface in T::IMPLEMENTS {
450 builder = builder.implements(*interface);
451 }
452 for (name, value, docs, flags) in T::constants() {
453 builder = builder
454 .dyn_constant(*name, *value, docs, *flags)
455 .expect("Failed to register constant");
456 }
457
458 if let Some(modifier) = T::BUILDER_MODIFIER {
459 builder = modifier(builder);
460 }
461
462 builder = builder.flags(ClassFlags::Interface);
463 builder
466 .registration(|ce, arg_info| {
467 T::get_metadata().set_ce(ce);
468 T::get_metadata().set_arg_info(arg_info);
469 })
470 .docs(T::DOC_COMMENTS)
471 });
472 self
473 }
474
475 #[expect(
481 clippy::expect_used,
482 reason = "registration runs in MINIT, where a failure is an extension bug the engine cannot recover from"
483 )]
484 pub fn class<T: RegisteredClass>(mut self) -> Self {
485 self.classes.push(|| {
486 let mut builder = ClassBuilder::new(T::CLASS_NAME);
487 for (method, flags) in T::method_builders() {
488 builder = builder.method(method, flags);
489 }
490 for (method, flags) in T::interface_method_implementations() {
493 builder = builder.method(method, flags);
494 }
495 if let Some(parent) = T::EXTENDS {
496 builder = builder.extends(parent);
497 }
498 for interface in T::IMPLEMENTS {
500 builder = builder.implements(*interface);
501 }
502 for interface in T::interface_implementations() {
505 builder = builder.implements(interface);
506 }
507 for (name, value, docs, flags) in T::constants() {
508 builder = builder
509 .dyn_constant(*name, *value, docs, *flags)
510 .expect("Failed to register constant");
511 }
512 for desc in T::get_metadata().all_properties() {
513 let default_stub = if desc.nullable {
514 Some("null".into())
515 } else {
516 None
517 };
518 builder = builder.property(crate::builders::ClassProperty {
519 name: desc.name.into(),
520 flags: desc.flags,
521 default: None,
522 docs: desc.docs,
523 ty: Some(desc.ty),
524 nullable: desc.nullable,
525 readonly: desc.readonly,
526 default_stub,
527 });
528 }
529 for (name, flags, default, docs) in T::static_properties() {
530 let default_stub = default.map(crate::convert::IntoZvalDyn::stub_value);
531 let default_fn = default.map(|v| {
532 Box::new(move || v.as_zval(true))
533 as Box<dyn FnOnce() -> crate::error::Result<crate::types::Zval>>
534 });
535 builder = builder.property(crate::builders::ClassProperty {
536 name: (*name).into(),
537 flags: *flags,
538 default: default_fn,
539 docs,
540 ty: None,
541 nullable: false,
542 readonly: false,
543 default_stub,
544 });
545 }
546 if let Some(modifier) = T::BUILDER_MODIFIER {
547 builder = modifier(builder);
548 }
549
550 builder
551 .flags(T::FLAGS)
552 .object_override::<T>()
553 .registration(|ce, arg_info| {
554 T::get_metadata().set_ce(ce);
555 T::get_metadata().set_arg_info(arg_info);
556 })
557 .docs(T::DOC_COMMENTS)
558 });
559 self
560 }
561
562 #[cfg(feature = "enum")]
564 pub fn enumeration<T>(mut self) -> Self
565 where
566 T: RegisteredClass + RegisteredEnum,
567 {
568 self.enums.push(|| {
569 let mut builder = EnumBuilder::new(T::CLASS_NAME);
570 for case in T::CASES {
571 builder = builder.case(case);
572 }
573 for (method, flags) in T::method_builders() {
574 builder = builder.method(method, flags);
575 }
576
577 builder
578 .registration(|ce, arg_info| {
579 T::get_metadata().set_ce(ce);
580 T::get_metadata().set_arg_info(arg_info);
581 })
582 .docs(T::DOC_COMMENTS)
583 });
584
585 self
586 }
587}
588
589#[cfg(feature = "observer")]
590impl<'a> ModuleBuilder<'a> {
591 pub fn zend_extension<F, H>(
615 self,
616 factory: F,
617 ) -> crate::zend::zend_extension::ZendExtensionBuilder<'a>
618 where
619 F: Fn() -> H + Send + Sync + 'static,
620 H: crate::zend::ZendExtensionHandler,
621 {
622 crate::zend::zend_extension::ZendExtensionBuilder::new(self, factory)
623 }
624}
625
626pub struct ModuleStartup {
629 #[cfg(feature = "observer")]
630 name: String,
631 #[cfg(feature = "observer")]
632 version: String,
633 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
634 classes: Vec<fn() -> ClassBuilder>,
635 interfaces: Vec<fn() -> ClassBuilder>,
636 #[cfg(feature = "enum")]
637 enums: Vec<fn() -> EnumBuilder>,
638}
639
640impl ModuleStartup {
641 pub fn startup(self, _ty: i32, mod_num: i32) -> Result<()> {
650 for (name, val) in self.constants {
651 val.register_constant(&name, mod_num)?;
652 }
653
654 self.interfaces
657 .into_iter()
658 .try_for_each(|c| c().register())?;
659
660 self.classes.into_iter().try_for_each(|c| c().register())?;
661
662 #[cfg(feature = "enum")]
663 self.enums
664 .into_iter()
665 .try_for_each(|builder| builder().register())?;
666
667 #[cfg(feature = "observer")]
669 unsafe {
670 crate::zend::observer::observer_startup();
671 crate::zend::error_observer::error_observer_startup();
672 crate::zend::exception_observer::exception_observer_startup();
673 crate::zend::zend_extension::zend_extension_startup(&self.name, &self.version);
674 }
675
676 Ok(())
677 }
678}
679
680pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
682
683pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
685
686impl ModuleBuilder<'_> {
687 fn validate(&self) -> Result<()> {
690 match first_duplicate(self.functions.iter().map(|f| f.name.as_str()), true) {
691 Some(name) => Err(crate::error::Error::DuplicateFunction {
692 function: name.to_owned(),
693 }),
694 None => Ok(()),
695 }
696 }
697}
698
699impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup, ModuleAllocations) {
702 type Error = crate::error::Error;
703
704 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
705 builder.validate()?;
706 let mut arg_info = Vec::with_capacity(builder.functions.len());
707 let mut function_table = Vec::with_capacity(builder.functions.len() + 1);
708 for function in builder.functions {
709 let (entry, args) = function.build()?;
710 function_table.push(entry);
711 arg_info.push(args);
712 }
713 function_table.push(FunctionEntry::end());
714 let function_table = function_table.into_boxed_slice();
715 let functions = function_table.as_ptr();
716
717 #[cfg(feature = "observer")]
718 let ext_name = builder.name.clone();
719 #[cfg(feature = "observer")]
720 let ext_version = builder.version.clone();
721
722 let owned_name = CString::new(builder.name)?;
723 let owned_version = CString::new(builder.version)?;
724 let name = owned_name.as_ptr();
725 let version = owned_version.as_ptr();
726
727 let startup = ModuleStartup {
728 #[cfg(feature = "observer")]
729 name: ext_name,
730 #[cfg(feature = "observer")]
731 version: ext_version,
732 constants: builder
733 .constants
734 .into_iter()
735 .map(|(n, v, _)| (n, v))
736 .collect(),
737 classes: builder.classes,
738 interfaces: builder.interfaces,
739 #[cfg(feature = "enum")]
740 enums: builder.enums,
741 };
742
743 #[cfg(not(php_zts))]
744 let module_entry = ModuleEntry {
745 size: mem::size_of::<ModuleEntry>().try_into()?,
746 zend_api: ZEND_MODULE_API_NO,
747 zend_debug: u8::from(PHP_DEBUG),
748 zts: u8::from(PHP_ZTS),
749 ini_entry: ptr::null(),
750 deps: ptr::null(),
751 name,
752 functions,
753 module_startup_func: builder.startup_func,
754 module_shutdown_func: builder.shutdown_func,
755 request_startup_func: builder.request_startup_func,
756 request_shutdown_func: builder.request_shutdown_func,
757 info_func: builder.info_func,
758 version,
759 globals_size: builder.globals_size,
760 globals_ptr: builder.globals_ptr,
761 globals_ctor: builder.globals_ctor,
762 globals_dtor: builder.globals_dtor,
763 post_deactivate_func: builder.post_deactivate_func,
764 module_started: 0,
765 type_: 0,
766 handle: ptr::null_mut(),
767 module_number: 0,
768 build_id: unsafe { ext_php_rs_php_build_id() },
769 };
770
771 #[cfg(php_zts)]
772 let module_entry = ModuleEntry {
773 size: mem::size_of::<ModuleEntry>().try_into()?,
774 zend_api: ZEND_MODULE_API_NO,
775 zend_debug: u8::from(PHP_DEBUG),
776 zts: u8::from(PHP_ZTS),
777 ini_entry: ptr::null(),
778 deps: ptr::null(),
779 name,
780 functions,
781 module_startup_func: builder.startup_func,
782 module_shutdown_func: builder.shutdown_func,
783 request_startup_func: builder.request_startup_func,
784 request_shutdown_func: builder.request_shutdown_func,
785 info_func: builder.info_func,
786 version,
787 globals_size: builder.globals_size,
788 globals_id_ptr: builder.globals_id_ptr,
789 globals_ctor: builder.globals_ctor,
790 globals_dtor: builder.globals_dtor,
791 post_deactivate_func: builder.post_deactivate_func,
792 module_started: 0,
793 type_: 0,
794 handle: ptr::null_mut(),
795 module_number: 0,
796 build_id: unsafe { ext_php_rs_php_build_id() },
797 };
798
799 Ok((
800 module_entry,
801 startup,
802 ModuleAllocations {
803 functions: function_table,
804 arg_info: arg_info.into_boxed_slice(),
805 name: owned_name,
806 version: owned_version,
807 },
808 ))
809 }
810}
811
812#[cfg(test)]
813mod tests {
814 use crate::test::{
815 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
816 };
817
818 use super::*;
819
820 #[test]
821 fn test_new() {
822 let builder = ModuleBuilder::new("test", "1.0");
823 assert_eq!(builder.name, "test");
824 assert_eq!(builder.version, "1.0");
825 assert!(builder.functions.is_empty());
826 assert!(builder.constants.is_empty());
827 assert_eq!(builder.classes.len(), 0);
828 assert_eq!(builder.interfaces.len(), 0);
829 assert!(builder.startup_func.is_none());
830 assert!(builder.shutdown_func.is_none());
831 assert!(builder.request_startup_func.is_none());
832 assert!(builder.request_shutdown_func.is_none());
833 assert!(builder.post_deactivate_func.is_none());
834 assert!(builder.info_func.is_none());
835 #[cfg(feature = "enum")]
836 assert_eq!(builder.enums.len(), 0);
837 }
838
839 #[test]
840 fn functions_that_differ_only_by_case_fail_the_module_build() {
841 let builder = ModuleBuilder::new("test", "1.0")
842 .function(FunctionBuilder::new("say_hello", test_function))
843 .function(FunctionBuilder::new("Say_Hello", test_function));
844 let built = <(ModuleEntry, ModuleStartup, ModuleAllocations)>::try_from(builder);
845 assert!(matches!(
846 built,
847 Err(crate::error::Error::DuplicateFunction { function }) if function == "Say_Hello"
848 ));
849 }
850
851 #[test]
852 fn test_name() {
853 let builder = ModuleBuilder::new("test", "1.0").name("new_test");
854 assert_eq!(builder.name, "new_test");
855 }
856
857 #[test]
858 fn test_version() {
859 let builder = ModuleBuilder::new("test", "1.0").version("2.0");
860 assert_eq!(builder.version, "2.0");
861 }
862
863 #[test]
864 fn test_startup_function() {
865 let builder =
866 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
867 assert!(builder.startup_func.is_some());
868 }
869
870 #[test]
871 fn test_shutdown_function() {
872 let builder =
873 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
874 assert!(builder.shutdown_func.is_some());
875 }
876
877 #[test]
878 fn test_request_startup_function() {
879 let builder = ModuleBuilder::new("test", "1.0")
880 .request_startup_function(test_startup_shutdown_function);
881 assert!(builder.request_startup_func.is_some());
882 }
883
884 #[test]
885 fn test_request_shutdown_function() {
886 let builder = ModuleBuilder::new("test", "1.0")
887 .request_shutdown_function(test_startup_shutdown_function);
888 assert!(builder.request_shutdown_func.is_some());
889 }
890
891 #[test]
892 fn test_set_post_deactivate_function() {
893 let builder =
894 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
895 assert!(builder.post_deactivate_func.is_some());
896 }
897
898 #[test]
899 fn test_set_info_function() {
900 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
901 assert!(builder.info_func.is_some());
902 }
903
904 #[test]
905 fn test_add_function() {
906 let builder =
907 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
908 assert_eq!(builder.functions.len(), 1);
909 }
910
911 #[test]
912 #[cfg(feature = "embed")]
913 fn test_add_constant() {
914 let builder =
915 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
916 assert_eq!(builder.constants.len(), 1);
917 assert_eq!(builder.constants[0].0, "TEST_CONST");
918 assert_eq!(builder.constants[0].2, DocComments::default());
920 }
921}