mirror of
https://github.com/danog/ext-php-rs.git
synced 2024-11-30 04:39:04 +01:00
Add ability to add proper object constructor (#83)
This commit is contained in:
parent
c16c5d48cb
commit
125ec3bc94
@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
- Constructors that return `Self` can now be added to classes. [#83]
|
||||
- `Default` is no longer required to be implemented on classes, however, a
|
||||
constructor must be specified if you want to construct the class from PHP.
|
||||
- Constructors can return `Self` or `Result<Self, E>`, where
|
||||
`E: Into<PhpException>`.
|
||||
|
||||
[#83]: https://github.com/davidcole1340/ext-php-rs/pull/83
|
||||
|
||||
## Version 0.5.1
|
||||
|
||||
- `PhpException` no longer requires a lifetime [#80].
|
||||
|
@ -14,6 +14,7 @@ pub struct Class {
|
||||
pub parent: Option<String>,
|
||||
pub interfaces: Vec<String>,
|
||||
pub methods: Vec<crate::method::Method>,
|
||||
pub constructor: Option<crate::method::Method>,
|
||||
pub constants: Vec<crate::constant::Constant>,
|
||||
pub properties: HashMap<String, Property>,
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi
|
||||
let args = build_args(inputs, &attr_args.defaults)?;
|
||||
let optional = find_optional_parameter(args.iter(), attr_args.optional);
|
||||
let arg_definitions = build_arg_definitions(&args);
|
||||
let arg_parser = build_arg_parser(args.iter(), &optional)?;
|
||||
let arg_parser = build_arg_parser(args.iter(), &optional, "e! { return; })?;
|
||||
let arg_accessors = build_arg_accessors(&args);
|
||||
|
||||
let return_type = get_return_type(output)?;
|
||||
@ -157,6 +157,7 @@ pub fn find_optional_parameter<'a>(
|
||||
pub fn build_arg_parser<'a>(
|
||||
args: impl Iterator<Item = &'a Arg>,
|
||||
optional: &Option<String>,
|
||||
ret: &TokenStream,
|
||||
) -> Result<TokenStream> {
|
||||
let mut rest_optional = false;
|
||||
|
||||
@ -194,13 +195,15 @@ pub fn build_arg_parser<'a>(
|
||||
.parse();
|
||||
|
||||
if parser.is_err() {
|
||||
return;
|
||||
#ret
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn build_arg_accessors(args: &[Arg]) -> Vec<TokenStream> {
|
||||
args.iter().map(|arg| arg.get_accessor()).collect()
|
||||
args.iter()
|
||||
.map(|arg| arg.get_accessor("e! { return; }))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_return_type(output_type: &ReturnType) -> Result<Option<(String, bool)>> {
|
||||
@ -286,7 +289,7 @@ impl Arg {
|
||||
}
|
||||
|
||||
/// Returns a [`TokenStream`] containing the line required to retrieve the value from the argument.
|
||||
pub fn get_accessor(&self) -> TokenStream {
|
||||
pub fn get_accessor(&self, ret: &TokenStream) -> TokenStream {
|
||||
let name = &self.name;
|
||||
let name_ident = self.get_name_ident();
|
||||
|
||||
@ -310,7 +313,7 @@ impl Arg {
|
||||
)
|
||||
.throw()
|
||||
.expect(concat!("Failed to throw exception: Invalid value given for argument `", #name, "`."));
|
||||
return;
|
||||
#ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,6 +82,7 @@ pub enum ParsedAttribute {
|
||||
prop_name: Option<String>,
|
||||
ty: PropAttrTy,
|
||||
},
|
||||
Constructor,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, FromMeta)]
|
||||
@ -151,7 +152,14 @@ pub fn parser(args: AttributeArgs, input: ItemImpl) -> Result<TokenStream> {
|
||||
PropAttrTy::Setter => prop.add_setter(ident)?,
|
||||
}
|
||||
}
|
||||
class.methods.push(parsed_method.method);
|
||||
if parsed_method.constructor {
|
||||
if class.constructor.is_some() {
|
||||
bail!("You cannot have two constructors on the same class.");
|
||||
}
|
||||
class.constructor = Some(parsed_method.method);
|
||||
} else {
|
||||
class.methods.push(parsed_method.method);
|
||||
}
|
||||
parsed_method.tokens
|
||||
}
|
||||
item => item.to_token_stream(),
|
||||
@ -239,6 +247,7 @@ pub fn parse_attribute(attr: &Attribute) -> Result<ParsedAttribute> {
|
||||
ty: PropAttrTy::Setter,
|
||||
}
|
||||
}
|
||||
"constructor" => ParsedAttribute::Constructor,
|
||||
attr => bail!("Invalid attribute `#[{}]`.", attr),
|
||||
})
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ pub struct ParsedMethod {
|
||||
pub tokens: TokenStream,
|
||||
pub method: Method,
|
||||
pub property: Option<(String, PropAttrTy)>,
|
||||
pub constructor: bool,
|
||||
}
|
||||
|
||||
impl ParsedMethod {
|
||||
@ -49,11 +50,13 @@ impl ParsedMethod {
|
||||
tokens: TokenStream,
|
||||
method: Method,
|
||||
property: Option<(String, PropAttrTy)>,
|
||||
constructor: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
tokens,
|
||||
method,
|
||||
property,
|
||||
constructor,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -64,6 +67,7 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
|
||||
let mut visibility = Visibility::Public;
|
||||
let mut as_prop = None;
|
||||
let mut identifier = None;
|
||||
let mut is_constructor = false;
|
||||
|
||||
for attr in input.attrs.iter() {
|
||||
match parse_attribute(attr)? {
|
||||
@ -89,6 +93,7 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
|
||||
});
|
||||
as_prop = Some((prop_name, ty))
|
||||
}
|
||||
ParsedAttribute::Constructor => is_constructor = true,
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,6 +107,20 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
|
||||
..
|
||||
} = &sig;
|
||||
|
||||
let name = identifier.unwrap_or_else(|| rename_rule.rename(ident.to_string()));
|
||||
if name == "__construct" {
|
||||
is_constructor = true;
|
||||
}
|
||||
|
||||
if is_constructor && (!matches!(visibility, Visibility::Public) || as_prop.is_some()) {
|
||||
bail!("`#[constructor]` attribute cannot be combined with the visibility or getter/setter attributes.");
|
||||
}
|
||||
|
||||
let bail = if is_constructor {
|
||||
quote! { return ConstructorResult::ArgError; }
|
||||
} else {
|
||||
quote! { return; }
|
||||
};
|
||||
let internal_ident = Ident::new(&format!("_internal_php_{}", ident), Span::call_site());
|
||||
let args = build_args(inputs, &defaults)?;
|
||||
let optional = function::find_optional_parameter(
|
||||
@ -112,34 +131,55 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
|
||||
optional,
|
||||
);
|
||||
let (arg_definitions, is_static) = build_arg_definitions(&args);
|
||||
let arg_parser = build_arg_parser(args.iter(), &optional)?;
|
||||
let arg_accessors = build_arg_accessors(&args);
|
||||
let arg_parser = build_arg_parser(args.iter(), &optional, &bail)?;
|
||||
let arg_accessors = build_arg_accessors(&args, &bail);
|
||||
let this = if is_static {
|
||||
quote! { Self:: }
|
||||
} else {
|
||||
quote! { this. }
|
||||
};
|
||||
|
||||
let func = quote! {
|
||||
#input
|
||||
let func = if is_constructor {
|
||||
quote! {
|
||||
#input
|
||||
|
||||
#[doc(hidden)]
|
||||
pub extern "C" fn #internal_ident(ex: &mut ::ext_php_rs::php::execution_data::ExecutionData, retval: &mut ::ext_php_rs::php::types::zval::Zval) {
|
||||
use ::ext_php_rs::php::types::zval::IntoZval;
|
||||
#[doc(hidden)]
|
||||
pub fn #internal_ident(
|
||||
ex: &mut ::ext_php_rs::php::execution_data::ExecutionData
|
||||
) -> ::ext_php_rs::php::types::object::ConstructorResult<Self> {
|
||||
use ::ext_php_rs::php::types::zval::IntoZval;
|
||||
use ::ext_php_rs::php::types::object::ConstructorResult;
|
||||
|
||||
#(#arg_definitions)*
|
||||
#arg_parser
|
||||
#(#arg_definitions)*
|
||||
#arg_parser
|
||||
|
||||
let result = #this #ident(#(#arg_accessors, )*);
|
||||
Self::#ident(#(#arg_accessors,)*).into()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#input
|
||||
|
||||
if let Err(e) = result.set_zval(retval, false) {
|
||||
let e: ::ext_php_rs::php::exceptions::PhpException = e.into();
|
||||
e.throw().expect("Failed to throw exception");
|
||||
#[doc(hidden)]
|
||||
pub extern "C" fn #internal_ident(
|
||||
ex: &mut ::ext_php_rs::php::execution_data::ExecutionData,
|
||||
retval: &mut ::ext_php_rs::php::types::zval::Zval
|
||||
) {
|
||||
use ::ext_php_rs::php::types::zval::IntoZval;
|
||||
|
||||
#(#arg_definitions)*
|
||||
#arg_parser
|
||||
|
||||
let result = #this #ident(#(#arg_accessors, )*);
|
||||
|
||||
if let Err(e) = result.set_zval(retval, false) {
|
||||
let e: ::ext_php_rs::php::exceptions::PhpException = e.into();
|
||||
e.throw().expect("Failed to throw exception");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let name = identifier.unwrap_or_else(|| rename_rule.rename(ident.to_string()));
|
||||
let method = Method {
|
||||
name,
|
||||
ident: internal_ident.to_string(),
|
||||
@ -151,7 +191,7 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<Par
|
||||
visibility,
|
||||
};
|
||||
|
||||
Ok(ParsedMethod::new(func, method, as_prop))
|
||||
Ok(ParsedMethod::new(func, method, as_prop, is_constructor))
|
||||
}
|
||||
|
||||
fn build_args(
|
||||
@ -216,6 +256,7 @@ fn build_arg_definitions(args: &[Arg]) -> (Vec<TokenStream>, bool) {
|
||||
fn build_arg_parser<'a>(
|
||||
args: impl Iterator<Item = &'a Arg>,
|
||||
optional: &Option<String>,
|
||||
ret: &TokenStream,
|
||||
) -> Result<TokenStream> {
|
||||
function::build_arg_parser(
|
||||
args.filter_map(|arg| match arg {
|
||||
@ -223,13 +264,14 @@ fn build_arg_parser<'a>(
|
||||
_ => None,
|
||||
}),
|
||||
optional,
|
||||
ret,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_arg_accessors(args: &[Arg]) -> Vec<TokenStream> {
|
||||
fn build_arg_accessors(args: &[Arg], ret: &TokenStream) -> Vec<TokenStream> {
|
||||
args.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
Arg::Typed(arg) => Some(arg.get_accessor()),
|
||||
Arg::Typed(arg) => Some(arg.get_accessor(ret)),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
@ -241,27 +283,27 @@ impl Method {
|
||||
Ident::new(&self.ident, Span::call_site())
|
||||
}
|
||||
|
||||
pub fn get_arg_definitions(&self) -> impl Iterator<Item = TokenStream> + '_ {
|
||||
self.args.iter().filter_map(move |arg| match arg {
|
||||
Arg::Typed(arg) => {
|
||||
let def = arg.get_arg_definition();
|
||||
let prelude = self.optional.as_ref().and_then(|opt| {
|
||||
if opt.eq(&arg.name) {
|
||||
Some(quote! { .not_required() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
Some(quote! { #prelude.arg(#def) })
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_builder(&self, class_path: &Ident) -> TokenStream {
|
||||
let name = &self.name;
|
||||
let name_ident = self.get_name_ident();
|
||||
let args = self
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
Arg::Typed(arg) => {
|
||||
let def = arg.get_arg_definition();
|
||||
let prelude = self.optional.as_ref().and_then(|opt| {
|
||||
if opt.eq(&arg.name) {
|
||||
Some(quote! { .not_required() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
Some(quote! { #prelude.arg(#def) })
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let args = self.get_arg_definitions();
|
||||
let output = self.output.as_ref().map(|(ty, nullable)| {
|
||||
let ty: Type = syn::parse_str(ty).unwrap();
|
||||
|
||||
|
@ -94,12 +94,34 @@ pub fn generate_registered_class_impl(class: &Class) -> Result<TokenStream> {
|
||||
.properties
|
||||
.iter()
|
||||
.map(|(name, prop)| prop.as_prop_tuple(name));
|
||||
let constructor = if let Some(constructor) = &class.constructor {
|
||||
let func = Ident::new(&constructor.ident, Span::call_site());
|
||||
let args = constructor.get_arg_definitions();
|
||||
quote! {
|
||||
Some(::ext_php_rs::php::types::object::ConstructorMeta {
|
||||
constructor: Self::#func,
|
||||
build_fn: {
|
||||
use ext_php_rs::php::function::FunctionBuilder;
|
||||
fn build_fn(func: FunctionBuilder) -> FunctionBuilder {
|
||||
func
|
||||
#(#args)*
|
||||
}
|
||||
build_fn
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
static #meta: ::ext_php_rs::php::types::object::ClassMetadata<#self_ty> = ::ext_php_rs::php::types::object::ClassMetadata::new();
|
||||
|
||||
impl ::ext_php_rs::php::types::object::RegisteredClass for #self_ty {
|
||||
const CLASS_NAME: &'static str = #class_name;
|
||||
const CONSTRUCTOR: ::std::option::Option<
|
||||
::ext_php_rs::php::types::object::ConstructorMeta<Self>
|
||||
> = #constructor;
|
||||
|
||||
fn get_metadata() -> &'static ::ext_php_rs::php::types::object::ClassMetadata<Self> {
|
||||
&#meta
|
||||
|
@ -4,15 +4,6 @@ Structs can be exported to PHP as classes with the `#[php_class]` attribute
|
||||
macro. This attribute derives the `RegisteredClass` trait on your struct, as
|
||||
well as registering the class to be registered with the `#[php_module]` macro.
|
||||
|
||||
The implementation of `RegisteredClass` requires the implementation of `Default`
|
||||
on the struct. This is because the struct is initialized before the constructor
|
||||
is called, therefore it must have default values for all properties.
|
||||
|
||||
Note that Rust struct properties **are not** PHP properties, so if you want the
|
||||
user to be able to access these, you must provide getters and/or setters.
|
||||
Properties are supported internally, however, they are not usable through the
|
||||
automatic macros. Support for properties is planned.
|
||||
|
||||
## Options
|
||||
|
||||
The attribute takes some options to modify the output of the class:
|
||||
@ -33,21 +24,22 @@ placed underneath the `#[php_class]` attribute.
|
||||
|
||||
You may also use the `#[prop]` attribute on a struct field to use the field as a
|
||||
PHP property. By default, the field will be accessible from PHP publically with
|
||||
the same name as the field. You can rename the property with options:
|
||||
the same name as the field. Property types must implement `IntoZval` and
|
||||
`FromZval`.
|
||||
|
||||
You can rename the property with options:
|
||||
|
||||
- `rename` - Allows you to rename the property, e.g.
|
||||
`#[prop(rename = "new_name")]`
|
||||
|
||||
## Example
|
||||
|
||||
This example creates a PHP class `Human`, adding a PHP property `address` with
|
||||
an empty string as the default value.
|
||||
This example creates a PHP class `Human`, adding a PHP property `address`.
|
||||
|
||||
```rust
|
||||
# extern crate ext_php_rs;
|
||||
# use ext_php_rs::prelude::*;
|
||||
#[php_class]
|
||||
#[derive(Default)]
|
||||
pub struct Human {
|
||||
name: String,
|
||||
age: i32,
|
||||
|
@ -42,6 +42,22 @@ The rest of the options are passed as separate attributes:
|
||||
The `#[defaults]` and `#[optional]` attributes operate the same as the
|
||||
equivalent function attribute parameters.
|
||||
|
||||
### Constructors
|
||||
|
||||
By default, if a class does not have a constructor, it is not constructable from
|
||||
PHP. It can only be returned from a Rust function to PHP.
|
||||
|
||||
Constructors are Rust methods whick can take any amount of parameters and
|
||||
returns either `Self` or `Result<Self, E>`, where `E: Into<PhpException>`. When
|
||||
the error variant of `Result` is encountered, it is thrown as an exception and
|
||||
the class is not constructed.
|
||||
|
||||
Constructors are designated by either naming the method `__construct` or by
|
||||
annotating a method with the `#[constructor]` attribute. Note that when using
|
||||
the attribute, the function is not exported to PHP like a regular method.
|
||||
|
||||
Constructors cannot use the visibility or rename attributes listed above.
|
||||
|
||||
## Constants
|
||||
|
||||
Constants are defined as regular Rust `impl` constants. Any type that implements
|
||||
@ -89,9 +105,9 @@ constant for the maximum age of a `Human`.
|
||||
impl Human {
|
||||
const MAX_AGE: i32 = 100;
|
||||
|
||||
pub fn __construct(&mut self, name: String, age: i32) {
|
||||
self.name = name;
|
||||
self.age = age;
|
||||
// No `#[constructor]` attribute required here - the name is `__construct`.
|
||||
pub fn __construct(name: String, age: i32) -> Self {
|
||||
Self { name, age, address: String::new() }
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@ -110,8 +126,6 @@ impl Human {
|
||||
}
|
||||
|
||||
pub fn introduce(&self) {
|
||||
use ext_php_rs::php::types::object::RegisteredClass;
|
||||
|
||||
println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address);
|
||||
}
|
||||
|
||||
|
23
src/lib.rs
23
src/lib.rs
@ -256,12 +256,21 @@ pub use ext_php_rs_derive::php_function;
|
||||
/// Methods can take a immutable or a mutable reference to `self`, but cannot consume `self`. They
|
||||
/// can also take no reference to `self` which indicates a static method.
|
||||
///
|
||||
/// ## Constructors
|
||||
///
|
||||
/// You may add *one* constructor to the impl block. This method must be called `__construct` or be
|
||||
/// tagged with the `#[constructor]` attribute, and it will not be exported to PHP like a regular
|
||||
/// method.
|
||||
///
|
||||
/// The constructor method must not take a reference to `self` and must return `Self` or
|
||||
/// [`Result<Self, E>`][`Result`], where `E: Into<PhpException>`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use ext_php_rs::prelude::*;
|
||||
/// #[php_class]
|
||||
/// #[derive(Debug, Default)]
|
||||
/// #[derive(Debug)]
|
||||
/// pub struct Human {
|
||||
/// name: String,
|
||||
/// age: i32,
|
||||
@ -274,9 +283,8 @@ pub use ext_php_rs_derive::php_function;
|
||||
///
|
||||
/// #[optional(age)]
|
||||
/// #[defaults(age = 0)]
|
||||
/// pub fn __construct(&mut self, name: String, age: i32) {
|
||||
/// self.name = name;
|
||||
/// self.age = age;
|
||||
/// pub fn __construct(name: String, age: i32) -> Self {
|
||||
/// Self { name, age }
|
||||
/// }
|
||||
///
|
||||
/// pub fn get_name(&self) -> String {
|
||||
@ -340,9 +348,8 @@ pub use ext_php_rs_derive::php_module;
|
||||
|
||||
/// Annotates a struct that will be exported to PHP as a class.
|
||||
///
|
||||
/// The struct that this attribute is used on must implement [`Default`], as this is used to
|
||||
/// initialize the struct before the constructor is called. You may define a constructor with
|
||||
/// the [`macro@php_impl`] macro which can modify the properties of the struct.
|
||||
/// By default, the class cannot be constructed from PHP. You must add a constructor method in the
|
||||
/// [`macro@php_impl`] impl block to be able to construct the object from PHP.
|
||||
///
|
||||
/// This attribute takes a set of optional arguments:
|
||||
///
|
||||
@ -372,7 +379,6 @@ pub use ext_php_rs_derive::php_module;
|
||||
/// ```
|
||||
/// # use ext_php_rs::prelude::*;
|
||||
/// #[php_class]
|
||||
/// #[derive(Default)]
|
||||
/// pub struct Example {
|
||||
/// x: i32,
|
||||
/// y: String,
|
||||
@ -394,7 +400,6 @@ pub use ext_php_rs_derive::php_module;
|
||||
///
|
||||
/// #[php_class(name = "Redis\\Exception\\RedisException")]
|
||||
/// #[extends(ClassEntry::exception())]
|
||||
/// #[derive(Default)]
|
||||
/// pub struct Example;
|
||||
///
|
||||
/// #[php_function]
|
||||
|
@ -2,7 +2,12 @@
|
||||
|
||||
use crate::{
|
||||
errors::{Error, Result},
|
||||
php::types::object::{ZendClassObject, ZendObject},
|
||||
php::{
|
||||
exceptions::PhpException,
|
||||
execution_data::ExecutionData,
|
||||
function::FunctionBuilder,
|
||||
types::object::{ClassObject, ConstructorMeta, ConstructorResult, ZendObject},
|
||||
},
|
||||
};
|
||||
use std::{alloc::Layout, convert::TryInto, ffi::CString, fmt::Debug};
|
||||
|
||||
@ -269,16 +274,61 @@ impl ClassBuilder {
|
||||
/// Panics if the class name associated with `T` is not the same as the class name specified
|
||||
/// when creating the builder.
|
||||
pub fn object_override<T: RegisteredClass>(mut self) -> Self {
|
||||
unsafe extern "C" fn create_object<T: RegisteredClass>(
|
||||
_: *mut ClassEntry,
|
||||
) -> *mut ZendObject {
|
||||
let ptr = ZendClassObject::<T>::new_ptr(None);
|
||||
(*ptr).get_mut_zend_obj()
|
||||
extern "C" fn create_object<T: RegisteredClass>(_: *mut ClassEntry) -> *mut ZendObject {
|
||||
// SAFETY: After calling this function, PHP will always call the constructor defined below,
|
||||
// which assumes that the object is uninitialized.
|
||||
let obj = unsafe { ClassObject::<T>::new_uninit() };
|
||||
obj.into_inner().get_mut_zend_obj()
|
||||
}
|
||||
|
||||
assert_eq!(self.name.as_str(), T::CLASS_NAME);
|
||||
extern "C" fn constructor<T: RegisteredClass>(ex: &mut ExecutionData, _: &mut Zval) {
|
||||
let ConstructorMeta { constructor, .. } = match T::CONSTRUCTOR {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
PhpException::default("You cannot instantiate this class from PHP.".into())
|
||||
.throw()
|
||||
.expect("Failed to throw exception when constructing class");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let this = match constructor(ex) {
|
||||
ConstructorResult::Ok(this) => this,
|
||||
ConstructorResult::Exception(e) => {
|
||||
e.throw()
|
||||
.expect("Failed to throw exception while constructing class");
|
||||
return;
|
||||
}
|
||||
ConstructorResult::ArgError => return,
|
||||
};
|
||||
let this_obj = match ex.get_object::<T>() {
|
||||
Some(obj) => obj,
|
||||
None => {
|
||||
PhpException::default("Failed to retrieve reference to `this` object.".into())
|
||||
.throw()
|
||||
.expect("Failed to throw exception while constructing class");
|
||||
return;
|
||||
}
|
||||
};
|
||||
this_obj.initialize(this);
|
||||
}
|
||||
|
||||
debug_assert_eq!(
|
||||
self.name.as_str(),
|
||||
T::CLASS_NAME,
|
||||
"Class name in builder does not match class name in `impl RegisteredClass`."
|
||||
);
|
||||
self.object_override = Some(create_object::<T>);
|
||||
self
|
||||
self.method(
|
||||
{
|
||||
let mut func = FunctionBuilder::new("__construct", constructor::<T>);
|
||||
if let Some(ConstructorMeta { build_fn, .. }) = T::CONSTRUCTOR {
|
||||
func = build_fn(func);
|
||||
}
|
||||
func.build().expect("Failed to build constructor function")
|
||||
},
|
||||
MethodFlags::Public,
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds the class, returning a reference to the class entry.
|
||||
|
@ -1,13 +1,10 @@
|
||||
//! Functions for interacting with the execution data passed to PHP functions\
|
||||
//! introduced in Rust.
|
||||
|
||||
use crate::{
|
||||
bindings::{zend_execute_data, ZEND_MM_ALIGNMENT, ZEND_MM_ALIGNMENT_MASK},
|
||||
errors::{Error, Result},
|
||||
};
|
||||
use crate::bindings::{zend_execute_data, ZEND_MM_ALIGNMENT, ZEND_MM_ALIGNMENT_MASK};
|
||||
|
||||
use super::types::{
|
||||
object::{ClassObject, RegisteredClass, ZendClassObject, ZendObject},
|
||||
object::{RegisteredClass, ZendClassObject, ZendObject},
|
||||
zval::Zval,
|
||||
};
|
||||
|
||||
@ -17,25 +14,18 @@ pub type ExecutionData = zend_execute_data;
|
||||
impl ExecutionData {
|
||||
/// Attempts to retrieve a reference to the underlying class object of the Zend object.
|
||||
///
|
||||
/// Returns a [`ClassObject`] if the execution data contained a valid object, otherwise
|
||||
/// returns [`None`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must guarantee that the function is called on an instance of [`ExecutionData`]
|
||||
/// that:
|
||||
///
|
||||
/// 1. Contains an object.
|
||||
/// 2. The object was originally derived from `T`.
|
||||
pub unsafe fn get_object<T: RegisteredClass>(&self) -> Option<ClassObject<'static, T>> {
|
||||
let ptr = ZendClassObject::<T>::from_zend_obj_ptr(self.This.object()?)?;
|
||||
Some(ClassObject::from_zend_class_object(ptr, false))
|
||||
/// Returns a [`ZendClassObject`] if the execution data contained a valid object of type `T`,
|
||||
/// otherwise returns [`None`].
|
||||
pub fn get_object<T: RegisteredClass>(&self) -> Option<&mut ZendClassObject<T>> {
|
||||
// TODO(david): This should be a `&mut self` function but we need to fix arg parser first.
|
||||
ZendClassObject::from_zend_obj_mut(self.get_self()?)
|
||||
}
|
||||
|
||||
/// Attempts to retrieve the 'this' object, which can be used in class methods
|
||||
/// to retrieve the underlying Zend object.
|
||||
pub fn get_self(&self) -> Result<&mut ZendObject> {
|
||||
unsafe { self.This.value.obj.as_mut() }.ok_or(Error::InvalidScope)
|
||||
pub fn get_self(&self) -> Option<&mut ZendObject> {
|
||||
// TODO(david): This should be a `&mut self` function but we need to fix arg parser first.
|
||||
unsafe { self.This.value.obj.as_mut() }
|
||||
}
|
||||
|
||||
/// Translation of macro `ZEND_CALL_ARG(call, n)`
|
||||
|
@ -43,9 +43,7 @@ static CLOSURE_META: ClassMetadata<Closure> = ClassMetadata::new();
|
||||
///
|
||||
/// When the `__invoke` method is called from PHP, the `invoke` method is called on the `dyn PhpClosure`\
|
||||
/// trait object, and from there everything is basically the same as a regular PHP function.
|
||||
pub struct Closure {
|
||||
func: Option<Box<dyn PhpClosure>>,
|
||||
}
|
||||
pub struct Closure(Box<dyn PhpClosure>);
|
||||
|
||||
unsafe impl Send for Closure {}
|
||||
unsafe impl Sync for Closure {}
|
||||
@ -74,9 +72,7 @@ impl Closure {
|
||||
where
|
||||
T: PhpClosure + 'static,
|
||||
{
|
||||
Self {
|
||||
func: Some(Box::new(func) as Box<dyn PhpClosure>),
|
||||
}
|
||||
Self(Box::new(func) as Box<dyn PhpClosure>)
|
||||
}
|
||||
|
||||
/// Wraps a [`FnOnce`] Rust closure into a type which can be returned to PHP. If the closure
|
||||
@ -136,20 +132,11 @@ impl Closure {
|
||||
|
||||
/// External function used by the Zend interpreter to call the closure.
|
||||
extern "C" fn invoke(ex: &mut ExecutionData, ret: &mut Zval) {
|
||||
let mut this = unsafe { ex.get_object::<Self>() }.expect("asdf");
|
||||
let this = ex
|
||||
.get_object::<Self>()
|
||||
.expect("Internal closure function called on non-closure class");
|
||||
|
||||
match this.func.as_mut() {
|
||||
Some(closure) => closure.invoke(ex, ret),
|
||||
None => panic!("You cannot instantiate a `RustClosure` from PHP."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Closure {
|
||||
fn default() -> Self {
|
||||
PhpException::default("You cannot instantiate a default instance of `RustClosure`.".into());
|
||||
|
||||
Self { func: None }
|
||||
this.0.invoke(ex, ret)
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +161,7 @@ impl RegisteredClass for Closure {
|
||||
/// This trait is automatically implemented on functions with up to 8 parameters.
|
||||
pub unsafe trait PhpClosure {
|
||||
/// Invokes the closure.
|
||||
fn invoke(&mut self, ex: &mut ExecutionData, ret: &mut Zval);
|
||||
fn invoke(&mut self, ex: &ExecutionData, ret: &mut Zval);
|
||||
}
|
||||
|
||||
/// Implemented on [`FnOnce`] types which can be used as PHP closures. See [`Closure`].
|
||||
@ -190,7 +177,7 @@ unsafe impl<R> PhpClosure for Box<dyn Fn() -> R>
|
||||
where
|
||||
R: IntoZval,
|
||||
{
|
||||
fn invoke(&mut self, _: &mut ExecutionData, ret: &mut Zval) {
|
||||
fn invoke(&mut self, _: &ExecutionData, ret: &mut Zval) {
|
||||
if let Err(e) = self().set_zval(ret, false) {
|
||||
let _ = PhpException::default(format!("Failed to return closure result to PHP: {}", e))
|
||||
.throw();
|
||||
@ -202,7 +189,7 @@ unsafe impl<R> PhpClosure for Box<dyn FnMut() -> R>
|
||||
where
|
||||
R: IntoZval,
|
||||
{
|
||||
fn invoke(&mut self, _: &mut ExecutionData, ret: &mut Zval) {
|
||||
fn invoke(&mut self, _: &ExecutionData, ret: &mut Zval) {
|
||||
if let Err(e) = self().set_zval(ret, false) {
|
||||
let _ = PhpException::default(format!("Failed to return closure result to PHP: {}", e))
|
||||
.throw();
|
||||
@ -241,7 +228,7 @@ macro_rules! php_closure_impl {
|
||||
|
||||
impl<$($gen),*, Ret> PhpOnceClosure for Box<dyn FnOnce($($gen),*) -> Ret>
|
||||
where
|
||||
$($gen: FromZval<'static> + 'static,)*
|
||||
$(for<'a> $gen: FromZval<'a> + 'static,)*
|
||||
Ret: IntoZval + 'static,
|
||||
{
|
||||
fn into_closure(self) -> Closure {
|
||||
@ -268,10 +255,10 @@ macro_rules! php_closure_impl {
|
||||
($fnty: ident; $($gen: ident),*) => {
|
||||
unsafe impl<$($gen),*, Ret> PhpClosure for Box<dyn $fnty($($gen),*) -> Ret>
|
||||
where
|
||||
$($gen: FromZval<'static>,)*
|
||||
$(for<'a> $gen: FromZval<'a>,)*
|
||||
Ret: IntoZval
|
||||
{
|
||||
fn invoke(&mut self, ex: &mut ExecutionData, ret: &mut Zval) {
|
||||
fn invoke(&mut self, ex: &ExecutionData, ret: &mut Zval) {
|
||||
$(
|
||||
let mut $gen = Arg::new(stringify!($gen), $gen::TYPE);
|
||||
)*
|
||||
|
@ -28,8 +28,10 @@ use crate::{
|
||||
php::{
|
||||
class::ClassEntry,
|
||||
enums::DataType,
|
||||
exceptions::PhpResult,
|
||||
exceptions::{PhpException, PhpResult},
|
||||
execution_data::ExecutionData,
|
||||
flags::ZvalTypeFlags,
|
||||
function::FunctionBuilder,
|
||||
globals::ExecutorGlobals,
|
||||
types::{array::OwnedHashTable, string::ZendString},
|
||||
},
|
||||
@ -279,6 +281,14 @@ impl ZendObject {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromZval<'a> for &'a ZendObject {
|
||||
const TYPE: DataType = DataType::Object(None);
|
||||
|
||||
fn from_zval(zval: &'a Zval) -> Option<Self> {
|
||||
zval.object()
|
||||
}
|
||||
}
|
||||
|
||||
/// `FromZendObject` is implemented by types which can be extracted from a Zend object.
|
||||
///
|
||||
/// Normal usage is through the helper method `ZendObject::extract`:
|
||||
@ -378,162 +388,228 @@ impl<'a, T: RegisteredClass> IntoZval for ClassRef<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClassObject<'a, T: RegisteredClass> {
|
||||
ptr: &'a mut ZendClassObject<T>,
|
||||
free: bool,
|
||||
}
|
||||
/// The owned variant of [`ZendClassObject`].
|
||||
pub struct ClassObject<T>(NonNull<ZendClassObject<T>>);
|
||||
|
||||
impl<T: RegisteredClass> Default for ClassObject<'_, T> {
|
||||
fn default() -> Self {
|
||||
let ptr = unsafe {
|
||||
ZendClassObject::new_ptr(None)
|
||||
.as_mut()
|
||||
.expect("Failed to allocate memory for class object.")
|
||||
};
|
||||
|
||||
Self { ptr, free: true }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RegisteredClass + 'a> ClassObject<'a, T> {
|
||||
/// Creates a class object from a pre-existing Rust object.
|
||||
impl<T: RegisteredClass> ClassObject<T> {
|
||||
/// Creates a new [`ZendObject`] of type `T`, where `T` is a [`RegisteredClass`] in PHP, storing the given
|
||||
/// value `val` inside the object.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `obj` - The object to create a class object for.
|
||||
pub fn new(obj: T) -> Self {
|
||||
let ptr = unsafe {
|
||||
ZendClassObject::new_ptr(Some(obj))
|
||||
.as_mut()
|
||||
.expect("Failed to allocate memory for class object.")
|
||||
};
|
||||
/// * `val` - The value to store inside the object.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if memory was unable to be allocated for the new object.
|
||||
pub fn new(val: T) -> Self {
|
||||
unsafe { Self::internal_new(MaybeUninit::new(val), true) }
|
||||
}
|
||||
|
||||
Self { ptr, free: true }
|
||||
/// Creates a new [`ZendObject`] of type `T`, with an uninitialized internal object.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// As the object is uninitialized, the caller must ensure the following until the internal object is
|
||||
/// initialized:
|
||||
///
|
||||
/// * The [`Drop`] implementation is never called.
|
||||
/// * The [`Clone`] implementation is never called.
|
||||
/// * The [`Debug`] implementation is never called.
|
||||
/// * The object is never dereferenced to `T`.
|
||||
///
|
||||
/// If any of these conditions are not met while not initialized, the corresponding function will panic.
|
||||
/// Converting the object into its inner with the [`into_inner`] function is valid, however.
|
||||
///
|
||||
/// [`into_inner`]: #method.into_inner
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if memory was unable to be allocated for the new object.
|
||||
pub unsafe fn new_uninit() -> Self {
|
||||
Self::internal_new(MaybeUninit::uninit(), false)
|
||||
}
|
||||
|
||||
/// Creates a new [`ZendObject`] of type `T`, storing the given (and potentially uninitialized) `val`
|
||||
/// inside the object.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `val` - Value to store inside the object. See safety section.
|
||||
/// * `init` - Whether the given `val` was initialized.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Providing an initialized variant of [`MaybeUninit<T>`] is safe.
|
||||
///
|
||||
/// Providing an uninitalized variant of [`MaybeUninit<T>`] is unsafe. As the object is uninitialized,
|
||||
/// the caller must ensure the following until the internal object is initialized:
|
||||
///
|
||||
/// * The [`Drop`] implementation is never called.
|
||||
/// * The [`Clone`] implementation is never called.
|
||||
/// * The [`Debug`] implementation is never called.
|
||||
/// * The object is never dereferenced to `T`.
|
||||
///
|
||||
/// If any of these conditions are not met while not initialized, the corresponding function will panic.
|
||||
/// Converting the object into its inner with the [`into_inner`] function is valid, however. You can initialize
|
||||
/// the object with the [`initialize`] function.
|
||||
///
|
||||
/// [`into_inner`]: #method.into_inner
|
||||
/// [`initialize`]: #method.initialize
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if memory was unable to be allocated for the new object.
|
||||
unsafe fn internal_new(val: MaybeUninit<T>, init: bool) -> Self {
|
||||
let size = mem::size_of::<ZendClassObject<T>>();
|
||||
let meta = T::get_metadata();
|
||||
let ce = meta.ce() as *const _ as *mut _;
|
||||
let obj = ext_php_rs_zend_object_alloc(size as _, ce) as *mut ZendClassObject<T>;
|
||||
let obj = obj
|
||||
.as_mut()
|
||||
.expect("Failed to allocate for new Zend object");
|
||||
|
||||
zend_object_std_init(&mut obj.std, ce);
|
||||
object_properties_init(&mut obj.std, ce);
|
||||
|
||||
obj.obj = val;
|
||||
obj.init = init;
|
||||
obj.std.handlers = meta.handlers();
|
||||
Self(obj.into())
|
||||
}
|
||||
|
||||
/// Initializes the class object with the value `val`.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `val` - The value to initialize the object with.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the old value in an [`Option`] if the object had already been initialized, [`None`]
|
||||
/// otherwise.
|
||||
pub fn initialize(&mut self, val: T) -> Option<T> {
|
||||
// Instead of using the `Deref` implementation, we write a wrapper function, as calling `deref()`
|
||||
// on an uninitialized object will panic.
|
||||
|
||||
// SAFETY: All constructors of `NewClassObject` guarantee that it will contain a valid pointer.
|
||||
unsafe { self.0.as_mut() }.initialize(val)
|
||||
}
|
||||
|
||||
/// Converts the [`ClassObject`] into the inner pointer, which is a pointer to [`ZendClassObject`].
|
||||
/// The caller is responsible for freeing the returned pointer when finished.
|
||||
pub fn into_inner(self) -> &'static mut ZendClassObject<T> {
|
||||
let mut this = ManuallyDrop::new(self);
|
||||
// SAFETY: All constructors of `ClassObject` guarantee that it will contain a valid pointer.
|
||||
unsafe { this.0.as_mut() }
|
||||
}
|
||||
|
||||
/// Converts the class object into an owned [`ZendObject`], removing any reference to
|
||||
/// the embedded struct type `T`.
|
||||
pub fn into_owned_object(self) -> OwnedZendObject {
|
||||
let mut this = ManuallyDrop::new(self);
|
||||
OwnedZendObject((&mut this.ptr.std).into())
|
||||
}
|
||||
|
||||
/// Consumes the class object, releasing the internal pointer without releasing the internal object.
|
||||
///
|
||||
/// Used to transfer ownership of the object to PHP.
|
||||
pub(crate) fn into_raw(mut self) -> *mut ZendClassObject<T> {
|
||||
self.free = false;
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Returns an immutable reference to the underlying class object.
|
||||
pub(crate) fn internal(&self) -> &ZendClassObject<T> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying class object.
|
||||
pub(crate) fn internal_mut(&mut self) -> &mut ZendClassObject<T> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Creates a new instance of [`ClassObject`] around a pre-existing class object.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `ptr` - Pointer to the class object.
|
||||
/// * `free` - Whether to release the underlying object which `ptr` points to.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Caller must guarantee that `ptr` points to an aligned, non-null instance of
|
||||
/// [`ZendClassObject`]. Caller must also guarantee that `ptr` will at least live for
|
||||
/// the lifetime `'a` (as long as the resulting object lives).
|
||||
///
|
||||
/// Caller must also guarantee that it is expected to free `ptr` after dropping the
|
||||
/// resulting [`ClassObject`] to prevent use-after-free situations.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when the given `ptr` is null.
|
||||
pub(crate) unsafe fn from_zend_class_object(ptr: *mut ZendClassObject<T>, free: bool) -> Self {
|
||||
Self {
|
||||
ptr: ptr.as_mut().expect("Given pointer was null"),
|
||||
free,
|
||||
}
|
||||
OwnedZendObject((&mut this.deref_mut().std).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RegisteredClass> Drop for ClassObject<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
if self.free {
|
||||
unsafe { ext_php_rs_zend_object_release(&mut (*self.ptr).std) };
|
||||
}
|
||||
impl<T: RegisteredClass + Default> Default for ClassObject<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RegisteredClass> Deref for ClassObject<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// SAFETY: Class object constructor guarantees memory is allocated.
|
||||
unsafe { &*self.ptr.obj.as_ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RegisteredClass> DerefMut for ClassObject<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
// SAFETY: Class object constructor guarantees memory is allocated.
|
||||
unsafe { &mut *self.ptr.obj.as_mut_ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RegisteredClass + Clone> Clone for ClassObject<'_, T> {
|
||||
impl<T: RegisteredClass + Clone> Clone for ClassObject<T> {
|
||||
fn clone(&self) -> Self {
|
||||
// SAFETY: Class object constructor guarantees memory is allocated.
|
||||
let mut new = Self::new(unsafe { &*self.internal().obj.as_ptr() }.clone());
|
||||
unsafe {
|
||||
zend_objects_clone_members(
|
||||
&mut new.internal_mut().std,
|
||||
&self.internal().std as *const _ as *mut _,
|
||||
)
|
||||
if !self.init {
|
||||
panic!("Attempted to clone uninitialized class object");
|
||||
}
|
||||
|
||||
// SAFETY: All constructors of `NewClassObject` guarantee that it will contain a valid pointer.
|
||||
// The constructor also guarantees that the internal `ZendClassObject` pointer will contain a valid,
|
||||
// initialized `obj`, therefore we can dereference both safely.
|
||||
let mut new = Self::new(unsafe { self.0.as_ref().obj.assume_init_ref().clone() });
|
||||
unsafe { zend_objects_clone_members(&mut new.std, &self.std as *const _ as *mut _) };
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RegisteredClass + Debug> Debug for ClassObject<'_, T> {
|
||||
impl<T: Debug> Debug for ClassObject<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.internal().obj.fmt(f)
|
||||
if !self.init {
|
||||
panic!("Attempted to call `Debug` implementation on uninitialized class object");
|
||||
}
|
||||
|
||||
// TODO(david): Implement `Debug` for `ZendClassObject`?
|
||||
self.deref().obj.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RegisteredClass> IntoZval for ClassObject<'_, T> {
|
||||
impl<T: RegisteredClass> IntoZval for ClassObject<T> {
|
||||
const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
|
||||
|
||||
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
|
||||
unsafe { zv.set_object(&mut (*self.into_raw()).std) };
|
||||
|
||||
let obj = self.into_inner();
|
||||
zv.set_object(&mut obj.std);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromZval<'a> for &'a ZendObject {
|
||||
const TYPE: DataType = DataType::Object(None);
|
||||
impl<T> Deref for ClassObject<T> {
|
||||
type Target = ZendClassObject<T>;
|
||||
|
||||
fn from_zval(zval: &'a Zval) -> Option<Self> {
|
||||
zval.object()
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// SAFETY: All constructors of `ClassObject` guarantee that it will contain a valid pointer.
|
||||
let ptr = unsafe { self.0.as_ref() };
|
||||
if !ptr.init {
|
||||
panic!("Attempted to access uninitialized class object");
|
||||
}
|
||||
ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ClassObject<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
// SAFETY: All constructors of `ClassObject` guarantee that it will contain a valid pointer.
|
||||
let ptr = unsafe { self.0.as_mut() };
|
||||
if !ptr.init {
|
||||
panic!("Attempted to access uninitialized class object");
|
||||
}
|
||||
ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for ClassObject<T> {
|
||||
fn drop(&mut self) {
|
||||
if !self.init {
|
||||
panic!("Attempted to drop uninitialized class object");
|
||||
}
|
||||
|
||||
// SAFETY: All constructors guarantee that `self` contains a valid pointer. Further, all constructors
|
||||
// guarantee that the `std` field of `ZendClassObject` will be initialized.
|
||||
unsafe { ext_php_rs_zend_object_release(&mut self.0.as_mut().std) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Object constructor metadata.
|
||||
pub struct ConstructorMeta<T> {
|
||||
/// Constructor function.
|
||||
pub constructor: fn(&mut ExecutionData) -> ConstructorResult<T>,
|
||||
/// Function called to build the constructor function. Usually adds arguments.
|
||||
pub build_fn: fn(FunctionBuilder) -> FunctionBuilder,
|
||||
}
|
||||
|
||||
/// Implemented on Rust types which are exported to PHP. Allows users to get and set PHP properties on
|
||||
/// the object.
|
||||
pub trait RegisteredClass: Default + Sized
|
||||
pub trait RegisteredClass: Sized
|
||||
where
|
||||
Self: 'static,
|
||||
{
|
||||
/// PHP class name of the registered class.
|
||||
const CLASS_NAME: &'static str;
|
||||
|
||||
/// Optional class constructor.
|
||||
const CONSTRUCTOR: Option<ConstructorMeta<Self>> = None;
|
||||
|
||||
/// Returns a reference to the class metadata, which stores the class entry and handlers.
|
||||
///
|
||||
/// This must be statically allocated, and is usually done through the [`macro@php_class`]
|
||||
@ -587,39 +663,41 @@ where
|
||||
/// Returns a hash table containing the properties of the class.
|
||||
///
|
||||
/// The key should be the name of the property and the value should be a reference to the property
|
||||
/// with reference to `self`. The value is a trait object for [`Prop`].
|
||||
///
|
||||
/// [`Prop`]: super::props::Prop
|
||||
/// with reference to `self`. The value is a [`Property`].
|
||||
fn get_properties<'a>() -> HashMap<&'static str, Property<'a, Self>>;
|
||||
}
|
||||
|
||||
/// Representation of a Zend class object in memory. Usually seen through its managed variant
|
||||
/// Representation of a Zend class object in memory. Usually seen through its owned variant
|
||||
/// of [`ClassObject`].
|
||||
#[repr(C)]
|
||||
pub(crate) struct ZendClassObject<T> {
|
||||
pub struct ZendClassObject<T> {
|
||||
obj: MaybeUninit<T>,
|
||||
init: bool,
|
||||
std: zend_object,
|
||||
}
|
||||
|
||||
impl<T: RegisteredClass> ZendClassObject<T> {
|
||||
/// Allocates memory for a new PHP object. The memory is allocated using the Zend memory manager,
|
||||
/// and therefore it is returned as a pointer.
|
||||
pub(crate) fn new_ptr(val: Option<T>) -> *mut Self {
|
||||
let size = mem::size_of::<Self>();
|
||||
let meta = T::get_metadata();
|
||||
let ce = meta.ce() as *const _ as *mut _;
|
||||
unsafe {
|
||||
let obj = (ext_php_rs_zend_object_alloc(size as _, ce) as *mut Self)
|
||||
.as_mut()
|
||||
.expect("Failed to allocate memory for new class object.");
|
||||
/// Initializes the class object with the value `val`.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `val` - The value to initialize the object with.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the old value in an [`Option`] if the object had already been initialized, [`None`]
|
||||
/// otherwise.
|
||||
pub fn initialize(&mut self, val: T) -> Option<T> {
|
||||
let old = Some(mem::replace(&mut self.obj, MaybeUninit::new(val))).and_then(|v| {
|
||||
if self.init {
|
||||
Some(unsafe { v.assume_init() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
self.init = true;
|
||||
|
||||
zend_object_std_init(&mut obj.std, ce);
|
||||
object_properties_init(&mut obj.std, ce);
|
||||
|
||||
obj.obj = MaybeUninit::new(val.unwrap_or_default());
|
||||
obj.std.handlers = meta.handlers();
|
||||
obj
|
||||
}
|
||||
old
|
||||
}
|
||||
|
||||
/// Returns a reference to the [`ZendClassObject`] of a given object `T`. Returns [`None`]
|
||||
@ -634,6 +712,7 @@ impl<T: RegisteredClass> ZendClassObject<T> {
|
||||
/// Caller must guarantee that the given `obj` was created by Zend, which means that it
|
||||
/// is immediately followed by a [`zend_object`].
|
||||
pub(crate) unsafe fn from_obj_ptr(obj: &T) -> Option<&mut Self> {
|
||||
// TODO(david): Remove this function
|
||||
let ptr = (obj as *const T as *mut Self).as_mut()?;
|
||||
|
||||
if ptr.std.is_instance::<T>() {
|
||||
@ -643,16 +722,30 @@ impl<T: RegisteredClass> ZendClassObject<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the [`ZendClassObject`] of a given zend object `obj`. Returns [`None`]
|
||||
/// if the given object is not of the type `T`.
|
||||
/// Returns a mutable reference to the [`ZendClassObject`] of a given zend object `obj`.
|
||||
/// Returns [`None`] if the given object is not of the type `T`.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `obj` - The zend object to get the [`ZendClassObject`] for.
|
||||
pub(crate) fn from_zend_obj_ptr<'a>(obj: *const zend_object) -> Option<&'a mut Self> {
|
||||
let ptr = obj as *const zend_object as *const i8;
|
||||
pub fn from_zend_obj(std: &zend_object) -> Option<&Self> {
|
||||
Some(Self::_from_zend_obj(std)?)
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the [`ZendClassObject`] of a given zend object `obj`.
|
||||
/// Returns [`None`] if the given object is not of the type `T`.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `obj` - The zend object to get the [`ZendClassObject`] for.
|
||||
pub fn from_zend_obj_mut(std: &mut zend_object) -> Option<&mut Self> {
|
||||
Self::_from_zend_obj(std)
|
||||
}
|
||||
|
||||
fn _from_zend_obj(std: &zend_object) -> Option<&mut Self> {
|
||||
let std = std as *const zend_object as *const i8;
|
||||
let ptr = unsafe {
|
||||
let ptr = ptr.offset(0 - Self::std_offset() as isize) as *const Self;
|
||||
let ptr = std.offset(0 - Self::std_offset() as isize) as *const Self;
|
||||
(ptr as *mut Self).as_mut()?
|
||||
};
|
||||
|
||||
@ -664,7 +757,7 @@ impl<T: RegisteredClass> ZendClassObject<T> {
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying Zend object.
|
||||
pub(crate) fn get_mut_zend_obj(&mut self) -> &mut zend_object {
|
||||
pub fn get_mut_zend_obj(&mut self) -> &mut zend_object {
|
||||
&mut self.std
|
||||
}
|
||||
|
||||
@ -687,6 +780,22 @@ impl<T> Drop for ZendClassObject<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for ZendClassObject<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// SAFETY: All constructors guarantee that `obj` is valid.
|
||||
unsafe { self.obj.assume_init_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ZendClassObject<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
// SAFETY: All constructors guarantee that `obj` is valid.
|
||||
unsafe { self.obj.assume_init_mut() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the class entry and handlers for a Rust type which has been exported to PHP.
|
||||
pub struct ClassMetadata<T> {
|
||||
handlers_init: AtomicBool,
|
||||
@ -762,6 +871,34 @@ impl<T: RegisteredClass> ClassMetadata<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Result returned from a constructor of a class.
|
||||
pub enum ConstructorResult<T> {
|
||||
/// Successfully constructed the class, contains the new class object.
|
||||
Ok(T),
|
||||
/// An exception occured while constructing the class.
|
||||
Exception(PhpException),
|
||||
/// Invalid arguments were given to the constructor.
|
||||
ArgError,
|
||||
}
|
||||
|
||||
impl<T, E> From<std::result::Result<T, E>> for ConstructorResult<T>
|
||||
where
|
||||
E: Into<PhpException>,
|
||||
{
|
||||
fn from(result: std::result::Result<T, E>) -> Self {
|
||||
match result {
|
||||
Ok(x) => Self::Ok(x),
|
||||
Err(e) => Self::Exception(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for ConstructorResult<T> {
|
||||
fn from(result: T) -> Self {
|
||||
Self::Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl ZendObjectHandlers {
|
||||
/// Initializes a given set of object handlers by copying the standard object handlers into
|
||||
/// the memory location, as well as setting up the `T` type destructor.
|
||||
@ -785,11 +922,15 @@ impl ZendObjectHandlers {
|
||||
}
|
||||
|
||||
unsafe extern "C" fn free_obj<T: RegisteredClass>(object: *mut zend_object) {
|
||||
let obj = ZendClassObject::<T>::from_zend_obj_ptr(object)
|
||||
let obj = object
|
||||
.as_mut()
|
||||
.and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
|
||||
.expect("Invalid object pointer given for `free_obj`");
|
||||
|
||||
// Manually drop the object as it is wrapped with `MaybeUninit`.
|
||||
ptr::drop_in_place(obj.obj.as_mut_ptr());
|
||||
if obj.init {
|
||||
ptr::drop_in_place(obj.obj.as_mut_ptr());
|
||||
}
|
||||
|
||||
zend_object_std_dtor(object)
|
||||
}
|
||||
@ -810,8 +951,8 @@ impl ZendObjectHandlers {
|
||||
rv: *mut Zval,
|
||||
) -> PhpResult<*mut Zval> {
|
||||
let obj = object
|
||||
.as_ref()
|
||||
.and_then(|obj| ZendClassObject::<T>::from_zend_obj_ptr(obj))
|
||||
.as_mut()
|
||||
.and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
|
||||
.ok_or("Invalid object pointer given")?;
|
||||
let prop_name = member
|
||||
.as_ref()
|
||||
@ -857,8 +998,8 @@ impl ZendObjectHandlers {
|
||||
cache_slot: *mut *mut c_void,
|
||||
) -> PhpResult<*mut Zval> {
|
||||
let obj = object
|
||||
.as_ref()
|
||||
.and_then(|obj| ZendClassObject::<T>::from_zend_obj_ptr(obj))
|
||||
.as_mut()
|
||||
.and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
|
||||
.ok_or("Invalid object pointer given")?;
|
||||
let prop_name = member
|
||||
.as_ref()
|
||||
@ -895,8 +1036,8 @@ impl ZendObjectHandlers {
|
||||
props: &mut HashTable,
|
||||
) -> PhpResult {
|
||||
let obj = object
|
||||
.as_ref()
|
||||
.and_then(|obj| ZendClassObject::<T>::from_zend_obj_ptr(obj))
|
||||
.as_mut()
|
||||
.and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
|
||||
.ok_or("Invalid object pointer given")?;
|
||||
let self_ = obj.obj.assume_init_mut();
|
||||
let struct_props = T::get_properties();
|
||||
@ -940,8 +1081,8 @@ impl ZendObjectHandlers {
|
||||
cache_slot: *mut *mut c_void,
|
||||
) -> PhpResult<c_int> {
|
||||
let obj = object
|
||||
.as_ref()
|
||||
.and_then(|obj| ZendClassObject::<T>::from_zend_obj_ptr(obj))
|
||||
.as_mut()
|
||||
.and_then(|obj| ZendClassObject::<T>::from_zend_obj_mut(obj))
|
||||
.ok_or("Invalid object pointer given")?;
|
||||
let prop_name = member
|
||||
.as_ref()
|
||||
@ -1005,7 +1146,7 @@ impl ZendObjectHandlers {
|
||||
impl<'a, T: RegisteredClass> FromZendObject<'a> for &'a T {
|
||||
fn from_zend_object(obj: &'a ZendObject) -> Result<Self> {
|
||||
// TODO(david): Error is kinda wrong, should have something like `WrongObject`
|
||||
let cobj = ZendClassObject::<T>::from_zend_obj_ptr(obj).ok_or(Error::InvalidPointer)?;
|
||||
let cobj = ZendClassObject::<T>::from_zend_obj(obj).ok_or(Error::InvalidPointer)?;
|
||||
Ok(unsafe { cobj.obj.assume_init_ref() })
|
||||
}
|
||||
}
|
||||
@ -1018,21 +1159,22 @@ impl<'a, T: RegisteredClass> FromZval<'a> for &'a T {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RegisteredClass> FromZendObject<'a> for &'a mut T {
|
||||
fn from_zend_object(obj: &'a ZendObject) -> Result<Self> {
|
||||
// TODO(david): Error is kinda wrong, should have something like `WrongObject`
|
||||
let cobj = ZendClassObject::<T>::from_zend_obj_ptr(obj).ok_or(Error::InvalidPointer)?;
|
||||
Ok(unsafe { cobj.obj.assume_init_mut() })
|
||||
}
|
||||
}
|
||||
// TODO(david): Need something like `FromZendObjectMut` and `FromZvalMut`
|
||||
// impl<'a, T: RegisteredClass> FromZendObject<'a> for &'a mut T {
|
||||
// fn from_zend_object(obj: &'a ZendObject) -> Result<Self> {
|
||||
// // TODO(david): Error is kinda wrong, should have something like `WrongObject`
|
||||
// let cobj = ZendClassObject::<T>::from_zend_obj_mut(obj).ok_or(Error::InvalidPointer)?;
|
||||
// Ok(unsafe { cobj.obj.assume_init_mut() })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl<'a, T: RegisteredClass> FromZval<'a> for &'a mut T {
|
||||
// const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
|
||||
|
||||
impl<'a, T: RegisteredClass> FromZval<'a> for &'a mut T {
|
||||
const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
|
||||
|
||||
fn from_zval(zval: &'a Zval) -> Option<Self> {
|
||||
Self::from_zend_object(zval.object()?).ok()
|
||||
}
|
||||
}
|
||||
// fn from_zval(zval: &'a Zval) -> Option<Self> {
|
||||
// Self::from_zend_object(zval.object()?).ok()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<T: RegisteredClass> IntoZendObject for T {
|
||||
fn into_zend_object(self) -> Result<OwnedZendObject> {
|
||||
|
@ -172,7 +172,7 @@ impl Zval {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the zval if it is an object.
|
||||
/// Returns a mutable reference to the object contained in the [`Zval`], if any.
|
||||
pub fn object_mut(&mut self) -> Option<&mut ZendObject> {
|
||||
if self.is_object() {
|
||||
unsafe { self.value.obj.as_mut() }
|
||||
|
Loading…
Reference in New Issue
Block a user