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::FunctionBuilder,
13    exception::PhpException,
14    props::Property,
15    zend::{ClassEntry, ExecuteData, ZendObjectHandlers},
16};
17
18/// Implemented on Rust types which are exported to PHP. Allows users to get and
19/// set PHP properties on the object.
20pub trait RegisteredClass: Sized + 'static {
21    /// PHP class name of the registered class.
22    const CLASS_NAME: &'static str;
23
24    /// Optional class constructor.
25    const CONSTRUCTOR: Option<ConstructorMeta<Self>> = None;
26
27    /// Returns a reference to the class metadata, which stores the class entry
28    /// and handlers.
29    ///
30    /// This must be statically allocated, and is usually done through the
31    /// [`macro@php_class`] macro.
32    ///
33    /// [`macro@php_class`]: crate::php_class
34    fn get_metadata() -> &'static ClassMetadata<Self>;
35
36    /// Returns a hash table containing the properties of the class.
37    ///
38    /// The key should be the name of the property and the value should be a
39    /// reference to the property with reference to `self`. The value is a
40    /// [`Property`].
41    ///
42    /// Instead of using this method directly, you should access the properties
43    /// through the [`ClassMetadata::get_properties`] function, which builds the
44    /// hashmap one and stores it in memory.
45    fn get_properties<'a>() -> HashMap<&'static str, Property<'a, Self>>;
46}
47
48/// Stores metadata about a classes Rust constructor, including the function
49/// pointer and the arguments of the function.
50pub struct ConstructorMeta<T> {
51    /// Constructor function.
52    pub constructor: fn(&mut ExecuteData) -> ConstructorResult<T>,
53    /// Function called to build the constructor function. Usually adds
54    /// arguments.
55    pub build_fn: fn(FunctionBuilder) -> FunctionBuilder,
56}
57
58/// Result returned from a constructor of a class.
59pub enum ConstructorResult<T> {
60    /// Successfully constructed the class, contains the new class object.
61    Ok(T),
62    /// An exception occurred while constructing the class.
63    Exception(PhpException),
64    /// Invalid arguments were given to the constructor.
65    ArgError,
66}
67
68impl<T, E> From<std::result::Result<T, E>> for ConstructorResult<T>
69where
70    E: Into<PhpException>,
71{
72    fn from(result: std::result::Result<T, E>) -> Self {
73        match result {
74            Ok(x) => Self::Ok(x),
75            Err(e) => Self::Exception(e.into()),
76        }
77    }
78}
79
80impl<T> From<T> for ConstructorResult<T> {
81    fn from(result: T) -> Self {
82        Self::Ok(result)
83    }
84}
85
86/// Stores the class entry and handlers for a Rust type which has been exported
87/// to PHP. Usually allocated statically.
88pub struct ClassMetadata<T> {
89    handlers: OnceCell<ZendObjectHandlers>,
90    properties: OnceCell<HashMap<&'static str, Property<'static, T>>>,
91    ce: AtomicPtr<ClassEntry>,
92
93    // `AtomicPtr` is used here because it is `Send + Sync`.
94    // fn() -> T could have been used but that is incompatible with const fns at
95    // the moment.
96    phantom: PhantomData<AtomicPtr<T>>,
97}
98
99impl<T> ClassMetadata<T> {
100    /// Creates a new class metadata instance.
101    pub const fn new() -> Self {
102        Self {
103            handlers: OnceCell::new(),
104            properties: OnceCell::new(),
105            ce: AtomicPtr::new(std::ptr::null_mut()),
106            phantom: PhantomData,
107        }
108    }
109}
110
111impl<T> Default for ClassMetadata<T> {
112    fn default() -> Self {
113        Self::new()
114    }
115}
116
117impl<T: RegisteredClass> ClassMetadata<T> {
118    /// Returns an immutable reference to the object handlers contained inside
119    /// the class metadata.
120    pub fn handlers(&self) -> &ZendObjectHandlers {
121        self.handlers.get_or_init(ZendObjectHandlers::new::<T>)
122    }
123
124    /// Checks if the class entry has been stored, returning a boolean.
125    pub fn has_ce(&self) -> bool {
126        !self.ce.load(Ordering::SeqCst).is_null()
127    }
128
129    /// Retrieves a reference to the stored class entry.
130    ///
131    /// # Panics
132    ///
133    /// Panics if there is no class entry stored inside the class metadata.
134    pub fn ce(&self) -> &'static ClassEntry {
135        // SAFETY: There are only two values that can be stored in the atomic ptr: null
136        // or a static reference to a class entry. On the latter case,
137        // `as_ref()` will return `None` and the function will panic.
138        unsafe { self.ce.load(Ordering::SeqCst).as_ref() }
139            .expect("Attempted to retrieve class entry before it has been stored.")
140    }
141
142    /// Stores a reference to a class entry inside the class metadata.
143    ///
144    /// # Parameters
145    ///
146    /// * `ce` - The class entry to store.
147    ///
148    /// # Panics
149    ///
150    /// Panics if the class entry has already been set in the class metadata.
151    /// This function should only be called once.
152    pub fn set_ce(&self, ce: &'static mut ClassEntry) {
153        self.ce
154            .compare_exchange(
155                std::ptr::null_mut(),
156                ce,
157                Ordering::SeqCst,
158                Ordering::Relaxed,
159            )
160            .expect("Class entry has already been set");
161    }
162
163    /// Retrieves a reference to the hashmap storing the classes property
164    /// accessors.
165    ///
166    /// # Returns
167    ///
168    /// Immutable reference to the properties hashmap.
169    pub fn get_properties(&self) -> &HashMap<&'static str, Property<'static, T>> {
170        self.properties.get_or_init(T::get_properties)
171    }
172}