Skip to main content

ext_php_rs/zend/
ini_entry_def.rs

1//! Builder for creating inis and methods in PHP.
2//! See <https://www.phpinternalsbook.com/php7/extensions_design/ini_settings.html> for details.
3
4use std::{ffi::CStr, os::raw::c_char, ptr};
5
6use crate::{ffi::zend_ini_entry_def, ffi::zend_register_ini_entries, flags::IniEntryPermission};
7
8/// A Zend ini entry definition.
9///
10/// Register ini definitions from your module's `startup_function` with
11/// [`IniEntryDef::register`]. The table is borrowed by the engine for the life
12/// of the process — PHP 8.5 stores it as `zend_ini_entry.def` and the CLI SAPI
13/// reads `def->value` for `php --ini` — so it must be a `static`, exactly like
14/// a C extension's `zend_ini_entry_def[]`:
15///
16/// ```rust,ignore
17/// static INI: IniEntryDefs<2> = IniEntryDefs::new([
18///     IniEntryDef::new(c"my_ext.enabled", c"1", IniEntryPermission::All),
19///     IniEntryDef::end(),
20/// ]);
21///
22/// pub fn startup(_ty: i32, mod_num: i32) -> i32 {
23///     IniEntryDef::register(INI.as_slice(), mod_num);
24///     0
25/// }
26/// ```
27pub type IniEntryDef = zend_ini_entry_def;
28
29/// A `static`-able table of [`IniEntryDef`].
30///
31/// [`IniEntryDef`] holds raw pointers, so an array of them is not `Sync` and
32/// cannot be a `static` on its own. This wrapper is the crate's equivalent of a
33/// C extension's `zend_ini_entry_def[]` in `.rodata`.
34#[repr(transparent)]
35pub struct IniEntryDefs<const N: usize>([IniEntryDef; N]);
36
37// SAFETY: every pointer inside comes from a `&'static CStr`, so it stays valid
38// for the life of the process. The table is immutable once constructed and the
39// engine only reads through it.
40unsafe impl<const N: usize> Sync for IniEntryDefs<N> {}
41
42impl<const N: usize> IniEntryDefs<N> {
43    /// Wraps an INI definition table so it can live in a `static`.
44    ///
45    /// The last entry must be [`IniEntryDef::end`].
46    #[must_use]
47    pub const fn new(entries: [IniEntryDef; N]) -> Self {
48        Self(entries)
49    }
50
51    /// Returns the table as a slice, for [`IniEntryDef::register`].
52    #[must_use]
53    pub const fn as_slice(&self) -> &[IniEntryDef] {
54        &self.0
55    }
56}
57
58impl IniEntryDef {
59    /// Creates a new ini entry definition.
60    ///
61    /// # Panics
62    ///
63    /// * If the name is longer than `u16::MAX` or the value longer than
64    ///   `u32::MAX`.
65    #[must_use]
66    #[expect(
67        clippy::needless_pass_by_value,
68        reason = "IniEntryPermission is Copy and a reference is not const-friendly here"
69    )]
70    #[expect(
71        clippy::cast_possible_truncation,
72        reason = "lengths are asserted in range above; permission bits fit in u8"
73    )]
74    pub const fn new(
75        name: &'static CStr,
76        default_value: &'static CStr,
77        permission: IniEntryPermission,
78    ) -> Self {
79        let name_length = name.count_bytes();
80        let value_length = default_value.count_bytes();
81        assert!(name_length <= u16::MAX as usize, "Invalid name length");
82        assert!(value_length <= u32::MAX as usize, "Invalid value length");
83
84        let mut template = Self::end();
85        template.name = name.as_ptr();
86        template.name_length = name_length as u16;
87        template.value = default_value.as_ptr();
88        template.value_length = value_length as u32;
89        template.modifiable = permission.bits() as u8;
90        template
91    }
92
93    /// Returns an empty ini entry def, signifying the end of a ini list.
94    #[must_use]
95    pub const fn end() -> Self {
96        Self {
97            name: ptr::null::<c_char>(),
98            on_modify: None,
99            mh_arg1: std::ptr::null_mut(),
100            mh_arg2: std::ptr::null_mut(),
101            mh_arg3: std::ptr::null_mut(),
102            value: std::ptr::null(),
103            displayer: None,
104            modifiable: 0,
105            value_length: 0,
106            name_length: 0,
107        }
108    }
109
110    /// Registers a list of ini entries.
111    ///
112    /// `entries` must be terminated by [`IniEntryDef::end`] and must live for
113    /// the whole process: `zend_register_ini_entries_ex` interns the names and
114    /// values, but PHP 8.5 also keeps the table itself as
115    /// `zend_ini_entry.def`.
116    ///
117    /// # Panics
118    ///
119    /// If `entries` is empty or its last element is not [`IniEntryDef::end`].
120    pub fn register(entries: &'static [Self], module_number: i32) {
121        assert!(
122            entries.last().is_some_and(|last| last.name.is_null()),
123            "INI entry tables must be terminated by `IniEntryDef::end()`"
124        );
125
126        unsafe { zend_register_ini_entries(entries.as_ptr(), module_number) };
127    }
128}