Skip to main content

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    /// Returns a clone of this object for PHP's `clone` operator.
133    ///
134    /// When PHP executes `clone $obj`, this method is called to produce a copy
135    /// of the Rust data. For types that derive `Clone`, the `#[php_class]` macro
136    /// generates an implementation returning `Some(self.clone())`. Types that
137    /// don't implement `Clone` use the default (returns `None`), which causes
138    /// PHP to throw an error when attempting to clone.
139    ///
140    /// # Returns
141    ///
142    /// `Some(Self)` if the object can be cloned, `None` otherwise.
143    #[must_use]
144    fn clone_obj(&self) -> Option<Self> {
145        None
146    }
147}
148
149/// Stores metadata about a classes Rust constructor, including the function
150/// pointer and the arguments of the function.
151pub struct ConstructorMeta<T> {
152    /// Constructor function.
153    pub constructor: fn(&mut ExecuteData) -> ConstructorResult<T>,
154    /// Function called to build the constructor function. Usually adds
155    /// arguments.
156    pub build_fn: fn(FunctionBuilder) -> FunctionBuilder,
157    /// Add constructor modification
158    pub flags: Option<MethodFlags>,
159}
160
161/// Result returned from a constructor of a class.
162pub enum ConstructorResult<T> {
163    /// Successfully constructed the class, contains the new class object.
164    Ok(T),
165    /// An exception occurred while constructing the class.
166    Exception(PhpException),
167    /// Invalid arguments were given to the constructor.
168    ArgError,
169}
170
171impl<T, E> From<std::result::Result<T, E>> for ConstructorResult<T>
172where
173    E: Into<PhpException>,
174{
175    fn from(result: std::result::Result<T, E>) -> Self {
176        match result {
177            Ok(x) => Self::Ok(x),
178            Err(e) => Self::Exception(e.into()),
179        }
180    }
181}
182
183impl<T> From<T> for ConstructorResult<T> {
184    fn from(result: T) -> Self {
185        Self::Ok(result)
186    }
187}
188
189/// Stores the class entry and handlers for a Rust type which has been exported
190/// to PHP. Usually allocated statically.
191pub struct ClassMetadata<T> {
192    handlers: OnceCell<ZendObjectHandlers>,
193    properties: OnceCell<HashMap<&'static str, PropertyInfo<'static, T>>>,
194    ce: AtomicPtr<ClassEntry>,
195
196    // `AtomicPtr` is used here because it is `Send + Sync`.
197    // fn() -> T could have been used but that is incompatible with const fns at
198    // the moment.
199    phantom: PhantomData<AtomicPtr<T>>,
200}
201
202impl<T> ClassMetadata<T> {
203    /// Creates a new class metadata instance.
204    #[must_use]
205    pub const fn new() -> Self {
206        Self {
207            handlers: OnceCell::new(),
208            properties: OnceCell::new(),
209            ce: AtomicPtr::new(std::ptr::null_mut()),
210            phantom: PhantomData,
211        }
212    }
213}
214
215impl<T> Default for ClassMetadata<T> {
216    fn default() -> Self {
217        Self::new()
218    }
219}
220
221impl<T: RegisteredClass> ClassMetadata<T> {
222    /// Returns an immutable reference to the object handlers contained inside
223    /// the class metadata.
224    pub fn handlers(&self) -> &ZendObjectHandlers {
225        self.handlers.get_or_init(ZendObjectHandlers::new::<T>)
226    }
227
228    /// Checks if the class entry has been stored, returning a boolean.
229    pub fn has_ce(&self) -> bool {
230        !self.ce.load(Ordering::SeqCst).is_null()
231    }
232
233    /// Retrieves a reference to the stored class entry.
234    ///
235    /// # Panics
236    ///
237    /// Panics if there is no class entry stored inside the class metadata.
238    pub fn ce(&self) -> &'static ClassEntry {
239        // SAFETY: There are only two values that can be stored in the atomic ptr: null
240        // or a static reference to a class entry. On the latter case,
241        // `as_ref()` will return `None` and the function will panic.
242        unsafe { self.ce.load(Ordering::SeqCst).as_ref() }
243            .expect("Attempted to retrieve class entry before it has been stored.")
244    }
245
246    /// Stores a reference to a class entry inside the class metadata.
247    ///
248    /// # Parameters
249    ///
250    /// * `ce` - The class entry to store.
251    ///
252    /// # Panics
253    ///
254    /// Panics if the class entry has already been set in the class metadata.
255    /// This function should only be called once.
256    pub fn set_ce(&self, ce: &'static mut ClassEntry) {
257        self.ce
258            .compare_exchange(
259                std::ptr::null_mut(),
260                ce,
261                Ordering::SeqCst,
262                Ordering::Relaxed,
263            )
264            .expect("Class entry has already been set");
265    }
266
267    /// Retrieves a reference to the hashmap storing the classes property
268    /// accessors.
269    ///
270    /// # Returns
271    ///
272    /// Immutable reference to the properties hashmap.
273    pub fn get_properties(&self) -> &HashMap<&'static str, PropertyInfo<'static, T>> {
274        self.properties.get_or_init(T::get_properties)
275    }
276}