Skip to content

Commit 0f2cabb

Browse files
authored
Add Display impl for ZendObject (#74)
* Add support to capture execptions from PHP * Add extract function to Zval * Add FromZendObject trait for casting objects to other types
1 parent a64d889 commit 0f2cabb

File tree

4 files changed

+104
-5
lines changed

4 files changed

+104
-5
lines changed

build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ const ALLOWED_BINDINGS: &[&str] = &[
176176
"std_object_handlers",
177177
"zend_array_destroy",
178178
"zend_array_dup",
179+
"zend_call_known_function",
179180
"zend_ce_argument_count_error",
180181
"zend_ce_arithmetic_error",
181182
"zend_ce_compile_error",

src/php/globals.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::bindings::{_zend_executor_globals, ext_php_rs_executor_globals};
44

5-
use super::types::array::HashTable;
5+
use super::types::{array::HashTable, object::ZendObject};
66

77
/// Stores global variables used in the PHP executor.
88
pub type ExecutorGlobals = _zend_executor_globals;
@@ -16,8 +16,32 @@ impl ExecutorGlobals {
1616
.expect("Static executor globals were invalid")
1717
}
1818

19+
fn get_mut() -> &'static mut Self {
20+
// SAFETY: PHP executor globals are statically declared therefore should never
21+
// return an invalid pointer.
22+
// TODO: Should this be syncronized?
23+
unsafe { ext_php_rs_executor_globals().as_mut() }
24+
.expect("Static executor globals were invalid")
25+
}
26+
1927
/// Attempts to retrieve the global class hash table.
2028
pub fn class_table(&self) -> Option<&HashTable> {
2129
unsafe { self.class_table.as_ref() }
2230
}
31+
32+
/// Attempts to extract the last PHP exception captured by the interpreter.
33+
///
34+
/// Note that the caller is responsible for freeing the memory here or it'll leak.
35+
pub fn take_exception() -> Option<*mut ZendObject> {
36+
let globals = Self::get_mut();
37+
38+
let mut exception_ptr = std::ptr::null_mut();
39+
std::mem::swap(&mut exception_ptr, &mut globals.exception);
40+
41+
if !exception_ptr.is_null() {
42+
Some(exception_ptr)
43+
} else {
44+
None
45+
}
46+
}
2347
}

src/php/types/object.rs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@ use std::{
1717
use crate::{
1818
bindings::{
1919
ext_php_rs_zend_object_alloc, ext_php_rs_zend_object_release, object_properties_init,
20-
std_object_handlers, zend_is_true, zend_object, zend_object_handlers, zend_object_std_dtor,
21-
zend_object_std_init, zend_objects_clone_members, zend_std_get_properties,
22-
zend_std_has_property, zend_std_read_property, zend_std_write_property, zend_string,
23-
HashTable, ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET,
20+
std_object_handlers, zend_call_known_function, zend_is_true, zend_object,
21+
zend_object_handlers, zend_object_std_dtor, zend_object_std_init,
22+
zend_objects_clone_members, zend_std_get_properties, zend_std_has_property,
23+
zend_std_read_property, zend_std_write_property, zend_string, HashTable, ZEND_ISEMPTY,
24+
ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET,
2425
},
2526
errors::{Error, Result},
2627
php::{
2728
class::ClassEntry,
2829
enums::DataType,
2930
exceptions::PhpResult,
3031
flags::ZvalTypeFlags,
32+
globals::ExecutorGlobals,
3133
types::{array::OwnedHashTable, string::ZendString},
3234
},
3335
};
@@ -166,6 +168,68 @@ impl ZendObject {
166168
fn mut_ptr(&self) -> *mut Self {
167169
(self as *const Self) as *mut Self
168170
}
171+
172+
/// Extracts some type from a Zend object.
173+
///
174+
/// This is a wrapper function around `FromZendObject::extract()`.
175+
pub fn extract<'a, T>(&'a self) -> Result<T>
176+
where
177+
T: FromZendObject<'a>,
178+
{
179+
T::from_zend_object(self)
180+
}
181+
}
182+
183+
/// `FromZendObject` is implemented by types which can be extracted from a Zend object.
184+
///
185+
/// Normal usage is through the helper method `ZendObject::extract`:
186+
///
187+
/// ```rust,ignore
188+
/// let obj: ZendObject = ...;
189+
/// let repr: String = obj.extract();
190+
/// let props: HashMap = obj.extract();
191+
/// ```
192+
///
193+
/// Should be functionally equivalent to casting an object to another compatable type.
194+
pub trait FromZendObject<'a>: Sized {
195+
/// Extracts `Self` from the source `ZendObject`.
196+
fn from_zend_object(obj: &'a ZendObject) -> Result<Self>;
197+
}
198+
199+
impl FromZendObject<'_> for String {
200+
fn from_zend_object(obj: &ZendObject) -> Result<Self> {
201+
let mut ret = Zval::new();
202+
unsafe {
203+
zend_call_known_function(
204+
(*obj.ce).__tostring,
205+
obj as *const _ as *mut _,
206+
obj.ce,
207+
&mut ret,
208+
0,
209+
std::ptr::null_mut(),
210+
std::ptr::null_mut(),
211+
);
212+
}
213+
214+
if let Some(err) = ExecutorGlobals::take_exception() {
215+
// TODO: become an error
216+
let class_name = obj.get_class_name();
217+
panic!(
218+
"Uncaught exception during call to {}::__toString(): {:?}",
219+
class_name.expect("unable to determine class name"),
220+
err
221+
);
222+
} else if let Some(output) = ret.extract() {
223+
Ok(output)
224+
} else {
225+
// TODO: become an error
226+
let class_name = obj.get_class_name();
227+
panic!(
228+
"{}::__toString() must return a string",
229+
class_name.expect("unable to determine class name"),
230+
);
231+
}
232+
}
169233
}
170234

171235
impl Debug for ZendObject {

src/php/types/zval.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,16 @@ impl Zval {
461461
unsafe { zval_ptr_dtor(self) };
462462
self.u1.type_info = ty.bits();
463463
}
464+
465+
/// Extracts some type from a `Zval`.
466+
///
467+
/// This is a wrapper function around `TryFrom`.
468+
pub fn extract<'a, T>(&'a self) -> Option<T>
469+
where
470+
T: FromZval<'a>,
471+
{
472+
FromZval::from_zval(self)
473+
}
464474
}
465475

466476
impl Debug for Zval {

0 commit comments

Comments
 (0)