Skip to content

Commit faad01b

Browse files
Add ability to define properties when using macros (#56)
* Panic instead of return result when adding property This will almost always be unwrapped * Remove unused `set_refcount` function Wasn't valid either way * Add ability to get and set properties on class objects * Updated documentation adding properties
1 parent 3ea6f0c commit faad01b

File tree

10 files changed

+175
-31
lines changed

10 files changed

+175
-31
lines changed

example/skel/src/allocator.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ unsafe impl GlobalAlloc for PhpAllocator {
2727
0,
2828
) as *mut u8;
2929

30-
eprintln!("allocating {} bytes at {:?}", layout.size(), ptr);
30+
// eprintln!("allocating {} bytes at {:?}", layout.size(), ptr);
3131

3232
ptr
3333
}
3434

3535
unsafe fn dealloc(&self, ptr: *mut u8, layout: std::alloc::Layout) {
36-
eprintln!("deallocating {} bytes at {:?}", layout.size(), ptr);
36+
// eprintln!("deallocating {} bytes at {:?}", layout.size(), ptr);
3737

3838
_efree(
3939
ptr as *mut _,

example/skel/src/lib.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,21 @@ pub fn test_str(input: &str) -> &str {
101101
// info_table_end!();
102102
// }
103103

104-
#[php_class(name = "Redis\\Exception\\RedisClientException")]
105-
#[extends(ClassEntry::exception())]
106-
#[derive(Default)]
107-
struct RedisException;
104+
// #[php_class(name = "Redis\\Exception\\RedisClientException")]
105+
// #[extends(ClassEntry::exception())]
106+
// #[derive(Default)]
107+
// struct RedisException;
108108

109-
#[php_function]
110-
pub fn test_exception() -> Result<i32, PhpException<'static>> {
111-
Err(PhpException::from_class::<RedisException>(
112-
"Hello world".into(),
113-
))
114-
}
109+
// #[php_function]
110+
// pub fn test_exception() -> Result<i32, PhpException<'static>> {
111+
// Err(PhpException::from_class::<RedisException>(
112+
// "Hello world".into(),
113+
// ))
114+
// }
115115

116116
#[php_class]
117+
#[property(test = 0)]
118+
#[property(another = "Hello world")]
117119
#[derive(Default)]
118120
pub struct Test {
119121
test: String,
@@ -122,6 +124,10 @@ pub struct Test {
122124
#[php_impl]
123125
impl Test {
124126
pub fn get(&mut self) -> &Test {
127+
use ext_php_rs::php::types::object::ZendObjectOverride;
128+
129+
dbg!(unsafe { self.get_property::<String>("test") });
130+
unsafe { self.set_property("test", "another string") };
125131
self
126132
}
127133

example/skel/test.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
$x->set_str('Hello World');
88
var_dump($x->get_str());
99
var_dump($x->get());
10+
# $x->test = 'hello world';
11+
var_dump($x->get());
1012
var_dump($x->get_str());
1113
// var_dump($x);
1214
var_dump('program done');

ext-php-rs-derive/src/class.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
use std::collections::HashMap;
2+
13
use crate::STATE;
24
use anyhow::{anyhow, bail, Result};
35
use darling::{FromMeta, ToTokens};
46
use proc_macro2::{Ident, Span, TokenStream};
57
use quote::quote;
6-
use syn::{Attribute, AttributeArgs, Expr, ItemStruct};
8+
use syn::{Attribute, AttributeArgs, Expr, ItemStruct, Token};
79

810
#[derive(Debug, Default)]
911
pub struct Class {
@@ -12,12 +14,14 @@ pub struct Class {
1214
pub interfaces: Vec<String>,
1315
pub methods: Vec<crate::method::Method>,
1416
pub constants: Vec<crate::constant::Constant>,
17+
pub properties: HashMap<String, (String, Option<String>)>,
1518
}
1619

1720
#[derive(Debug)]
1821
pub enum ParsedAttribute {
1922
Extends(Expr),
2023
Implements(Expr),
24+
Property(Box<PropertyAttr>),
2125
}
2226

2327
#[derive(Default, Debug, FromMeta)]
@@ -32,6 +36,7 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result<TokenStream>
3236

3337
let mut parent = None;
3438
let mut interfaces = vec![];
39+
let mut properties = HashMap::<String, (String, Option<String>)>::new();
3540

3641
input.attrs = {
3742
let mut unused = vec![];
@@ -44,6 +49,15 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result<TokenStream>
4449
ParsedAttribute::Implements(class) => {
4550
interfaces.push(class.to_token_stream().to_string());
4651
}
52+
ParsedAttribute::Property(attr) => {
53+
properties.insert(
54+
attr.name.to_string(),
55+
(
56+
attr.default.to_token_stream().to_string(),
57+
attr.flags.map(|flags| flags.to_token_stream().to_string()),
58+
),
59+
);
60+
}
4761
},
4862
None => unused.push(attr),
4963
}
@@ -93,6 +107,7 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result<TokenStream>
93107
class_name,
94108
parent,
95109
interfaces,
110+
properties,
96111
..Default::default()
97112
};
98113

@@ -111,6 +126,31 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result<TokenStream>
111126
Ok(output)
112127
}
113128

129+
#[derive(Debug)]
130+
pub struct PropertyAttr {
131+
pub name: Ident,
132+
pub default: Expr,
133+
pub flags: Option<Expr>,
134+
}
135+
136+
impl syn::parse::Parse for PropertyAttr {
137+
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
138+
let name: Ident = input.parse()?;
139+
let _: Token![=] = input.parse()?;
140+
let default: Expr = input.parse()?;
141+
let flags = input
142+
.parse::<Token![,]>()
143+
.and_then(|_| input.parse::<Expr>())
144+
.ok();
145+
146+
Ok(PropertyAttr {
147+
name,
148+
default,
149+
flags,
150+
})
151+
}
152+
}
153+
114154
fn parse_attribute(attr: &Attribute) -> Result<Option<ParsedAttribute>> {
115155
let name = attr.path.to_token_stream().to_string();
116156

@@ -127,6 +167,13 @@ fn parse_attribute(attr: &Attribute) -> Result<Option<ParsedAttribute>> {
127167
.map_err(|_| anyhow!("Unable to parse `#[{}]` attribute.", name))?;
128168
Some(ParsedAttribute::Implements(meta))
129169
}
170+
"property" => {
171+
let attr: PropertyAttr = attr
172+
.parse_args()
173+
.map_err(|_| anyhow!("Unable to parse `#[{}]` attribute.", name))?;
174+
175+
Some(ParsedAttribute::Property(Box::new(attr)))
176+
}
130177
_ => None,
131178
})
132179
}

ext-php-rs-derive/src/startup_function.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ pub fn parser(input: ItemFn) -> Result<TokenStream> {
2121
let func = quote! {
2222
#[doc(hidden)]
2323
pub extern "C" fn #ident(ty: i32, module_number: i32) -> i32 {
24-
pub use ::ext_php_rs::php::constants::IntoConst;
24+
use ::ext_php_rs::php::constants::IntoConst;
25+
use ::ext_php_rs::php::flags::PropertyFlags;
2526

2627
fn internal() {
2728
#(#stmts)*
@@ -82,12 +83,41 @@ fn build_classes(classes: &HashMap<String, Class>) -> Result<Vec<TokenStream>> {
8283
Ok(quote! { .implements(#expr) })
8384
})
8485
.collect::<Result<Vec<_>>>()?;
86+
let properties = class
87+
.properties
88+
.iter()
89+
.map(|(name, (default, flags))| {
90+
let default_expr: Expr = syn::parse_str(default).map_err(|_| {
91+
anyhow!(
92+
"Invalid default value given for property `{}` type: `{}`",
93+
name,
94+
default
95+
)
96+
})?;
97+
let flags_expr: Expr = syn::parse_str(
98+
flags
99+
.as_ref()
100+
.map(|flags| flags.as_str())
101+
.unwrap_or("PropertyFlags::Public"),
102+
)
103+
.map_err(|_| {
104+
anyhow!(
105+
"Invalid default value given for property `{}` type: `{}`",
106+
name,
107+
default
108+
)
109+
})?;
110+
111+
Ok(quote! { .property(#name, #default_expr, #flags_expr) })
112+
})
113+
.collect::<Result<Vec<_>>>()?;
85114

86115
Ok(quote! {{
87116
let class = ::ext_php_rs::php::class::ClassBuilder::new(#class_name)
88117
#(#methods)*
89118
#(#constants)*
90119
#(#interfaces)*
120+
#(#properties)*
91121
#parent
92122
.object_override::<#ident>()
93123
.build()

guide/src/macros/impl.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ impl Human {
6969
}
7070

7171
pub fn introduce(&self) {
72-
println!("My name is {} and I am {} years old.", self.name, self.age);
72+
use ext_php_rs::php::types::object::ZendObjectOverride;
73+
74+
// SAFETY: The `Human` struct is only constructed from PHP.
75+
let address: String = unsafe { self.get_property("address") }.unwrap();
76+
println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, address);
7377
}
7478

7579
pub fn get_max_age() -> i32 {

guide/src/macros/structs.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,20 @@ placed underneath the `#[php_class]` attribute.
3030
- `#[implements(ce)]` - Implements the given interface on the class. Can be used
3131
multiple times. `ce` must be a valid Rust expression when it is called inside
3232
the `#[php_module]` function.
33+
- `#[property(name = default[, flags])]` - Adds a PHP property to the class. Can
34+
be get and set through functions defined through the trait
35+
`ZendObjectOverride`.
3336

3437
## Example
3538

36-
This example creates a PHP class `Human`:
39+
This example creates a PHP class `Human`, adding a PHP property `address` with
40+
an empty string as the default value.
3741

3842
```rust
3943
# extern crate ext_php_rs;
4044
# use ext_php_rs::prelude::*;
4145
#[php_class]
46+
#[property(address = "")]
4247
#[derive(Default)]
4348
pub struct Human {
4449
name: String,
@@ -65,3 +70,14 @@ pub fn throw_exception() -> Result<i32, PhpException<'static>> {
6570
Err(PhpException::from_class::<RedisException>("Not good!".into()))
6671
}
6772
```
73+
74+
Creating a class with a private property called `test`:
75+
76+
```rust
77+
# extern crate ext_php_rs;
78+
# use ext_php_rs::prelude::*;
79+
#[php_class]
80+
#[property(test = "Default value", PropertyFlags::Private)]
81+
#[derive(Default)]
82+
pub struct TestClass;
83+
```

src/php/class.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,23 +204,28 @@ impl<'a> ClassBuilder<'a> {
204204
/// Adds a property to the class. The initial type of the property is given by the type
205205
/// of the given default. Note that the user can change the type.
206206
///
207-
/// Returns a result containing the class builder if the property was successfully added.
208-
///
209207
/// # Parameters
210208
///
211209
/// * `name` - The name of the property to add to the class.
212210
/// * `default` - The default value of the property.
213211
/// * `flags` - Flags relating to the property. See [`PropertyFlags`].
212+
///
213+
/// # Panics
214+
///
215+
/// Function will panic if the given `default` cannot be converted into a [`Zval`].
214216
pub fn property<T: Into<String>>(
215217
mut self,
216218
name: T,
217219
default: impl IntoZval,
218220
flags: PropertyFlags,
219-
) -> Result<Self> {
220-
let default = default.as_zval(true)?;
221+
) -> Self {
222+
let default = match default.as_zval(true) {
223+
Ok(default) => default,
224+
Err(_) => panic!("Invalid default value for property `{}`.", name.into()),
225+
};
221226

222227
self.properties.push((name.into(), default, flags));
223-
Ok(self)
228+
self
224229
}
225230

226231
/// Adds a constant to the class. The type of the constant is defined by the type of the given

src/php/types/object.rs

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use crate::{
2626

2727
use super::{
2828
array::ZendHashTable,
29-
zval::{IntoZval, Zval},
29+
zval::{FromZval, IntoZval, Zval},
3030
};
3131

3232
pub type ZendObject = zend_object;
@@ -97,10 +97,6 @@ impl ZendObject {
9797
let name = ZendString::new(name, false)?;
9898
let mut value = value.as_zval(false)?;
9999

100-
if value.is_string() {
101-
value.set_refcount(0);
102-
}
103-
104100
unsafe {
105101
self.handlers()?.write_property.ok_or(Error::InvalidScope)?(
106102
self,
@@ -207,6 +203,49 @@ pub trait ZendObjectOverride: Default {
207203
/// the class with PHP.
208204
#[doc(hidden)]
209205
fn set_class(ce: &'static ClassEntry);
206+
207+
/// Attempts to retrieve a property from the class object.
208+
///
209+
/// # Parameters
210+
///
211+
/// * `name` - The name of the property.
212+
///
213+
/// # Returns
214+
///
215+
/// Returns a given type `T` inside an option which is the value of the zval, or [`None`]
216+
/// if the property could not be found.
217+
///
218+
/// # Safety
219+
///
220+
/// Caller must guarantee that the object the function is called on is immediately followed
221+
/// by a [`zend_object`], which is true when the object was instantiated from [`ZendClassObject`].
222+
unsafe fn get_property<'a, T: FromZval<'a>>(&'a self, name: &str) -> Option<T> {
223+
let obj = ZendClassObject::<Self>::from_obj_ptr(self)?;
224+
let zv = obj.std.get_property(name).ok()?;
225+
zv.try_into().ok()
226+
}
227+
228+
/// Attempts to set the value of a property on the class object.
229+
///
230+
/// # Parameters
231+
///
232+
/// * `name` - The name of the property to set.
233+
/// * `value` - The value to set the property to.
234+
///
235+
/// # Returns
236+
///
237+
/// Returns nothing in an option if the property was successfully set. Returns none if setting
238+
/// the value failed.
239+
///
240+
/// # Safety
241+
///
242+
/// Caller must guarantee that the object the function is called on is immediately followed
243+
/// by a [`zend_object`], which is true when the object was instantiated from [`ZendClassObject`].
244+
unsafe fn set_property(&mut self, name: &str, value: impl IntoZval) -> Option<()> {
245+
let obj = ZendClassObject::<Self>::from_obj_ptr(self)?;
246+
obj.std.set_property(name, value).ok()?;
247+
Some(())
248+
}
210249
}
211250

212251
impl<T: ZendObjectOverride> IntoZval for &T {

src/php/types/zval.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -360,11 +360,6 @@ impl<'a> Zval {
360360
self.value.arr = val.into_ptr();
361361
}
362362

363-
/// Sets the reference count of the Zval.
364-
pub(crate) fn set_refcount(&mut self, rc: u32) {
365-
unsafe { (*self.value.counted).gc.refcount = rc }
366-
}
367-
368363
/// Used to drop the Zval but keep the value of the zval intact.
369364
///
370365
/// This is important when copying the value of the zval, as the actual value

0 commit comments

Comments
 (0)