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, prop_info.docs);
256 }
257 if let Some(modifier) = T::BUILDER_MODIFIER {
258 builder = modifier(builder);
259 }
260
261 builder
262 .object_override::<T>()
263 .registration(|ce| {
264 T::get_metadata().set_ce(ce);
265 })
266 .docs(T::DOC_COMMENTS)
267 });
268 self
269 }
270
271 #[cfg(feature = "enum")]
273 pub fn enumeration<T>(mut self) -> Self
274 where
275 T: RegisteredClass + RegisteredEnum,
276 {
277 self.enums.push(|| {
278 let mut builder = EnumBuilder::new(T::CLASS_NAME);
279 for case in T::CASES {
280 builder = builder.case(case);
281 }
282 for (method, flags) in T::method_builders() {
283 builder = builder.method(method, flags);
284 }
285
286 builder
287 .registration(|ce| {
288 T::get_metadata().set_ce(ce);
289 })
290 .docs(T::DOC_COMMENTS)
291 });
292
293 self
294 }
295}
296
297pub struct ModuleStartup {
300 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
301 classes: Vec<fn() -> ClassBuilder>,
302 interfaces: Vec<fn() -> ClassBuilder>,
303 #[cfg(feature = "enum")]
304 enums: Vec<fn() -> EnumBuilder>,
305}
306
307impl ModuleStartup {
308 pub fn startup(self, _ty: i32, mod_num: i32) -> Result<()> {
319 for (name, val) in self.constants {
320 val.register_constant(&name, mod_num)?;
321 }
322
323 self.classes.into_iter().map(|c| c()).for_each(|c| {
324 c.register().expect("Failed to build class");
325 });
326
327 self.interfaces.into_iter().map(|c| c()).for_each(|c| {
328 c.register().expect("Failed to build interface");
329 });
330
331 #[cfg(feature = "enum")]
332 self.enums
333 .into_iter()
334 .map(|builder| builder())
335 .for_each(|e| {
336 e.register().expect("Failed to build enum");
337 });
338
339 Ok(())
340 }
341}
342
343pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
345
346pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
348
349impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup) {
352 type Error = crate::error::Error;
353
354 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
355 let mut functions = builder
356 .functions
357 .into_iter()
358 .map(FunctionBuilder::build)
359 .collect::<Result<Vec<_>>>()?;
360 functions.push(FunctionEntry::end());
361 let functions = Box::into_raw(functions.into_boxed_slice()) as *const FunctionEntry;
362
363 let name = CString::new(builder.name)?.into_raw();
364 let version = CString::new(builder.version)?.into_raw();
365
366 let startup = ModuleStartup {
367 constants: builder
368 .constants
369 .into_iter()
370 .map(|(n, v, _)| (n, v))
371 .collect(),
372 classes: builder.classes,
373 interfaces: builder.interfaces,
374 #[cfg(feature = "enum")]
375 enums: builder.enums,
376 };
377
378 Ok((
379 ModuleEntry {
380 size: mem::size_of::<ModuleEntry>().try_into()?,
381 zend_api: ZEND_MODULE_API_NO,
382 zend_debug: u8::from(PHP_DEBUG),
383 zts: u8::from(PHP_ZTS),
384 ini_entry: ptr::null(),
385 deps: ptr::null(),
386 name,
387 functions,
388 module_startup_func: builder.startup_func,
389 module_shutdown_func: builder.shutdown_func,
390 request_startup_func: builder.request_startup_func,
391 request_shutdown_func: builder.request_shutdown_func,
392 info_func: builder.info_func,
393 version,
394 globals_size: 0,
395 #[cfg(not(php_zts))]
396 globals_ptr: ptr::null_mut(),
397 #[cfg(php_zts)]
398 globals_id_ptr: ptr::null_mut(),
399 globals_ctor: None,
400 globals_dtor: None,
401 post_deactivate_func: builder.post_deactivate_func,
402 module_started: 0,
403 type_: 0,
404 handle: ptr::null_mut(),
405 module_number: 0,
406 build_id: unsafe { ext_php_rs_php_build_id() },
407 },
408 startup,
409 ))
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use crate::test::{
416 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
417 };
418
419 use super::*;
420
421 #[test]
422 fn test_new() {
423 let builder = ModuleBuilder::new("test", "1.0");
424 assert_eq!(builder.name, "test");
425 assert_eq!(builder.version, "1.0");
426 assert!(builder.functions.is_empty());
427 assert!(builder.constants.is_empty());
428 assert!(builder.classes.is_empty());
429 assert!(builder.interfaces.is_empty());
430 assert!(builder.startup_func.is_none());
431 assert!(builder.shutdown_func.is_none());
432 assert!(builder.request_startup_func.is_none());
433 assert!(builder.request_shutdown_func.is_none());
434 assert!(builder.post_deactivate_func.is_none());
435 assert!(builder.info_func.is_none());
436 #[cfg(feature = "enum")]
437 assert!(builder.enums.is_empty());
438 }
439
440 #[test]
441 fn test_name() {
442 let builder = ModuleBuilder::new("test", "1.0").name("new_test");
443 assert_eq!(builder.name, "new_test");
444 }
445
446 #[test]
447 fn test_version() {
448 let builder = ModuleBuilder::new("test", "1.0").version("2.0");
449 assert_eq!(builder.version, "2.0");
450 }
451
452 #[test]
453 fn test_startup_function() {
454 let builder =
455 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
456 assert!(builder.startup_func.is_some());
457 }
458
459 #[test]
460 fn test_shutdown_function() {
461 let builder =
462 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
463 assert!(builder.shutdown_func.is_some());
464 }
465
466 #[test]
467 fn test_request_startup_function() {
468 let builder = ModuleBuilder::new("test", "1.0")
469 .request_startup_function(test_startup_shutdown_function);
470 assert!(builder.request_startup_func.is_some());
471 }
472
473 #[test]
474 fn test_request_shutdown_function() {
475 let builder = ModuleBuilder::new("test", "1.0")
476 .request_shutdown_function(test_startup_shutdown_function);
477 assert!(builder.request_shutdown_func.is_some());
478 }
479
480 #[test]
481 fn test_set_post_deactivate_function() {
482 let builder =
483 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
484 assert!(builder.post_deactivate_func.is_some());
485 }
486
487 #[test]
488 fn test_set_info_function() {
489 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
490 assert!(builder.info_func.is_some());
491 }
492
493 #[test]
494 fn test_add_function() {
495 let builder =
496 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
497 assert_eq!(builder.functions.len(), 1);
498 }
499
500 #[test]
501 #[cfg(feature = "embed")]
502 fn test_add_constant() {
503 let builder =
504 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
505 assert_eq!(builder.constants.len(), 1);
506 assert_eq!(builder.constants[0].0, "TEST_CONST");
507 assert_eq!(builder.constants[0].2, DocComments::default());
509 }
510}