• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

davidcole1340 / ext-php-rs / 15778794992

20 Jun 2025 12:19PM UTC coverage: 20.64% (-1.4%) from 22.034%
15778794992

Pull #463

github

web-flow
Merge b618ded48 into 660f308c0
Pull Request #463: feat(cargo-php): --features, --all-features, --no-default-features

0 of 11 new or added lines in 1 file covered. (0.0%)

52 existing lines in 10 files now uncovered.

761 of 3687 relevant lines covered (20.64%)

3.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

47.31
/src/builders/module.rs
1
use std::{convert::TryFrom, ffi::CString, mem, ptr};
2

3
use super::{ClassBuilder, FunctionBuilder};
4
use 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
/// Builds a Zend module extension to be registered with PHP. Must be called
15
/// from within an external function called `get_module`, returning a mutable
16
/// pointer to a `ModuleEntry`.
17
///
18
/// ```rust,no_run
19
/// use ext_php_rs::{
20
///     builders::ModuleBuilder,
21
///     zend::ModuleEntry,
22
///     info_table_start, info_table_end, info_table_row
23
/// };
24
///
25
/// #[no_mangle]
26
/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
27
///     info_table_start!();
28
///     info_table_row!("column 1", "column 2");
29
///     info_table_end!();
30
/// }
31
///
32
/// #[no_mangle]
33
/// pub extern "C" fn get_module() -> *mut ModuleEntry {
34
///     let (entry, _) = ModuleBuilder::new("ext-name", "ext-version")
35
///         .info_function(php_module_info)
36
///         .try_into()
37
///         .unwrap();
38
///     entry.into_raw()
39
/// }
40
/// ```
41
#[must_use]
42
#[derive(Debug, Default)]
43
pub 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

57
impl ModuleBuilder<'_> {
58
    /// Creates a new module builder with a given name and version.
59
    ///
60
    /// # Arguments
61
    ///
62
    /// * `name` - The name of the extension.
63
    /// * `version` - The current version of the extension.
64
    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
12✔
65
        Self {
66
            name: name.into(),
36✔
67
            version: version.into(),
36✔
68
            functions: vec![],
24✔
69
            constants: vec![],
24✔
70
            classes: vec![],
12✔
71
            ..Default::default()
72
        }
73
    }
74

75
    /// Sets the startup function for the extension.
76
    ///
77
    /// # Arguments
78
    ///
79
    /// * `func` - The function to be called on startup.
80
    pub fn startup_function(mut self, func: StartupShutdownFunc) -> Self {
3✔
81
        self.startup_func = Some(func);
3✔
82
        self
3✔
83
    }
84

85
    /// Sets the shutdown function for the extension.
86
    ///
87
    /// # Arguments
88
    ///
89
    /// * `func` - The function to be called on shutdown.
90
    pub fn shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
1✔
91
        self.shutdown_func = Some(func);
1✔
92
        self
1✔
93
    }
94

95
    /// Sets the request startup function for the extension.
96
    ///
97
    /// # Arguments
98
    ///
99
    /// * `func` - The function to be called when startup is requested.
100
    pub fn request_startup_function(mut self, func: StartupShutdownFunc) -> Self {
1✔
101
        self.request_startup_func = Some(func);
1✔
102
        self
1✔
103
    }
104

105
    /// Sets the request shutdown function for the extension.
106
    ///
107
    /// # Arguments
108
    ///
109
    /// * `func` - The function to be called when shutdown is requested.
110
    pub fn request_shutdown_function(mut self, func: StartupShutdownFunc) -> Self {
1✔
111
        self.request_shutdown_func = Some(func);
1✔
112
        self
1✔
113
    }
114

115
    /// Sets the post request shutdown function for the extension.
116
    ///
117
    /// This function can be useful if you need to do any final cleanup at the
118
    /// very end of a request, after all other resources have been released. For
119
    /// example, if your extension creates any persistent resources that last
120
    /// beyond a single request, you could use this function to clean those up.
121
    /// # Arguments
122
    ///
123
    /// * `func` - The function to be called when shutdown is requested.
124
    pub fn post_deactivate_function(mut self, func: unsafe extern "C" fn() -> i32) -> Self {
1✔
125
        self.post_deactivate_func = Some(func);
1✔
126
        self
1✔
127
    }
128

129
    /// Sets the extension information function for the extension.
130
    ///
131
    /// # Arguments
132
    ///
133
    /// * `func` - The function to be called to retrieve the information about
134
    ///   the extension.
135
    pub fn info_function(mut self, func: InfoFunc) -> Self {
1✔
136
        self.info_func = Some(func);
1✔
137
        self
1✔
138
    }
139

140
    /// Adds a function to the extension.
141
    ///
142
    /// # Arguments
143
    ///
144
    /// * `func` - The function to be added to the extension.
145
    pub fn function(mut self, func: FunctionBuilder<'static>) -> Self {
4✔
146
        self.functions.push(func);
12✔
147
        self
4✔
148
    }
149

150
    /// Adds a constant to the extension.
151
    ///
152
    /// # Arguments
153
    ///
154
    /// * `const` - Tuple containing the name, value and doc comments for the
155
    ///   constant. This is a tuple to support the [`wrap_constant`] macro.
156
    ///
157
    /// [`wrap_constant`]: crate::wrap_constant
158
    pub fn constant(
1✔
159
        mut self,
160
        r#const: (&str, impl IntoConst + Send + 'static, DocComments),
161
    ) -> Self {
162
        let (name, val, docs) = r#const;
4✔
163
        self.constants.push((
3✔
164
            name.into(),
3✔
165
            Box::new(val) as Box<dyn IntoConst + Send>,
2✔
166
            docs,
1✔
167
        ));
168
        self
1✔
169
    }
170

171
    /// Adds a class to the extension.
172
    ///
173
    /// # Panics
174
    ///
175
    /// * Panics if a constant could not be registered.
176
    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

211
/// Artifacts from the [`ModuleBuilder`] that should be revisited inside the
212
/// extension startup function.
213
pub struct ModuleStartup {
214
    constants: Vec<(String, Box<dyn IntoConst + Send>)>,
215
    classes: Vec<fn() -> ClassBuilder>,
216
}
217

218
impl ModuleStartup {
219
    /// Completes startup of the module. Should only be called inside the module
220
    /// startup function.
221
    ///
222
    /// # Errors
223
    ///
224
    /// * Returns an error if a constant could not be registered.
225
    ///
226
    /// # Panics
227
    ///
228
    /// * Panics if a class could not be registered.
229
    pub fn startup(self, _ty: i32, mod_num: i32) -> Result<()> {
1✔
230
        for (name, val) in self.constants {
1✔
231
            val.register_constant(&name, mod_num)?;
×
232
        }
233

234
        self.classes.into_iter().map(|c| c()).for_each(|c| {
1✔
235
            c.register().expect("Failed to build class");
×
236
        });
237
        Ok(())
×
238
    }
239
}
240

241
/// A function to be called when the extension is starting up or shutting down.
242
pub type StartupShutdownFunc = unsafe extern "C" fn(_type: i32, _module_number: i32) -> i32;
243

244
/// A function to be called when `phpinfo();` is called.
245
pub type InfoFunc = unsafe extern "C" fn(zend_module: *mut ModuleEntry);
246

247
/// Builds a [`ModuleEntry`] and [`ModuleStartup`] from a [`ModuleBuilder`].
248
/// This is the entry point for the module to be registered with PHP.
249
impl TryFrom<ModuleBuilder<'_>> for (ModuleEntry, ModuleStartup) {
250
    type Error = crate::error::Error;
251

252
    fn try_from(builder: ModuleBuilder) -> Result<Self, Self::Error> {
2✔
253
        let mut functions = builder
4✔
254
            .functions
2✔
255
            .into_iter()
256
            .map(FunctionBuilder::build)
2✔
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();
2✔
262
        let version = CString::new(builder.version)?.into_raw();
2✔
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),
2✔
UNCOV
278
                zts: u8::from(PHP_ZTS),
×
UNCOV
279
                ini_entry: ptr::null(),
×
UNCOV
280
                deps: ptr::null(),
×
UNCOV
281
                name,
×
UNCOV
282
                functions,
×
UNCOV
283
                module_startup_func: builder.startup_func,
×
UNCOV
284
                module_shutdown_func: builder.shutdown_func,
×
UNCOV
285
                request_startup_func: builder.request_startup_func,
×
UNCOV
286
                request_shutdown_func: builder.request_shutdown_func,
×
UNCOV
287
                info_func: builder.info_func,
×
UNCOV
288
                version,
×
289
                globals_size: 0,
290
                #[cfg(not(php_zts))]
UNCOV
291
                globals_ptr: ptr::null_mut(),
×
292
                #[cfg(php_zts)]
293
                globals_id_ptr: ptr::null_mut(),
UNCOV
294
                globals_ctor: None,
×
UNCOV
295
                globals_dtor: None,
×
UNCOV
296
                post_deactivate_func: builder.post_deactivate_func,
×
297
                module_started: 0,
298
                type_: 0,
UNCOV
299
                handle: ptr::null_mut(),
×
300
                module_number: 0,
UNCOV
301
                build_id: unsafe { ext_php_rs_php_build_id() },
×
302
            },
UNCOV
303
            startup,
×
304
        ))
305
    }
306
}
307

308
#[cfg(test)]
309
mod 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
        // TODO: Check if the value is 42
388
        assert_eq!(builder.constants[0].2, DocComments::default());
389
    }
390
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc