use crate::{
boxed::ZBox,
error::Result,
exception::PhpException,
flags::DataType,
types::{ZendObject, Zval},
};
pub trait FromZval<'a>: Sized {
const TYPE: DataType;
fn from_zval(zval: &'a Zval) -> Option<Self>;
}
impl<'a, T> FromZval<'a> for Option<T>
where
T: FromZval<'a>,
{
const TYPE: DataType = T::TYPE;
fn from_zval(zval: &'a Zval) -> Option<Self> {
Some(T::from_zval(zval))
}
}
pub trait FromZvalMut<'a>: Sized {
const TYPE: DataType;
fn from_zval_mut(zval: &'a mut Zval) -> Option<Self>;
}
impl<'a, T> FromZvalMut<'a> for T
where
T: FromZval<'a>,
{
const TYPE: DataType = <T as FromZval>::TYPE;
#[inline]
fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
Self::from_zval(zval)
}
}
pub trait FromZendObject<'a>: Sized {
fn from_zend_object(obj: &'a ZendObject) -> Result<Self>;
}
pub trait FromZendObjectMut<'a>: Sized {
fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result<Self>;
}
impl<'a, T> FromZendObjectMut<'a> for T
where
T: FromZendObject<'a>,
{
#[inline]
fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result<Self> {
Self::from_zend_object(obj)
}
}
pub trait IntoZendObject {
fn into_zend_object(self) -> Result<ZBox<ZendObject>>;
}
pub trait IntoZval: Sized {
const TYPE: DataType;
const NULLABLE: bool;
fn into_zval(self, persistent: bool) -> Result<Zval> {
let mut zval = Zval::new();
self.set_zval(&mut zval, persistent)?;
Ok(zval)
}
fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()>;
}
impl IntoZval for () {
const TYPE: DataType = DataType::Void;
const NULLABLE: bool = true;
#[inline]
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
zv.set_null();
Ok(())
}
}
impl<T> IntoZval for Option<T>
where
T: IntoZval,
{
const TYPE: DataType = T::TYPE;
const NULLABLE: bool = true;
#[inline]
fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
if let Some(val) = self {
val.set_zval(zv, persistent)
} else {
zv.set_null();
Ok(())
}
}
}
impl<T, E> IntoZval for std::result::Result<T, E>
where
T: IntoZval,
E: Into<PhpException>,
{
const TYPE: DataType = T::TYPE;
const NULLABLE: bool = T::NULLABLE;
fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
match self {
Ok(val) => val.set_zval(zv, persistent),
Err(e) => {
let ex: PhpException = e.into();
ex.throw()
}
}
}
}
pub trait IntoZvalDyn {
fn as_zval(&self, persistent: bool) -> Result<Zval>;
fn get_type(&self) -> DataType;
fn stub_value(&self) -> String {
match self.as_zval(false) {
Ok(zval) => zval_to_stub(&zval),
Err(_) => "null".to_string(),
}
}
}
#[must_use]
#[allow(clippy::match_same_arms)]
pub fn zval_to_stub(zval: &Zval) -> String {
use crate::flags::DataType;
match zval.get_type() {
DataType::Null | DataType::Undef => "null".to_string(),
DataType::True => "true".to_string(),
DataType::False => "false".to_string(),
DataType::Long => zval
.long()
.map_or_else(|| "null".to_string(), |v| v.to_string()),
DataType::Double => zval
.double()
.map_or_else(|| "null".to_string(), |v| v.to_string()),
DataType::String => {
if let Some(s) = zval.str() {
let escaped = s
.replace('\\', "\\\\")
.replace('\'', "\\'")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t");
format!("'{escaped}'")
} else {
"null".to_string()
}
}
DataType::Array => {
#[allow(clippy::explicit_iter_loop)]
if let Some(arr) = zval.array() {
let is_sequential = arr.iter().enumerate().all(|(i, (key, _))| {
matches!(key, crate::types::ArrayKey::Long(idx) if i64::try_from(i).is_ok_and(|ii| idx == ii))
});
let mut parts = Vec::new();
for (key, val) in arr.iter() {
let val_str = zval_to_stub(val);
if is_sequential {
parts.push(val_str);
} else {
match key {
crate::types::ArrayKey::Long(idx) => {
parts.push(format!("{idx} => {val_str}"));
}
crate::types::ArrayKey::String(key) => {
let key_escaped = key.replace('\\', "\\\\").replace('\'', "\\'");
parts.push(format!("'{key_escaped}' => {val_str}"));
}
crate::types::ArrayKey::Str(key) => {
let key_escaped = key.replace('\\', "\\\\").replace('\'', "\\'");
parts.push(format!("'{key_escaped}' => {val_str}"));
}
}
}
}
format!("[{}]", parts.join(", "))
} else {
"[]".to_string()
}
}
_ => "null".to_string(),
}
}
impl<T: IntoZval + Clone> IntoZvalDyn for T {
fn as_zval(&self, persistent: bool) -> Result<Zval> {
self.clone().into_zval(persistent)
}
fn get_type(&self) -> DataType {
Self::TYPE
}
}
impl IntoZvalDyn for Zval {
fn as_zval(&self, _persistent: bool) -> Result<Zval> {
Ok(self.shallow_clone())
}
fn get_type(&self) -> DataType {
self.get_type()
}
}