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}