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 pub fn function(mut self, func: FunctionBuilder<'static>) -> Self {
172 self.functions.push(func);
173 self
174 }
175
176 pub fn constant(
185 mut self,
186 r#const: (&str, impl IntoConst + Send + 'static, DocComments),
187 ) -> Self {
188 let (name, val, docs) = r#const;
189 self.constants.push((
190 name.into(),
191 Box::new(val) as Box<dyn IntoConst + Send>,
192 docs,
193 ));
194 self
195 }
196
197 pub fn interface<T: RegisteredClass>(mut self) -> Self {
203 self.interfaces.push(|| {
204 let mut builder = ClassBuilder::new(T::CLASS_NAME);
205 for (method, flags) in T::method_builders() {
206 builder = builder.method(method, flags);
207 }
208 for interface in T::IMPLEMENTS {
209 builder = builder.implements(*interface);
210 }
211 for (name, value, docs) in T::constants() {
212 builder = builder
213 .dyn_constant(*name, *value, docs)
214 .expect("Failed to register constant");
215 }
216
217 if let Some(modifier) = T::BUILDER_MODIFIER {
218 builder = modifier(builder);
219 }
220
221 builder = builder.flags(ClassFlags::Interface);
222 builder
223 .object_override::<T>()
224 .registration(|ce| {
225 T::get_metadata().set_ce(ce);
226 })
227 .docs(T::DOC_COMMENTS)
228 });
229 self
230 }
231
232 pub fn class<T: RegisteredClass>(mut self) -> Self {
238 self.classes.push(|| {
239 let mut builder = ClassBuilder::new(T::CLASS_NAME);
240 for (method, flags) in T::method_builders() {
241 builder = builder.method(method, flags);
242 }
243 if let Some(parent) = T::EXTENDS {
244 builder = builder.extends(parent);
245 }
246 for interface in T::IMPLEMENTS {
247 builder = builder.implements(*interface);
248 }
249 for (name, value, docs) in T::constants() {
250 builder = builder
251 .dyn_constant(*name, *value, docs)
252 .expect("Failed to register constant");
253 }
254 for (name, prop_info) in T::get_properties() {
255 builder = builder.property(name, prop_info.flags, None, prop_info.docs);
256 }
257 for (name, flags, default, docs) in T::static_properties() {
258 let default_fn = default.map(|v| {
259 Box::new(move || v.as_zval(true))
260 as Box<dyn FnOnce() -> crate::error::Result<crate::types::Zval>>
261 });
262 builder = builder.property(*name, *flags, default_fn, docs);
263 }
264 if let Some(modifier) = T::BUILDER_MODIFIER {
265 builder = modifier(builder);
266 }
267
268 builder
269 .object_override::<T>()
270 .registration(|ce| {
271 T::get_metadata().set_ce(ce);
272 })
273 .docs(T::DOC_COMMENTS)
274 });
275 self
276 }
277
278 #[cfg(feature = "enum")]
280 pub fn enumeration<T>(mut self) -> Self
281 where
282 T: RegisteredClass + RegisteredEnum,
283 {
284 self.enums.push(|| {
285 let mut builder = EnumBuilder::new(T::CLASS_NAME);
286 for case in T::CASES {
287 builder = builder.case(case);
288 }
289 for (method, flags) in T::method_builders() {
290 builder = builder.method(method, flags);
291 }
292
293 builder
294 .registration(|ce| {
295 T::get_metadata().set_ce(ce);
296 })
297 .docs(T::DOC_COMMENTS)
298 });
299
300 self
301 }
302}
303
304pub struct ModuleStartup {
307 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
308 classes: Vec<fn() -> ClassBuilder>,
309 interfaces: Vec<fn() -> ClassBuilder>,
310 #[cfg(feature = "enum")]
311 enums: Vec<fn() -> EnumBuilder>,
312}
313
314impl ModuleStartup {
315 pub fn startup(self, _ty: i32, mod_num: i32) -> Result<()> {
326 for (name, val) in self.constants {
327 val.register_constant(&name, mod_num)?;
328 }
329
330 self.classes.into_iter().map(|c| c()).for_each(|c| {
331 c.register().expect("Failed to build class");
332 });
333
334 self.interfaces.into_iter().map(|c| c()).for_each(|c| {
335 c.register().expect("Failed to build interface");
336 });
337
338 #[cfg(feature = "enum")]
339 self.enums
340 .into_iter()
341 .map(|builder| builder())
342 .for_each(|e| {
343 e.register().expect("Failed to build enum");
344 });
345
346 Ok(())
347 }
348}
349
350pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
352
353pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
355
356impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup) {
359 type Error = crate::error::Error;
360
361 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
362 let mut functions = builder
363 .functions
364 .into_iter()
365 .map(FunctionBuilder::build)
366 .collect::<Result<Vec<_>>>()?;
367 functions.push(FunctionEntry::end());
368 let functions = Box::into_raw(functions.into_boxed_slice()) as *const FunctionEntry;
369
370 let name = CString::new(builder.name)?.into_raw();
371 let version = CString::new(builder.version)?.into_raw();
372
373 let startup = ModuleStartup {
374 constants: builder
375 .constants
376 .into_iter()
377 .map(|(n, v, _)| (n, v))
378 .collect(),
379 classes: builder.classes,
380 interfaces: builder.interfaces,
381 #[cfg(feature = "enum")]
382 enums: builder.enums,
383 };
384
385 #[cfg(not(php_zts))]
386 let module_entry = ModuleEntry {
387 size: mem::size_of::<ModuleEntry>().try_into()?,
388 zend_api: ZEND_MODULE_API_NO,
389 zend_debug: u8::from(PHP_DEBUG),
390 zts: u8::from(PHP_ZTS),
391 ini_entry: ptr::null(),
392 deps: ptr::null(),
393 name,
394 functions,
395 module_startup_func: builder.startup_func,
396 module_shutdown_func: builder.shutdown_func,
397 request_startup_func: builder.request_startup_func,
398 request_shutdown_func: builder.request_shutdown_func,
399 info_func: builder.info_func,
400 version,
401 globals_size: 0,
402 globals_ptr: ptr::null_mut(),
403 globals_ctor: None,
404 globals_dtor: None,
405 post_deactivate_func: builder.post_deactivate_func,
406 module_started: 0,
407 type_: 0,
408 handle: ptr::null_mut(),
409 module_number: 0,
410 build_id: unsafe { ext_php_rs_php_build_id() },
411 };
412
413 #[cfg(php_zts)]
414 let module_entry = ModuleEntry {
415 size: mem::size_of::<ModuleEntry>().try_into()?,
416 zend_api: ZEND_MODULE_API_NO,
417 zend_debug: u8::from(PHP_DEBUG),
418 zts: u8::from(PHP_ZTS),
419 ini_entry: ptr::null(),
420 deps: ptr::null(),
421 name,
422 functions,
423 module_startup_func: builder.startup_func,
424 module_shutdown_func: builder.shutdown_func,
425 request_startup_func: builder.request_startup_func,
426 request_shutdown_func: builder.request_shutdown_func,
427 info_func: builder.info_func,
428 version,
429 globals_size: 0,
430 globals_id_ptr: ptr::null_mut(),
431 globals_ctor: None,
432 globals_dtor: None,
433 post_deactivate_func: builder.post_deactivate_func,
434 module_started: 0,
435 type_: 0,
436 handle: ptr::null_mut(),
437 module_number: 0,
438 build_id: unsafe { ext_php_rs_php_build_id() },
439 };
440
441 Ok((module_entry, startup))
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use crate::test::{
448 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
449 };
450
451 use super::*;
452
453 #[test]
454 fn test_new() {
455 let builder = ModuleBuilder::new("test", "1.0");
456 assert_eq!(builder.name, "test");
457 assert_eq!(builder.version, "1.0");
458 assert!(builder.functions.is_empty());
459 assert!(builder.constants.is_empty());
460 assert!(builder.classes.is_empty());
461 assert!(builder.interfaces.is_empty());
462 assert!(builder.startup_func.is_none());
463 assert!(builder.shutdown_func.is_none());
464 assert!(builder.request_startup_func.is_none());
465 assert!(builder.request_shutdown_func.is_none());
466 assert!(builder.post_deactivate_func.is_none());
467 assert!(builder.info_func.is_none());
468 #[cfg(feature = "enum")]
469 assert!(builder.enums.is_empty());
470 }
471
472 #[test]
473 fn test_name() {
474 let builder = ModuleBuilder::new("test", "1.0").name("new_test");
475 assert_eq!(builder.name, "new_test");
476 }
477
478 #[test]
479 fn test_version() {
480 let builder = ModuleBuilder::new("test", "1.0").version("2.0");
481 assert_eq!(builder.version, "2.0");
482 }
483
484 #[test]
485 fn test_startup_function() {
486 let builder =
487 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
488 assert!(builder.startup_func.is_some());
489 }
490
491 #[test]
492 fn test_shutdown_function() {
493 let builder =
494 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
495 assert!(builder.shutdown_func.is_some());
496 }
497
498 #[test]
499 fn test_request_startup_function() {
500 let builder = ModuleBuilder::new("test", "1.0")
501 .request_startup_function(test_startup_shutdown_function);
502 assert!(builder.request_startup_func.is_some());
503 }
504
505 #[test]
506 fn test_request_shutdown_function() {
507 let builder = ModuleBuilder::new("test", "1.0")
508 .request_shutdown_function(test_startup_shutdown_function);
509 assert!(builder.request_shutdown_func.is_some());
510 }
511
512 #[test]
513 fn test_set_post_deactivate_function() {
514 let builder =
515 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
516 assert!(builder.post_deactivate_func.is_some());
517 }
518
519 #[test]
520 fn test_set_info_function() {
521 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
522 assert!(builder.info_func.is_some());
523 }
524
525 #[test]
526 fn test_add_function() {
527 let builder =
528 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
529 assert_eq!(builder.functions.len(), 1);
530 }
531
532 #[test]
533 #[cfg(feature = "embed")]
534 fn test_add_constant() {
535 let builder =
536 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
537 assert_eq!(builder.constants.len(), 1);
538 assert_eq!(builder.constants[0].0, "TEST_CONST");
539 assert_eq!(builder.constants[0].2, DocComments::default());
541 }
542}