use crate::{
convert::{FromZval, IntoZval},
error::{Error, Result},
exception::PhpResult,
types::Zval,
};
pub trait Prop<'a> {
fn get(&self, zv: &mut Zval) -> Result<()>;
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>>;
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> {
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>)
}
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 }
}
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()),
},
}
}
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()),
},
}
}
}