ext_php_rs/class.rs
1//! Types and traits used for registering classes with PHP.
2
3use std::{
4 collections::HashMap,
5 marker::PhantomData,
6 sync::atomic::{AtomicPtr, Ordering},
7};
8
9use once_cell::sync::OnceCell;
10
11use 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.
23pub 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.
27pub 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]
100 fn interface_implementations() -> Vec<ClassEntryInfo> {
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]
113 fn interface_method_implementations() -> Vec<(FunctionBuilder<'static>, MethodFlags)> {
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.
135pub 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.
146pub 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
155impl<T, E> From<std::result::Result<T, E>> for ConstructorResult<T>
156where
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
167impl<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.
175pub 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
186impl<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
199impl<T> Default for ClassMetadata<T> {
200 fn default() -> Self {
201 Self::new()
202 }
203}
204
205impl<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 {
214 !self.ce.load(Ordering::SeqCst).is_null()
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) {
241 self.ce
242 .compare_exchange(
243 std::ptr::null_mut(),
244 ce,
245 Ordering::SeqCst,
246 Ordering::Relaxed,
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}