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}