Skip to content

Commit 125ec3b

Browse files
Add ability to add proper object constructor (#83)
1 parent c16c5d4 commit 125ec3b

File tree

14 files changed

+539
-274
lines changed

14 files changed

+539
-274
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
- Constructors that return `Self` can now be added to classes. [#83]
4+
- `Default` is no longer required to be implemented on classes, however, a
5+
constructor must be specified if you want to construct the class from PHP.
6+
- Constructors can return `Self` or `Result<Self, E>`, where
7+
`E: Into<PhpException>`.
8+
9+
[#83]: https://github.com/davidcole1340/ext-php-rs/pull/83
10+
311
## Version 0.5.1
412

513
- `PhpException` no longer requires a lifetime [#80].

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub struct Class {
1414
pub parent: Option<String>,
1515
pub interfaces: Vec<String>,
1616
pub methods: Vec<crate::method::Method>,
17+
pub constructor: Option<crate::method::Method>,
1718
pub constants: Vec<crate::constant::Constant>,
1819
pub properties: HashMap<String, Property>,
1920
}

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi
5656
let args = build_args(inputs, &attr_args.defaults)?;
5757
let optional = find_optional_parameter(args.iter(), attr_args.optional);
5858
let arg_definitions = build_arg_definitions(&args);
59-
let arg_parser = build_arg_parser(args.iter(), &optional)?;
59+
let arg_parser = build_arg_parser(args.iter(), &optional, &quote! { return; })?;
6060
let arg_accessors = build_arg_accessors(&args);
6161

6262
let return_type = get_return_type(output)?;
@@ -157,6 +157,7 @@ pub fn find_optional_parameter<'a>(
157157
pub fn build_arg_parser<'a>(
158158
args: impl Iterator<Item = &'a Arg>,
159159
optional: &Option<String>,
160+
ret: &TokenStream,
160161
) -> Result<TokenStream> {
161162
let mut rest_optional = false;
162163

@@ -194,13 +195,15 @@ pub fn build_arg_parser<'a>(
194195
.parse();
195196

196197
if parser.is_err() {
197-
return;
198+
#ret
198199
}
199200
})
200201
}
201202

202203
fn build_arg_accessors(args: &[Arg]) -> Vec<TokenStream> {
203-
args.iter().map(|arg| arg.get_accessor()).collect()
204+
args.iter()
205+
.map(|arg| arg.get_accessor(&quote! { return; }))
206+
.collect()
204207
}
205208

206209
pub fn get_return_type(output_type: &ReturnType) -> Result<Option<(String, bool)>> {
@@ -286,7 +289,7 @@ impl Arg {
286289
}
287290

288291
/// Returns a [`TokenStream`] containing the line required to retrieve the value from the argument.
289-
pub fn get_accessor(&self) -> TokenStream {
292+
pub fn get_accessor(&self, ret: &TokenStream) -> TokenStream {
290293
let name = &self.name;
291294
let name_ident = self.get_name_ident();
292295

@@ -310,7 +313,7 @@ impl Arg {
310313
)
311314
.throw()
312315
.expect(concat!("Failed to throw exception: Invalid value given for argument `", #name, "`."));
313-
return;
316+
#ret
314317
}
315318
}
316319
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ pub enum ParsedAttribute {
8282
prop_name: Option<String>,
8383
ty: PropAttrTy,
8484
},
85+
Constructor,
8586
}
8687

8788
#[derive(Default, Debug, FromMeta)]
@@ -151,7 +152,14 @@ pub fn parser(args: AttributeArgs, input: ItemImpl) -> Result<TokenStream> {
151152
PropAttrTy::Setter => prop.add_setter(ident)?,
152153
}
153154
}
154-
class.methods.push(parsed_method.method);
155+
if parsed_method.constructor {
156+
if class.constructor.is_some() {
157+
bail!("You cannot have two constructors on the same class.");
158+
}
159+
class.constructor = Some(parsed_method.method);
160+
} else {
161+
class.methods.push(parsed_method.method);
162+
}
155163
parsed_method.tokens
156164
}
157165
item => item.to_token_stream(),
@@ -239,6 +247,7 @@ pub fn parse_attribute(attr: &Attribute) -> Result<ParsedAttribute> {
239247
ty: PropAttrTy::Setter,
240248
}
241249
}
250+
"constructor" => ParsedAttribute::Constructor,
242251
attr => bail!("Invalid attribute `#[{}]`.", attr),
243252
})
244253
}

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

Lines changed: 77 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,21 @@ pub struct ParsedMethod {
4242
pub tokens: TokenStream,
4343
pub method: Method,
4444
pub property: Option<(String, PropAttrTy)>,
45+
pub constructor: bool,
4546
}
4647

4748
impl ParsedMethod {
4849
pub fn new(
4950
tokens: TokenStream,
5051
method: Method,
5152
property: Option<(String, PropAttrTy)>,
53+
constructor: bool,
5254
) -> Self {
5355
Self {
5456
tokens,
5557
method,
5658
property,
59+
constructor,
5760
}
5861
}
5962
}
@@ -64,6 +67,7 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
6467
let mut visibility = Visibility::Public;
6568
let mut as_prop = None;
6669
let mut identifier = None;
70+
let mut is_constructor = false;
6771

6872
for attr in input.attrs.iter() {
6973
match parse_attribute(attr)? {
@@ -89,6 +93,7 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
8993
});
9094
as_prop = Some((prop_name, ty))
9195
}
96+
ParsedAttribute::Constructor => is_constructor = true,
9297
}
9398
}
9499

@@ -102,6 +107,20 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
102107
..
103108
} = &sig;
104109

110+
let name = identifier.unwrap_or_else(|| rename_rule.rename(ident.to_string()));
111+
if name == "__construct" {
112+
is_constructor = true;
113+
}
114+
115+
if is_constructor && (!matches!(visibility, Visibility::Public) || as_prop.is_some()) {
116+
bail!("`#[constructor]` attribute cannot be combined with the visibility or getter/setter attributes.");
117+
}
118+
119+
let bail = if is_constructor {
120+
quote! { return ConstructorResult::ArgError; }
121+
} else {
122+
quote! { return; }
123+
};
105124
let internal_ident = Ident::new(&format!("_internal_php_{}", ident), Span::call_site());
106125
let args = build_args(inputs, &defaults)?;
107126
let optional = function::find_optional_parameter(
@@ -112,34 +131,55 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
112131
optional,
113132
);
114133
let (arg_definitions, is_static) = build_arg_definitions(&args);
115-
let arg_parser = build_arg_parser(args.iter(), &optional)?;
116-
let arg_accessors = build_arg_accessors(&args);
134+
let arg_parser = build_arg_parser(args.iter(), &optional, &bail)?;
135+
let arg_accessors = build_arg_accessors(&args, &bail);
117136
let this = if is_static {
118137
quote! { Self:: }
119138
} else {
120139
quote! { this. }
121140
};
122141

123-
let func = quote! {
124-
#input
142+
let func = if is_constructor {
143+
quote! {
144+
#input
145+
146+
#[doc(hidden)]
147+
pub fn #internal_ident(
148+
ex: &mut ::ext_php_rs::php::execution_data::ExecutionData
149+
) -> ::ext_php_rs::php::types::object::ConstructorResult<Self> {
150+
use ::ext_php_rs::php::types::zval::IntoZval;
151+
use ::ext_php_rs::php::types::object::ConstructorResult;
152+
153+
#(#arg_definitions)*
154+
#arg_parser
155+
156+
Self::#ident(#(#arg_accessors,)*).into()
157+
}
158+
}
159+
} else {
160+
quote! {
161+
#input
125162

126-
#[doc(hidden)]
127-
pub extern "C" fn #internal_ident(ex: &mut ::ext_php_rs::php::execution_data::ExecutionData, retval: &mut ::ext_php_rs::php::types::zval::Zval) {
128-
use ::ext_php_rs::php::types::zval::IntoZval;
163+
#[doc(hidden)]
164+
pub extern "C" fn #internal_ident(
165+
ex: &mut ::ext_php_rs::php::execution_data::ExecutionData,
166+
retval: &mut ::ext_php_rs::php::types::zval::Zval
167+
) {
168+
use ::ext_php_rs::php::types::zval::IntoZval;
129169

130-
#(#arg_definitions)*
131-
#arg_parser
170+
#(#arg_definitions)*
171+
#arg_parser
132172

133-
let result = #this #ident(#(#arg_accessors, )*);
173+
let result = #this #ident(#(#arg_accessors, )*);
134174

135-
if let Err(e) = result.set_zval(retval, false) {
136-
let e: ::ext_php_rs::php::exceptions::PhpException = e.into();
137-
e.throw().expect("Failed to throw exception");
175+
if let Err(e) = result.set_zval(retval, false) {
176+
let e: ::ext_php_rs::php::exceptions::PhpException = e.into();
177+
e.throw().expect("Failed to throw exception");
178+
}
138179
}
139180
}
140181
};
141182

142-
let name = identifier.unwrap_or_else(|| rename_rule.rename(ident.to_string()));
143183
let method = Method {
144184
name,
145185
ident: internal_ident.to_string(),
@@ -151,7 +191,7 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
151191
visibility,
152192
};
153193

154-
Ok(ParsedMethod::new(func, method, as_prop))
194+
Ok(ParsedMethod::new(func, method, as_prop, is_constructor))
155195
}
156196

157197
fn build_args(
@@ -216,20 +256,22 @@ fn build_arg_definitions(args: &[Arg]) -> (Vec<TokenStream>, bool) {
216256
fn build_arg_parser<'a>(
217257
args: impl Iterator<Item = &'a Arg>,
218258
optional: &Option<String>,
259+
ret: &TokenStream,
219260
) -> Result<TokenStream> {
220261
function::build_arg_parser(
221262
args.filter_map(|arg| match arg {
222263
Arg::Typed(arg) => Some(arg),
223264
_ => None,
224265
}),
225266
optional,
267+
ret,
226268
)
227269
}
228270

229-
fn build_arg_accessors(args: &[Arg]) -> Vec<TokenStream> {
271+
fn build_arg_accessors(args: &[Arg], ret: &TokenStream) -> Vec<TokenStream> {
230272
args.iter()
231273
.filter_map(|arg| match arg {
232-
Arg::Typed(arg) => Some(arg.get_accessor()),
274+
Arg::Typed(arg) => Some(arg.get_accessor(ret)),
233275
_ => None,
234276
})
235277
.collect()
@@ -241,27 +283,27 @@ impl Method {
241283
Ident::new(&self.ident, Span::call_site())
242284
}
243285

286+
pub fn get_arg_definitions(&self) -> impl Iterator<Item = TokenStream> + '_ {
287+
self.args.iter().filter_map(move |arg| match arg {
288+
Arg::Typed(arg) => {
289+
let def = arg.get_arg_definition();
290+
let prelude = self.optional.as_ref().and_then(|opt| {
291+
if opt.eq(&arg.name) {
292+
Some(quote! { .not_required() })
293+
} else {
294+
None
295+
}
296+
});
297+
Some(quote! { #prelude.arg(#def) })
298+
}
299+
_ => None,
300+
})
301+
}
302+
244303
pub fn get_builder(&self, class_path: &Ident) -> TokenStream {
245304
let name = &self.name;
246305
let name_ident = self.get_name_ident();
247-
let args = self
248-
.args
249-
.iter()
250-
.filter_map(|arg| match arg {
251-
Arg::Typed(arg) => {
252-
let def = arg.get_arg_definition();
253-
let prelude = self.optional.as_ref().and_then(|opt| {
254-
if opt.eq(&arg.name) {
255-
Some(quote! { .not_required() })
256-
} else {
257-
None
258-
}
259-
});
260-
Some(quote! { #prelude.arg(#def) })
261-
}
262-
_ => None,
263-
})
264-
.collect::<Vec<_>>();
306+
let args = self.get_arg_definitions();
265307
let output = self.output.as_ref().map(|(ty, nullable)| {
266308
let ty: Type = syn::parse_str(ty).unwrap();
267309

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,34 @@ pub fn generate_registered_class_impl(class: &Class) -> Result<TokenStream> {
9494
.properties
9595
.iter()
9696
.map(|(name, prop)| prop.as_prop_tuple(name));
97+
let constructor = if let Some(constructor) = &class.constructor {
98+
let func = Ident::new(&constructor.ident, Span::call_site());
99+
let args = constructor.get_arg_definitions();
100+
quote! {
101+
Some(::ext_php_rs::php::types::object::ConstructorMeta {
102+
constructor: Self::#func,
103+
build_fn: {
104+
use ext_php_rs::php::function::FunctionBuilder;
105+
fn build_fn(func: FunctionBuilder) -> FunctionBuilder {
106+
func
107+
#(#args)*
108+
}
109+
build_fn
110+
}
111+
})
112+
}
113+
} else {
114+
quote! { None }
115+
};
97116

98117
Ok(quote! {
99118
static #meta: ::ext_php_rs::php::types::object::ClassMetadata<#self_ty> = ::ext_php_rs::php::types::object::ClassMetadata::new();
100119

101120
impl ::ext_php_rs::php::types::object::RegisteredClass for #self_ty {
102121
const CLASS_NAME: &'static str = #class_name;
122+
const CONSTRUCTOR: ::std::option::Option<
123+
::ext_php_rs::php::types::object::ConstructorMeta<Self>
124+
> = #constructor;
103125

104126
fn get_metadata() -> &'static ::ext_php_rs::php::types::object::ClassMetadata<Self> {
105127
&#meta

guide/src/macros/classes.md

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,6 @@ Structs can be exported to PHP as classes with the `#[php_class]` attribute
44
macro. This attribute derives the `RegisteredClass` trait on your struct, as
55
well as registering the class to be registered with the `#[php_module]` macro.
66

7-
The implementation of `RegisteredClass` requires the implementation of `Default`
8-
on the struct. This is because the struct is initialized before the constructor
9-
is called, therefore it must have default values for all properties.
10-
11-
Note that Rust struct properties **are not** PHP properties, so if you want the
12-
user to be able to access these, you must provide getters and/or setters.
13-
Properties are supported internally, however, they are not usable through the
14-
automatic macros. Support for properties is planned.
15-
167
## Options
178

189
The attribute takes some options to modify the output of the class:
@@ -33,21 +24,22 @@ placed underneath the `#[php_class]` attribute.
3324

3425
You may also use the `#[prop]` attribute on a struct field to use the field as a
3526
PHP property. By default, the field will be accessible from PHP publically with
36-
the same name as the field. You can rename the property with options:
27+
the same name as the field. Property types must implement `IntoZval` and
28+
`FromZval`.
29+
30+
You can rename the property with options:
3731

3832
- `rename` - Allows you to rename the property, e.g.
3933
`#[prop(rename = "new_name")]`
4034

4135
## Example
4236

43-
This example creates a PHP class `Human`, adding a PHP property `address` with
44-
an empty string as the default value.
37+
This example creates a PHP class `Human`, adding a PHP property `address`.
4538

4639
```rust
4740
# extern crate ext_php_rs;
4841
# use ext_php_rs::prelude::*;
4942
#[php_class]
50-
#[derive(Default)]
5143
pub struct Human {
5244
name: String,
5345
age: i32,

0 commit comments

Comments
 (0)