use std::{convert::TryInto, fmt::Debug, ops::DerefMut};
use crate::{
boxed::{ZBox, ZBoxable},
class::RegisteredClass,
convert::{FromZendObject, FromZval, FromZvalMut, IntoZval},
error::{Error, Result},
ffi::{
ext_php_rs_zend_object_release, zend_call_known_function, zend_object, zend_objects_new,
HashTable, ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET,
},
flags::DataType,
rc::PhpRc,
types::{ZendClassObject, ZendStr, Zval},
zend::{ce, ClassEntry, ExecutorGlobals, ZendObjectHandlers},
};
pub type ZendObject = zend_object;
impl ZendObject {
pub fn new(ce: &ClassEntry) -> ZBox<Self> {
unsafe {
let ptr = zend_objects_new(ce as *const _ as *mut _);
ZBox::from_raw(
ptr.as_mut()
.expect("Failed to allocate memory for Zend object"),
)
}
}
pub fn new_stdclass() -> ZBox<Self> {
Self::new(ce::stdclass())
}
pub fn from_class_object<T: RegisteredClass>(obj: ZBox<ZendClassObject<T>>) -> ZBox<Self> {
let this = obj.into_raw();
unsafe { ZBox::from_raw(this.get_mut_zend_obj()) }
}
pub fn get_class_name(&self) -> Result<String> {
unsafe {
self.handlers()?
.get_class_name
.and_then(|f| f(self).as_ref())
.ok_or(Error::InvalidScope)
.and_then(|s| s.try_into())
}
}
pub fn is_instance<T: RegisteredClass>(&self) -> bool {
(self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _))
}
pub fn get_property<'a, T>(&'a self, name: &str) -> Result<T>
where
T: FromZval<'a>,
{
if !self.has_property(name, PropertyQuery::Exists)? {
return Err(Error::InvalidProperty);
}
let mut name = ZendStr::new(name, false)?;
let mut rv = Zval::new();
let zv = unsafe {
self.handlers()?.read_property.ok_or(Error::InvalidScope)?(
self.mut_ptr(),
name.deref_mut(),
1,
std::ptr::null_mut(),
&mut rv,
)
.as_ref()
}
.ok_or(Error::InvalidScope)?;
T::from_zval(zv).ok_or_else(|| Error::ZvalConversion(zv.get_type()))
}
pub fn set_property(&mut self, name: &str, value: impl IntoZval) -> Result<()> {
let mut name = ZendStr::new(name, false)?;
let mut value = value.into_zval(false)?;
unsafe {
self.handlers()?.write_property.ok_or(Error::InvalidScope)?(
self,
name.deref_mut(),
&mut value,
std::ptr::null_mut(),
)
.as_ref()
}
.ok_or(Error::InvalidScope)?;
Ok(())
}
pub fn has_property(&self, name: &str, query: PropertyQuery) -> Result<bool> {
let mut name = ZendStr::new(name, false)?;
Ok(unsafe {
self.handlers()?.has_property.ok_or(Error::InvalidScope)?(
self.mut_ptr(),
name.deref_mut(),
query as _,
std::ptr::null_mut(),
)
} > 0)
}
pub fn get_properties(&self) -> Result<&HashTable> {
unsafe {
self.handlers()?
.get_properties
.and_then(|props| props(self.mut_ptr()).as_ref())
.ok_or(Error::InvalidScope)
}
}
pub fn extract<'a, T>(&'a self) -> Result<T>
where
T: FromZendObject<'a>,
{
T::from_zend_object(self)
}
#[inline]
unsafe fn handlers(&self) -> Result<&ZendObjectHandlers> {
self.handlers.as_ref().ok_or(Error::InvalidScope)
}
#[inline]
fn mut_ptr(&self) -> *mut Self {
(self as *const Self) as *mut Self
}
}
unsafe impl ZBoxable for ZendObject {
fn free(&mut self) {
unsafe { ext_php_rs_zend_object_release(self) }
}
}
impl Debug for ZendObject {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut dbg = f.debug_struct(
self.get_class_name()
.unwrap_or_else(|_| "ZendObject".to_string())
.as_str(),
);
if let Ok(props) = self.get_properties() {
for (id, key, val) in props.iter() {
dbg.field(key.unwrap_or_else(|| id.to_string()).as_str(), val);
}
}
dbg.finish()
}
}
impl<'a> FromZval<'a> for &'a ZendObject {
const TYPE: DataType = DataType::Object(None);
fn from_zval(zval: &'a Zval) -> Option<Self> {
zval.object()
}
}
impl<'a> FromZvalMut<'a> for &'a mut ZendObject {
const TYPE: DataType = DataType::Object(None);
fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
zval.object_mut()
}
}
impl IntoZval for ZBox<ZendObject> {
const TYPE: DataType = DataType::Object(None);
#[inline]
fn set_zval(mut self, zv: &mut Zval, _: bool) -> Result<()> {
self.dec_count();
zv.set_object(self.into_raw());
Ok(())
}
}
impl<'a> IntoZval for &'a mut ZendObject {
const TYPE: DataType = DataType::Object(None);
#[inline]
fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
zv.set_object(self);
Ok(())
}
}
impl FromZendObject<'_> for String {
fn from_zend_object(obj: &ZendObject) -> Result<Self> {
let mut ret = Zval::new();
unsafe {
zend_call_known_function(
(*obj.ce).__tostring,
obj as *const _ as *mut _,
obj.ce,
&mut ret,
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
);
}
if let Some(err) = ExecutorGlobals::take_exception() {
let class_name = obj.get_class_name();
panic!(
"Uncaught exception during call to {}::__toString(): {:?}",
class_name.expect("unable to determine class name"),
err
);
} else if let Some(output) = ret.extract() {
Ok(output)
} else {
let class_name = obj.get_class_name();
panic!(
"{}::__toString() must return a string",
class_name.expect("unable to determine class name"),
);
}
}
}
impl<T: RegisteredClass> From<ZBox<ZendClassObject<T>>> for ZBox<ZendObject> {
#[inline]
fn from(obj: ZBox<ZendClassObject<T>>) -> Self {
ZendObject::from_class_object(obj)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u32)]
pub enum PropertyQuery {
Isset = ZEND_PROPERTY_ISSET,
NotEmpty = ZEND_ISEMPTY,
Exists = ZEND_PROPERTY_EXISTS,
}