1use std::{convert::TryFrom, ffi::CString, mem, ptr};
2
3use super::{ClassBuilder, FunctionBuilder};
4#[cfg(feature = "enum")]
5use crate::{builders::enum_builder::EnumBuilder, enum_::RegisteredEnum};
6use crate::{
7 class::RegisteredClass,
8 constant::IntoConst,
9 describe::DocComments,
10 error::Result,
11 ffi::{ext_php_rs_php_build_id, ZEND_MODULE_API_NO},
12 zend::{FunctionEntry, ModuleEntry},
13 PHP_DEBUG, PHP_ZTS,
14};
15
16#[must_use]
44#[derive(Debug, Default)]
45pub struct ModuleBuilder<'a> {
46 pub(crate) name: String,
47 pub(crate) version: String,
48 pub(crate) functions: Vec<FunctionBuilder<'a>>,
49 pub(crate) constants: Vec<(String, Box<dyn IntoConst + Send>, DocComments)>,
50 pub(crate) classes: Vec<fn() -> ClassBuilder>,
51 #[cfg(feature = "enum")]
52 pub(crate) enums: Vec<fn() -> EnumBuilder>,
53 startup_func: Option<StartupShutdownFunc>,
54 shutdown_func: Option<StartupShutdownFunc>,
55 request_startup_func: Option<StartupShutdownFunc>,
56 request_shutdown_func: Option<StartupShutdownFunc>,
57 post_deactivate_func: Option<unsafe extern "C" fn() -> i32>,
58 info_func: Option<InfoFunc>,
59}
60
61impl ModuleBuilder<'_> {
62 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
69 Self {
70 name: name.into(),
71 version: version.into(),
72 functions: vec![],
73 constants: vec![],
74 classes: vec![],
75 ..Default::default()
76 }
77 }
78
79 pub fn name(mut self, name: impl Into<String>) -> Self {
85 self.name = name.into();
86 self
87 }
88
89 pub fn version(mut self, version: impl Into<String>) -> Self {
95 self.version = version.into();
96 self
97 }
98
99 pub fn startup_function(mut self, func: StartupShutdownFunc) -> Self {
105 self.startup_func = Some(func);
106 self
107 }
108
109 pub fn shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
115 self.shutdown_func = Some(func);
116 self
117 }
118
119 pub fn request_startup_function(mut self, func: StartupShutdownFunc) -> Self {
125 self.request_startup_func = Some(func);
126 self
127 }
128
129 pub fn request_shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
135 self.request_shutdown_func = Some(func);
136 self
137 }
138
139 pub fn post_deactivate_function(mut self, func: unsafe extern "C" fn() -> i32) -> Self {
149 self.post_deactivate_func = Some(func);
150 self
151 }
152
153 pub fn info_function(mut self, func: InfoFunc) -> Self {
160 self.info_func = Some(func);
161 self
162 }
163
164 pub fn function(mut self, func: FunctionBuilder<'static>) -> Self {
170 self.functions.push(func);
171 self
172 }
173
174 pub fn constant(
183 mut self,
184 r#const: (&str, impl IntoConst + Send + 'static, DocComments),
185 ) -> Self {
186 let (name, val, docs) = r#const;
187 self.constants.push((
188 name.into(),
189 Box::new(val) as Box<dyn IntoConst + Send>,
190 docs,
191 ));
192 self
193 }
194
195 pub fn class<T: RegisteredClass>(mut self) -> Self {
201 self.classes.push(|| {
202 let mut builder = ClassBuilder::new(T::CLASS_NAME);
203 for (method, flags) in T::method_builders() {
204 builder = builder.method(method, flags);
205 }
206 if let Some(parent) = T::EXTENDS {
207 builder = builder.extends(parent);
208 }
209 for interface in T::IMPLEMENTS {
210 builder = builder.implements(*interface);
211 }
212 for (name, value, docs) in T::constants() {
213 builder = builder
214 .dyn_constant(*name, *value, docs)
215 .expect("Failed to register constant");
216 }
217 for (name, prop_info) in T::get_properties() {
218 builder = builder.property(name, prop_info.flags, prop_info.docs);
219 }
220 if let Some(modifier) = T::BUILDER_MODIFIER {
221 builder = modifier(builder);
222 }
223
224 builder
225 .object_override::<T>()
226 .registration(|ce| {
227 T::get_metadata().set_ce(ce);
228 })
229 .docs(T::DOC_COMMENTS)
230 });
231 self
232 }
233
234 #[cfg(feature = "enum")]
236 pub fn enumeration<T>(mut self) -> Self
237 where
238 T: RegisteredClass + RegisteredEnum,
239 {
240 self.enums.push(|| {
241 let mut builder = EnumBuilder::new(T::CLASS_NAME);
242 for case in T::CASES {
243 builder = builder.case(case);
244 }
245 for (method, flags) in T::method_builders() {
246 builder = builder.method(method, flags);
247 }
248
249 builder
250 .registration(|ce| {
251 T::get_metadata().set_ce(ce);
252 })
253 .docs(T::DOC_COMMENTS)
254 });
255
256 self
257 }
258}
259
260pub struct ModuleStartup {
263 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
264 classes: Vec<fn() -> ClassBuilder>,
265 #[cfg(feature = "enum")]
266 enums: Vec<fn() -> EnumBuilder>,
267}
268
269impl ModuleStartup {
270 pub fn startup(self, _ty: i32, mod_num: i32) -> Result<()> {
281 for (name, val) in self.constants {
282 val.register_constant(&name, mod_num)?;
283 }
284
285 self.classes.into_iter().map(|c| c()).for_each(|c| {
286 c.register().expect("Failed to build class");
287 });
288
289 #[cfg(feature = "enum")]
290 self.enums
291 .into_iter()
292 .map(|builder| builder())
293 .for_each(|e| {
294 e.register().expect("Failed to build enum");
295 });
296
297 Ok(())
298 }
299}
300
301pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
303
304pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
306
307impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup) {
310 type Error = crate::error::Error;
311
312 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
313 let mut functions = builder
314 .functions
315 .into_iter()
316 .map(FunctionBuilder::build)
317 .collect::<Result<Vec<_>>>()?;
318 functions.push(FunctionEntry::end());
319 let functions = Box::into_raw(functions.into_boxed_slice()) as *const FunctionEntry;
320
321 let name = CString::new(builder.name)?.into_raw();
322 let version = CString::new(builder.version)?.into_raw();
323
324 let startup = ModuleStartup {
325 constants: builder
326 .constants
327 .into_iter()
328 .map(|(n, v, _)| (n, v))
329 .collect(),
330 classes: builder.classes,
331 #[cfg(feature = "enum")]
332 enums: builder.enums,
333 };
334
335 Ok((
336 ModuleEntry {
337 size: mem::size_of::<ModuleEntry>().try_into()?,
338 zend_api: ZEND_MODULE_API_NO,
339 zend_debug: u8::from(PHP_DEBUG),
340 zts: u8::from(PHP_ZTS),
341 ini_entry: ptr::null(),
342 deps: ptr::null(),
343 name,
344 functions,
345 module_startup_func: builder.startup_func,
346 module_shutdown_func: builder.shutdown_func,
347 request_startup_func: builder.request_startup_func,
348 request_shutdown_func: builder.request_shutdown_func,
349 info_func: builder.info_func,
350 version,
351 globals_size: 0,
352 #[cfg(not(php_zts))]
353 globals_ptr: ptr::null_mut(),
354 #[cfg(php_zts)]
355 globals_id_ptr: ptr::null_mut(),
356 globals_ctor: None,
357 globals_dtor: None,
358 post_deactivate_func: builder.post_deactivate_func,
359 module_started: 0,
360 type_: 0,
361 handle: ptr::null_mut(),
362 module_number: 0,
363 build_id: unsafe { ext_php_rs_php_build_id() },
364 },
365 startup,
366 ))
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use crate::test::{
373 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
374 };
375
376 use super::*;
377
378 #[test]
379 fn test_new() {
380 let builder = ModuleBuilder::new("test", "1.0");
381 assert_eq!(builder.name, "test");
382 assert_eq!(builder.version, "1.0");
383 assert!(builder.functions.is_empty());
384 assert!(builder.constants.is_empty());
385 assert!(builder.classes.is_empty());
386 assert!(builder.startup_func.is_none());
387 assert!(builder.shutdown_func.is_none());
388 assert!(builder.request_startup_func.is_none());
389 assert!(builder.request_shutdown_func.is_none());
390 assert!(builder.post_deactivate_func.is_none());
391 assert!(builder.info_func.is_none());
392 #[cfg(feature = "enum")]
393 assert!(builder.enums.is_empty());
394 }
395
396 #[test]
397 fn test_name() {
398 let builder = ModuleBuilder::new("test", "1.0").name("new_test");
399 assert_eq!(builder.name, "new_test");
400 }
401
402 #[test]
403 fn test_version() {
404 let builder = ModuleBuilder::new("test", "1.0").version("2.0");
405 assert_eq!(builder.version, "2.0");
406 }
407
408 #[test]
409 fn test_startup_function() {
410 let builder =
411 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
412 assert!(builder.startup_func.is_some());
413 }
414
415 #[test]
416 fn test_shutdown_function() {
417 let builder =
418 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
419 assert!(builder.shutdown_func.is_some());
420 }
421
422 #[test]
423 fn test_request_startup_function() {
424 let builder = ModuleBuilder::new("test", "1.0")
425 .request_startup_function(test_startup_shutdown_function);
426 assert!(builder.request_startup_func.is_some());
427 }
428
429 #[test]
430 fn test_request_shutdown_function() {
431 let builder = ModuleBuilder::new("test", "1.0")
432 .request_shutdown_function(test_startup_shutdown_function);
433 assert!(builder.request_shutdown_func.is_some());
434 }
435
436 #[test]
437 fn test_set_post_deactivate_function() {
438 let builder =
439 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
440 assert!(builder.post_deactivate_func.is_some());
441 }
442
443 #[test]
444 fn test_set_info_function() {
445 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
446 assert!(builder.info_func.is_some());
447 }
448
449 #[test]
450 fn test_add_function() {
451 let builder =
452 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
453 assert_eq!(builder.functions.len(), 1);
454 }
455
456 #[test]
457 #[cfg(feature = "embed")]
458 fn test_add_constant() {
459 let builder =
460 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
461 assert_eq!(builder.constants.len(), 1);
462 assert_eq!(builder.constants[0].0, "TEST_CONST");
463 assert_eq!(builder.constants[0].2, DocComments::default());
465 }
466}