1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
//! Types and traits used for registering classes with PHP.
use std::{
marker::PhantomData,
sync::atomic::{AtomicPtr, Ordering},
};
use once_cell::sync::OnceCell;
use crate::{
builders::{ClassBuilder, FunctionBuilder},
convert::IntoZvalDyn,
describe::DocComments,
exception::PhpException,
flags::{ClassFlags, MethodFlags, PropertyFlags},
internal::property::PropertyDescriptor,
zend::{ClassEntry, ExecuteData, ZendObjectHandlers},
};
/// A type alias for a tuple containing a function pointer to a class entry
/// and a string representing the class name used in stubs.
pub type ClassEntryInfo = (fn() -> &'static ClassEntry, &'static str);
/// Implemented on Rust types which are exported to PHP. Allows users to get and
/// set PHP properties on the object.
pub trait RegisteredClass: Sized + 'static {
/// PHP class name of the registered class.
const CLASS_NAME: &'static str;
/// Function to be called when building the class. Allows user to modify the
/// class at runtime (add runtime constants etc).
const BUILDER_MODIFIER: Option<fn(ClassBuilder) -> ClassBuilder>;
/// Parent class entry. Optional.
const EXTENDS: Option<ClassEntryInfo>;
/// Interfaces implemented by the class.
const IMPLEMENTS: &'static [ClassEntryInfo];
/// PHP flags applied to the class.
const FLAGS: ClassFlags = ClassFlags::empty();
/// Doc comments for the class.
const DOC_COMMENTS: DocComments = &[];
/// Returns a reference to the class metadata, which stores the class entry,
/// handlers, and property descriptors.
///
/// This must be statically allocated, and is usually done through the
/// [`macro@php_class`] macro.
///
/// [`macro@php_class`]: crate::php_class
fn get_metadata() -> &'static ClassMetadata<Self>;
/// Returns method properties (from `#[php(getter)]`/`#[php(setter)]`).
/// Resolved via autoref specialization in the macro-generated code.
#[must_use]
fn method_properties() -> &'static [PropertyDescriptor<Self>] {
&[]
}
/// Returns the method builders required to build the class.
fn method_builders() -> Vec<(FunctionBuilder<'static>, MethodFlags)>;
/// Returns the class constructor (if any).
fn constructor() -> Option<ConstructorMeta<Self>>;
/// Returns the constants provided by the class.
fn constants() -> &'static [(&'static str, &'static dyn IntoZvalDyn, DocComments)];
/// Returns the static properties provided by the class.
///
/// Static properties are declared at the class level and managed by PHP,
/// not by Rust handlers. Each tuple contains (name, flags, default, docs).
/// The default value is optional - `None` means null default.
#[must_use]
fn static_properties() -> &'static [(
&'static str,
PropertyFlags,
Option<&'static (dyn IntoZvalDyn + Sync)>,
DocComments,
)] {
&[]
}
/// Returns interfaces from `#[php_impl_interface]` trait implementations.
///
/// This method is generated by the `#[php_class]` macro and uses the
/// `inventory` crate to collect interface registrations across crate
/// boundaries at link time.
///
/// The default implementation returns an empty vector. The macro overrides
/// this to iterate over `InterfaceRegistration` entries matching this type.
#[must_use]
fn interface_implementations() -> Vec<ClassEntryInfo> {
Vec::new()
}
/// Returns methods from `#[php_impl_interface]` trait implementations.
///
/// This method is generated by the `#[php_class]` macro and uses the
/// `inventory` crate to collect method registrations across crate
/// boundaries at link time.
///
/// The default implementation returns an empty vector. The macro overrides
/// this to iterate over `MethodRegistration` entries matching this type.
#[must_use]
fn interface_method_implementations() -> Vec<(FunctionBuilder<'static>, MethodFlags)> {
Vec::new()
}
/// Returns a default instance of the class for immediate initialization.
///
/// This is used when PHP creates an object without calling the constructor,
/// such as when throwing exceptions via `zend_throw_exception_ex`. For types
/// that derive `Default`, this will return `Some(Self::default())`, allowing
/// the object to be properly initialized even without a constructor call.
#[must_use]
fn default_init() -> Option<Self> {
None
}
/// Returns a clone of this object for PHP's `clone` operator.
///
/// When PHP executes `clone $obj`, this method is called to produce a copy
/// of the Rust data. For types that derive `Clone`, the `#[php_class]` macro
/// generates an implementation returning `Some(self.clone())`. Types that
/// don't implement `Clone` use the default (returns `None`), which causes
/// PHP to throw an error when attempting to clone.
#[must_use]
fn clone_obj(&self) -> Option<Self> {
None
}
}
/// Stores metadata about a classes Rust constructor, including the function
/// pointer and the arguments of the function.
pub struct ConstructorMeta<T> {
/// Constructor function.
pub constructor: fn(&mut ExecuteData) -> ConstructorResult<T>,
/// Function called to build the constructor function. Usually adds
/// arguments.
pub build_fn: fn(FunctionBuilder) -> FunctionBuilder,
/// Add constructor modification
pub flags: Option<MethodFlags>,
}
/// Result returned from a constructor of a class.
pub enum ConstructorResult<T> {
/// Successfully constructed the class, contains the new class object.
Ok(T),
/// An exception occurred while constructing the class.
Exception(PhpException),
/// Invalid arguments were given to the constructor.
ArgError,
}
impl<T, E> From<std::result::Result<T, E>> for ConstructorResult<T>
where
E: Into<PhpException>,
{
fn from(result: std::result::Result<T, E>) -> Self {
match result {
Ok(x) => Self::Ok(x),
Err(e) => Self::Exception(e.into()),
}
}
}
impl<T> From<T> for ConstructorResult<T> {
fn from(result: T) -> Self {
Self::Ok(result)
}
}
/// Stores the class entry, handlers, and property descriptors for a Rust type
/// which has been exported to PHP. Usually allocated statically.
pub struct ClassMetadata<T: 'static> {
handlers: OnceCell<ZendObjectHandlers>,
field_properties: &'static [PropertyDescriptor<T>],
method_properties: OnceCell<&'static [PropertyDescriptor<T>]>,
method_mangled_names: OnceCell<Box<[Box<str>]>>,
ce: AtomicPtr<ClassEntry>,
// `AtomicPtr` is used here because it is `Send + Sync`.
// fn() -> T could have been used but that is incompatible with const fns at
// the moment.
phantom: PhantomData<AtomicPtr<T>>,
}
impl<T: 'static> ClassMetadata<T> {
/// Creates a new class metadata instance with the given field properties.
#[must_use]
pub const fn new(field_properties: &'static [PropertyDescriptor<T>]) -> Self {
Self {
handlers: OnceCell::new(),
field_properties,
method_properties: OnceCell::new(),
method_mangled_names: OnceCell::new(),
ce: AtomicPtr::new(std::ptr::null_mut()),
phantom: PhantomData,
}
}
}
impl<T: 'static> Default for ClassMetadata<T> {
fn default() -> Self {
Self::new(&[])
}
}
impl<T: RegisteredClass> ClassMetadata<T> {
/// Returns an immutable reference to the object handlers contained inside
/// the class metadata.
pub fn handlers(&self) -> &ZendObjectHandlers {
self.handlers.get_or_init(ZendObjectHandlers::new::<T>)
}
/// Checks if the class entry has been stored, returning a boolean.
pub fn has_ce(&self) -> bool {
!self.ce.load(Ordering::SeqCst).is_null()
}
/// Retrieves a reference to the stored class entry.
///
/// # Panics
///
/// Panics if there is no class entry stored inside the class metadata.
pub fn ce(&self) -> &'static ClassEntry {
// SAFETY: There are only two values that can be stored in the atomic
// ptr: null or a static reference to a class entry. On the null case,
// `as_ref()` will return `None` and the function will panic.
unsafe { self.ce.load(Ordering::SeqCst).as_ref() }
.expect("Attempted to retrieve class entry before it has been stored.")
}
/// Stores a reference to a class entry inside the class metadata.
///
/// # Panics
///
/// Panics if the class entry has already been set in the class metadata.
/// This function should only be called once.
pub fn set_ce(&self, ce: &'static mut ClassEntry) {
self.ce
.compare_exchange(
std::ptr::null_mut(),
ce,
Ordering::SeqCst,
Ordering::Relaxed,
)
.expect("Class entry has already been set");
}
/// Finds a property descriptor by name.
///
/// Checks field properties first, then method properties. Linear scan is
/// used since typical PHP classes have few properties and cache locality
/// beats hashing at small N.
#[must_use]
#[inline]
pub fn find_property(&self, name: &str) -> Option<&PropertyDescriptor<T>> {
self.field_properties
.iter()
.find(|p| p.name == name)
.or_else(|| self.method_properties().iter().find(|p| p.name == name))
}
/// Returns the field properties (from `#[php(prop)]` struct fields).
#[must_use]
#[inline]
pub fn field_properties(&self) -> &'static [PropertyDescriptor<T>] {
self.field_properties
}
/// Returns the method properties (from `#[php(getter)]`/`#[php(setter)]`).
/// Lazily initialized on first access from `RegisteredClass::method_properties()`.
#[must_use]
#[inline]
pub fn method_properties(&self) -> &'static [PropertyDescriptor<T>] {
self.method_properties.get_or_init(T::method_properties)
}
/// Iterates over all properties (field + method).
pub fn all_properties(&self) -> impl Iterator<Item = &PropertyDescriptor<T>> {
self.field_properties
.iter()
.chain(self.method_properties().iter())
}
/// Returns pre-computed PHP-convention mangled names for method properties.
///
/// Lazily initialized on first access. One allocation per class for the
/// entire process lifetime. Field properties already carry compile-time
/// mangled names in their [`PropertyDescriptor::mangled_name`] field.
#[must_use]
#[inline]
pub fn method_mangled_names(&self) -> &[Box<str>] {
self.method_mangled_names.get_or_init(|| {
self.method_properties()
.iter()
.map(|desc| {
if desc.flags.contains(PropertyFlags::Private) {
format!("\0{}\0{}", T::CLASS_NAME, desc.name).into_boxed_str()
} else if desc.flags.contains(PropertyFlags::Protected) {
format!("\0*\0{}", desc.name).into_boxed_str()
} else {
desc.name.into()
}
})
.collect()
})
}
}