1use std::{convert::TryFrom, ffi::CString, mem, ptr};
2
3use super::{ClassBuilder, FunctionBuilder};
4use crate::{
5 class::RegisteredClass,
6 constant::IntoConst,
7 describe::DocComments,
8 error::Result,
9 ffi::{ext_php_rs_php_build_id, ZEND_MODULE_API_NO},
10 zend::{FunctionEntry, ModuleEntry},
11 PHP_DEBUG, PHP_ZTS,
12};
13
14#[must_use]
42#[derive(Debug, Default)]
43pub struct ModuleBuilder<'a> {
44 pub(crate) name: String,
45 pub(crate) version: String,
46 pub(crate) functions: Vec<FunctionBuilder<'a>>,
47 pub(crate) constants: Vec<(String, Box<dyn IntoConst + Send>, DocComments)>,
48 pub(crate) classes: Vec<fn() -> ClassBuilder>,
49 startup_func: Option<StartupShutdownFunc>,
50 shutdown_func: Option<StartupShutdownFunc>,
51 request_startup_func: Option<StartupShutdownFunc>,
52 request_shutdown_func: Option<StartupShutdownFunc>,
53 post_deactivate_func: Option<unsafe extern "C" fn() -> i32>,
54 info_func: Option<InfoFunc>,
55}
56
57impl ModuleBuilder<'_> {
58 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
65 Self {
66 name: name.into(),
67 version: version.into(),
68 functions: vec![],
69 constants: vec![],
70 classes: vec![],
71 ..Default::default()
72 }
73 }
74
75 pub fn startup_function(mut self, func: StartupShutdownFunc) -> Self {
81 self.startup_func = Some(func);
82 self
83 }
84
85 pub fn shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
91 self.shutdown_func = Some(func);
92 self
93 }
94
95 pub fn request_startup_function(mut self, func: StartupShutdownFunc) -> Self {
101 self.request_startup_func = Some(func);
102 self
103 }
104
105 pub fn request_shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
111 self.request_shutdown_func = Some(func);
112 self
113 }
114
115 pub fn post_deactivate_function(mut self, func: unsafe extern "C" fn() -> i32) -> Self {
125 self.post_deactivate_func = Some(func);
126 self
127 }
128
129 pub fn info_function(mut self, func: InfoFunc) -> Self {
136 self.info_func = Some(func);
137 self
138 }
139
140 pub fn function(mut self, func: FunctionBuilder<'static>) -> Self {
146 self.functions.push(func);
147 self
148 }
149
150 pub fn constant(
159 mut self,
160 r#const: (&str, impl IntoConst + Send + 'static, DocComments),
161 ) -> Self {
162 let (name, val, docs) = r#const;
163 self.constants.push((
164 name.into(),
165 Box::new(val) as Box<dyn IntoConst + Send>,
166 docs,
167 ));
168 self
169 }
170
171 pub fn class<T: RegisteredClass>(mut self) -> Self {
177 self.classes.push(|| {
178 let mut builder = ClassBuilder::new(T::CLASS_NAME);
179 for (method, flags) in T::method_builders() {
180 builder = builder.method(method, flags);
181 }
182 if let Some(parent) = T::EXTENDS {
183 builder = builder.extends(parent);
184 }
185 for interface in T::IMPLEMENTS {
186 builder = builder.implements(*interface);
187 }
188 for (name, value, docs) in T::constants() {
189 builder = builder
190 .dyn_constant(*name, *value, docs)
191 .expect("Failed to register constant");
192 }
193 for (name, prop_info) in T::get_properties() {
194 builder = builder.property(name, prop_info.flags, prop_info.docs);
195 }
196 if let Some(modifier) = T::BUILDER_MODIFIER {
197 builder = modifier(builder);
198 }
199
200 builder
201 .object_override::<T>()
202 .registration(|ce| {
203 T::get_metadata().set_ce(ce);
204 })
205 .docs(T::DOC_COMMENTS)
206 });
207 self
208 }
209}
210
211pub struct ModuleStartup {
214 constants: Vec<(String, Box<dyn IntoConst + Send>)>,
215 classes: Vec<fn() -> ClassBuilder>,
216}
217
218impl ModuleStartup {
219 pub fn startup(self, _ty: i32, mod_num: i32) -> Result<()> {
230 for (name, val) in self.constants {
231 val.register_constant(&name, mod_num)?;
232 }
233
234 self.classes.into_iter().map(|c| c()).for_each(|c| {
235 c.register().expect("Failed to build class");
236 });
237 Ok(())
238 }
239}
240
241pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
243
244pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
246
247impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup) {
250 type Error = crate::error::Error;
251
252 fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
253 let mut functions = builder
254 .functions
255 .into_iter()
256 .map(FunctionBuilder::build)
257 .collect::<Result<Vec<_>>>()?;
258 functions.push(FunctionEntry::end());
259 let functions = Box::into_raw(functions.into_boxed_slice()) as *const FunctionEntry;
260
261 let name = CString::new(builder.name)?.into_raw();
262 let version = CString::new(builder.version)?.into_raw();
263
264 let startup = ModuleStartup {
265 constants: builder
266 .constants
267 .into_iter()
268 .map(|(n, v, _)| (n, v))
269 .collect(),
270 classes: builder.classes,
271 };
272
273 Ok((
274 ModuleEntry {
275 size: mem::size_of::<ModuleEntry>().try_into()?,
276 zend_api: ZEND_MODULE_API_NO,
277 zend_debug: u8::from(PHP_DEBUG),
278 zts: u8::from(PHP_ZTS),
279 ini_entry: ptr::null(),
280 deps: ptr::null(),
281 name,
282 functions,
283 module_startup_func: builder.startup_func,
284 module_shutdown_func: builder.shutdown_func,
285 request_startup_func: builder.request_startup_func,
286 request_shutdown_func: builder.request_shutdown_func,
287 info_func: builder.info_func,
288 version,
289 globals_size: 0,
290 #[cfg(not(php_zts))]
291 globals_ptr: ptr::null_mut(),
292 #[cfg(php_zts)]
293 globals_id_ptr: ptr::null_mut(),
294 globals_ctor: None,
295 globals_dtor: None,
296 post_deactivate_func: builder.post_deactivate_func,
297 module_started: 0,
298 type_: 0,
299 handle: ptr::null_mut(),
300 module_number: 0,
301 build_id: unsafe { ext_php_rs_php_build_id() },
302 },
303 startup,
304 ))
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use crate::test::{
311 test_deactivate_function, test_function, test_info_function, test_startup_shutdown_function,
312 };
313
314 use super::*;
315
316 #[test]
317 fn test_new() {
318 let builder = ModuleBuilder::new("test", "1.0");
319 assert_eq!(builder.name, "test");
320 assert_eq!(builder.version, "1.0");
321 assert!(builder.functions.is_empty());
322 assert!(builder.constants.is_empty());
323 assert!(builder.classes.is_empty());
324 assert!(builder.startup_func.is_none());
325 assert!(builder.shutdown_func.is_none());
326 assert!(builder.request_startup_func.is_none());
327 assert!(builder.request_shutdown_func.is_none());
328 assert!(builder.post_deactivate_func.is_none());
329 assert!(builder.info_func.is_none());
330 }
331
332 #[test]
333 fn test_startup_function() {
334 let builder =
335 ModuleBuilder::new("test", "1.0").startup_function(test_startup_shutdown_function);
336 assert!(builder.startup_func.is_some());
337 }
338
339 #[test]
340 fn test_shutdown_function() {
341 let builder =
342 ModuleBuilder::new("test", "1.0").shutdown_function(test_startup_shutdown_function);
343 assert!(builder.shutdown_func.is_some());
344 }
345
346 #[test]
347 fn test_request_startup_function() {
348 let builder = ModuleBuilder::new("test", "1.0")
349 .request_startup_function(test_startup_shutdown_function);
350 assert!(builder.request_startup_func.is_some());
351 }
352
353 #[test]
354 fn test_request_shutdown_function() {
355 let builder = ModuleBuilder::new("test", "1.0")
356 .request_shutdown_function(test_startup_shutdown_function);
357 assert!(builder.request_shutdown_func.is_some());
358 }
359
360 #[test]
361 fn test_set_post_deactivate_function() {
362 let builder =
363 ModuleBuilder::new("test", "1.0").post_deactivate_function(test_deactivate_function);
364 assert!(builder.post_deactivate_func.is_some());
365 }
366
367 #[test]
368 fn test_set_info_function() {
369 let builder = ModuleBuilder::new("test", "1.0").info_function(test_info_function);
370 assert!(builder.info_func.is_some());
371 }
372
373 #[test]
374 fn test_add_function() {
375 let builder =
376 ModuleBuilder::new("test", "1.0").function(FunctionBuilder::new("test", test_function));
377 assert_eq!(builder.functions.len(), 1);
378 }
379
380 #[test]
381 #[cfg(feature = "embed")]
382 fn test_add_constant() {
383 let builder =
384 ModuleBuilder::new("test", "1.0").constant(("TEST_CONST", 42, DocComments::default()));
385 assert_eq!(builder.constants.len(), 1);
386 assert_eq!(builder.constants[0].0, "TEST_CONST");
387 assert_eq!(builder.constants[0].2, DocComments::default());
389 }
390}