Skip to main content

ext_php_rs/
class.rs

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