use std::{convert::TryFrom, ops::Deref};
use crate::{
convert::{FromZval, IntoZvalDyn},
error::{Error, Result},
ffi::_call_user_function_impl,
flags::DataType,
zend::ExecutorGlobals,
};
use super::Zval;
#[derive(Debug)]
pub struct ZendCallable<'a>(OwnedZval<'a>);
impl<'a> ZendCallable<'a> {
pub fn new(callable: &'a Zval) -> Result<Self> {
if callable.is_callable() {
Ok(Self(OwnedZval::Reference(callable)))
} else {
Err(Error::Callable)
}
}
pub fn new_owned(callable: Zval) -> Result<Self> {
if callable.is_callable() {
Ok(Self(OwnedZval::Owned(callable)))
} else {
Err(Error::Callable)
}
}
pub fn try_from_name(name: &str) -> Result<Self> {
let mut callable = Zval::new();
callable.set_string(name, false)?;
Self::new_owned(callable)
}
#[inline(always)]
pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
if !self.0.is_callable() {
return Err(Error::Callable);
}
let mut retval = Zval::new();
let len = params.len();
let params = params
.into_iter()
.map(|val| val.as_zval(false))
.collect::<Result<Vec<_>>>()?;
let packed = params.into_boxed_slice();
let result = unsafe {
_call_user_function_impl(
std::ptr::null_mut(),
self.0.as_ref() as *const crate::ffi::_zval_struct as *mut crate::ffi::_zval_struct,
&mut retval,
len as _,
packed.as_ptr() as *mut _,
std::ptr::null_mut(),
)
};
if result < 0 {
Err(Error::Callable)
} else if let Some(e) = ExecutorGlobals::take_exception() {
Err(Error::Exception(e))
} else {
Ok(retval)
}
}
}
impl<'a> FromZval<'a> for ZendCallable<'a> {
const TYPE: DataType = DataType::Callable;
fn from_zval(zval: &'a Zval) -> Option<Self> {
ZendCallable::new(zval).ok()
}
}
impl<'a> TryFrom<Zval> for ZendCallable<'a> {
type Error = Error;
fn try_from(value: Zval) -> Result<Self> {
ZendCallable::new_owned(value)
}
}
#[derive(Debug)]
enum OwnedZval<'a> {
Reference(&'a Zval),
Owned(Zval),
}
impl<'a> OwnedZval<'a> {
fn as_ref(&self) -> &Zval {
match self {
OwnedZval::Reference(zv) => zv,
OwnedZval::Owned(zv) => zv,
}
}
}
impl<'a> Deref for OwnedZval<'a> {
type Target = Zval;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}