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>)
}
#[must_use]
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 }
}
#[must_use]
pub fn method_getter<V>(get: fn(&T) -> V) -> Self
where
V: IntoZval + 'a,
{
let 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>;
Self::Method {
get: Some(get),
set: None,
}
}
#[must_use]
pub fn method_setter<V>(set: fn(&mut T, V)) -> Self
where
for<'b> V: FromZval<'b> + 'a,
{
let 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: None,
set: Some(set),
}
}
#[must_use]
pub fn combine(self, other: Self) -> Self {
match (self, other) {
(Property::Method { get: g1, set: s1 }, Property::Method { get: g2, set: s2 }) => {
Property::Method {
get: g1.or(g2),
set: s1.or(s2),
}
}
(Property::Field(_), other @ Property::Method { .. }) => other,
(this @ (Property::Method { .. } | Property::Field(_)), Property::Field(_)) => this,
}
}
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()),
},
}
}
}