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},
13};
14#[cfg(feature = "enum")]
15use crate::{builders::enum_builder::EnumBuilder, enum_::RegisteredEnum};
16
17#[must_use]
45#[derive(Debug, Default)]
46pub struct ModuleBuilder<'a> {
47 pub(crate) name: String,
48 pub(crate) version: String,
49 pub(crate) functions: Vec<FunctionBuilder<'a>>,
50 pub(crate) constants: Vec<(String, Box<dyn IntoConst + Send>, DocComments)>,
51 pub(crate) classes: Vec<fn() -> ClassBuilder>,
52 pub(crate) interfaces: Vec<fn() -> ClassBuilder>,
53 #[cfg(feature = "enum")]
54 pub(crate) enums: Vec<fn() -> EnumBuilder>,
55 startup_func: Option<StartupShutdownFunc>,
56 shutdown_func: Option<StartupShutdownFunc>,
57 request_startup_func: Option<StartupShutdownFunc>,
58 request_shutdown_func: Option<StartupShutdownFunc>,
59 post_deactivate_func: Option<unsafe extern "C" fn() -> i32>,
60 info_func: Option<InfoFunc>,
61}
62
63impl ModuleBuilder<'_> {
64 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
71 Self {
72 name: name.into(),
73 version: version.into(),
74 functions: vec![],
75 constants: vec![],
76 classes: vec![],
77 ..Default::default()
78 }
79 }
80
81 pub fn name(mut self, name: impl Into<String>) -> Self {
87 self.name = name.into();
88 self
89 }
90
91 pub fn version(mut self, version: impl Into<String>) -> Self {
97 self.version = version.into();
98 self
99 }
100
101 pub fn startup_function(mut self, func: StartupShutdownFunc) -> Self {
107 self.startup_func = Some(func);
108 self
109 }
110
111 pub fn shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
117 self.shutdown_func = Some(func);
118 self
119 }
120
121 pub fn request_startup_function(mut self, func: StartupShutdownFunc) -> Self {
127 self.request_startup_func = Some(func);
128 self
129 }
130
131 pub fn request_shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
137 self.request_shutdown_func = Some(func);
138 self
139 }
140
141 pub fn post_deactivate_function(mut self, func: unsafe extern "C" fn() -> i32) -> Self {
151 self.post_deactivate_func = Some(func);
152 self
153 }
154
155 pub fn info_function(mut self, func: InfoFunc) -> Self {
162 self.info_func = Some(func);
163 self
164 }
165
166 #[cfg(feature = "observer")]
204 pub fn fcall_observer<F, O>(self, factory: F) -> Self
205 where
206 F: Fn() -> O + Send + Sync + 'static,
207 O: crate::zend::FcallObserver + Send + Sync,
208 {
209 let boxed_factory: Box<
210 dyn Fn() -> Box<dyn crate::zend::FcallObserver + Send + Sync> + Send + Sync,
211 > = Box::new(move || Box::new(factory()));
212 crate::zend::observer::register_fcall_observer_factory(boxed_factory);
213 self
214 }
215
216 #[cfg(feature = "observer")]
257 pub fn error_observer<F, O>(self, factory: F) -> Self
258 where
259 F: Fn() -> O + Send + Sync + 'static,
260 O: crate::zend::ErrorObserver + Send + Sync,
261 {
262 let boxed_factory: Box<
263 dyn Fn() -> Box<dyn crate::zend::ErrorObserver + Send + Sync> + Send + Sync,
264 > = Box::new(move || Box::new(factory()));
265 crate::zend::error_observer::register_error_observer_factory(boxed_factory);
266 self
267 }
268
269 #[cfg(feature = "observer")]
309 pub fn exception_observer<F, O>(self, factory: F) -> Self
310 where
311 F: Fn() -> O + Send + Sync + 'static,
312 O: crate::zend::ExceptionObserver + Send + Sync,
313 {
314 let boxed_factory: Box<
315 dyn Fn() -> Box<dyn crate::zend::ExceptionObserver + Send + Sync> + Send + Sync,
316 > = Box::new(move || Box::new(factory()));
317 crate::zend::exception_observer::register_exception_observer_factory(boxed_factory);
318 self
319 }
320
321 pub fn function(mut self, func: FunctionBuilder<'static>) -> Self {
327 self.functions.push(func);
328 self
329 }
330
331 pub fn constant(
340 mut self,
341 r#const: (&str, impl IntoConst + Send + 'static, DocComments),
342 ) -> Self {
343 let (name, val, docs) = r#const;
344 self.constants.push((
345 name.into(),
346 Box::new(val) as Box<dyn IntoConst + Send>,
347 docs,
348 ));
349 self
350 }
351
352 pub fn interface<T: RegisteredClass>(mut self) -> Self {
358 self.interfaces.push(|| {
359 let mut builder = ClassBuilder::new(T::CLASS_NAME);
360 for (method, flags) in T::method_builders() {
361 builder = builder.method(method, flags);
362 }
363 for interface in T::IMPLEMENTS {
364 builder = builder.implements(*interface);
365 }
366 for (name, value, docs) in T::constants() {
367 builder = builder
368 .dyn_constant(*name, *value, docs)
369 .expect("Failed to register constant");
370 }
371
372 if let Some(modifier) = T::BUILDER_MODIFIER {
373 builder = modifier(builder);
374 }
375
376 builder = builder.flags(ClassFlags::Interface);
377 builder
379 .registration(|ce| {
380 T::get_metadata().set_ce(ce);
381 })
382 .docs(T::DOC_COMMENTS)
383 });
384 self
385 }
386
387 pub fn class<T: RegisteredClass>(mut self) -> Self {
393 self.classes.push(|| {
394 let mut builder = ClassBuilder::new(T::CLASS_NAME);
395 for (method, flags) in T::method_builders() {
396 builder = builder.method(method, flags);
397 }
398 for (method, flags) in T::interface_method_implementations() {
401 builder = builder.method(method, flags);
402 }
403 if let Some(parent) = T::EXTENDS {
404 builder = builder.extends(parent);
405 }
406 for interface in T::IMPLEMENTS {
408 builder = builder.implements(*interface);
409 }
410 for interface in T::interface_implementations() {
413 builder = builder.implements(interface);
414 }
415 for (name, value, docs) in T::constants() {
416 builder = builder
417 .dyn_constant(*name, *value, docs)
418 .expect("Failed to register constant");
419 }
420 for (name, prop_info) in T::get_properties() {
421 builder = builder.property(name, prop_info.flags, None, prop_info.docs);
422 }
423 for (name, flags, default, docs) in T::static_properties() {
424 let default_fn = default.map(|v| {
425 Box::new(move || v.as_zval(true))
426 as Box<dyn FnOnce() -> crate::error::Result<crate::types::Zval>>
427 });
428 builder = builder.property(*name, *flags, default_fn, docs);
429 }
430 if let Some(modifier) = T::BUILDER_MODIFIER {
431 builder = modifier(builder);
432 }
433
434 builder
435 .flags(T::FLAGS)
436 .object_override::<T>()
437 .registration(|ce| {
438 T::get_metadata().set_ce(ce);
439 })
440 .docs(T::DOC_COMMENTS)
441 });
442 self
443 }
444
445 #[cfg(feature = "enum")]
447 pub fn enumeration<T>(mut self) -> Self
448 where
449 T: RegisteredClass + RegisteredEnum,
450 {
451 self.enums.push(|| {
452 let mut builder = EnumBuilder::new(T::CLASS_NAME);
453 for case in T::CASES {
454 builder = builder.case(case);
455 }
456 for (method, flags) in T::method_builders() {
457 builder = builder.method(method, flags);
458 }
459
460 builder
461 .registration(|ce| {
462 T::get_metadata().set_ce(ce);
463 })
464 .docs(T::DOC_COMMENTS)
465 });
466
467 self
468 }
469}
470
471pub struct ModuleStartup {
474 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
475 classes: Vec<fn() -> ClassBuilder>,
476 interfaces: Vec<fn() -> ClassBuilder>,
477 #[cfg(feature = "enum")]
478 enums: Vec<fn() -> EnumBuilder>,
479}
480
481impl ModuleStartup {
482 pub fn startup(self, _ty: i32, mod_num: i32) -> Result<()> {
493 for (name, val) in self.constants {
494 val.register_constant(&name, mod_num)?;
495 }
496
497 self.interfaces.into_iter().map(|c| c()).for_each(|c| {
500 c.register().expect("Failed to build interface");
501 });
502
503 self.classes.into_iter().map(|c| c()).for_each(|c| {
504 c.register().expect("Failed to build class");
505 });
506
507 #[cfg(feature = "enum")]
508 self.enums
509 .into_iter()
510 .map(|builder| builder())
511 .for_each(|e| {
512 e.register().expect("Failed to build enum");
513 });
514
515 #[cfg(feature = "observer")]
517 unsafe {
518 crate::zend::observer::observer_startup();
519 crate::zend::error_observer::error_observer_startup();
520 crate::zend::exception_observer::exception_observer_startup();
521 }
522
523 Ok(())
524 }
525}
526
527pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
529
530pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
532
533impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup) {
536 type Error = crate::error::Error;
537
538 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
539 let mut functions = builder
540 .functions
541 .into_iter()
542 .map(FunctionBuilder::build)
543 .collect::<Result<Vec<_>>>()?;
544 functions.push(FunctionEntry::end());
545 let functions = Box::into_raw(functions.into_boxed_slice()) as *const FunctionEntry;
546
547 let name = CString::new(builder.name)?.into_raw();
548 let version = CString::new(builder.version)?.into_raw();
549
550 let startup = ModuleStartup {
551 constants: builder
552 .constants
553 .into_iter()
554 .map(|(n, v, _)| (n, v))
555 .collect(),
556 classes: builder.classes,
557 interfaces: builder.interfaces,
558 #[cfg(feature = "enum")]
559 enums: builder.enums,
560 };
561
562 #[cfg(not(php_zts))]
563 let module_entry = ModuleEntry {
564 size: mem::size_of::<ModuleEntry>().try_into()?,
565 zend_api: ZEND_MODULE_API_NO,
566 zend_debug: u8::from(PHP_DEBUG),
567 zts: u8::from(PHP_ZTS),
568 ini_entry: ptr::null(),
569 deps: ptr::null(),
570 name,
571 functions,
572 module_startup_func: builder.startup_func,
573 module_shutdown_func: builder.shutdown_func,
574 request_startup_func: builder.request_startup_func,
575 request_shutdown_func: builder.request_shutdown_func,
576 info_func: builder.info_func,
577 version,
578 globals_size: 0,
579 globals_ptr: ptr::null_mut(),
580 globals_ctor: None,
581 globals_dtor: None,
582 post_deactivate_func: builder.post_deactivate_func,
583 module_started: 0,
584 type_: 0,
585 handle: ptr::null_mut(),
586 module_number: 0,
587 build_id: unsafe { ext_php_rs_php_build_id() },
588 };
589
590 #[cfg(php_zts)]
591 let module_entry = ModuleEntry {
592 size: mem::size_of::<ModuleEntry>().try_into()?,
593 zend_api: ZEND_MODULE_API_NO,
594 zend_debug: u8::from(PHP_DEBUG),
595 zts: u8::from(PHP_ZTS),
596 ini_entry: ptr::null(),
597 deps: ptr::null(),
598 name,
599 functions,
600 module_startup_func: builder.startup_func,
601 module_shutdown_func: builder.shutdown_func,
602 request_startup_func: builder.request_startup_func,
603 request_shutdown_func: builder.request_shutdown_func,
604 info_func: builder.info_func,
605 version,
606 globals_size: 0,
607 globals_id_ptr: ptr::null_mut(),
608 globals_ctor: None,
609 globals_dtor: None,
610 post_deactivate_func: builder.post_deactivate_func,
611 module_started: 0,
612 type_: 0,
613 handle: ptr::null_mut(),
614 module_number: 0,
615 build_id: unsafe { ext_php_rs_php_build_id() },
616 };
617
618 Ok((module_entry, startup))
619 }
620}
621
622#[cfg(test)]
623mod tests {
624 use crate::test::{
625 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
626 };
627
628 use super::*;
629
630 #[test]
631 fn test_new() {
632 let builder = ModuleBuilder::new("test", "1.0");
633 assert_eq!(builder.name, "test");
634 assert_eq!(builder.version, "1.0");
635 assert!(builder.functions.is_empty());
636 assert!(builder.constants.is_empty());
637 assert!(builder.classes.is_empty());
638 assert!(builder.interfaces.is_empty());
639 assert!(builder.startup_func.is_none());
640 assert!(builder.shutdown_func.is_none());
641 assert!(builder.request_startup_func.is_none());
642 assert!(builder.request_shutdown_func.is_none());
643 assert!(builder.post_deactivate_func.is_none());
644 assert!(builder.info_func.is_none());
645 #[cfg(feature = "enum")]
646 assert!(builder.enums.is_empty());
647 }
648
649 #[test]
650 fn test_name() {
651 let builder = ModuleBuilder::new("test", "1.0").name("new_test");
652 assert_eq!(builder.name, "new_test");
653 }
654
655 #[test]
656 fn test_version() {
657 let builder = ModuleBuilder::new("test", "1.0").version("2.0");
658 assert_eq!(builder.version, "2.0");
659 }
660
661 #[test]
662 fn test_startup_function() {
663 let builder =
664 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
665 assert!(builder.startup_func.is_some());
666 }
667
668 #[test]
669 fn test_shutdown_function() {
670 let builder =
671 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
672 assert!(builder.shutdown_func.is_some());
673 }
674
675 #[test]
676 fn test_request_startup_function() {
677 let builder = ModuleBuilder::new("test", "1.0")
678 .request_startup_function(test_startup_shutdown_function);
679 assert!(builder.request_startup_func.is_some());
680 }
681
682 #[test]
683 fn test_request_shutdown_function() {
684 let builder = ModuleBuilder::new("test", "1.0")
685 .request_shutdown_function(test_startup_shutdown_function);
686 assert!(builder.request_shutdown_func.is_some());
687 }
688
689 #[test]
690 fn test_set_post_deactivate_function() {
691 let builder =
692 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
693 assert!(builder.post_deactivate_func.is_some());
694 }
695
696 #[test]
697 fn test_set_info_function() {
698 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
699 assert!(builder.info_func.is_some());
700 }
701
702 #[test]
703 fn test_add_function() {
704 let builder =
705 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
706 assert_eq!(builder.functions.len(), 1);
707 }
708
709 #[test]
710 #[cfg(feature = "embed")]
711 fn test_add_constant() {
712 let builder =
713 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
714 assert_eq!(builder.constants.len(), 1);
715 assert_eq!(builder.constants[0].0, "TEST_CONST");
716 assert_eq!(builder.constants[0].2, DocComments::default());
718 }
719}