Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ZendClassObject as self parameter #103

Merged
merged 4 commits into from
Oct 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
- Changed (almost all) module paths. Too many changes to list them all, check
out the docs.
- Removed `skel` project.
- Allow methods to accept references to `ZendClassObject<T>` instead of `self`.
[#103]

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

## Version 0.5.3

Expand Down
2 changes: 1 addition & 1 deletion crates/macros/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ impl Arg {
pub fn get_type_ident(&self) -> TokenStream {
let ty: Type = syn::parse_str(&self.ty).unwrap();
quote! {
<#ty as ::ext_php_rs::convert::FromZval>::TYPE
<#ty as ::ext_php_rs::convert::FromZvalMut>::TYPE
}
}

Expand Down
6 changes: 4 additions & 2 deletions crates/macros/src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub enum ParsedAttribute {
ty: PropAttrTy,
},
Constructor,
This,
}

#[derive(Default, Debug, FromMeta)]
Expand Down Expand Up @@ -138,9 +139,9 @@ pub fn parser(args: AttributeArgs, input: ItemImpl) -> Result<TokenStream> {
#constant
}
}
syn::ImplItem::Method(mut method) => {
syn::ImplItem::Method(method) => {
let parsed_method =
method::parser(&mut method, args.rename_methods.unwrap_or_default())?;
method::parser(method, args.rename_methods.unwrap_or_default())?;
if let Some((prop, ty)) = parsed_method.property {
let prop = match class.properties.entry(prop) {
Entry::Occupied(entry) => entry.into_mut(),
Expand Down Expand Up @@ -248,6 +249,7 @@ pub fn parse_attribute(attr: &Attribute) -> Result<ParsedAttribute> {
}
}
"constructor" => ParsedAttribute::Constructor,
"this" => ParsedAttribute::This,
attr => bail!("Invalid attribute `#[{}]`.", attr),
})
}
Expand Down
101 changes: 58 additions & 43 deletions crates/macros/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ use crate::{
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{punctuated::Punctuated, FnArg, ImplItemMethod, Lit, Pat, Signature, Token, Type};
use syn::{punctuated::Punctuated, FnArg, ImplItemMethod, Lit, Pat, Token, Type};

#[derive(Debug, Clone)]
pub enum Arg {
Receiver(bool),
Receiver(MethodType),
Typed(function::Arg),
}

Expand Down Expand Up @@ -45,6 +45,13 @@ pub struct ParsedMethod {
pub constructor: bool,
}

#[derive(Debug, Clone, Copy)]
pub enum MethodType {
Receiver { mutable: bool },
ReceiverClassObject,
Static,
}

impl ParsedMethod {
pub fn new(
tokens: TokenStream,
Expand All @@ -61,7 +68,7 @@ impl ParsedMethod {
}
}

pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<ParsedMethod> {
pub fn parser(mut input: ImplItemMethod, rename_rule: RenameRule) -> Result<ParsedMethod> {
let mut defaults = HashMap::new();
let mut optional = None;
let mut visibility = Visibility::Public;
Expand Down Expand Up @@ -94,19 +101,13 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
as_prop = Some((prop_name, ty))
}
ParsedAttribute::Constructor => is_constructor = true,
_ => bail!("Invalid attribute for method."),
}
}

input.attrs.clear();

let ImplItemMethod { sig, .. } = &input;
let Signature {
ident,
output,
inputs,
..
} = &sig;

let ident = &input.sig.ident;
let name = identifier.unwrap_or_else(|| rename_rule.rename(ident.to_string()));
if name == "__construct" {
is_constructor = true;
Expand All @@ -122,31 +123,25 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
quote! { return; }
};
let internal_ident = Ident::new(&format!("_internal_php_{}", ident), Span::call_site());
let args = build_args(inputs, &defaults)?;
let args = build_args(&mut input.sig.inputs, &defaults)?;
let optional = function::find_optional_parameter(
args.iter().filter_map(|arg| match arg {
Arg::Typed(arg) => Some(arg),
_ => None,
}),
optional,
);
let (arg_definitions, is_static) = build_arg_definitions(&args);
let (arg_definitions, method_type) = build_arg_definitions(&args);
let arg_parser = build_arg_parser(
args.iter(),
&optional,
&bail,
if is_static {
ParserType::StaticMethod
} else {
ParserType::Method
match method_type {
MethodType::Static => ParserType::StaticMethod,
_ => ParserType::Method,
},
)?;
let arg_accessors = build_arg_accessors(&args, &bail);
let this = if is_static {
quote! { Self:: }
} else {
quote! { this. }
};

let func = if is_constructor {
quote! {
Expand All @@ -166,6 +161,11 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
}
}
} else {
let this = match method_type {
MethodType::Receiver { .. } => quote! { this. },
MethodType::ReceiverClassObject | MethodType::Static => quote! { Self:: },
};

quote! {
#input

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

let result = #this #ident(#(#arg_accessors, )*);
let result = #this #ident(#(#arg_accessors,)*);

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

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

fn build_args(
inputs: &Punctuated<FnArg, Token![,]>,
inputs: &mut Punctuated<FnArg, Token![,]>,
defaults: &HashMap<String, Lit>,
) -> Result<Vec<Arg>> {
inputs
.iter()
.iter_mut()
.map(|arg| match arg {
FnArg::Receiver(receiver) => {
if receiver.reference.is_none() {
bail!("`self` parameter must be a reference.");
}
Ok(Arg::Receiver(receiver.mutability.is_some()))
Ok(Arg::Receiver(MethodType::Receiver {
mutable: receiver.mutability.is_some(),
}))
}
FnArg::Typed(ty) => {
let name = match &*ty.pat {
Pat::Ident(pat) => pat.ident.to_string(),
_ => bail!("Invalid parameter type."),
};
let default = defaults.get(&name);
Ok(Arg::Typed(
crate::function::Arg::from_type(name.clone(), &ty.ty, default, false)
.ok_or_else(|| anyhow!("Invalid parameter type for `{}`.", name))?,
))
let mut this = false;
let attrs = std::mem::take(&mut ty.attrs);
for attr in attrs.into_iter() {
match parse_attribute(&attr)? {
ParsedAttribute::This => this = true,
_ => bail!("Invalid attribute for argument."),
}
}

if this {
Ok(Arg::Receiver(MethodType::ReceiverClassObject))
} else {
let name = match &*ty.pat {
Pat::Ident(pat) => pat.ident.to_string(),
_ => bail!("Invalid parameter type."),
};
let default = defaults.get(&name);
Ok(Arg::Typed(
crate::function::Arg::from_type(name.clone(), &ty.ty, default, false)
.ok_or_else(|| anyhow!("Invalid parameter type for `{}`.", name))?,
))
}
}
})
.collect()
}

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

(
args.iter()
.filter_map(|ty| match ty {
Arg::Receiver(_) => {
_static = false;

Arg::Receiver(t) => {
method_type = *t;
None
}
Arg::Typed(arg) => {
Expand All @@ -251,7 +265,7 @@ fn build_arg_definitions(args: &[Arg]) -> (Vec<TokenStream>, bool) {
}
})
.collect(),
_static,
method_type,
)
}

Expand All @@ -276,6 +290,7 @@ fn build_arg_accessors(args: &[Arg], ret: &TokenStream) -> Vec<TokenStream> {
args.iter()
.filter_map(|arg| match arg {
Arg::Typed(arg) => Some(arg.get_accessor(ret)),
Arg::Receiver(MethodType::ReceiverClassObject) => Some(quote! { this }),
_ => None,
})
.collect()
Expand Down
5 changes: 3 additions & 2 deletions guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
- [`HashMap`](./types/hashmap.md)
- [`Binary`](./types/binary.md)
- [`Option`](./types/option.md)
- [`Object`](./types/object.md)
- [`Closure`](./types/closure.md)
- [Object](./types/object.md)
- [Class Object](./types/class_object.md)
- [Closure](./types/closure.md)
- [Macros](./macros/index.md)
- [Module](./macros/module.md)
- [Module Startup Function](./macros/module_startup.md)
Expand Down
13 changes: 11 additions & 2 deletions guide/src/macros/impl.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ methods is they are bounded by their class object.
Class methods can take a `&self` or `&mut self` parameter. They cannot take a
consuming `self` parameter. Static methods can omit this `self` parameter.

To access the underlying Zend object, you can take a reference to a
`ZendClassObject<T>` in place of the self parameter, where the parameter is
annotated with the `#[this]` attribute. This can also be used to return a
reference to `$this`.

By default, all methods are renamed in PHP to the camel-case variant of the Rust
method name. This can be changed on the `#[php_impl]` attribute, by passing one
of the following as the `rename_methods` option:
Expand Down Expand Up @@ -92,9 +97,9 @@ constant for the maximum age of a `Human`.

```rust
# extern crate ext_php_rs;
# use ext_php_rs::prelude::*;
# use ext_php_rs::{prelude::*, types::ZendClassObject};
# #[php_class]
# #[derive(Default)]
# #[derive(Debug, Default)]
# pub struct Human {
# name: String,
# age: i32,
Expand Down Expand Up @@ -129,6 +134,10 @@ impl Human {
println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address);
}

pub fn get_raw_obj(#[this] this: &mut ZendClassObject<Human>) {
dbg!(this);
}

pub fn get_max_age() -> i32 {
Self::MAX_AGE
}
Expand Down
62 changes: 62 additions & 0 deletions guide/src/types/class_object.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Class Object

A class object is an instance of a Rust struct (which has been registered as a
PHP class) that has been allocated alongside an object. You can think of a class
object as a superset of an object, as a class object contains a Zend object.

| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation |
| ------------- | --------------------- | --------------- | ------------------------- | ------------------------------ |
| No | `&ZendClassObject<T>` | Yes | `&mut ZendClassObject<T>` | Zend object and a Rust struct. |

## Examples

### Returning a reference to `self`

```rust
# extern crate ext_php_rs;
use ext_php_rs::{prelude::*, types::ZendClassObject};

#[php_class]
pub struct Example {
foo: i32,
bar: i32
}

#[php_impl]
impl Example {
// Even though this function doesn't have a `self` type, it is still treated as an associated method
// and not a static method.
pub fn builder_pattern(#[this] this: &mut ZendClassObject<Example>) -> &mut ZendClassObject<Example> {
// do something with `this`
this
}
}
# #[php_module]
# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
# module
# }
```

### Creating a new class instance

```rust
# extern crate ext_php_rs;
use ext_php_rs::prelude::*;

#[php_class]
pub struct Example {
foo: i32,
bar: i32
}

#[php_impl]
impl Example {
pub fn make_new(foo: i32, bar: i32) -> Example {
Example { foo, bar }
}
}
# #[php_module]
# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
# module
# }
```
Loading