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
//! Types and traits used for registering classes with PHP.
use std::{
collections::HashMap,
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::PropertyInfo,
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
/// and handlers.
///
/// 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 a hash table containing the properties of the class.
///
/// The key should be the name of the property and the value should be a
/// reference to the property with reference to `self`. The value is a
/// [`PropertyInfo`].
///
/// Instead of using this method directly, you should access the properties
/// through the [`ClassMetadata::get_properties`] function, which builds the
/// hashmap one and stores it in memory.
fn get_properties<'a>() -> HashMap<&'static str, PropertyInfo<'a, 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.
///
/// # Returns
///
/// `Some(Self)` if the type can be default-initialized, `None` otherwise.
#[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.
///
/// # Returns
///
/// `Some(Self)` if the object can be cloned, `None` otherwise.
#[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 and handlers for a Rust type which has been exported
/// to PHP. Usually allocated statically.
pub struct ClassMetadata<T> {
handlers: OnceCell<ZendObjectHandlers>,
properties: OnceCell<HashMap<&'static str, PropertyInfo<'static, T>>>,
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> ClassMetadata<T> {
/// Creates a new class metadata instance.
#[must_use]
pub const fn new() -> Self {
Self {
handlers: OnceCell::new(),
properties: OnceCell::new(),
ce: AtomicPtr::new(std::ptr::null_mut()),
phantom: PhantomData,
}
}
}
impl<T> 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 latter 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.
///
/// # Parameters
///
/// * `ce` - The class entry to store.
///
/// # 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");
}
/// Retrieves a reference to the hashmap storing the classes property
/// accessors.
///
/// # Returns
///
/// Immutable reference to the properties hashmap.
pub fn get_properties(&self) -> &HashMap<&'static str, PropertyInfo<'static, T>> {
self.properties.get_or_init(T::get_properties)
}
}