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
578pub struct ModuleStartup {
581 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
582 classes: Vec<fn() -> ClassBuilder>,
583 interfaces: Vec<fn() -> ClassBuilder>,
584 #[cfg(feature = "enum")]
585 enums: Vec<fn() -> EnumBuilder>,
586}
587
588impl ModuleStartup {
589 pub fn startup(self, _ty: i32, mod_num: i32) -> Result<()> {
600 for (name, val) in self.constants {
601 val.register_constant(&name, mod_num)?;
602 }
603
604 self.interfaces.into_iter().map(|c| c()).for_each(|c| {
607 c.register().expect("Failed to build interface");
608 });
609
610 self.classes.into_iter().map(|c| c()).for_each(|c| {
611 c.register().expect("Failed to build class");
612 });
613
614 #[cfg(feature = "enum")]
615 self.enums
616 .into_iter()
617 .map(|builder| builder())
618 .for_each(|e| {
619 e.register().expect("Failed to build enum");
620 });
621
622 #[cfg(feature = "observer")]
624 unsafe {
625 crate::zend::observer::observer_startup();
626 crate::zend::error_observer::error_observer_startup();
627 crate::zend::exception_observer::exception_observer_startup();
628 }
629
630 Ok(())
631 }
632}
633
634pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
636
637pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
639
640impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup) {
643 type Error = crate::error::Error;
644
645 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
646 let mut functions = builder
647 .functions
648 .into_iter()
649 .map(FunctionBuilder::build)
650 .collect::<Result<Vec<_>>>()?;
651 functions.push(FunctionEntry::end());
652 let functions = Box::into_raw(functions.into_boxed_slice()) as *const FunctionEntry;
653
654 let name = CString::new(builder.name)?.into_raw();
655 let version = CString::new(builder.version)?.into_raw();
656
657 let startup = ModuleStartup {
658 constants: builder
659 .constants
660 .into_iter()
661 .map(|(n, v, _)| (n, v))
662 .collect(),
663 classes: builder.classes,
664 interfaces: builder.interfaces,
665 #[cfg(feature = "enum")]
666 enums: builder.enums,
667 };
668
669 #[cfg(not(php_zts))]
670 let module_entry = ModuleEntry {
671 size: mem::size_of::<ModuleEntry>().try_into()?,
672 zend_api: ZEND_MODULE_API_NO,
673 zend_debug: u8::from(PHP_DEBUG),
674 zts: u8::from(PHP_ZTS),
675 ini_entry: ptr::null(),
676 deps: ptr::null(),
677 name,
678 functions,
679 module_startup_func: builder.startup_func,
680 module_shutdown_func: builder.shutdown_func,
681 request_startup_func: builder.request_startup_func,
682 request_shutdown_func: builder.request_shutdown_func,
683 info_func: builder.info_func,
684 version,
685 globals_size: builder.globals_size,
686 globals_ptr: builder.globals_ptr,
687 globals_ctor: builder.globals_ctor,
688 globals_dtor: builder.globals_dtor,
689 post_deactivate_func: builder.post_deactivate_func,
690 module_started: 0,
691 type_: 0,
692 handle: ptr::null_mut(),
693 module_number: 0,
694 build_id: unsafe { ext_php_rs_php_build_id() },
695 };
696
697 #[cfg(php_zts)]
698 let module_entry = ModuleEntry {
699 size: mem::size_of::<ModuleEntry>().try_into()?,
700 zend_api: ZEND_MODULE_API_NO,
701 zend_debug: u8::from(PHP_DEBUG),
702 zts: u8::from(PHP_ZTS),
703 ini_entry: ptr::null(),
704 deps: ptr::null(),
705 name,
706 functions,
707 module_startup_func: builder.startup_func,
708 module_shutdown_func: builder.shutdown_func,
709 request_startup_func: builder.request_startup_func,
710 request_shutdown_func: builder.request_shutdown_func,
711 info_func: builder.info_func,
712 version,
713 globals_size: builder.globals_size,
714 globals_id_ptr: builder.globals_id_ptr,
715 globals_ctor: builder.globals_ctor,
716 globals_dtor: builder.globals_dtor,
717 post_deactivate_func: builder.post_deactivate_func,
718 module_started: 0,
719 type_: 0,
720 handle: ptr::null_mut(),
721 module_number: 0,
722 build_id: unsafe { ext_php_rs_php_build_id() },
723 };
724
725 Ok((module_entry, startup))
726 }
727}
728
729#[cfg(test)]
730mod tests {
731 use crate::test::{
732 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
733 };
734
735 use super::*;
736
737 #[test]
738 fn test_new() {
739 let builder = ModuleBuilder::new("test", "1.0");
740 assert_eq!(builder.name, "test");
741 assert_eq!(builder.version, "1.0");
742 assert!(builder.functions.is_empty());
743 assert!(builder.constants.is_empty());
744 assert!(builder.classes.is_empty());
745 assert!(builder.interfaces.is_empty());
746 assert!(builder.startup_func.is_none());
747 assert!(builder.shutdown_func.is_none());
748 assert!(builder.request_startup_func.is_none());
749 assert!(builder.request_shutdown_func.is_none());
750 assert!(builder.post_deactivate_func.is_none());
751 assert!(builder.info_func.is_none());
752 #[cfg(feature = "enum")]
753 assert!(builder.enums.is_empty());
754 }
755
756 #[test]
757 fn test_name() {
758 let builder = ModuleBuilder::new("test", "1.0").name("new_test");
759 assert_eq!(builder.name, "new_test");
760 }
761
762 #[test]
763 fn test_version() {
764 let builder = ModuleBuilder::new("test", "1.0").version("2.0");
765 assert_eq!(builder.version, "2.0");
766 }
767
768 #[test]
769 fn test_startup_function() {
770 let builder =
771 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
772 assert!(builder.startup_func.is_some());
773 }
774
775 #[test]
776 fn test_shutdown_function() {
777 let builder =
778 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
779 assert!(builder.shutdown_func.is_some());
780 }
781
782 #[test]
783 fn test_request_startup_function() {
784 let builder = ModuleBuilder::new("test", "1.0")
785 .request_startup_function(test_startup_shutdown_function);
786 assert!(builder.request_startup_func.is_some());
787 }
788
789 #[test]
790 fn test_request_shutdown_function() {
791 let builder = ModuleBuilder::new("test", "1.0")
792 .request_shutdown_function(test_startup_shutdown_function);
793 assert!(builder.request_shutdown_func.is_some());
794 }
795
796 #[test]
797 fn test_set_post_deactivate_function() {
798 let builder =
799 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
800 assert!(builder.post_deactivate_func.is_some());
801 }
802
803 #[test]
804 fn test_set_info_function() {
805 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
806 assert!(builder.info_func.is_some());
807 }
808
809 #[test]
810 fn test_add_function() {
811 let builder =
812 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
813 assert_eq!(builder.functions.len(), 1);
814 }
815
816 #[test]
817 #[cfg(feature = "embed")]
818 fn test_add_constant() {
819 let builder =
820 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
821 assert_eq!(builder.constants.len(), 1);
822 assert_eq!(builder.constants[0].0, "TEST_CONST");
823 assert_eq!(builder.constants[0].2, DocComments::default());
825 }
826}