mirror of
https://github.com/danog/ext-php-rs.git
synced 2024-12-12 09:09:46 +01:00
255 lines
8.4 KiB
Rust
255 lines
8.4 KiB
Rust
//! Types and traits for adding properties to PHP classes registered from Rust.
|
|
//!
|
|
//! There are two types of properties:
|
|
//!
|
|
//! * Field properties, referencing a property on a struct.
|
|
//! * Method properties, a getter and/or setter function called to get/set the
|
|
//! value of the property.
|
|
//!
|
|
//! Field types which can be used as a property implement [`Prop`]. This is
|
|
//! automatically implemented on any type which implements [`Clone`],
|
|
//! [`IntoZval`] and [`FromZval`].
|
|
//!
|
|
//! Method property types only have to implement [`IntoZval`] for setters and
|
|
//! [`FromZval`] for getters.
|
|
//!
|
|
//! Properties are stored in the [`Property`] type, which allows us to store
|
|
//! field and method properties in one data structure. Properties are usually
|
|
//! retrieved via the [`RegisteredClass`] trait.
|
|
//!
|
|
//! [`RegisteredClass`]: crate::class::RegisteredClass
|
|
|
|
use crate::{
|
|
convert::{FromZval, IntoZval},
|
|
error::{Error, Result},
|
|
exception::PhpResult,
|
|
types::Zval,
|
|
};
|
|
|
|
/// Implemented on types which can be used as PHP properties.
|
|
///
|
|
/// Generally, this should not be directly implemented on types, as it is
|
|
/// automatically implemented on types that implement [`Clone`], [`IntoZval`]
|
|
/// and [`FromZval`], which will be required to implement this trait regardless.
|
|
pub trait Prop<'a> {
|
|
/// Gets the value of `self` by setting the value of `zv`.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// * `zv` - The zval to set the value of.
|
|
fn get(&self, zv: &mut Zval) -> Result<()>;
|
|
|
|
/// Sets the value of `self` with the contents of a given zval `zv`.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// * `zv` - The zval containing the new value of `self`.
|
|
fn set(&mut self, zv: &'a Zval) -> Result<()>;
|
|
}
|
|
|
|
impl<'a, T: Clone + IntoZval + FromZval<'a>> Prop<'a> for T {
|
|
fn get(&self, zv: &mut Zval) -> Result<()> {
|
|
self.clone().set_zval(zv, false)
|
|
}
|
|
|
|
fn set(&mut self, zv: &'a Zval) -> Result<()> {
|
|
let x = Self::from_zval(zv).ok_or_else(|| Error::ZvalConversion(zv.get_type()))?;
|
|
*self = x;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub type PropertyGetter<'a, T> = Option<Box<dyn Fn(&T, &mut Zval) -> PhpResult + Send + Sync + 'a>>;
|
|
pub type PropertySetter<'a, T> = Option<Box<dyn Fn(&mut T, &Zval) -> PhpResult + Send + Sync + 'a>>;
|
|
|
|
/// Represents a property added to a PHP class.
|
|
///
|
|
/// There are two types of properties:
|
|
///
|
|
/// * Field properties, where the data is stored inside a struct field.
|
|
/// * Method properties, where getter and/or setter functions are provided,
|
|
/// which are used to get and set the value of the property.
|
|
pub enum Property<'a, T> {
|
|
Field(Box<dyn (Fn(&mut T) -> &mut dyn Prop) + Send + Sync>),
|
|
Method {
|
|
get: PropertyGetter<'a, T>,
|
|
set: PropertySetter<'a, T>,
|
|
},
|
|
}
|
|
|
|
impl<'a, T: 'a> Property<'a, T> {
|
|
/// Creates a field property.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// * `f` - The function used to get a mutable reference to the property.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```no_run
|
|
/// # use ext_php_rs::props::Property;
|
|
/// struct Test {
|
|
/// pub a: i32,
|
|
/// }
|
|
///
|
|
/// let prop: Property<Test> = Property::field(|test: &mut Test| &mut test.a);
|
|
/// ```
|
|
pub fn field<F>(f: F) -> Self
|
|
where
|
|
F: (Fn(&mut T) -> &mut dyn Prop) + Send + Sync + 'static,
|
|
{
|
|
Self::Field(Box::new(f) as Box<dyn (Fn(&mut T) -> &mut dyn Prop) + Send + Sync>)
|
|
}
|
|
|
|
/// Creates a method property with getters and setters.
|
|
///
|
|
/// If either the getter or setter is not given, an exception will be thrown
|
|
/// when attempting to retrieve/set the property.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// * `get` - Function used to get the value of the property, in an
|
|
/// [`Option`].
|
|
/// * `set` - Function used to set the value of the property, in an
|
|
/// [`Option`].
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```no_run
|
|
/// # use ext_php_rs::props::Property;
|
|
/// struct Test;
|
|
///
|
|
/// impl Test {
|
|
/// pub fn get_prop(&self) -> String {
|
|
/// "Hello".into()
|
|
/// }
|
|
///
|
|
/// pub fn set_prop(&mut self, val: String) {
|
|
/// println!("{}", val);
|
|
/// }
|
|
/// }
|
|
///
|
|
/// let prop: Property<Test> = Property::method(Some(Test::get_prop), Some(Test::set_prop));
|
|
/// ```
|
|
pub fn method<V>(get: Option<fn(&T) -> V>, set: Option<fn(&mut T, V)>) -> Self
|
|
where
|
|
for<'b> V: IntoZval + FromZval<'b> + 'a,
|
|
{
|
|
let get = get.map(|get| {
|
|
Box::new(move |self_: &T, retval: &mut Zval| -> PhpResult {
|
|
let value = get(self_);
|
|
value
|
|
.set_zval(retval, false)
|
|
.map_err(|e| format!("Failed to return property value to PHP: {e:?}"))?;
|
|
Ok(())
|
|
}) as Box<dyn Fn(&T, &mut Zval) -> PhpResult + Send + Sync + 'a>
|
|
});
|
|
|
|
let set = set.map(|set| {
|
|
Box::new(move |self_: &mut T, value: &Zval| -> PhpResult {
|
|
let val = V::from_zval(value)
|
|
.ok_or("Unable to convert property value into required type.")?;
|
|
set(self_, val);
|
|
Ok(())
|
|
}) as Box<dyn Fn(&mut T, &Zval) -> PhpResult + Send + Sync + 'a>
|
|
});
|
|
|
|
Self::Method { get, set }
|
|
}
|
|
|
|
/// Attempts to retrieve the value of the property from the given object
|
|
/// `self_`.
|
|
///
|
|
/// The value of the property, if successfully retrieved, is loaded into the
|
|
/// given [`Zval`] `retval`. If unsuccessful, a [`PhpException`] is
|
|
/// returned inside the error variant of a result.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// * `self_` - The object to retrieve the property from.
|
|
/// * `retval` - The [`Zval`] to load the value of the property into.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Nothing upon success, a [`PhpException`] inside an error variant when
|
|
/// the property could not be retrieved.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```no_run
|
|
/// # use ext_php_rs::props::Property;
|
|
/// # use ext_php_rs::types::Zval;
|
|
/// struct Test {
|
|
/// pub a: i32,
|
|
/// }
|
|
///
|
|
/// let prop: Property<Test> = Property::field(|obj: &mut Test| &mut obj.a);
|
|
///
|
|
/// let mut test = Test { a: 500 };
|
|
/// let mut zv = Zval::new();
|
|
/// prop.get(&mut test, &mut zv).unwrap();
|
|
/// assert_eq!(zv.long(), Some(500));
|
|
/// ```
|
|
///
|
|
/// [`PhpException`]: crate::exception::PhpException
|
|
pub fn get(&self, self_: &'a mut T, retval: &mut Zval) -> PhpResult {
|
|
match self {
|
|
Property::Field(field) => field(self_)
|
|
.get(retval)
|
|
.map_err(|e| format!("Failed to get property value: {e:?}").into()),
|
|
Property::Method { get, set: _ } => match get {
|
|
Some(get) => get(self_, retval),
|
|
None => Err("No getter available for this property.".into()),
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Attempts to set the value of the property inside the given object
|
|
/// `self_`.
|
|
///
|
|
/// The new value of the property is supplied inside the given [`Zval`]
|
|
/// `value`. If unsuccessful, a [`PhpException`] is returned inside the
|
|
/// error variant of a result.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// * `self_` - The object to set the property in.
|
|
/// * `value` - The [`Zval`] containing the new content for the property.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// Nothing upon success, a [`PhpException`] inside an error variant when
|
|
/// the property could not be set.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```no_run
|
|
/// # use ext_php_rs::props::Property;
|
|
/// # use ext_php_rs::types::Zval;
|
|
/// # use ext_php_rs::convert::IntoZval;
|
|
/// struct Test {
|
|
/// pub a: i32,
|
|
/// }
|
|
///
|
|
/// let prop: Property<Test> = Property::field(|obj: &mut Test| &mut obj.a);
|
|
///
|
|
/// let mut test = Test { a: 500 };
|
|
/// let zv = 100.into_zval(false).unwrap();
|
|
/// prop.set(&mut test, &zv).unwrap();
|
|
/// assert_eq!(test.a, 100);
|
|
/// ```
|
|
///
|
|
/// [`PhpException`]: crate::exception::PhpException
|
|
pub fn set(&self, self_: &'a mut T, value: &Zval) -> PhpResult {
|
|
match self {
|
|
Property::Field(field) => field(self_)
|
|
.set(value)
|
|
.map_err(|e| format!("Failed to set property value: {e:?}").into()),
|
|
Property::Method { get: _, set } => match set {
|
|
Some(set) => set(self_, value),
|
|
None => Err("No setter available for this property.".into()),
|
|
},
|
|
}
|
|
}
|
|
}
|