mirror of
https://github.com/danog/ext-php-rs.git
synced 2024-12-02 09:37:51 +01:00
Merge pull request #268 from joelwurtz/feat/iterator
feat(iterator): add helper for zend_object_iterator and iterable type
This commit is contained in:
commit
3d66f175c6
@ -94,6 +94,7 @@ bind! {
|
||||
zend_internal_arg_info,
|
||||
zend_is_callable,
|
||||
zend_is_identical,
|
||||
zend_is_iterable,
|
||||
zend_long,
|
||||
zend_lookup_class_ex,
|
||||
zend_module_entry,
|
||||
@ -163,6 +164,7 @@ bind! {
|
||||
IS_UNDEF,
|
||||
IS_VOID,
|
||||
IS_PTR,
|
||||
IS_ITERABLE,
|
||||
MAY_BE_ANY,
|
||||
MAY_BE_BOOL,
|
||||
PHP_INI_USER,
|
||||
|
@ -97,6 +97,7 @@ pub const IS_RESOURCE: u32 = 9;
|
||||
pub const IS_REFERENCE: u32 = 10;
|
||||
pub const IS_CONSTANT_AST: u32 = 11;
|
||||
pub const IS_CALLABLE: u32 = 12;
|
||||
pub const IS_ITERABLE: u32 = 13;
|
||||
pub const IS_VOID: u32 = 14;
|
||||
pub const IS_MIXED: u32 = 16;
|
||||
pub const IS_INDIRECT: u32 = 12;
|
||||
@ -1658,6 +1659,9 @@ extern "C" {
|
||||
named_params: *mut HashTable,
|
||||
);
|
||||
}
|
||||
extern "C" {
|
||||
pub fn zend_is_iterable(iterable: *const zval) -> bool;
|
||||
}
|
||||
pub const _zend_expected_type_Z_EXPECTED_LONG: _zend_expected_type = 0;
|
||||
pub const _zend_expected_type_Z_EXPECTED_LONG_OR_NULL: _zend_expected_type = 1;
|
||||
pub const _zend_expected_type_Z_EXPECTED_BOOL: _zend_expected_type = 2;
|
||||
|
55
guide/src/types/iterable.md
Normal file
55
guide/src/types/iterable.md
Normal file
@ -0,0 +1,55 @@
|
||||
# `Iterable`
|
||||
|
||||
`Iterable`s are represented either by an `array` or `Traversable` type.
|
||||
|
||||
| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation |
|
||||
|---------------|----------------|-----------------| ---------------- |----------------------------------|
|
||||
| Yes | No | No | No | `ZendHashTable` or `ZendIterator` |
|
||||
|
||||
Converting from a zval to a `Iterable` is valid when the value is either an array or an object
|
||||
that implements the `Traversable` interface. This means that any value that can be used in a
|
||||
`foreach` loop can be converted into a `Iterable`.
|
||||
|
||||
## Rust example
|
||||
|
||||
```rust,no_run
|
||||
# #![cfg_attr(windows, feature(abi_vectorcall))]
|
||||
# extern crate ext_php_rs;
|
||||
# use ext_php_rs::prelude::*;
|
||||
# use ext_php_rs::types::Iterable;
|
||||
#[php_function]
|
||||
pub fn test_iterable(mut iterable: Iterable) {
|
||||
for (k, v) in iterable.iter().expect("cannot rewind iterator") {
|
||||
println!("k: {} v: {}", k.string().unwrap(), v.string().unwrap());
|
||||
}
|
||||
}
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## PHP example
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$generator = function() {
|
||||
yield 'hello' => 'world';
|
||||
yield 'rust' => 'php';
|
||||
};
|
||||
|
||||
$array = [
|
||||
'hello' => 'world',
|
||||
'rust' => 'php',
|
||||
];
|
||||
|
||||
test_iterable($generator());
|
||||
test_iterable($array);
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
k: hello v: world
|
||||
k: rust v: php
|
||||
k: hello v: world
|
||||
k: rust v: php
|
||||
```
|
52
guide/src/types/iterator.md
Normal file
52
guide/src/types/iterator.md
Normal file
@ -0,0 +1,52 @@
|
||||
# `ZendIterator`
|
||||
|
||||
`ZendIterator`s are represented by the `Traversable` type.
|
||||
|
||||
| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation |
|
||||
|---------------| -------------- |-----------------| ---------------- | ------------------ |
|
||||
| No | Yes | No | No | `ZendIterator` |
|
||||
|
||||
Converting from a zval to a `ZendIterator` is valid when there is an associated iterator to
|
||||
the variable. This means that any value, at the exception of an `array`, that can be used in
|
||||
a `foreach` loop can be converted into a `ZendIterator`. As an example, a `Generator` can be
|
||||
used but also a the result of a `query` call with `PDO`.
|
||||
|
||||
If you want a more universal `iterable` type that also supports arrays, see [Iterable](./iterable.md).
|
||||
|
||||
## Rust example
|
||||
|
||||
```rust,no_run
|
||||
# #![cfg_attr(windows, feature(abi_vectorcall))]
|
||||
# extern crate ext_php_rs;
|
||||
# use ext_php_rs::prelude::*;
|
||||
# use ext_php_rs::types::ZendIterator;
|
||||
#[php_function]
|
||||
pub fn test_iterator(iterator: &mut ZendIterator) {
|
||||
for (k, v) in iterator.iter().expect("cannot rewind iterator") {
|
||||
// Note that the key can be anything, even an object
|
||||
// when iterating over Traversables!
|
||||
println!("k: {} v: {}", k.string().unwrap(), v.string().unwrap());
|
||||
}
|
||||
}
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
## PHP example
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$generator = function() {
|
||||
yield 'hello' => 'world';
|
||||
yield 'rust' => 'php';
|
||||
};
|
||||
|
||||
test_iterator($generator());
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
k: hello v: world
|
||||
k: rust v: php
|
||||
```
|
@ -172,6 +172,7 @@ impl ToStub for DataType {
|
||||
DataType::Reference => "reference",
|
||||
DataType::Callable => "callable",
|
||||
DataType::Bool => "bool",
|
||||
DataType::Iterable => "iterable",
|
||||
_ => "mixed",
|
||||
}
|
||||
)
|
||||
|
12
src/flags.rs
12
src/flags.rs
@ -8,10 +8,10 @@ use crate::ffi::{
|
||||
CONST_CS, CONST_DEPRECATED, CONST_NO_FILE_CACHE, CONST_PERSISTENT, E_COMPILE_ERROR,
|
||||
E_COMPILE_WARNING, E_CORE_ERROR, E_CORE_WARNING, E_DEPRECATED, E_ERROR, E_NOTICE, E_PARSE,
|
||||
E_RECOVERABLE_ERROR, E_STRICT, E_USER_DEPRECATED, E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING,
|
||||
E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_INDIRECT, IS_LONG,
|
||||
IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE,
|
||||
IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR,
|
||||
PHP_INI_SYSTEM, PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS,
|
||||
E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_INDIRECT,
|
||||
IS_ITERABLE, IS_LONG, IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE,
|
||||
IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL,
|
||||
PHP_INI_PERDIR, PHP_INI_SYSTEM, PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS,
|
||||
ZEND_ACC_CALL_VIA_TRAMPOLINE, ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED,
|
||||
ZEND_ACC_CTOR, ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING,
|
||||
ZEND_ACC_FAKE_CLOSURE, ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK,
|
||||
@ -49,6 +49,7 @@ bitflags! {
|
||||
const ConstantExpression = IS_CONSTANT_AST;
|
||||
const Void = IS_VOID;
|
||||
const Ptr = IS_PTR;
|
||||
const Iterable = IS_ITERABLE;
|
||||
|
||||
const InternedStringEx = Self::String.bits();
|
||||
const StringEx = Self::String.bits() | Self::RefCounted.bits();
|
||||
@ -237,6 +238,7 @@ pub enum DataType {
|
||||
Double,
|
||||
String,
|
||||
Array,
|
||||
Iterable,
|
||||
Object(Option<&'static str>),
|
||||
Resource,
|
||||
Reference,
|
||||
@ -277,6 +279,7 @@ impl DataType {
|
||||
DataType::Mixed => IS_MIXED,
|
||||
DataType::Bool => _IS_BOOL,
|
||||
DataType::Ptr => IS_PTR,
|
||||
DataType::Iterable => IS_ITERABLE,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -383,6 +386,7 @@ impl Display for DataType {
|
||||
DataType::Mixed => write!(f, "Mixed"),
|
||||
DataType::Ptr => write!(f, "Pointer"),
|
||||
DataType::Indirect => write!(f, "Indirect"),
|
||||
DataType::Iterable => write!(f, "Iterable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use std::{
|
||||
collections::HashMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
ffi::CString,
|
||||
fmt::Debug,
|
||||
fmt::{Debug, Display},
|
||||
iter::FromIterator,
|
||||
u64,
|
||||
};
|
||||
@ -463,7 +463,7 @@ impl ZendHashTable {
|
||||
/// assert!(!ht.has_numerical_keys());
|
||||
/// ```
|
||||
pub fn has_numerical_keys(&self) -> bool {
|
||||
!self.iter().any(|(_, k, _)| k.is_some())
|
||||
!self.into_iter().any(|(k, _)| !k.is_long())
|
||||
}
|
||||
|
||||
/// Checks if the hashtable has numerical, sequential keys.
|
||||
@ -490,31 +490,9 @@ impl ZendHashTable {
|
||||
/// ```
|
||||
pub fn has_sequential_keys(&self) -> bool {
|
||||
!self
|
||||
.iter()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.any(|(i, (k, strk, _))| i as u64 != k || strk.is_some())
|
||||
}
|
||||
|
||||
/// Returns an iterator over the key(s) and value contained inside the
|
||||
/// hashtable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ext_php_rs::types::ZendHashTable;
|
||||
///
|
||||
/// let mut ht = ZendHashTable::new();
|
||||
///
|
||||
/// for (idx, key, val) in ht.iter() {
|
||||
/// // ^ Index if inserted at an index.
|
||||
/// // ^ Optional string key, if inserted like a hashtable.
|
||||
/// // ^ Inserted value.
|
||||
///
|
||||
/// dbg!(idx, key, val);
|
||||
/// }
|
||||
#[inline]
|
||||
pub fn iter(&self) -> Iter {
|
||||
Iter::new(self)
|
||||
.any(|(i, (k, _))| ArrayKey::Long(i as i64) != k)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the values contained inside the hashtable, as
|
||||
@ -534,6 +512,28 @@ impl ZendHashTable {
|
||||
pub fn values(&self) -> Values {
|
||||
Values::new(self)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the key(s) and value contained inside the
|
||||
/// hashtable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ext_php_rs::types::ZendHashTable;
|
||||
///
|
||||
/// let mut ht = ZendHashTable::new();
|
||||
///
|
||||
/// for (key, val) in ht.iter() {
|
||||
/// // ^ Index if inserted at an index.
|
||||
/// // ^ Optional string key, if inserted like a hashtable.
|
||||
/// // ^ Inserted value.
|
||||
///
|
||||
/// dbg!(key, val);
|
||||
/// }
|
||||
#[inline]
|
||||
pub fn iter(&self) -> Iter {
|
||||
self.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl ZBoxable for ZendHashTable {
|
||||
@ -546,10 +546,7 @@ unsafe impl ZBoxable for ZendHashTable {
|
||||
impl Debug for ZendHashTable {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_map()
|
||||
.entries(
|
||||
self.iter()
|
||||
.map(|(k, k2, v)| (k2.unwrap_or_else(|| k.to_string()), v)),
|
||||
)
|
||||
.entries(self.into_iter().map(|(k, v)| (k.to_string(), v)))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@ -574,10 +571,54 @@ impl ToOwned for ZendHashTable {
|
||||
/// Immutable iterator upon a reference to a hashtable.
|
||||
pub struct Iter<'a> {
|
||||
ht: &'a ZendHashTable,
|
||||
current_num: u64,
|
||||
current_num: i64,
|
||||
pos: HashPosition,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ArrayKey {
|
||||
Long(i64),
|
||||
String(String),
|
||||
}
|
||||
|
||||
/// Represent the key of a PHP array, which can be either a long or a string.
|
||||
impl ArrayKey {
|
||||
/// Check if the key is an integer.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns true if the key is an integer, false otherwise.
|
||||
pub fn is_long(&self) -> bool {
|
||||
match self {
|
||||
ArrayKey::Long(_) => true,
|
||||
ArrayKey::String(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ArrayKey {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ArrayKey::Long(key) => write!(f, "{}", key),
|
||||
ArrayKey::String(key) => write!(f, "{}", key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromZval<'a> for ArrayKey {
|
||||
const TYPE: DataType = DataType::String;
|
||||
|
||||
fn from_zval(zval: &'a Zval) -> Option<Self> {
|
||||
if let Some(key) = zval.long() {
|
||||
return Some(ArrayKey::Long(key));
|
||||
}
|
||||
if let Some(key) = zval.string() {
|
||||
return Some(ArrayKey::String(key));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iter<'a> {
|
||||
/// Creates a new iterator over a hashtable.
|
||||
///
|
||||
@ -593,49 +634,39 @@ impl<'a> Iter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a ZendHashTable {
|
||||
type Item = (ArrayKey, &'a Zval);
|
||||
type IntoIter = Iter<'a>;
|
||||
|
||||
/// Returns an iterator over the key(s) and value contained inside the
|
||||
/// hashtable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ext_php_rs::types::ZendHashTable;
|
||||
///
|
||||
/// let mut ht = ZendHashTable::new();
|
||||
///
|
||||
/// for (key, val) in ht.iter() {
|
||||
/// // ^ Index if inserted at an index.
|
||||
/// // ^ Optional string key, if inserted like a hashtable.
|
||||
/// // ^ Inserted value.
|
||||
///
|
||||
/// dbg!(key, val);
|
||||
/// }
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
Iter::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = (u64, Option<String>, &'a Zval);
|
||||
type Item = (ArrayKey, &'a Zval);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let key_type = unsafe {
|
||||
zend_hash_get_current_key_type_ex(
|
||||
self.ht as *const ZendHashTable as *mut ZendHashTable,
|
||||
&mut self.pos as *mut HashPosition,
|
||||
)
|
||||
};
|
||||
|
||||
if key_type == -1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let key = Zval::new();
|
||||
unsafe {
|
||||
zend_hash_get_current_key_zval_ex(
|
||||
self.ht as *const ZendHashTable as *mut ZendHashTable,
|
||||
&key as *const Zval as *mut Zval,
|
||||
&mut self.pos as *mut HashPosition,
|
||||
);
|
||||
}
|
||||
let value = unsafe {
|
||||
&*zend_hash_get_current_data_ex(
|
||||
self.ht as *const ZendHashTable as *mut ZendHashTable,
|
||||
&mut self.pos as *mut HashPosition,
|
||||
)
|
||||
};
|
||||
let r: (u64, Option<String>, &Zval) = match key.is_long() {
|
||||
true => (key.long().unwrap_or(0) as u64, None, value),
|
||||
false => (self.current_num, key.try_into().ok(), value),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
zend_hash_move_forward_ex(
|
||||
self.ht as *const ZendHashTable as *mut ZendHashTable,
|
||||
&mut self.pos as *mut HashPosition,
|
||||
)
|
||||
};
|
||||
self.current_num += 1;
|
||||
|
||||
Some(r)
|
||||
self.next_zval()
|
||||
.map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v))
|
||||
}
|
||||
|
||||
fn count(self) -> usize
|
||||
@ -666,6 +697,7 @@ impl<'a> DoubleEndedIterator for Iter<'a> {
|
||||
}
|
||||
|
||||
let key = Zval::new();
|
||||
|
||||
unsafe {
|
||||
zend_hash_get_current_key_zval_ex(
|
||||
self.ht as *const ZendHashTable as *mut ZendHashTable,
|
||||
@ -679,9 +711,10 @@ impl<'a> DoubleEndedIterator for Iter<'a> {
|
||||
&mut self.pos as *mut HashPosition,
|
||||
)
|
||||
};
|
||||
let r: (u64, Option<String>, &Zval) = match key.is_long() {
|
||||
true => (key.long().unwrap_or(0) as u64, None, value),
|
||||
false => (self.current_num, key.try_into().ok(), value),
|
||||
|
||||
let key = match ArrayKey::from_zval(&key) {
|
||||
Some(key) => key,
|
||||
None => ArrayKey::Long(self.current_num),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
@ -692,7 +725,52 @@ impl<'a> DoubleEndedIterator for Iter<'a> {
|
||||
};
|
||||
self.current_num -= 1;
|
||||
|
||||
Some(r)
|
||||
Some((key, value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Iter<'a> {
|
||||
pub fn next_zval(&'b mut self) -> Option<(Zval, &'a Zval)> {
|
||||
let key_type = unsafe {
|
||||
zend_hash_get_current_key_type_ex(
|
||||
self.ht as *const ZendHashTable as *mut ZendHashTable,
|
||||
&mut self.pos as *mut HashPosition,
|
||||
)
|
||||
};
|
||||
|
||||
if key_type == -1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut key = Zval::new();
|
||||
|
||||
unsafe {
|
||||
zend_hash_get_current_key_zval_ex(
|
||||
self.ht as *const ZendHashTable as *mut ZendHashTable,
|
||||
&key as *const Zval as *mut Zval,
|
||||
&mut self.pos as *mut HashPosition,
|
||||
);
|
||||
}
|
||||
let value = unsafe {
|
||||
&*zend_hash_get_current_data_ex(
|
||||
self.ht as *const ZendHashTable as *mut ZendHashTable,
|
||||
&mut self.pos as *mut HashPosition,
|
||||
)
|
||||
};
|
||||
|
||||
if !key.is_long() && !key.is_string() {
|
||||
key.set_long(self.current_num)
|
||||
}
|
||||
|
||||
unsafe {
|
||||
zend_hash_move_forward_ex(
|
||||
self.ht as *const ZendHashTable as *mut ZendHashTable,
|
||||
&mut self.pos as *mut HashPosition,
|
||||
)
|
||||
};
|
||||
self.current_num += 1;
|
||||
|
||||
Some((key, value))
|
||||
}
|
||||
}
|
||||
|
||||
@ -715,7 +793,7 @@ impl<'a> Iterator for Values<'a> {
|
||||
type Item = &'a Zval;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next().map(|(_, _, zval)| zval)
|
||||
self.0.next().map(|(_, zval)| zval)
|
||||
}
|
||||
|
||||
fn count(self) -> usize
|
||||
@ -734,7 +812,7 @@ impl<'a> ExactSizeIterator for Values<'a> {
|
||||
|
||||
impl<'a> DoubleEndedIterator for Values<'a> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
self.0.next_back().map(|(_, _, zval)| zval)
|
||||
self.0.next_back().map(|(_, zval)| zval)
|
||||
}
|
||||
}
|
||||
|
||||
@ -780,9 +858,9 @@ where
|
||||
fn try_from(value: &'a ZendHashTable) -> Result<Self> {
|
||||
let mut hm = HashMap::with_capacity(value.len());
|
||||
|
||||
for (idx, key, val) in value.iter() {
|
||||
for (key, val) in value {
|
||||
hm.insert(
|
||||
key.unwrap_or_else(|| idx.to_string()),
|
||||
key.to_string(),
|
||||
V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
|
||||
);
|
||||
}
|
||||
@ -849,7 +927,7 @@ where
|
||||
fn try_from(value: &'a ZendHashTable) -> Result<Self> {
|
||||
let mut vec = Vec::with_capacity(value.len());
|
||||
|
||||
for (_, _, val) in value.iter() {
|
||||
for (_, val) in value {
|
||||
vec.push(T::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?);
|
||||
}
|
||||
|
||||
|
66
src/types/iterable.rs
Normal file
66
src/types/iterable.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use super::array::Iter as ZendHashTableIter;
|
||||
use super::iterator::Iter as ZendIteratorIter;
|
||||
use crate::convert::FromZval;
|
||||
use crate::flags::DataType;
|
||||
use crate::types::{ZendHashTable, ZendIterator, Zval};
|
||||
|
||||
/// This type represents a PHP iterable, which can be either an array or an
|
||||
/// object implementing the Traversable interface.
|
||||
#[derive(Debug)]
|
||||
pub enum Iterable<'a> {
|
||||
Array(&'a ZendHashTable),
|
||||
Traversable(&'a mut ZendIterator),
|
||||
}
|
||||
|
||||
impl<'a> Iterable<'a> {
|
||||
/// Creates a new rust iterator from a PHP iterable.
|
||||
/// May return None if a Traversable cannot be rewound.
|
||||
pub fn iter(&mut self) -> Option<Iter> {
|
||||
match self {
|
||||
Iterable::Array(array) => Some(Iter::Array(array.iter())),
|
||||
Iterable::Traversable(traversable) => Some(Iter::Traversable(traversable.iter()?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut Iterable<'a> {
|
||||
type Item = (Zval, &'a Zval);
|
||||
type IntoIter = Iter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter().expect("Could not rewind iterator!")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromZval<'a> for Iterable<'a> {
|
||||
const TYPE: DataType = DataType::Iterable;
|
||||
|
||||
fn from_zval(zval: &'a Zval) -> Option<Self> {
|
||||
if let Some(array) = zval.array() {
|
||||
return Some(Iterable::Array(array));
|
||||
}
|
||||
|
||||
if let Some(traversable) = zval.traversable() {
|
||||
return Some(Iterable::Traversable(traversable));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Rust iterator over a PHP iterable.
|
||||
pub enum Iter<'a> {
|
||||
Array(ZendHashTableIter<'a>),
|
||||
Traversable(ZendIteratorIter<'a>),
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = (Zval, &'a Zval);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Iter::Array(array) => array.next_zval(),
|
||||
Iter::Traversable(traversable) => traversable.next(),
|
||||
}
|
||||
}
|
||||
}
|
327
src/types/iterator.rs
Normal file
327
src/types/iterator.rs
Normal file
@ -0,0 +1,327 @@
|
||||
use crate::convert::FromZvalMut;
|
||||
use crate::ffi::{zend_object_iterator, ZEND_RESULT_CODE_SUCCESS};
|
||||
use crate::flags::DataType;
|
||||
use crate::types::Zval;
|
||||
use crate::zend::ExecutorGlobals;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
/// A PHP Iterator.
|
||||
///
|
||||
/// In PHP, iterators are represented as zend_object_iterator. This allows user
|
||||
/// to iterate over objects implementing Traversable interface using foreach.
|
||||
///
|
||||
/// Use ZendIterable to iterate over both iterators and arrays.
|
||||
pub type ZendIterator = zend_object_iterator;
|
||||
|
||||
impl ZendIterator {
|
||||
/// Creates a new rust iterator from a zend_object_iterator.
|
||||
///
|
||||
/// Returns a iterator over the zend_object_iterator, or None if the
|
||||
/// iterator cannot be rewound.
|
||||
pub fn iter(&mut self) -> Option<Iter> {
|
||||
self.index = 0;
|
||||
|
||||
if self.rewind() {
|
||||
return Some(Iter { zi: self });
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Check if the current position of the iterator is valid.
|
||||
///
|
||||
/// As an example this will call the user defined valid method of the
|
||||
/// ['\Iterator'] interface. see <https://www.php.net/manual/en/iterator.valid.php>
|
||||
pub fn valid(&mut self) -> bool {
|
||||
if let Some(valid) = unsafe { (*self.funcs).valid } {
|
||||
let valid = unsafe { valid(&mut *self) == ZEND_RESULT_CODE_SUCCESS };
|
||||
|
||||
if ExecutorGlobals::has_exception() {
|
||||
return false;
|
||||
}
|
||||
|
||||
valid
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Rewind the iterator to the first element.
|
||||
///
|
||||
/// As an example this will call the user defined rewind method of the
|
||||
/// ['\Iterator'] interface. see <https://www.php.net/manual/en/iterator.rewind.php>
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns true if the iterator was successfully rewind, false otherwise.
|
||||
/// (when there is an exception during rewind)
|
||||
pub fn rewind(&mut self) -> bool {
|
||||
if let Some(rewind) = unsafe { (*self.funcs).rewind } {
|
||||
unsafe {
|
||||
rewind(&mut *self);
|
||||
}
|
||||
}
|
||||
|
||||
!ExecutorGlobals::has_exception()
|
||||
}
|
||||
|
||||
/// Move the iterator forward to the next element.
|
||||
///
|
||||
/// As an example this will call the user defined next method of the
|
||||
/// ['\Iterator'] interface. see <https://www.php.net/manual/en/iterator.next.php>
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns true if the iterator was successfully move, false otherwise.
|
||||
/// (when there is an exception during next)
|
||||
pub fn move_forward(&mut self) -> bool {
|
||||
if let Some(move_forward) = unsafe { (*self.funcs).move_forward } {
|
||||
unsafe {
|
||||
move_forward(&mut *self);
|
||||
}
|
||||
}
|
||||
|
||||
!ExecutorGlobals::has_exception()
|
||||
}
|
||||
|
||||
/// Get the current data of the iterator.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a reference to the current data of the iterator if available
|
||||
/// , ['None'] otherwise.
|
||||
pub fn get_current_data<'a>(&mut self) -> Option<&'a Zval> {
|
||||
let get_current_data = unsafe { (*self.funcs).get_current_data }?;
|
||||
let value = unsafe { &*get_current_data(&mut *self) };
|
||||
|
||||
if ExecutorGlobals::has_exception() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(value)
|
||||
}
|
||||
|
||||
/// Get the current key of the iterator.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a new ['Zval'] containing the current key of the iterator if
|
||||
/// available , ['None'] otherwise.
|
||||
pub fn get_current_key(&mut self) -> Option<Zval> {
|
||||
let get_current_key = unsafe { (*self.funcs).get_current_key? };
|
||||
let mut key = Zval::new();
|
||||
|
||||
unsafe {
|
||||
get_current_key(&mut *self, &mut key);
|
||||
}
|
||||
|
||||
if ExecutorGlobals::has_exception() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut ZendIterator {
|
||||
type Item = (Zval, &'a Zval);
|
||||
type IntoIter = Iter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter().expect("Could not rewind iterator!")
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ZendIterator {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ZendIterator").finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Immutable iterator upon a reference to a PHP iterator.
|
||||
pub struct Iter<'a> {
|
||||
zi: &'a mut ZendIterator,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = (Zval, &'a Zval);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Call next when index > 0, so next is really called at the start of each
|
||||
// iteration, which allow to work better with generator iterator
|
||||
if self.zi.index > 0 && !self.zi.move_forward() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !self.zi.valid() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.zi.index += 1;
|
||||
|
||||
let real_index = self.zi.index - 1;
|
||||
|
||||
let key = match self.zi.get_current_key() {
|
||||
None => {
|
||||
let mut z = Zval::new();
|
||||
z.set_long(real_index as i64);
|
||||
z
|
||||
}
|
||||
Some(key) => key,
|
||||
};
|
||||
|
||||
self.zi.get_current_data().map(|value| (key, value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromZvalMut<'a> for &'a mut ZendIterator {
|
||||
const TYPE: DataType = DataType::Object(Some("Traversable"));
|
||||
|
||||
fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
|
||||
zval.object()?.get_class_entry().get_iterator(zval, false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "embed")]
|
||||
mod tests {
|
||||
use crate::embed::Embed;
|
||||
|
||||
#[test]
|
||||
fn test_generator() {
|
||||
Embed::run(|| {
|
||||
let result = Embed::run_script("src/types/iterator.test.php");
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
let generator = Embed::eval("$generator;");
|
||||
|
||||
assert!(generator.is_ok());
|
||||
|
||||
let zval = generator.unwrap();
|
||||
|
||||
assert!(zval.is_traversable());
|
||||
|
||||
let iterator = zval.traversable().unwrap();
|
||||
|
||||
assert!(iterator.valid());
|
||||
|
||||
{
|
||||
let mut iter = iterator.iter().unwrap();
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert_eq!(key.long(), Some(0));
|
||||
assert!(value.is_long());
|
||||
assert_eq!(value.long().unwrap(), 1);
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert_eq!(key.long(), Some(1));
|
||||
assert!(value.is_long());
|
||||
assert_eq!(value.long().unwrap(), 2);
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert_eq!(key.long(), Some(2));
|
||||
assert!(value.is_long());
|
||||
assert_eq!(value.long().unwrap(), 3);
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert!(key.is_object());
|
||||
assert!(value.is_object());
|
||||
|
||||
let next = iter.next();
|
||||
|
||||
assert!(next.is_none());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iterator() {
|
||||
Embed::run(|| {
|
||||
let result = Embed::run_script("src/types/iterator.test.php");
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
let generator = Embed::eval("$iterator;");
|
||||
|
||||
assert!(generator.is_ok());
|
||||
|
||||
let zval = generator.unwrap();
|
||||
|
||||
assert!(zval.is_traversable());
|
||||
|
||||
let iterator = zval.traversable().unwrap();
|
||||
|
||||
assert!(iterator.valid());
|
||||
|
||||
{
|
||||
let mut iter = iterator.iter().unwrap();
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert!(!key.is_long());
|
||||
assert_eq!(key.str(), Some("key"));
|
||||
assert!(value.is_string());
|
||||
assert_eq!(value.str(), Some("foo"));
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert!(key.is_long());
|
||||
assert_eq!(key.long(), Some(10));
|
||||
assert!(value.is_string());
|
||||
assert_eq!(value.string().unwrap(), "bar");
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert_eq!(key.long(), Some(2));
|
||||
assert!(value.is_string());
|
||||
assert_eq!(value.string().unwrap(), "baz");
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert!(key.is_object());
|
||||
assert!(value.is_object());
|
||||
|
||||
let next = iter.next();
|
||||
|
||||
assert!(next.is_none());
|
||||
}
|
||||
|
||||
// Test rewind
|
||||
{
|
||||
let mut iter = iterator.iter().unwrap();
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert_eq!(key.str(), Some("key"));
|
||||
assert!(value.is_string());
|
||||
assert_eq!(value.string().unwrap(), "foo");
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert_eq!(key.long(), Some(10));
|
||||
assert!(value.is_string());
|
||||
assert_eq!(value.string().unwrap(), "bar");
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert_eq!(key.long(), Some(2));
|
||||
assert!(value.is_string());
|
||||
assert_eq!(value.string().unwrap(), "baz");
|
||||
|
||||
let (key, value) = iter.next().unwrap();
|
||||
|
||||
assert!(key.is_object());
|
||||
assert!(value.is_object());
|
||||
|
||||
let next = iter.next();
|
||||
|
||||
assert!(next.is_none());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
52
src/types/iterator.test.php
Normal file
52
src/types/iterator.test.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
function create_generator() {
|
||||
yield 1;
|
||||
yield 2;
|
||||
yield 3;
|
||||
yield new class {} => new class {};
|
||||
}
|
||||
|
||||
class TestIterator implements \Iterator {
|
||||
private $count = 0;
|
||||
|
||||
public function current(): mixed
|
||||
{
|
||||
return match ($this->count) {
|
||||
0 => 'foo',
|
||||
1 => 'bar',
|
||||
2 => 'baz',
|
||||
3 => new class {},
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
$this->count++;
|
||||
}
|
||||
|
||||
public function key(): mixed
|
||||
{
|
||||
return match ($this->count) {
|
||||
0 => 'key',
|
||||
1 => 10,
|
||||
2 => 2,
|
||||
3 => new class {},
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return $this->count < 4;
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$generator = create_generator();
|
||||
$iterator = new TestIterator();
|
@ -6,6 +6,8 @@
|
||||
mod array;
|
||||
mod callable;
|
||||
mod class_object;
|
||||
mod iterable;
|
||||
mod iterator;
|
||||
mod long;
|
||||
mod object;
|
||||
mod string;
|
||||
@ -14,6 +16,8 @@ mod zval;
|
||||
pub use array::ZendHashTable;
|
||||
pub use callable::ZendCallable;
|
||||
pub use class_object::ZendClassObject;
|
||||
pub use iterable::Iterable;
|
||||
pub use iterator::ZendIterator;
|
||||
pub use long::ZendLong;
|
||||
pub use object::{PropertyQuery, ZendObject};
|
||||
pub use string::ZendStr;
|
||||
|
@ -133,6 +133,15 @@ impl ZendObject {
|
||||
(self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _))
|
||||
}
|
||||
|
||||
/// Returns whether this object is an instance of \Traversable
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the class entry is invalid.
|
||||
pub fn is_traversable(&self) -> bool {
|
||||
self.instance_of(ce::traversable())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
|
||||
let mut retval = Zval::new();
|
||||
@ -317,8 +326,8 @@ impl Debug for ZendObject {
|
||||
);
|
||||
|
||||
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);
|
||||
for (key, val) in props.iter() {
|
||||
dbg.field(key.to_string().as_str(), val);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr};
|
||||
|
||||
use crate::types::iterable::Iterable;
|
||||
use crate::types::ZendIterator;
|
||||
use crate::{
|
||||
binary::Pack,
|
||||
binary_slice::PackSlice,
|
||||
@ -12,7 +14,7 @@ use crate::{
|
||||
error::{Error, Result},
|
||||
ffi::{
|
||||
_zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, zend_is_callable,
|
||||
zend_is_identical, zend_resource, zend_value, zval, zval_ptr_dtor,
|
||||
zend_is_identical, zend_is_iterable, zend_resource, zend_value, zval, zval_ptr_dtor,
|
||||
},
|
||||
flags::DataType,
|
||||
flags::ZvalTypeFlags,
|
||||
@ -256,6 +258,25 @@ impl Zval {
|
||||
ZendCallable::new(self).ok()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the zval if it is traversable.
|
||||
pub fn traversable(&self) -> Option<&mut ZendIterator> {
|
||||
if self.is_traversable() {
|
||||
self.object()?.get_class_entry().get_iterator(self, false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterable over the zval if it is an array or traversable. (is
|
||||
/// iterable)
|
||||
pub fn iterable(&self) -> Option<Iterable> {
|
||||
if self.is_iterable() {
|
||||
Iterable::from_zval(self)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the zval if it is a pointer.
|
||||
///
|
||||
/// # Safety
|
||||
@ -371,6 +392,21 @@ impl Zval {
|
||||
unsafe { zend_is_identical(self_p as *mut Self, other_p as *mut Self) }
|
||||
}
|
||||
|
||||
/// Returns true if the zval is traversable, false otherwise.
|
||||
pub fn is_traversable(&self) -> bool {
|
||||
match self.object() {
|
||||
None => false,
|
||||
Some(obj) => obj.is_traversable(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the zval is iterable (array or traversable), false
|
||||
/// otherwise.
|
||||
pub fn is_iterable(&self) -> bool {
|
||||
let ptr: *const Self = self;
|
||||
unsafe { zend_is_iterable(ptr as *mut Self) }
|
||||
}
|
||||
|
||||
/// Returns true if the zval contains a pointer, false otherwise.
|
||||
pub fn is_ptr(&self) -> bool {
|
||||
self.get_type() == DataType::Ptr
|
||||
@ -626,6 +662,7 @@ impl Debug for Zval {
|
||||
DataType::Void => field!(Option::<()>::None),
|
||||
DataType::Bool => field!(self.bool()),
|
||||
DataType::Indirect => field!(self.indirect()),
|
||||
DataType::Iterable => field!(self.iterable()),
|
||||
// SAFETY: We are not accessing the pointer.
|
||||
DataType::Ptr => field!(unsafe { self.ptr::<c_void>() }),
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! Builder and objects for creating classes in the PHP world.
|
||||
|
||||
use crate::types::{ZendIterator, Zval};
|
||||
use crate::{
|
||||
boxed::ZBox,
|
||||
ffi::zend_class_entry,
|
||||
@ -97,6 +98,24 @@ impl ClassEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the iterator for the class for a specific instance
|
||||
///
|
||||
/// Returns [`None`] if there is no associated iterator for the class.
|
||||
pub fn get_iterator<'a>(&self, zval: &'a Zval, by_ref: bool) -> Option<&'a mut ZendIterator> {
|
||||
let ptr: *const Self = self;
|
||||
let zval_ptr: *const Zval = zval;
|
||||
|
||||
let iterator = unsafe {
|
||||
(*ptr).get_iterator?(
|
||||
ptr as *mut ClassEntry,
|
||||
zval_ptr as *mut Zval,
|
||||
if by_ref { 1 } else { 0 },
|
||||
)
|
||||
};
|
||||
|
||||
unsafe { iterator.as_mut() }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
unsafe { self.name.as_ref().and_then(|s| s.as_str().ok()) }
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ use std::str;
|
||||
use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
use crate::boxed::ZBox;
|
||||
use crate::exception::PhpResult;
|
||||
#[cfg(php82)]
|
||||
use crate::ffi::zend_atomic_bool_store;
|
||||
use crate::ffi::{
|
||||
@ -83,22 +84,20 @@ impl ExecutorGlobals {
|
||||
pub fn ini_values(&self) -> HashMap<String, Option<String>> {
|
||||
let hash_table = unsafe { &*self.ini_directives };
|
||||
let mut ini_hash_map: HashMap<String, Option<String>> = HashMap::new();
|
||||
for (_index, key, value) in hash_table.iter() {
|
||||
if let Some(key) = key {
|
||||
ini_hash_map.insert(key, unsafe {
|
||||
let ini_entry = &*value.ptr::<zend_ini_entry>().expect("Invalid ini entry");
|
||||
if ini_entry.value.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
(*ini_entry.value)
|
||||
.as_str()
|
||||
.expect("Ini value is not a string")
|
||||
.to_owned(),
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
for (key, value) in hash_table.iter() {
|
||||
ini_hash_map.insert(key.to_string(), unsafe {
|
||||
let ini_entry = &*value.ptr::<zend_ini_entry>().expect("Invalid ini entry");
|
||||
if ini_entry.value.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
(*ini_entry.value)
|
||||
.as_str()
|
||||
.expect("Ini value is not a string")
|
||||
.to_owned(),
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
ini_hash_map
|
||||
}
|
||||
@ -115,6 +114,13 @@ impl ExecutorGlobals {
|
||||
/// could lead to a deadlock if the globals are already borrowed immutably
|
||||
/// or mutably.
|
||||
pub fn take_exception() -> Option<ZBox<ZendObject>> {
|
||||
{
|
||||
// This avoid a write lock if there is no exception.
|
||||
if Self::get().exception.is_null() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut globals = Self::get_mut();
|
||||
|
||||
let mut exception_ptr = std::ptr::null_mut();
|
||||
@ -124,6 +130,25 @@ impl ExecutorGlobals {
|
||||
Some(unsafe { ZBox::from_raw(exception_ptr.as_mut()?) })
|
||||
}
|
||||
|
||||
/// Checks if the executor globals contain an exception.
|
||||
pub fn has_exception() -> bool {
|
||||
!Self::get().exception.is_null()
|
||||
}
|
||||
|
||||
/// Attempts to extract the last PHP exception captured by the interpreter.
|
||||
/// Returned inside a [`PhpResult`].
|
||||
///
|
||||
/// This function requires the executor globals to be mutably held, which
|
||||
/// could lead to a deadlock if the globals are already borrowed immutably
|
||||
/// or mutably.
|
||||
pub fn throw_if_exception() -> PhpResult<()> {
|
||||
if let Some(e) = Self::take_exception() {
|
||||
Err(crate::error::Error::Exception(e).into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Request an interrupt of the PHP VM. This will call the registered
|
||||
/// interrupt handler function.
|
||||
/// set with [`crate::ffi::zend_interrupt_function`].
|
||||
|
Loading…
Reference in New Issue
Block a user