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
//! 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::FunctionBuilder,
exception::PhpException,
props::Property,
zend::{ClassEntry, ExecuteData, ZendObjectHandlers},
};
/// 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;
/// Optional class constructor.
const CONSTRUCTOR: Option<ConstructorMeta<Self>> = None;
/// 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
/// [`Property`].
///
/// 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, Property<'a, Self>>;
}
/// 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,
}
/// 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 occured 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, Property<'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.
pub const fn new() -> Self {
Self {
handlers: OnceCell::new(),
properties: OnceCell::new(),
ce: AtomicPtr::new(std::ptr::null_mut()),
phantom: PhantomData,
}
}
}
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, Property<'static, T>> {
self.properties.get_or_init(T::get_properties)
}
}