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, 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 pub fn interface<T: RegisteredClass>(mut self) -> Self {
440 self.interfaces.push(|| {
441 let mut builder = ClassBuilder::new(T::CLASS_NAME);
442 for (method, flags) in T::method_builders() {
443 builder = builder.method(method, flags);
444 }
445 for interface in T::IMPLEMENTS {
446 builder = builder.implements(*interface);
447 }
448 for (name, value, docs) in T::constants() {
449 builder = builder
450 .dyn_constant(*name, *value, docs)
451 .expect("Failed to register constant");
452 }
453
454 if let Some(modifier) = T::BUILDER_MODIFIER {
455 builder = modifier(builder);
456 }
457
458 builder = builder.flags(ClassFlags::Interface);
459 builder
462 .registration(|ce| {
463 T::get_metadata().set_ce(ce);
464 })
465 .docs(T::DOC_COMMENTS)
466 });
467 self
468 }
469
470 pub fn class<T: RegisteredClass>(mut self) -> Self {
476 self.classes.push(|| {
477 let mut builder = ClassBuilder::new(T::CLASS_NAME);
478 for (method, flags) in T::method_builders() {
479 builder = builder.method(method, flags);
480 }
481 for (method, flags) in T::interface_method_implementations() {
484 builder = builder.method(method, flags);
485 }
486 if let Some(parent) = T::EXTENDS {
487 builder = builder.extends(parent);
488 }
489 for interface in T::IMPLEMENTS {
491 builder = builder.implements(*interface);
492 }
493 for interface in T::interface_implementations() {
496 builder = builder.implements(interface);
497 }
498 for (name, value, docs) in T::constants() {
499 builder = builder
500 .dyn_constant(*name, *value, docs)
501 .expect("Failed to register constant");
502 }
503 for desc in T::get_metadata().all_properties() {
504 let default_stub = if desc.nullable {
505 Some("null".into())
506 } else {
507 None
508 };
509 builder = builder.property(crate::builders::ClassProperty {
510 name: desc.name.into(),
511 flags: desc.flags,
512 default: None,
513 docs: desc.docs,
514 ty: Some(desc.ty),
515 nullable: desc.nullable,
516 readonly: desc.readonly,
517 default_stub,
518 });
519 }
520 for (name, flags, default, docs) in T::static_properties() {
521 let default_stub = default.map(crate::convert::IntoZvalDyn::stub_value);
522 let default_fn = default.map(|v| {
523 Box::new(move || v.as_zval(true))
524 as Box<dyn FnOnce() -> crate::error::Result<crate::types::Zval>>
525 });
526 builder = builder.property(crate::builders::ClassProperty {
527 name: (*name).into(),
528 flags: *flags,
529 default: default_fn,
530 docs,
531 ty: None,
532 nullable: false,
533 readonly: false,
534 default_stub,
535 });
536 }
537 if let Some(modifier) = T::BUILDER_MODIFIER {
538 builder = modifier(builder);
539 }
540
541 builder
542 .flags(T::FLAGS)
543 .object_override::<T>()
544 .registration(|ce| {
545 T::get_metadata().set_ce(ce);
546 })
547 .docs(T::DOC_COMMENTS)
548 });
549 self
550 }
551
552 #[cfg(feature = "enum")]
554 pub fn enumeration<T>(mut self) -> Self
555 where
556 T: RegisteredClass + RegisteredEnum,
557 {
558 self.enums.push(|| {
559 let mut builder = EnumBuilder::new(T::CLASS_NAME);
560 for case in T::CASES {
561 builder = builder.case(case);
562 }
563 for (method, flags) in T::method_builders() {
564 builder = builder.method(method, flags);
565 }
566
567 builder
568 .registration(|ce| {
569 T::get_metadata().set_ce(ce);
570 })
571 .docs(T::DOC_COMMENTS)
572 });
573
574 self
575 }
576}
577
578#[cfg(feature = "observer")]
579impl<'a> ModuleBuilder<'a> {
580 pub fn zend_extension<F, H>(
604 self,
605 factory: F,
606 ) -> crate::zend::zend_extension::ZendExtensionBuilder<'a>
607 where
608 F: Fn() -> H + Send + Sync + 'static,
609 H: crate::zend::ZendExtensionHandler,
610 {
611 crate::zend::zend_extension::ZendExtensionBuilder::new(self, factory)
612 }
613}
614
615pub struct ModuleStartup {
618 #[cfg(feature = "observer")]
619 name: String,
620 #[cfg(feature = "observer")]
621 version: String,
622 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
623 classes: Vec<fn() -> ClassBuilder>,
624 interfaces: Vec<fn() -> ClassBuilder>,
625 #[cfg(feature = "enum")]
626 enums: Vec<fn() -> EnumBuilder>,
627}
628
629impl ModuleStartup {
630 pub fn startup(self, _ty: i32, mod_num: i32) -> Result<()> {
641 for (name, val) in self.constants {
642 val.register_constant(&name, mod_num)?;
643 }
644
645 self.interfaces.into_iter().map(|c| c()).for_each(|c| {
648 c.register().expect("Failed to build interface");
649 });
650
651 self.classes.into_iter().map(|c| c()).for_each(|c| {
652 c.register().expect("Failed to build class");
653 });
654
655 #[cfg(feature = "enum")]
656 self.enums
657 .into_iter()
658 .map(|builder| builder())
659 .for_each(|e| {
660 e.register().expect("Failed to build enum");
661 });
662
663 #[cfg(feature = "observer")]
665 unsafe {
666 crate::zend::observer::observer_startup();
667 crate::zend::error_observer::error_observer_startup();
668 crate::zend::exception_observer::exception_observer_startup();
669 crate::zend::zend_extension::zend_extension_startup(&self.name, &self.version);
670 }
671
672 Ok(())
673 }
674}
675
676pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
678
679pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
681
682impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup) {
685 type Error = crate::error::Error;
686
687 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
688 let mut functions = builder
689 .functions
690 .into_iter()
691 .map(FunctionBuilder::build)
692 .collect::<Result<Vec<_>>>()?;
693 functions.push(FunctionEntry::end());
694 let functions = Box::into_raw(functions.into_boxed_slice()) as *const FunctionEntry;
695
696 #[cfg(feature = "observer")]
697 let ext_name = builder.name.clone();
698 #[cfg(feature = "observer")]
699 let ext_version = builder.version.clone();
700
701 let name = CString::new(builder.name)?.into_raw();
702 let version = CString::new(builder.version)?.into_raw();
703
704 let startup = ModuleStartup {
705 #[cfg(feature = "observer")]
706 name: ext_name,
707 #[cfg(feature = "observer")]
708 version: ext_version,
709 constants: builder
710 .constants
711 .into_iter()
712 .map(|(n, v, _)| (n, v))
713 .collect(),
714 classes: builder.classes,
715 interfaces: builder.interfaces,
716 #[cfg(feature = "enum")]
717 enums: builder.enums,
718 };
719
720 #[cfg(not(php_zts))]
721 let module_entry = ModuleEntry {
722 size: mem::size_of::<ModuleEntry>().try_into()?,
723 zend_api: ZEND_MODULE_API_NO,
724 zend_debug: u8::from(PHP_DEBUG),
725 zts: u8::from(PHP_ZTS),
726 ini_entry: ptr::null(),
727 deps: ptr::null(),
728 name,
729 functions,
730 module_startup_func: builder.startup_func,
731 module_shutdown_func: builder.shutdown_func,
732 request_startup_func: builder.request_startup_func,
733 request_shutdown_func: builder.request_shutdown_func,
734 info_func: builder.info_func,
735 version,
736 globals_size: builder.globals_size,
737 globals_ptr: builder.globals_ptr,
738 globals_ctor: builder.globals_ctor,
739 globals_dtor: builder.globals_dtor,
740 post_deactivate_func: builder.post_deactivate_func,
741 module_started: 0,
742 type_: 0,
743 handle: ptr::null_mut(),
744 module_number: 0,
745 build_id: unsafe { ext_php_rs_php_build_id() },
746 };
747
748 #[cfg(php_zts)]
749 let module_entry = ModuleEntry {
750 size: mem::size_of::<ModuleEntry>().try_into()?,
751 zend_api: ZEND_MODULE_API_NO,
752 zend_debug: u8::from(PHP_DEBUG),
753 zts: u8::from(PHP_ZTS),
754 ini_entry: ptr::null(),
755 deps: ptr::null(),
756 name,
757 functions,
758 module_startup_func: builder.startup_func,
759 module_shutdown_func: builder.shutdown_func,
760 request_startup_func: builder.request_startup_func,
761 request_shutdown_func: builder.request_shutdown_func,
762 info_func: builder.info_func,
763 version,
764 globals_size: builder.globals_size,
765 globals_id_ptr: builder.globals_id_ptr,
766 globals_ctor: builder.globals_ctor,
767 globals_dtor: builder.globals_dtor,
768 post_deactivate_func: builder.post_deactivate_func,
769 module_started: 0,
770 type_: 0,
771 handle: ptr::null_mut(),
772 module_number: 0,
773 build_id: unsafe { ext_php_rs_php_build_id() },
774 };
775
776 Ok((module_entry, startup))
777 }
778}
779
780#[cfg(test)]
781mod tests {
782 use crate::test::{
783 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
784 };
785
786 use super::*;
787
788 #[test]
789 fn test_new() {
790 let builder = ModuleBuilder::new("test", "1.0");
791 assert_eq!(builder.name, "test");
792 assert_eq!(builder.version, "1.0");
793 assert!(builder.functions.is_empty());
794 assert!(builder.constants.is_empty());
795 assert!(builder.classes.is_empty());
796 assert!(builder.interfaces.is_empty());
797 assert!(builder.startup_func.is_none());
798 assert!(builder.shutdown_func.is_none());
799 assert!(builder.request_startup_func.is_none());
800 assert!(builder.request_shutdown_func.is_none());
801 assert!(builder.post_deactivate_func.is_none());
802 assert!(builder.info_func.is_none());
803 #[cfg(feature = "enum")]
804 assert!(builder.enums.is_empty());
805 }
806
807 #[test]
808 fn test_name() {
809 let builder = ModuleBuilder::new("test", "1.0").name("new_test");
810 assert_eq!(builder.name, "new_test");
811 }
812
813 #[test]
814 fn test_version() {
815 let builder = ModuleBuilder::new("test", "1.0").version("2.0");
816 assert_eq!(builder.version, "2.0");
817 }
818
819 #[test]
820 fn test_startup_function() {
821 let builder =
822 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
823 assert!(builder.startup_func.is_some());
824 }
825
826 #[test]
827 fn test_shutdown_function() {
828 let builder =
829 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
830 assert!(builder.shutdown_func.is_some());
831 }
832
833 #[test]
834 fn test_request_startup_function() {
835 let builder = ModuleBuilder::new("test", "1.0")
836 .request_startup_function(test_startup_shutdown_function);
837 assert!(builder.request_startup_func.is_some());
838 }
839
840 #[test]
841 fn test_request_shutdown_function() {
842 let builder = ModuleBuilder::new("test", "1.0")
843 .request_shutdown_function(test_startup_shutdown_function);
844 assert!(builder.request_shutdown_func.is_some());
845 }
846
847 #[test]
848 fn test_set_post_deactivate_function() {
849 let builder =
850 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
851 assert!(builder.post_deactivate_func.is_some());
852 }
853
854 #[test]
855 fn test_set_info_function() {
856 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
857 assert!(builder.info_func.is_some());
858 }
859
860 #[test]
861 fn test_add_function() {
862 let builder =
863 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
864 assert_eq!(builder.functions.len(), 1);
865 }
866
867 #[test]
868 #[cfg(feature = "embed")]
869 fn test_add_constant() {
870 let builder =
871 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
872 assert_eq!(builder.constants.len(), 1);
873 assert_eq!(builder.constants[0].0, "TEST_CONST");
874 assert_eq!(builder.constants[0].2, DocComments::default());
876 }
877}