Skip to content

Commit b47f8ba

Browse files
Allow ZendClassObject as self parameter (#103)
* Allow `ZendClassObject` as `self` parameter * Fixed tests * Updated guide * Updated changelog
1 parent 466c165 commit b47f8ba

File tree

13 files changed

+216
-173
lines changed

13 files changed

+216
-173
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
- Changed (almost all) module paths. Too many changes to list them all, check
77
out the docs.
88
- Removed `skel` project.
9+
- Allow methods to accept references to `ZendClassObject<T>` instead of `self`.
10+
[#103]
911

1012
[#101]: https://github.com/davidcole1340/ext-php-rs/pull/101
13+
[#103]: https://github.com/davidcole1340/ext-php-rs/pull/103
1114

1215
## Version 0.5.3
1316

crates/macros/src/function.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ impl Arg {
309309
pub fn get_type_ident(&self) -> TokenStream {
310310
let ty: Type = syn::parse_str(&self.ty).unwrap();
311311
quote! {
312-
<#ty as ::ext_php_rs::convert::FromZval>::TYPE
312+
<#ty as ::ext_php_rs::convert::FromZvalMut>::TYPE
313313
}
314314
}
315315

crates/macros/src/impl_.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub enum ParsedAttribute {
8383
ty: PropAttrTy,
8484
},
8585
Constructor,
86+
This,
8687
}
8788

8889
#[derive(Default, Debug, FromMeta)]
@@ -138,9 +139,9 @@ pub fn parser(args: AttributeArgs, input: ItemImpl) -> Result<TokenStream> {
138139
#constant
139140
}
140141
}
141-
syn::ImplItem::Method(mut method) => {
142+
syn::ImplItem::Method(method) => {
142143
let parsed_method =
143-
method::parser(&mut method, args.rename_methods.unwrap_or_default())?;
144+
method::parser(method, args.rename_methods.unwrap_or_default())?;
144145
if let Some((prop, ty)) = parsed_method.property {
145146
let prop = match class.properties.entry(prop) {
146147
Entry::Occupied(entry) => entry.into_mut(),
@@ -248,6 +249,7 @@ pub fn parse_attribute(attr: &Attribute) -> Result<ParsedAttribute> {
248249
}
249250
}
250251
"constructor" => ParsedAttribute::Constructor,
252+
"this" => ParsedAttribute::This,
251253
attr => bail!("Invalid attribute `#[{}]`.", attr),
252254
})
253255
}

crates/macros/src/method.rs

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ use crate::{
88
};
99
use proc_macro2::{Ident, Span, TokenStream};
1010
use quote::quote;
11-
use syn::{punctuated::Punctuated, FnArg, ImplItemMethod, Lit, Pat, Signature, Token, Type};
11+
use syn::{punctuated::Punctuated, FnArg, ImplItemMethod, Lit, Pat, Token, Type};
1212

1313
#[derive(Debug, Clone)]
1414
pub enum Arg {
15-
Receiver(bool),
15+
Receiver(MethodType),
1616
Typed(function::Arg),
1717
}
1818

@@ -45,6 +45,13 @@ pub struct ParsedMethod {
4545
pub constructor: bool,
4646
}
4747

48+
#[derive(Debug, Clone, Copy)]
49+
pub enum MethodType {
50+
Receiver { mutable: bool },
51+
ReceiverClassObject,
52+
Static,
53+
}
54+
4855
impl ParsedMethod {
4956
pub fn new(
5057
tokens: TokenStream,
@@ -61,7 +68,7 @@ impl ParsedMethod {
6168
}
6269
}
6370

64-
pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<ParsedMethod> {
71+
pub fn parser(mut input: ImplItemMethod, rename_rule: RenameRule) -> Result<ParsedMethod> {
6572
let mut defaults = HashMap::new();
6673
let mut optional = None;
6774
let mut visibility = Visibility::Public;
@@ -94,19 +101,13 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
94101
as_prop = Some((prop_name, ty))
95102
}
96103
ParsedAttribute::Constructor => is_constructor = true,
104+
_ => bail!("Invalid attribute for method."),
97105
}
98106
}
99107

100108
input.attrs.clear();
101109

102-
let ImplItemMethod { sig, .. } = &input;
103-
let Signature {
104-
ident,
105-
output,
106-
inputs,
107-
..
108-
} = &sig;
109-
110+
let ident = &input.sig.ident;
110111
let name = identifier.unwrap_or_else(|| rename_rule.rename(ident.to_string()));
111112
if name == "__construct" {
112113
is_constructor = true;
@@ -122,31 +123,25 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
122123
quote! { return; }
123124
};
124125
let internal_ident = Ident::new(&format!("_internal_php_{}", ident), Span::call_site());
125-
let args = build_args(inputs, &defaults)?;
126+
let args = build_args(&mut input.sig.inputs, &defaults)?;
126127
let optional = function::find_optional_parameter(
127128
args.iter().filter_map(|arg| match arg {
128129
Arg::Typed(arg) => Some(arg),
129130
_ => None,
130131
}),
131132
optional,
132133
);
133-
let (arg_definitions, is_static) = build_arg_definitions(&args);
134+
let (arg_definitions, method_type) = build_arg_definitions(&args);
134135
let arg_parser = build_arg_parser(
135136
args.iter(),
136137
&optional,
137138
&bail,
138-
if is_static {
139-
ParserType::StaticMethod
140-
} else {
141-
ParserType::Method
139+
match method_type {
140+
MethodType::Static => ParserType::StaticMethod,
141+
_ => ParserType::Method,
142142
},
143143
)?;
144144
let arg_accessors = build_arg_accessors(&args, &bail);
145-
let this = if is_static {
146-
quote! { Self:: }
147-
} else {
148-
quote! { this. }
149-
};
150145

151146
let func = if is_constructor {
152147
quote! {
@@ -166,6 +161,11 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
166161
}
167162
}
168163
} else {
164+
let this = match method_type {
165+
MethodType::Receiver { .. } => quote! { this. },
166+
MethodType::ReceiverClassObject | MethodType::Static => quote! { Self:: },
167+
};
168+
169169
quote! {
170170
#input
171171

@@ -179,7 +179,7 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
179179
#(#arg_definitions)*
180180
#arg_parser
181181

182-
let result = #this #ident(#(#arg_accessors, )*);
182+
let result = #this #ident(#(#arg_accessors,)*);
183183

184184
if let Err(e) = result.set_zval(retval, false) {
185185
let e: ::ext_php_rs::exception::PhpException = e.into();
@@ -195,51 +195,65 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
195195
orig_ident: ident.to_string(),
196196
args,
197197
optional,
198-
output: crate::function::get_return_type(output)?,
199-
_static: is_static,
198+
output: crate::function::get_return_type(&input.sig.output)?,
199+
_static: matches!(method_type, MethodType::Static),
200200
visibility,
201201
};
202202

203203
Ok(ParsedMethod::new(func, method, as_prop, is_constructor))
204204
}
205205

206206
fn build_args(
207-
inputs: &Punctuated<FnArg, Token![,]>,
207+
inputs: &mut Punctuated<FnArg, Token![,]>,
208208
defaults: &HashMap<String, Lit>,
209209
) -> Result<Vec<Arg>> {
210210
inputs
211-
.iter()
211+
.iter_mut()
212212
.map(|arg| match arg {
213213
FnArg::Receiver(receiver) => {
214214
if receiver.reference.is_none() {
215215
bail!("`self` parameter must be a reference.");
216216
}
217-
Ok(Arg::Receiver(receiver.mutability.is_some()))
217+
Ok(Arg::Receiver(MethodType::Receiver {
218+
mutable: receiver.mutability.is_some(),
219+
}))
218220
}
219221
FnArg::Typed(ty) => {
220-
let name = match &*ty.pat {
221-
Pat::Ident(pat) => pat.ident.to_string(),
222-
_ => bail!("Invalid parameter type."),
223-
};
224-
let default = defaults.get(&name);
225-
Ok(Arg::Typed(
226-
crate::function::Arg::from_type(name.clone(), &ty.ty, default, false)
227-
.ok_or_else(|| anyhow!("Invalid parameter type for `{}`.", name))?,
228-
))
222+
let mut this = false;
223+
let attrs = std::mem::take(&mut ty.attrs);
224+
for attr in attrs.into_iter() {
225+
match parse_attribute(&attr)? {
226+
ParsedAttribute::This => this = true,
227+
_ => bail!("Invalid attribute for argument."),
228+
}
229+
}
230+
231+
if this {
232+
Ok(Arg::Receiver(MethodType::ReceiverClassObject))
233+
} else {
234+
let name = match &*ty.pat {
235+
Pat::Ident(pat) => pat.ident.to_string(),
236+
_ => bail!("Invalid parameter type."),
237+
};
238+
let default = defaults.get(&name);
239+
Ok(Arg::Typed(
240+
crate::function::Arg::from_type(name.clone(), &ty.ty, default, false)
241+
.ok_or_else(|| anyhow!("Invalid parameter type for `{}`.", name))?,
242+
))
243+
}
229244
}
230245
})
231246
.collect()
232247
}
233248

234-
fn build_arg_definitions(args: &[Arg]) -> (Vec<TokenStream>, bool) {
235-
let mut _static = true;
249+
fn build_arg_definitions(args: &[Arg]) -> (Vec<TokenStream>, MethodType) {
250+
let mut method_type = MethodType::Static;
236251

237252
(
238253
args.iter()
239254
.filter_map(|ty| match ty {
240-
Arg::Receiver(_) => {
241-
_static = false;
242-
255+
Arg::Receiver(t) => {
256+
method_type = *t;
243257
None
244258
}
245259
Arg::Typed(arg) => {
@@ -251,7 +265,7 @@ fn build_arg_definitions(args: &[Arg]) -> (Vec<TokenStream>, bool) {
251265
}
252266
})
253267
.collect(),
254-
_static,
268+
method_type,
255269
)
256270
}
257271

@@ -276,6 +290,7 @@ fn build_arg_accessors(args: &[Arg], ret: &TokenStream) -> Vec<TokenStream> {
276290
args.iter()
277291
.filter_map(|arg| match arg {
278292
Arg::Typed(arg) => Some(arg.get_accessor(ret)),
293+
Arg::Receiver(MethodType::ReceiverClassObject) => Some(quote! { this }),
279294
_ => None,
280295
})
281296
.collect()

guide/src/SUMMARY.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
- [`HashMap`](./types/hashmap.md)
1313
- [`Binary`](./types/binary.md)
1414
- [`Option`](./types/option.md)
15-
- [`Object`](./types/object.md)
16-
- [`Closure`](./types/closure.md)
15+
- [Object](./types/object.md)
16+
- [Class Object](./types/class_object.md)
17+
- [Closure](./types/closure.md)
1718
- [Macros](./macros/index.md)
1819
- [Module](./macros/module.md)
1920
- [Module Startup Function](./macros/module_startup.md)

guide/src/macros/impl.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ methods is they are bounded by their class object.
1717
Class methods can take a `&self` or `&mut self` parameter. They cannot take a
1818
consuming `self` parameter. Static methods can omit this `self` parameter.
1919

20+
To access the underlying Zend object, you can take a reference to a
21+
`ZendClassObject<T>` in place of the self parameter, where the parameter is
22+
annotated with the `#[this]` attribute. This can also be used to return a
23+
reference to `$this`.
24+
2025
By default, all methods are renamed in PHP to the camel-case variant of the Rust
2126
method name. This can be changed on the `#[php_impl]` attribute, by passing one
2227
of the following as the `rename_methods` option:
@@ -92,9 +97,9 @@ constant for the maximum age of a `Human`.
9297

9398
```rust
9499
# extern crate ext_php_rs;
95-
# use ext_php_rs::prelude::*;
100+
# use ext_php_rs::{prelude::*, types::ZendClassObject};
96101
# #[php_class]
97-
# #[derive(Default)]
102+
# #[derive(Debug, Default)]
98103
# pub struct Human {
99104
# name: String,
100105
# age: i32,
@@ -129,6 +134,10 @@ impl Human {
129134
println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address);
130135
}
131136

137+
pub fn get_raw_obj(#[this] this: &mut ZendClassObject<Human>) {
138+
dbg!(this);
139+
}
140+
132141
pub fn get_max_age() -> i32 {
133142
Self::MAX_AGE
134143
}

guide/src/types/class_object.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Class Object
2+
3+
A class object is an instance of a Rust struct (which has been registered as a
4+
PHP class) that has been allocated alongside an object. You can think of a class
5+
object as a superset of an object, as a class object contains a Zend object.
6+
7+
| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation |
8+
| ------------- | --------------------- | --------------- | ------------------------- | ------------------------------ |
9+
| No | `&ZendClassObject<T>` | Yes | `&mut ZendClassObject<T>` | Zend object and a Rust struct. |
10+
11+
## Examples
12+
13+
### Returning a reference to `self`
14+
15+
```rust
16+
# extern crate ext_php_rs;
17+
use ext_php_rs::{prelude::*, types::ZendClassObject};
18+
19+
#[php_class]
20+
pub struct Example {
21+
foo: i32,
22+
bar: i32
23+
}
24+
25+
#[php_impl]
26+
impl Example {
27+
// Even though this function doesn't have a `self` type, it is still treated as an associated method
28+
// and not a static method.
29+
pub fn builder_pattern(#[this] this: &mut ZendClassObject<Example>) -> &mut ZendClassObject<Example> {
30+
// do something with `this`
31+
this
32+
}
33+
}
34+
# #[php_module]
35+
# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
36+
# module
37+
# }
38+
```
39+
40+
### Creating a new class instance
41+
42+
```rust
43+
# extern crate ext_php_rs;
44+
use ext_php_rs::prelude::*;
45+
46+
#[php_class]
47+
pub struct Example {
48+
foo: i32,
49+
bar: i32
50+
}
51+
52+
#[php_impl]
53+
impl Example {
54+
pub fn make_new(foo: i32, bar: i32) -> Example {
55+
Example { foo, bar }
56+
}
57+
}
58+
# #[php_module]
59+
# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
60+
# module
61+
# }
62+
```

0 commit comments

Comments
 (0)