use std::collections::HashMap;
use crate::php::{
args::Arg, class::ClassBuilder, enums::DataType, exceptions::PhpException,
execution_data::ExecutionData, flags::MethodFlags, function::FunctionBuilder,
types::object::ClassMetadata,
};
use super::{
object::RegisteredClass,
props::Property,
zval::{FromZval, IntoZval, Zval},
};
static CLOSURE_META: ClassMetadata<Closure> = ClassMetadata::new();
pub struct Closure {
func: Option<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 {
func: Some(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() {
panic!("Closure has already been built.");
}
let ce = ClassBuilder::new("RustClosure")
.method(
FunctionBuilder::new("__invoke", Self::invoke)
.not_required()
.arg(Arg::new("args", DataType::Mixed).is_variadic())
.returns(DataType::Mixed, false, true)
.build()
.expect("Failed to build `RustClosure` PHP class."),
MethodFlags::Public,
)
.object_override::<Self>()
.build()
.expect("Failed to build `RustClosure` PHP class.");
CLOSURE_META.set_ce(ce);
}
extern "C" fn invoke(ex: &mut ExecutionData, ret: &mut Zval) {
let mut this = unsafe { ex.get_object::<Self>() }.expect("asdf");
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 }
}
}
impl RegisteredClass for Closure {
const CLASS_NAME: &'static str = "RustClosure";
fn get_metadata() -> &'static super::object::ClassMetadata<Self> {
&CLOSURE_META
}
fn get_properties<'a>() -> HashMap<&'static str, Property<'a, Self>> {
HashMap::new()
}
}
pub unsafe trait PhpClosure {
fn invoke(&mut self, ex: &mut ExecutionData, 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, _: &mut 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();
}
}
}
unsafe impl<R> PhpClosure for Box<dyn FnMut() -> R>
where
R: IntoZval,
{
fn invoke(&mut self, _: &mut 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();
}
}
}
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 this = match this.take() {
Some(this) => this,
None => {
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
$($gen: FromZval<'static> + 'static,)*
Ret: IntoZval + 'static,
{
fn into_closure(self) -> Closure {
let mut this = Some(self);
Closure::wrap(Box::new(move |$($gen),*| {
let this = match this.take() {
Some(this) => this,
None => {
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
$($gen: FromZval<'static>,)*
Ret: IntoZval
{
fn invoke(&mut self, ex: &mut ExecutionData, ret: &mut Zval) {
$(
let mut $gen = Arg::new(stringify!($gen), $gen::TYPE);
)*
parse_args!(ex, $($gen),*);
let result = self(
$(
match $gen.val() {
Some(val) => val,
None => {
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);