ext_php_rs/
props.rs

1//! Types and traits for adding properties to PHP classes registered from Rust.
2//!
3//! There are two types of properties:
4//!
5//! * Field properties, referencing a property on a struct.
6//! * Method properties, a getter and/or setter function called to get/set the
7//!   value of the property.
8//!
9//! Field types which can be used as a property implement [`Prop`]. This is
10//! automatically implemented on any type which implements [`Clone`],
11//! [`IntoZval`] and [`FromZval`].
12//!
13//! Method property types only have to implement [`IntoZval`] for setters and
14//! [`FromZval`] for getters.
15//!
16//! Properties are stored in the [`Property`] type, which allows us to store
17//! field and method properties in one data structure. Properties are usually
18//! retrieved via the [`RegisteredClass`] trait.
19//!
20//! [`RegisteredClass`]: crate::class::RegisteredClass
21
22use crate::{
23    convert::{FromZval, IntoZval},
24    error::{Error, Result},
25    exception::PhpResult,
26    types::Zval,
27};
28
29/// Implemented on types which can be used as PHP properties.
30///
31/// Generally, this should not be directly implemented on types, as it is
32/// automatically implemented on types that implement [`Clone`], [`IntoZval`]
33/// and [`FromZval`], which will be required to implement this trait regardless.
34pub trait Prop<'a> {
35    /// Gets the value of `self` by setting the value of `zv`.
36    ///
37    /// # Parameters
38    ///
39    /// * `zv` - The zval to set the value of.
40    fn get(&self, zv: &mut Zval) -> Result<()>;
41
42    /// Sets the value of `self` with the contents of a given zval `zv`.
43    ///
44    /// # Parameters
45    ///
46    /// * `zv` - The zval containing the new value of `self`.
47    fn set(&mut self, zv: &'a Zval) -> Result<()>;
48}
49
50impl<'a, T: Clone + IntoZval + FromZval<'a>> Prop<'a> for T {
51    fn get(&self, zv: &mut Zval) -> Result<()> {
52        self.clone().set_zval(zv, false)
53    }
54
55    fn set(&mut self, zv: &'a Zval) -> Result<()> {
56        let x = Self::from_zval(zv).ok_or_else(|| Error::ZvalConversion(zv.get_type()))?;
57        *self = x;
58        Ok(())
59    }
60}
61
62pub type PropertyGetter<'a, T> = Option<Box<dyn Fn(&T, &mut Zval) -> PhpResult + Send + Sync + 'a>>;
63pub type PropertySetter<'a, T> = Option<Box<dyn Fn(&mut T, &Zval) -> PhpResult + Send + Sync + 'a>>;
64
65/// Represents a property added to a PHP class.
66///
67/// There are two types of properties:
68///
69/// * Field properties, where the data is stored inside a struct field.
70/// * Method properties, where getter and/or setter functions are provided,
71///   which are used to get and set the value of the property.
72pub enum Property<'a, T> {
73    Field(Box<dyn (Fn(&mut T) -> &mut dyn Prop) + Send + Sync>),
74    Method {
75        get: PropertyGetter<'a, T>,
76        set: PropertySetter<'a, T>,
77    },
78}
79
80impl<'a, T: 'a> Property<'a, T> {
81    /// Creates a field property.
82    ///
83    /// # Parameters
84    ///
85    /// * `f` - The function used to get a mutable reference to the property.
86    ///
87    /// # Examples
88    ///
89    /// ```no_run
90    /// # use ext_php_rs::props::Property;
91    /// struct Test {
92    ///     pub a: i32,
93    /// }
94    ///
95    /// let prop: Property<Test> = Property::field(|test: &mut Test| &mut test.a);
96    /// ```
97    pub fn field<F>(f: F) -> Self
98    where
99        F: (Fn(&mut T) -> &mut dyn Prop) + Send + Sync + 'static,
100    {
101        Self::Field(Box::new(f) as Box<dyn (Fn(&mut T) -> &mut dyn Prop) + Send + Sync>)
102    }
103
104    /// Creates a method property with getters and setters.
105    ///
106    /// If either the getter or setter is not given, an exception will be thrown
107    /// when attempting to retrieve/set the property.
108    ///
109    /// # Parameters
110    ///
111    /// * `get` - Function used to get the value of the property, in an
112    ///   [`Option`].
113    /// * `set` - Function used to set the value of the property, in an
114    ///   [`Option`].
115    ///
116    /// # Examples
117    ///
118    /// ```no_run
119    /// # use ext_php_rs::props::Property;
120    /// struct Test;
121    ///
122    /// impl Test {
123    ///     pub fn get_prop(&self) -> String {
124    ///         "Hello".into()
125    ///     }
126    ///
127    ///     pub fn set_prop(&mut self, val: String) {
128    ///         println!("{}", val);
129    ///     }
130    /// }
131    ///
132    /// let prop: Property<Test> = Property::method(Some(Test::get_prop), Some(Test::set_prop));
133    /// ```
134    pub fn method<V>(get: Option<fn(&T) -> V>, set: Option<fn(&mut T, V)>) -> Self
135    where
136        for<'b> V: IntoZval + FromZval<'b> + 'a,
137    {
138        let get = get.map(|get| {
139            Box::new(move |self_: &T, retval: &mut Zval| -> PhpResult {
140                let value = get(self_);
141                value
142                    .set_zval(retval, false)
143                    .map_err(|e| format!("Failed to return property value to PHP: {e:?}"))?;
144                Ok(())
145            }) as Box<dyn Fn(&T, &mut Zval) -> PhpResult + Send + Sync + 'a>
146        });
147
148        let set = set.map(|set| {
149            Box::new(move |self_: &mut T, value: &Zval| -> PhpResult {
150                let val = V::from_zval(value)
151                    .ok_or("Unable to convert property value into required type.")?;
152                set(self_, val);
153                Ok(())
154            }) as Box<dyn Fn(&mut T, &Zval) -> PhpResult + Send + Sync + 'a>
155        });
156
157        Self::Method { get, set }
158    }
159
160    /// Attempts to retrieve the value of the property from the given object
161    /// `self_`.
162    ///
163    /// The value of the property, if successfully retrieved, is loaded into the
164    /// given [`Zval`] `retval`. If unsuccessful, a [`PhpException`] is
165    /// returned inside the error variant of a result.
166    ///
167    /// # Parameters
168    ///
169    /// * `self_` - The object to retrieve the property from.
170    /// * `retval` - The [`Zval`] to load the value of the property into.
171    ///
172    /// # Returns
173    ///
174    /// Nothing upon success, a [`PhpException`] inside an error variant when
175    /// the property could not be retrieved.
176    ///
177    /// # Examples
178    ///
179    /// ```no_run
180    /// # use ext_php_rs::props::Property;
181    /// # use ext_php_rs::types::Zval;
182    /// struct Test {
183    ///     pub a: i32,
184    /// }
185    ///
186    /// let prop: Property<Test> = Property::field(|obj: &mut Test| &mut obj.a);
187    ///
188    /// let mut test = Test { a: 500 };
189    /// let mut zv = Zval::new();
190    /// prop.get(&mut test, &mut zv).unwrap();
191    /// assert_eq!(zv.long(), Some(500));
192    /// ```
193    ///
194    /// [`PhpException`]: crate::exception::PhpException
195    pub fn get(&self, self_: &'a mut T, retval: &mut Zval) -> PhpResult {
196        match self {
197            Property::Field(field) => field(self_)
198                .get(retval)
199                .map_err(|e| format!("Failed to get property value: {e:?}").into()),
200            Property::Method { get, set: _ } => match get {
201                Some(get) => get(self_, retval),
202                None => Err("No getter available for this property.".into()),
203            },
204        }
205    }
206
207    /// Attempts to set the value of the property inside the given object
208    /// `self_`.
209    ///
210    /// The new value of the property is supplied inside the given [`Zval`]
211    /// `value`. If unsuccessful, a [`PhpException`] is returned inside the
212    /// error variant of a result.
213    ///
214    /// # Parameters
215    ///
216    /// * `self_` - The object to set the property in.
217    /// * `value` - The [`Zval`] containing the new content for the property.
218    ///
219    /// # Returns
220    ///
221    /// Nothing upon success, a [`PhpException`] inside an error variant when
222    /// the property could not be set.
223    ///
224    /// # Examples
225    ///
226    /// ```no_run
227    /// # use ext_php_rs::props::Property;
228    /// # use ext_php_rs::types::Zval;
229    /// # use ext_php_rs::convert::IntoZval;
230    /// struct Test {
231    ///     pub a: i32,
232    /// }
233    ///
234    /// let prop: Property<Test> = Property::field(|obj: &mut Test| &mut obj.a);
235    ///
236    /// let mut test = Test { a: 500 };
237    /// let zv = 100.into_zval(false).unwrap();
238    /// prop.set(&mut test, &zv).unwrap();
239    /// assert_eq!(test.a, 100);
240    /// ```
241    ///
242    /// [`PhpException`]: crate::exception::PhpException
243    pub fn set(&self, self_: &'a mut T, value: &Zval) -> PhpResult {
244        match self {
245            Property::Field(field) => field(self_)
246                .set(value)
247                .map_err(|e| format!("Failed to set property value: {e:?}").into()),
248            Property::Method { get: _, set } => match set {
249                Some(set) => set(self_, value),
250                None => Err("No setter available for this property.".into()),
251            },
252        }
253    }
254}