use crate::{
args::{Arg, ArgParser},
builders::{ClassBuilder, FunctionBuilder},
class::{ClassEntryInfo, ClassMetadata, RegisteredClass},
convert::{FromZval, IntoZval},
describe::DocComments,
exception::PhpException,
flags::{DataType, MethodFlags},
types::Zval,
zend::ExecuteData,
zend_fastcall,
};
static CLOSURE_META: ClassMetadata<Closure> = ClassMetadata::new(&[]);
pub struct Closure(Box<dyn PhpClosure>);
unsafe impl Send for Closure {}
unsafe impl Sync for Closure {}
impl Closure {
pub fn wrap<T>(func: T) -> Self
where
T: PhpClosure + 'static,
{
Self(Box::new(func) as Box<dyn PhpClosure>)
}
pub fn wrap_once<T>(func: T) -> Self
where
T: PhpOnceClosure + 'static,
{
func.into_closure()
}
pub fn build() {
if CLOSURE_META.has_ce() {
return;
}
ClassBuilder::new("RustClosure")
.method(
FunctionBuilder::new("__invoke", Self::invoke)
.not_required()
.arg(Arg::new("args", DataType::Mixed).is_variadic())
.returns(DataType::Mixed, false, true),
MethodFlags::Public,
)
.object_override::<Self>()
.registration(|ce| CLOSURE_META.set_ce(ce))
.register()
.expect("Failed to build `RustClosure` PHP class.");
}
zend_fastcall! {
extern "C" fn invoke(ex: &mut ExecuteData, ret: &mut Zval) {
let (parser, this) = ex.parser_method::<Self>();
let this = this.expect("Internal closure function called on non-closure class");
this.0.invoke(parser, ret);
}
}
}
impl RegisteredClass for Closure {
const CLASS_NAME: &'static str = "RustClosure";
const BUILDER_MODIFIER: Option<fn(ClassBuilder) -> ClassBuilder> = None;
const EXTENDS: Option<ClassEntryInfo> = None;
const IMPLEMENTS: &'static [ClassEntryInfo] = &[];
fn get_metadata() -> &'static ClassMetadata<Self> {
&CLOSURE_META
}
fn method_builders() -> Vec<(FunctionBuilder<'static>, MethodFlags)> {
unimplemented!()
}
fn constructor() -> Option<crate::class::ConstructorMeta<Self>> {
None
}
fn constants() -> &'static [(
&'static str,
&'static dyn crate::convert::IntoZvalDyn,
DocComments,
)] {
unimplemented!()
}
}
class_derives!(Closure);
#[allow(clippy::missing_safety_doc)]
pub unsafe trait PhpClosure {
fn invoke<'a>(&'a mut self, parser: ArgParser<'a, '_>, ret: &mut Zval);
}
pub trait PhpOnceClosure {
fn into_closure(self) -> Closure;
}
unsafe impl<R> PhpClosure for Box<dyn Fn() -> R>
where
R: IntoZval,
{
fn invoke(&mut self, _: ArgParser, 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();
}
}
}
unsafe impl<R> PhpClosure for Box<dyn FnMut() -> R>
where
R: IntoZval,
{
fn invoke(&mut self, _: ArgParser, 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();
}
}
}
impl<R> PhpOnceClosure for Box<dyn FnOnce() -> R>
where
R: IntoZval + 'static,
{
fn into_closure(self) -> Closure {
let mut this = Some(self);
Closure::wrap(Box::new(move || {
let Some(this) = this.take() else {
let _ = PhpException::default(
"Attempted to call `FnOnce` closure more than once.".into(),
)
.throw();
return Option::<R>::None;
};
Some(this())
}) as Box<dyn FnMut() -> Option<R>>)
}
}
macro_rules! php_closure_impl {
($($gen: ident),*) => {
php_closure_impl!(Fn; $($gen),*);
php_closure_impl!(FnMut; $($gen),*);
impl<$($gen),*, Ret> PhpOnceClosure for Box<dyn FnOnce($($gen),*) -> Ret>
where
$(for<'a> $gen: FromZval<'a> + 'static,)*
Ret: IntoZval + 'static,
{
fn into_closure(self) -> Closure {
let mut this = Some(self);
Closure::wrap(Box::new(move |$($gen),*| {
let Some(this) = this.take() else {
let _ = PhpException::default(
"Attempted to call `FnOnce` closure more than once.".into(),
)
.throw();
return Option::<Ret>::None;
};
Some(this($($gen),*))
}) as Box<dyn FnMut($($gen),*) -> Option<Ret>>)
}
}
};
($fnty: ident; $($gen: ident),*) => {
unsafe impl<$($gen),*, Ret> PhpClosure for Box<dyn $fnty($($gen),*) -> Ret>
where
$(for<'a> $gen: FromZval<'a>,)*
Ret: IntoZval
{
fn invoke(&mut self, parser: ArgParser, ret: &mut Zval) {
$(
let mut $gen = Arg::new(stringify!($gen), $gen::TYPE);
)*
let parser = parser
$(.arg(&mut $gen))*
.parse();
if parser.is_err() {
return;
}
let result = self(
$(
match $gen.consume() {
Ok(val) => val,
_ => {
let _ = PhpException::default(concat!("Invalid parameter type for `", stringify!($gen), "`.").into()).throw();
return;
}
}
),*
);
if let Err(e) = result.set_zval(ret, false) {
let _ = PhpException::default(format!("Failed to return closure result to PHP: {}", e)).throw();
}
}
}
};
}
php_closure_impl!(A);
php_closure_impl!(A, B);
php_closure_impl!(A, B, C);
php_closure_impl!(A, B, C, D);
php_closure_impl!(A, B, C, D, E);
php_closure_impl!(A, B, C, D, E, F);
php_closure_impl!(A, B, C, D, E, F, G);
php_closure_impl!(A, B, C, D, E, F, G, H);