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, 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    /// Adds an enum to the extension.
272    #[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
297/// Artifacts from the [`ModuleBuilder`] that should be revisited inside the
298/// extension startup function.
299pub 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    /// Completes startup of the module. Should only be called inside the module
309    /// startup function.
310    ///
311    /// # Errors
312    ///
313    /// * Returns an error if a constant could not be registered.
314    ///
315    /// # Panics
316    ///
317    /// * Panics if a class could not be registered.
318    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
343/// A function to be called when the extension is starting up or shutting down.
344pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
345
346/// A function to be called when `phpinfo();` is called.
347pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
348
349/// Builds a [`ModuleEntry`] and [`ModuleStartup`] from a [`ModuleBuilder`].
350/// This is the entry point for the module to be registered with PHP.
351impl 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        // TODO: Check if the value is 42
508        assert_eq!(builder.constants[0].2, DocComments::default());
509    }
510}