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 pub fn function(mut self, func: FunctionBuilder<'static>) -> Self {
222 self.functions.push(func);
223 self
224 }
225
226 pub fn constant(
235 mut self,
236 r#const: (&str, impl IntoConst + Send + 'static, DocComments),
237 ) -> Self {
238 let (name, val, docs) = r#const;
239 self.constants.push((
240 name.into(),
241 Box::new(val) as Box<dyn IntoConst + Send>,
242 docs,
243 ));
244 self
245 }
246
247 pub fn interface<T: RegisteredClass>(mut self) -> Self {
253 self.interfaces.push(|| {
254 let mut builder = ClassBuilder::new(T::CLASS_NAME);
255 for (method, flags) in T::method_builders() {
256 builder = builder.method(method, flags);
257 }
258 for interface in T::IMPLEMENTS {
259 builder = builder.implements(*interface);
260 }
261 for (name, value, docs) in T::constants() {
262 builder = builder
263 .dyn_constant(*name, *value, docs)
264 .expect("Failed to register constant");
265 }
266
267 if let Some(modifier) = T::BUILDER_MODIFIER {
268 builder = modifier(builder);
269 }
270
271 builder = builder.flags(ClassFlags::Interface);
272 builder
273 .object_override::<T>()
274 .registration(|ce| {
275 T::get_metadata().set_ce(ce);
276 })
277 .docs(T::DOC_COMMENTS)
278 });
279 self
280 }
281
282 pub fn class<T: RegisteredClass>(mut self) -> Self {
288 self.classes.push(|| {
289 let mut builder = ClassBuilder::new(T::CLASS_NAME);
290 for (method, flags) in T::method_builders() {
291 builder = builder.method(method, flags);
292 }
293 if let Some(parent) = T::EXTENDS {
294 builder = builder.extends(parent);
295 }
296 for interface in T::IMPLEMENTS {
297 builder = builder.implements(*interface);
298 }
299 for (name, value, docs) in T::constants() {
300 builder = builder
301 .dyn_constant(*name, *value, docs)
302 .expect("Failed to register constant");
303 }
304 for (name, prop_info) in T::get_properties() {
305 builder = builder.property(name, prop_info.flags, None, prop_info.docs);
306 }
307 for (name, flags, default, docs) in T::static_properties() {
308 let default_fn = default.map(|v| {
309 Box::new(move || v.as_zval(true))
310 as Box<dyn FnOnce() -> crate::error::Result<crate::types::Zval>>
311 });
312 builder = builder.property(*name, *flags, default_fn, docs);
313 }
314 if let Some(modifier) = T::BUILDER_MODIFIER {
315 builder = modifier(builder);
316 }
317
318 builder
319 .flags(T::FLAGS)
320 .object_override::<T>()
321 .registration(|ce| {
322 T::get_metadata().set_ce(ce);
323 })
324 .docs(T::DOC_COMMENTS)
325 });
326 self
327 }
328
329 #[cfg(feature = "enum")]
331 pub fn enumeration<T>(mut self) -> Self
332 where
333 T: RegisteredClass + RegisteredEnum,
334 {
335 self.enums.push(|| {
336 let mut builder = EnumBuilder::new(T::CLASS_NAME);
337 for case in T::CASES {
338 builder = builder.case(case);
339 }
340 for (method, flags) in T::method_builders() {
341 builder = builder.method(method, flags);
342 }
343
344 builder
345 .registration(|ce| {
346 T::get_metadata().set_ce(ce);
347 })
348 .docs(T::DOC_COMMENTS)
349 });
350
351 self
352 }
353}
354
355pub struct ModuleStartup {
358 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
359 classes: Vec<fn() -> ClassBuilder>,
360 interfaces: Vec<fn() -> ClassBuilder>,
361 #[cfg(feature = "enum")]
362 enums: Vec<fn() -> EnumBuilder>,
363}
364
365impl ModuleStartup {
366 pub fn startup(self, _ty: i32, mod_num: i32) -> Result<()> {
377 for (name, val) in self.constants {
378 val.register_constant(&name, mod_num)?;
379 }
380
381 self.classes.into_iter().map(|c| c()).for_each(|c| {
382 c.register().expect("Failed to build class");
383 });
384
385 self.interfaces.into_iter().map(|c| c()).for_each(|c| {
386 c.register().expect("Failed to build interface");
387 });
388
389 #[cfg(feature = "enum")]
390 self.enums
391 .into_iter()
392 .map(|builder| builder())
393 .for_each(|e| {
394 e.register().expect("Failed to build enum");
395 });
396
397 #[cfg(feature = "observer")]
399 unsafe {
400 crate::zend::observer::observer_startup();
401 }
402
403 Ok(())
404 }
405}
406
407pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
409
410pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
412
413impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup) {
416 type Error = crate::error::Error;
417
418 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
419 let mut functions = builder
420 .functions
421 .into_iter()
422 .map(FunctionBuilder::build)
423 .collect::<Result<Vec<_>>>()?;
424 functions.push(FunctionEntry::end());
425 let functions = Box::into_raw(functions.into_boxed_slice()) as *const FunctionEntry;
426
427 let name = CString::new(builder.name)?.into_raw();
428 let version = CString::new(builder.version)?.into_raw();
429
430 let startup = ModuleStartup {
431 constants: builder
432 .constants
433 .into_iter()
434 .map(|(n, v, _)| (n, v))
435 .collect(),
436 classes: builder.classes,
437 interfaces: builder.interfaces,
438 #[cfg(feature = "enum")]
439 enums: builder.enums,
440 };
441
442 #[cfg(not(php_zts))]
443 let module_entry = ModuleEntry {
444 size: mem::size_of::<ModuleEntry>().try_into()?,
445 zend_api: ZEND_MODULE_API_NO,
446 zend_debug: u8::from(PHP_DEBUG),
447 zts: u8::from(PHP_ZTS),
448 ini_entry: ptr::null(),
449 deps: ptr::null(),
450 name,
451 functions,
452 module_startup_func: builder.startup_func,
453 module_shutdown_func: builder.shutdown_func,
454 request_startup_func: builder.request_startup_func,
455 request_shutdown_func: builder.request_shutdown_func,
456 info_func: builder.info_func,
457 version,
458 globals_size: 0,
459 globals_ptr: ptr::null_mut(),
460 globals_ctor: None,
461 globals_dtor: None,
462 post_deactivate_func: builder.post_deactivate_func,
463 module_started: 0,
464 type_: 0,
465 handle: ptr::null_mut(),
466 module_number: 0,
467 build_id: unsafe { ext_php_rs_php_build_id() },
468 };
469
470 #[cfg(php_zts)]
471 let module_entry = ModuleEntry {
472 size: mem::size_of::<ModuleEntry>().try_into()?,
473 zend_api: ZEND_MODULE_API_NO,
474 zend_debug: u8::from(PHP_DEBUG),
475 zts: u8::from(PHP_ZTS),
476 ini_entry: ptr::null(),
477 deps: ptr::null(),
478 name,
479 functions,
480 module_startup_func: builder.startup_func,
481 module_shutdown_func: builder.shutdown_func,
482 request_startup_func: builder.request_startup_func,
483 request_shutdown_func: builder.request_shutdown_func,
484 info_func: builder.info_func,
485 version,
486 globals_size: 0,
487 globals_id_ptr: ptr::null_mut(),
488 globals_ctor: None,
489 globals_dtor: None,
490 post_deactivate_func: builder.post_deactivate_func,
491 module_started: 0,
492 type_: 0,
493 handle: ptr::null_mut(),
494 module_number: 0,
495 build_id: unsafe { ext_php_rs_php_build_id() },
496 };
497
498 Ok((module_entry, startup))
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use crate::test::{
505 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
506 };
507
508 use super::*;
509
510 #[test]
511 fn test_new() {
512 let builder = ModuleBuilder::new("test", "1.0");
513 assert_eq!(builder.name, "test");
514 assert_eq!(builder.version, "1.0");
515 assert!(builder.functions.is_empty());
516 assert!(builder.constants.is_empty());
517 assert!(builder.classes.is_empty());
518 assert!(builder.interfaces.is_empty());
519 assert!(builder.startup_func.is_none());
520 assert!(builder.shutdown_func.is_none());
521 assert!(builder.request_startup_func.is_none());
522 assert!(builder.request_shutdown_func.is_none());
523 assert!(builder.post_deactivate_func.is_none());
524 assert!(builder.info_func.is_none());
525 #[cfg(feature = "enum")]
526 assert!(builder.enums.is_empty());
527 }
528
529 #[test]
530 fn test_name() {
531 let builder = ModuleBuilder::new("test", "1.0").name("new_test");
532 assert_eq!(builder.name, "new_test");
533 }
534
535 #[test]
536 fn test_version() {
537 let builder = ModuleBuilder::new("test", "1.0").version("2.0");
538 assert_eq!(builder.version, "2.0");
539 }
540
541 #[test]
542 fn test_startup_function() {
543 let builder =
544 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
545 assert!(builder.startup_func.is_some());
546 }
547
548 #[test]
549 fn test_shutdown_function() {
550 let builder =
551 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
552 assert!(builder.shutdown_func.is_some());
553 }
554
555 #[test]
556 fn test_request_startup_function() {
557 let builder = ModuleBuilder::new("test", "1.0")
558 .request_startup_function(test_startup_shutdown_function);
559 assert!(builder.request_startup_func.is_some());
560 }
561
562 #[test]
563 fn test_request_shutdown_function() {
564 let builder = ModuleBuilder::new("test", "1.0")
565 .request_shutdown_function(test_startup_shutdown_function);
566 assert!(builder.request_shutdown_func.is_some());
567 }
568
569 #[test]
570 fn test_set_post_deactivate_function() {
571 let builder =
572 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
573 assert!(builder.post_deactivate_func.is_some());
574 }
575
576 #[test]
577 fn test_set_info_function() {
578 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
579 assert!(builder.info_func.is_some());
580 }
581
582 #[test]
583 fn test_add_function() {
584 let builder =
585 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
586 assert_eq!(builder.functions.len(), 1);
587 }
588
589 #[test]
590 #[cfg(feature = "embed")]
591 fn test_add_constant() {
592 let builder =
593 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
594 assert_eq!(builder.constants.len(), 1);
595 assert_eq!(builder.constants[0].0, "TEST_CONST");
596 assert_eq!(builder.constants[0].2, DocComments::default());
598 }
599}