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}