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

extphprs / ext-php-rs / 21440531827

28 Jan 2026 01:41PM UTC coverage: 34.836% (-0.5%) from 35.363%
21440531827

Pull #621

github

web-flow
Merge e96b63a0b into 90b596ade
Pull Request #621: feat(interface): php_impl_interface macro

16 of 126 new or added lines in 8 files covered. (12.7%)

1 existing line in 1 file now uncovered.

1916 of 5500 relevant lines covered (34.84%)

13.91 hits per line

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

23.53
/src/class.rs
1
//! Types and traits used for registering classes with PHP.
2

3
use std::{
4
    collections::HashMap,
5
    marker::PhantomData,
6
    sync::atomic::{AtomicPtr, Ordering},
7
};
8

9
use once_cell::sync::OnceCell;
10

11
use crate::{
12
    builders::{ClassBuilder, FunctionBuilder},
13
    convert::IntoZvalDyn,
14
    describe::DocComments,
15
    exception::PhpException,
16
    flags::{ClassFlags, MethodFlags, PropertyFlags},
17
    internal::property::PropertyInfo,
18
    zend::{ClassEntry, ExecuteData, ZendObjectHandlers},
19
};
20

21
/// A type alias for a tuple containing a function pointer to a class entry
22
/// and a string representing the class name used in stubs.
23
pub type ClassEntryInfo = (fn() -> &'static ClassEntry, &'static str);
24

25
/// Implemented on Rust types which are exported to PHP. Allows users to get and
26
/// set PHP properties on the object.
27
pub trait RegisteredClass: Sized + 'static {
28
    /// PHP class name of the registered class.
29
    const CLASS_NAME: &'static str;
30

31
    /// Function to be called when building the class. Allows user to modify the
32
    /// class at runtime (add runtime constants etc).
33
    const BUILDER_MODIFIER: Option<fn(ClassBuilder) -> ClassBuilder>;
34

35
    /// Parent class entry. Optional.
36
    const EXTENDS: Option<ClassEntryInfo>;
37

38
    /// Interfaces implemented by the class.
39
    const IMPLEMENTS: &'static [ClassEntryInfo];
40

41
    /// PHP flags applied to the class.
42
    const FLAGS: ClassFlags = ClassFlags::empty();
43

44
    /// Doc comments for the class.
45
    const DOC_COMMENTS: DocComments = &[];
46

47
    /// Returns a reference to the class metadata, which stores the class entry
48
    /// and handlers.
49
    ///
50
    /// This must be statically allocated, and is usually done through the
51
    /// [`macro@php_class`] macro.
52
    ///
53
    /// [`macro@php_class`]: crate::php_class
54
    fn get_metadata() -> &'static ClassMetadata<Self>;
55

56
    /// Returns a hash table containing the properties of the class.
57
    ///
58
    /// The key should be the name of the property and the value should be a
59
    /// reference to the property with reference to `self`. The value is a
60
    /// [`PropertyInfo`].
61
    ///
62
    /// Instead of using this method directly, you should access the properties
63
    /// through the [`ClassMetadata::get_properties`] function, which builds the
64
    /// hashmap one and stores it in memory.
65
    fn get_properties<'a>() -> HashMap<&'static str, PropertyInfo<'a, Self>>;
66

67
    /// Returns the method builders required to build the class.
68
    fn method_builders() -> Vec<(FunctionBuilder<'static>, MethodFlags)>;
69

70
    /// Returns the class constructor (if any).
71
    fn constructor() -> Option<ConstructorMeta<Self>>;
72

73
    /// Returns the constants provided by the class.
74
    fn constants() -> &'static [(&'static str, &'static dyn IntoZvalDyn, DocComments)];
75

76
    /// Returns the static properties provided by the class.
77
    ///
78
    /// Static properties are declared at the class level and managed by PHP,
79
    /// not by Rust handlers. Each tuple contains (name, flags, default, docs).
80
    /// The default value is optional - `None` means null default.
81
    #[must_use]
82
    fn static_properties() -> &'static [(
×
83
        &'static str,
84
        PropertyFlags,
85
        Option<&'static (dyn IntoZvalDyn + Sync)>,
86
        DocComments,
87
    )] {
88
        &[]
×
89
    }
90

91
    /// Returns interfaces from `#[php_impl_interface]` trait implementations.
92
    ///
93
    /// This method is generated by the `#[php_class]` macro and uses the
94
    /// `inventory` crate to collect interface registrations across crate
95
    /// boundaries at link time.
96
    ///
97
    /// The default implementation returns an empty vector. The macro overrides
98
    /// this to iterate over `InterfaceRegistration` entries matching this type.
99
    #[must_use]
NEW
100
    fn interface_implementations() -> Vec<ClassEntryInfo> {
×
NEW
101
        Vec::new()
×
102
    }
103

104
    /// Returns methods from `#[php_impl_interface]` trait implementations.
105
    ///
106
    /// This method is generated by the `#[php_class]` macro and uses the
107
    /// `inventory` crate to collect method registrations across crate
108
    /// boundaries at link time.
109
    ///
110
    /// The default implementation returns an empty vector. The macro overrides
111
    /// this to iterate over `MethodRegistration` entries matching this type.
112
    #[must_use]
NEW
113
    fn interface_method_implementations() -> Vec<(FunctionBuilder<'static>, MethodFlags)> {
×
NEW
114
        Vec::new()
×
115
    }
116

117
    /// Returns a default instance of the class for immediate initialization.
118
    ///
119
    /// This is used when PHP creates an object without calling the constructor,
120
    /// such as when throwing exceptions via `zend_throw_exception_ex`. For types
121
    /// that derive `Default`, this will return `Some(Self::default())`, allowing
122
    /// the object to be properly initialized even without a constructor call.
123
    ///
124
    /// # Returns
125
    ///
126
    /// `Some(Self)` if the type can be default-initialized, `None` otherwise.
127
    #[must_use]
128
    fn default_init() -> Option<Self> {
×
129
        None
×
130
    }
131
}
132

133
/// Stores metadata about a classes Rust constructor, including the function
134
/// pointer and the arguments of the function.
135
pub struct ConstructorMeta<T> {
136
    /// Constructor function.
137
    pub constructor: fn(&mut ExecuteData) -> ConstructorResult<T>,
138
    /// Function called to build the constructor function. Usually adds
139
    /// arguments.
140
    pub build_fn: fn(FunctionBuilder) -> FunctionBuilder,
141
    /// Add constructor modification
142
    pub flags: Option<MethodFlags>,
143
}
144

145
/// Result returned from a constructor of a class.
146
pub enum ConstructorResult<T> {
147
    /// Successfully constructed the class, contains the new class object.
148
    Ok(T),
149
    /// An exception occurred while constructing the class.
150
    Exception(PhpException),
151
    /// Invalid arguments were given to the constructor.
152
    ArgError,
153
}
154

155
impl<T, E> From<std::result::Result<T, E>> for ConstructorResult<T>
156
where
157
    E: Into<PhpException>,
158
{
159
    fn from(result: std::result::Result<T, E>) -> Self {
×
160
        match result {
×
161
            Ok(x) => Self::Ok(x),
×
162
            Err(e) => Self::Exception(e.into()),
×
163
        }
164
    }
165
}
166

167
impl<T> From<T> for ConstructorResult<T> {
168
    fn from(result: T) -> Self {
×
169
        Self::Ok(result)
×
170
    }
171
}
172

173
/// Stores the class entry and handlers for a Rust type which has been exported
174
/// to PHP. Usually allocated statically.
175
pub struct ClassMetadata<T> {
176
    handlers: OnceCell<ZendObjectHandlers>,
177
    properties: OnceCell<HashMap<&'static str, PropertyInfo<'static, T>>>,
178
    ce: AtomicPtr<ClassEntry>,
179

180
    // `AtomicPtr` is used here because it is `Send + Sync`.
181
    // fn() -> T could have been used but that is incompatible with const fns at
182
    // the moment.
183
    phantom: PhantomData<AtomicPtr<T>>,
184
}
185

186
impl<T> ClassMetadata<T> {
187
    /// Creates a new class metadata instance.
188
    #[must_use]
189
    pub const fn new() -> Self {
×
190
        Self {
191
            handlers: OnceCell::new(),
×
192
            properties: OnceCell::new(),
×
193
            ce: AtomicPtr::new(std::ptr::null_mut()),
×
194
            phantom: PhantomData,
195
        }
196
    }
197
}
198

199
impl<T> Default for ClassMetadata<T> {
200
    fn default() -> Self {
×
201
        Self::new()
×
202
    }
203
}
204

205
impl<T: RegisteredClass> ClassMetadata<T> {
206
    /// Returns an immutable reference to the object handlers contained inside
207
    /// the class metadata.
208
    pub fn handlers(&self) -> &ZendObjectHandlers {
×
209
        self.handlers.get_or_init(ZendObjectHandlers::new::<T>)
×
210
    }
211

212
    /// Checks if the class entry has been stored, returning a boolean.
213
    pub fn has_ce(&self) -> bool {
1✔
214
        !self.ce.load(Ordering::SeqCst).is_null()
3✔
215
    }
216

217
    /// Retrieves a reference to the stored class entry.
218
    ///
219
    /// # Panics
220
    ///
221
    /// Panics if there is no class entry stored inside the class metadata.
222
    pub fn ce(&self) -> &'static ClassEntry {
×
223
        // SAFETY: There are only two values that can be stored in the atomic ptr: null
224
        // or a static reference to a class entry. On the latter case,
225
        // `as_ref()` will return `None` and the function will panic.
226
        unsafe { self.ce.load(Ordering::SeqCst).as_ref() }
×
227
            .expect("Attempted to retrieve class entry before it has been stored.")
228
    }
229

230
    /// Stores a reference to a class entry inside the class metadata.
231
    ///
232
    /// # Parameters
233
    ///
234
    /// * `ce` - The class entry to store.
235
    ///
236
    /// # Panics
237
    ///
238
    /// Panics if the class entry has already been set in the class metadata.
239
    /// This function should only be called once.
240
    pub fn set_ce(&self, ce: &'static mut ClassEntry) {
1✔
241
        self.ce
1✔
242
            .compare_exchange(
243
                std::ptr::null_mut(),
1✔
244
                ce,
1✔
245
                Ordering::SeqCst,
1✔
246
                Ordering::Relaxed,
1✔
247
            )
248
            .expect("Class entry has already been set");
249
    }
250

251
    /// Retrieves a reference to the hashmap storing the classes property
252
    /// accessors.
253
    ///
254
    /// # Returns
255
    ///
256
    /// Immutable reference to the properties hashmap.
257
    pub fn get_properties(&self) -> &HashMap<&'static str, PropertyInfo<'static, T>> {
×
258
        self.properties.get_or_init(T::get_properties)
×
259
    }
260
}
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

© 2026 Coveralls, Inc