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    ///
41    /// # Errors
42    ///
43    /// If the property could not be read.
44    fn get(&self, zv: &mut Zval) -> Result<()>;
45
46    /// Sets the value of `self` with the contents of a given zval `zv`.
47    ///
48    /// # Parameters
49    ///
50    /// * `zv` - The zval containing the new value of `self`.
51    ///
52    /// # Errors
53    ///
54    /// If the property could not be set
55    fn set(&mut self, zv: &'a Zval) -> Result<()>;
56}
57
58impl<'a, T: Clone + IntoZval + FromZval<'a>> Prop<'a> for T {
59    fn get(&self, zv: &mut Zval) -> Result<()> {
60        self.clone().set_zval(zv, false)
61    }
62
63    fn set(&mut self, zv: &'a Zval) -> Result<()> {
64        let x = Self::from_zval(zv).ok_or_else(|| Error::ZvalConversion(zv.get_type()))?;
65        *self = x;
66        Ok(())
67    }
68}
69
70/// A getter for a property
71pub type PropertyGetter<'a, T> = Option<Box<dyn Fn(&T, &mut Zval) -> PhpResult + Send + Sync + 'a>>;
72/// A setter for a property
73pub type PropertySetter<'a, T> = Option<Box<dyn Fn(&mut T, &Zval) -> PhpResult + Send + Sync + 'a>>;
74
75/// Represents a property added to a PHP class.
76pub enum Property<'a, T> {
77    /// Field properties, where the data is stored inside a struct field.
78    Field(Box<dyn (Fn(&mut T) -> &mut dyn Prop) + Send + Sync>),
79    /// Method properties, where getter and/or setter functions are provided,
80    /// which are used to get and set the value of the property.
81    Method {
82        /// Getter function for the property.
83        get: PropertyGetter<'a, T>,
84        /// Setter function for the property.
85        set: PropertySetter<'a, T>,
86    },
87}
88
89impl<'a, T: 'a> Property<'a, T> {
90    /// Creates a field property.
91    ///
92    /// # Parameters
93    ///
94    /// * `f` - The function used to get a mutable reference to the property.
95    ///
96    /// # Examples
97    ///
98    /// ```no_run
99    /// # use ext_php_rs::props::Property;
100    /// struct Test {
101    ///     pub a: i32,
102    /// }
103    ///
104    /// let prop: Property<Test> = Property::field(|test: &mut Test| &mut test.a);
105    /// ```
106    pub fn field<F>(f: F) -> Self
107    where
108        F: (Fn(&mut T) -> &mut dyn Prop) + Send + Sync + 'static,
109    {
110        Self::Field(Box::new(f) as Box<dyn (Fn(&mut T) -> &mut dyn Prop) + Send + Sync>)
111    }
112
113    /// Creates a method property with getters and setters.
114    ///
115    /// If either the getter or setter is not given, an exception will be thrown
116    /// when attempting to retrieve/set the property.
117    ///
118    /// # Parameters
119    ///
120    /// * `get` - Function used to get the value of the property, in an
121    ///   [`Option`].
122    /// * `set` - Function used to set the value of the property, in an
123    ///   [`Option`].
124    ///
125    /// # Examples
126    ///
127    /// ```no_run
128    /// # use ext_php_rs::props::Property;
129    /// struct Test;
130    ///
131    /// impl Test {
132    ///     pub fn get_prop(&self) -> String {
133    ///         "Hello".into()
134    ///     }
135    ///
136    ///     pub fn set_prop(&mut self, val: String) {
137    ///         println!("{}", val);
138    ///     }
139    /// }
140    ///
141    /// let prop: Property<Test> = Property::method(Some(Test::get_prop), Some(Test::set_prop));
142    /// ```
143    #[must_use]
144    pub fn method<V>(get: Option<fn(&T) -> V>, set: Option<fn(&mut T, V)>) -> Self
145    where
146        for<'b> V: IntoZval + FromZval<'b> + 'a,
147    {
148        let get = get.map(|get| {
149            Box::new(move |self_: &T, retval: &mut Zval| -> PhpResult {
150                let value = get(self_);
151                value
152                    .set_zval(retval, false)
153                    .map_err(|e| format!("Failed to return property value to PHP: {e:?}"))?;
154                Ok(())
155            }) as Box<dyn Fn(&T, &mut Zval) -> PhpResult + Send + Sync + 'a>
156        });
157
158        let set = set.map(|set| {
159            Box::new(move |self_: &mut T, value: &Zval| -> PhpResult {
160                let val = V::from_zval(value)
161                    .ok_or("Unable to convert property value into required type.")?;
162                set(self_, val);
163                Ok(())
164            }) as Box<dyn Fn(&mut T, &Zval) -> PhpResult + Send + Sync + 'a>
165        });
166
167        Self::Method { get, set }
168    }
169
170    /// Attempts to retrieve the value of the property from the given object
171    /// `self_`.
172    ///
173    /// The value of the property, if successfully retrieved, is loaded into the
174    /// given [`Zval`] `retval`. If unsuccessful, a [`PhpException`] is
175    /// returned inside the error variant of a result.
176    ///
177    /// # Parameters
178    ///
179    /// * `self_` - The object to retrieve the property from.
180    /// * `retval` - The [`Zval`] to load the value of the property into.
181    ///
182    /// # Returns
183    ///
184    /// Nothing upon success
185    ///
186    /// # Errors
187    ///
188    /// A [`PhpException`] inside an error variant when the property could not
189    /// be retrieved.
190    ///
191    /// # Examples
192    ///
193    /// ```no_run
194    /// # use ext_php_rs::props::Property;
195    /// # use ext_php_rs::types::Zval;
196    /// struct Test {
197    ///     pub a: i32,
198    /// }
199    ///
200    /// let prop: Property<Test> = Property::field(|obj: &mut Test| &mut obj.a);
201    ///
202    /// let mut test = Test { a: 500 };
203    /// let mut zv = Zval::new();
204    /// prop.get(&mut test, &mut zv).unwrap();
205    /// assert_eq!(zv.long(), Some(500));
206    /// ```
207    ///
208    /// [`PhpException`]: crate::exception::PhpException
209    pub fn get(&self, self_: &'a mut T, retval: &mut Zval) -> PhpResult {
210        match self {
211            Property::Field(field) => field(self_)
212                .get(retval)
213                .map_err(|e| format!("Failed to get property value: {e:?}").into()),
214            Property::Method { get, set: _ } => match get {
215                Some(get) => get(self_, retval),
216                None => Err("No getter available for this property.".into()),
217            },
218        }
219    }
220
221    /// Attempts to set the value of the property inside the given object
222    /// `self_`.
223    ///
224    /// The new value of the property is supplied inside the given [`Zval`]
225    /// `value`. If unsuccessful, a [`PhpException`] is returned inside the
226    /// error variant of a result.
227    ///
228    /// # Parameters
229    ///
230    /// * `self_` - The object to set the property in.
231    /// * `value` - The [`Zval`] containing the new content for the property.
232    ///
233    /// # Returns
234    ///
235    /// Nothing upon success
236    ///
237    /// # Errors
238    ///
239    /// A [`PhpException`] inside an error variant when the property could not
240    /// be set.
241    ///
242    /// # Examples
243    ///
244    /// ```no_run
245    /// # use ext_php_rs::props::Property;
246    /// # use ext_php_rs::types::Zval;
247    /// # use ext_php_rs::convert::IntoZval;
248    /// struct Test {
249    ///     pub a: i32,
250    /// }
251    ///
252    /// let prop: Property<Test> = Property::field(|obj: &mut Test| &mut obj.a);
253    ///
254    /// let mut test = Test { a: 500 };
255    /// let zv = 100.into_zval(false).unwrap();
256    /// prop.set(&mut test, &zv).unwrap();
257    /// assert_eq!(test.a, 100);
258    /// ```
259    ///
260    /// [`PhpException`]: crate::exception::PhpException
261    pub fn set(&self, self_: &'a mut T, value: &Zval) -> PhpResult {
262        match self {
263            Property::Field(field) => field(self_)
264                .set(value)
265                .map_err(|e| format!("Failed to set property value: {e:?}").into()),
266            Property::Method { get: _, set } => match set {
267                Some(set) => set(self_, value),
268                None => Err("No setter available for this property.".into()),
269            },
270        }
271    }
272}