ext_php_rs/builders/
module.rs

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/// Builds a Zend module extension to be registered with PHP. Must be called
18/// from within an external function called `get_module`, returning a mutable
19/// pointer to a `ModuleEntry`.
20///
21/// ```rust,no_run
22/// use ext_php_rs::{
23///     builders::ModuleBuilder,
24///     zend::ModuleEntry,
25///     info_table_start, info_table_end, info_table_row
26/// };
27///
28/// #[unsafe(no_mangle)]
29/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
30///     info_table_start!();
31///     info_table_row!("column 1", "column 2");
32///     info_table_end!();
33/// }
34///
35/// #[unsafe(no_mangle)]
36/// pub extern "C" fn get_module() -> *mut ModuleEntry {
37///     let (entry, _) = ModuleBuilder::new("ext-name", "ext-version")
38///         .info_function(php_module_info)
39///         .try_into()
40///         .unwrap();
41///     entry.into_raw()
42/// }
43/// ```
44#[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    /// Creates a new module builder with a given name and version.
65    ///
66    /// # Arguments
67    ///
68    /// * `name` - The name of the extension.
69    /// * `version` - The current version of the extension.
70    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    /// Overrides module name.
82    ///
83    /// # Arguments
84    ///
85    /// * `name` - The name of the extension.
86    pub fn name(mut self, name: impl Into<String>) -> Self {
87        self.name = name.into();
88        self
89    }
90
91    /// Overrides module version.
92    ///
93    /// # Arguments
94    ///
95    /// * `version` - The current version of the extension.
96    pub fn version(mut self, version: impl Into<String>) -> Self {
97        self.version = version.into();
98        self
99    }
100
101    /// Sets the startup function for the extension.
102    ///
103    /// # Arguments
104    ///
105    /// * `func` - The function to be called on startup.
106    pub fn startup_function(mut self, func: StartupShutdownFunc) -> Self {
107        self.startup_func = Some(func);
108        self
109    }
110
111    /// Sets the shutdown function for the extension.
112    ///
113    /// # Arguments
114    ///
115    /// * `func` - The function to be called on shutdown.
116    pub fn shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
117        self.shutdown_func = Some(func);
118        self
119    }
120
121    /// Sets the request startup function for the extension.
122    ///
123    /// # Arguments
124    ///
125    /// * `func` - The function to be called when startup is requested.
126    pub fn request_startup_function(mut self, func: StartupShutdownFunc) -> Self {
127        self.request_startup_func = Some(func);
128        self
129    }
130
131    /// Sets the request shutdown function for the extension.
132    ///
133    /// # Arguments
134    ///
135    /// * `func` - The function to be called when shutdown is requested.
136    pub fn request_shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
137        self.request_shutdown_func = Some(func);
138        self
139    }
140
141    /// Sets the post request shutdown function for the extension.
142    ///
143    /// This function can be useful if you need to do any final cleanup at the
144    /// very end of a request, after all other resources have been released. For
145    /// example, if your extension creates any persistent resources that last
146    /// beyond a single request, you could use this function to clean those up.
147    /// # Arguments
148    ///
149    /// * `func` - The function to be called when shutdown is requested.
150    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    /// Sets the extension information function for the extension.
156    ///
157    /// # Arguments
158    ///
159    /// * `func` - The function to be called to retrieve the information about
160    ///   the extension.
161    pub fn info_function(mut self, func: InfoFunc) -> Self {
162        self.info_func = Some(func);
163        self
164    }
165
166    /// Adds a function to the extension.
167    ///
168    /// # Arguments
169    ///
170    /// * `func` - The function to be added to the extension.
171    pub fn function(mut self, func: FunctionBuilder<'static>) -> Self {
172        self.functions.push(func);
173        self
174    }
175
176    /// Adds a constant to the extension.
177    ///
178    /// # Arguments
179    ///
180    /// * `const` - Tuple containing the name, value and doc comments for the
181    ///   constant. This is a tuple to support the [`wrap_constant`] macro.
182    ///
183    /// [`wrap_constant`]: crate::wrap_constant
184    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    /// Adds a interface to the extension.
198    ///
199    /// # Panics
200    ///
201    /// * Panics if a constant could not be registered.
202    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    /// Adds a class to the extension.
233    ///
234    /// # Panics
235    ///
236    /// * Panics if a constant could not be registered.
237    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    /// Adds an enum to the extension.
279    #[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
304/// Artifacts from the [`ModuleBuilder`] that should be revisited inside the
305/// extension startup function.
306pub 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    /// Completes startup of the module. Should only be called inside the module
316    /// startup function.
317    ///
318    /// # Errors
319    ///
320    /// * Returns an error if a constant could not be registered.
321    ///
322    /// # Panics
323    ///
324    /// * Panics if a class could not be registered.
325    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
350/// A function to be called when the extension is starting up or shutting down.
351pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
352
353/// A function to be called when `phpinfo();` is called.
354pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
355
356/// Builds a [`ModuleEntry`] and [`ModuleStartup`] from a [`ModuleBuilder`].
357/// This is the entry point for the module to be registered with PHP.
358impl 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        // TODO: Check if the value is 42
540        assert_eq!(builder.constants[0].2, DocComments::default());
541    }
542}