mirror of
https://github.com/danog/ext-php-rs.git
synced 2024-12-11 16:49:53 +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_internal_arg_info,
|
||||||
zend_is_callable,
|
zend_is_callable,
|
||||||
zend_is_identical,
|
zend_is_identical,
|
||||||
|
zend_is_iterable,
|
||||||
zend_long,
|
zend_long,
|
||||||
zend_lookup_class_ex,
|
zend_lookup_class_ex,
|
||||||
zend_module_entry,
|
zend_module_entry,
|
||||||
@ -163,6 +164,7 @@ bind! {
|
|||||||
IS_UNDEF,
|
IS_UNDEF,
|
||||||
IS_VOID,
|
IS_VOID,
|
||||||
IS_PTR,
|
IS_PTR,
|
||||||
|
IS_ITERABLE,
|
||||||
MAY_BE_ANY,
|
MAY_BE_ANY,
|
||||||
MAY_BE_BOOL,
|
MAY_BE_BOOL,
|
||||||
PHP_INI_USER,
|
PHP_INI_USER,
|
||||||
|
@ -97,6 +97,7 @@ pub const IS_RESOURCE: u32 = 9;
|
|||||||
pub const IS_REFERENCE: u32 = 10;
|
pub const IS_REFERENCE: u32 = 10;
|
||||||
pub const IS_CONSTANT_AST: u32 = 11;
|
pub const IS_CONSTANT_AST: u32 = 11;
|
||||||
pub const IS_CALLABLE: u32 = 12;
|
pub const IS_CALLABLE: u32 = 12;
|
||||||
|
pub const IS_ITERABLE: u32 = 13;
|
||||||
pub const IS_VOID: u32 = 14;
|
pub const IS_VOID: u32 = 14;
|
||||||
pub const IS_MIXED: u32 = 16;
|
pub const IS_MIXED: u32 = 16;
|
||||||
pub const IS_INDIRECT: u32 = 12;
|
pub const IS_INDIRECT: u32 = 12;
|
||||||
@ -1658,6 +1659,9 @@ extern "C" {
|
|||||||
named_params: *mut HashTable,
|
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: _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_LONG_OR_NULL: _zend_expected_type = 1;
|
||||||
pub const _zend_expected_type_Z_EXPECTED_BOOL: _zend_expected_type = 2;
|
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::Reference => "reference",
|
||||||
DataType::Callable => "callable",
|
DataType::Callable => "callable",
|
||||||
DataType::Bool => "bool",
|
DataType::Bool => "bool",
|
||||||
|
DataType::Iterable => "iterable",
|
||||||
_ => "mixed",
|
_ => "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,
|
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_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_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,
|
E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_INDIRECT,
|
||||||
IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE,
|
IS_ITERABLE, IS_LONG, IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE,
|
||||||
IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR,
|
IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL,
|
||||||
PHP_INI_SYSTEM, PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS,
|
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_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_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,
|
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 ConstantExpression = IS_CONSTANT_AST;
|
||||||
const Void = IS_VOID;
|
const Void = IS_VOID;
|
||||||
const Ptr = IS_PTR;
|
const Ptr = IS_PTR;
|
||||||
|
const Iterable = IS_ITERABLE;
|
||||||
|
|
||||||
const InternedStringEx = Self::String.bits();
|
const InternedStringEx = Self::String.bits();
|
||||||
const StringEx = Self::String.bits() | Self::RefCounted.bits();
|
const StringEx = Self::String.bits() | Self::RefCounted.bits();
|
||||||
@ -237,6 +238,7 @@ pub enum DataType {
|
|||||||
Double,
|
Double,
|
||||||
String,
|
String,
|
||||||
Array,
|
Array,
|
||||||
|
Iterable,
|
||||||
Object(Option<&'static str>),
|
Object(Option<&'static str>),
|
||||||
Resource,
|
Resource,
|
||||||
Reference,
|
Reference,
|
||||||
@ -277,6 +279,7 @@ impl DataType {
|
|||||||
DataType::Mixed => IS_MIXED,
|
DataType::Mixed => IS_MIXED,
|
||||||
DataType::Bool => _IS_BOOL,
|
DataType::Bool => _IS_BOOL,
|
||||||
DataType::Ptr => IS_PTR,
|
DataType::Ptr => IS_PTR,
|
||||||
|
DataType::Iterable => IS_ITERABLE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -383,6 +386,7 @@ impl Display for DataType {
|
|||||||
DataType::Mixed => write!(f, "Mixed"),
|
DataType::Mixed => write!(f, "Mixed"),
|
||||||
DataType::Ptr => write!(f, "Pointer"),
|
DataType::Ptr => write!(f, "Pointer"),
|
||||||
DataType::Indirect => write!(f, "Indirect"),
|
DataType::Indirect => write!(f, "Indirect"),
|
||||||
|
DataType::Iterable => write!(f, "Iterable"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use std::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
convert::{TryFrom, TryInto},
|
convert::{TryFrom, TryInto},
|
||||||
ffi::CString,
|
ffi::CString,
|
||||||
fmt::Debug,
|
fmt::{Debug, Display},
|
||||||
iter::FromIterator,
|
iter::FromIterator,
|
||||||
u64,
|
u64,
|
||||||
};
|
};
|
||||||
@ -463,7 +463,7 @@ impl ZendHashTable {
|
|||||||
/// assert!(!ht.has_numerical_keys());
|
/// assert!(!ht.has_numerical_keys());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn has_numerical_keys(&self) -> bool {
|
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.
|
/// Checks if the hashtable has numerical, sequential keys.
|
||||||
@ -490,31 +490,9 @@ impl ZendHashTable {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn has_sequential_keys(&self) -> bool {
|
pub fn has_sequential_keys(&self) -> bool {
|
||||||
!self
|
!self
|
||||||
.iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.any(|(i, (k, strk, _))| i as u64 != k || strk.is_some())
|
.any(|(i, (k, _))| ArrayKey::Long(i as i64) != k)
|
||||||
}
|
|
||||||
|
|
||||||
/// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over the values contained inside the hashtable, as
|
/// Returns an iterator over the values contained inside the hashtable, as
|
||||||
@ -534,6 +512,28 @@ impl ZendHashTable {
|
|||||||
pub fn values(&self) -> Values {
|
pub fn values(&self) -> Values {
|
||||||
Values::new(self)
|
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 {
|
unsafe impl ZBoxable for ZendHashTable {
|
||||||
@ -546,10 +546,7 @@ unsafe impl ZBoxable for ZendHashTable {
|
|||||||
impl Debug for ZendHashTable {
|
impl Debug for ZendHashTable {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_map()
|
f.debug_map()
|
||||||
.entries(
|
.entries(self.into_iter().map(|(k, v)| (k.to_string(), v)))
|
||||||
self.iter()
|
|
||||||
.map(|(k, k2, v)| (k2.unwrap_or_else(|| k.to_string()), v)),
|
|
||||||
)
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -574,10 +571,54 @@ impl ToOwned for ZendHashTable {
|
|||||||
/// Immutable iterator upon a reference to a hashtable.
|
/// Immutable iterator upon a reference to a hashtable.
|
||||||
pub struct Iter<'a> {
|
pub struct Iter<'a> {
|
||||||
ht: &'a ZendHashTable,
|
ht: &'a ZendHashTable,
|
||||||
current_num: u64,
|
current_num: i64,
|
||||||
pos: HashPosition,
|
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> {
|
impl<'a> Iter<'a> {
|
||||||
/// Creates a new iterator over a hashtable.
|
/// 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> {
|
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> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let key_type = unsafe {
|
self.next_zval()
|
||||||
zend_hash_get_current_key_type_ex(
|
.map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v))
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count(self) -> usize
|
fn count(self) -> usize
|
||||||
@ -666,6 +697,7 @@ impl<'a> DoubleEndedIterator for Iter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let key = Zval::new();
|
let key = Zval::new();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
zend_hash_get_current_key_zval_ex(
|
zend_hash_get_current_key_zval_ex(
|
||||||
self.ht as *const ZendHashTable as *mut ZendHashTable,
|
self.ht as *const ZendHashTable as *mut ZendHashTable,
|
||||||
@ -679,9 +711,10 @@ impl<'a> DoubleEndedIterator for Iter<'a> {
|
|||||||
&mut self.pos as *mut HashPosition,
|
&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),
|
let key = match ArrayKey::from_zval(&key) {
|
||||||
false => (self.current_num, key.try_into().ok(), value),
|
Some(key) => key,
|
||||||
|
None => ArrayKey::Long(self.current_num),
|
||||||
};
|
};
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -692,7 +725,52 @@ impl<'a> DoubleEndedIterator for Iter<'a> {
|
|||||||
};
|
};
|
||||||
self.current_num -= 1;
|
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;
|
type Item = &'a Zval;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.0.next().map(|(_, _, zval)| zval)
|
self.0.next().map(|(_, zval)| zval)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count(self) -> usize
|
fn count(self) -> usize
|
||||||
@ -734,7 +812,7 @@ impl<'a> ExactSizeIterator for Values<'a> {
|
|||||||
|
|
||||||
impl<'a> DoubleEndedIterator for Values<'a> {
|
impl<'a> DoubleEndedIterator for Values<'a> {
|
||||||
fn next_back(&mut self) -> Option<Self::Item> {
|
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> {
|
fn try_from(value: &'a ZendHashTable) -> Result<Self> {
|
||||||
let mut hm = HashMap::with_capacity(value.len());
|
let mut hm = HashMap::with_capacity(value.len());
|
||||||
|
|
||||||
for (idx, key, val) in value.iter() {
|
for (key, val) in value {
|
||||||
hm.insert(
|
hm.insert(
|
||||||
key.unwrap_or_else(|| idx.to_string()),
|
key.to_string(),
|
||||||
V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
|
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> {
|
fn try_from(value: &'a ZendHashTable) -> Result<Self> {
|
||||||
let mut vec = Vec::with_capacity(value.len());
|
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()))?);
|
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 array;
|
||||||
mod callable;
|
mod callable;
|
||||||
mod class_object;
|
mod class_object;
|
||||||
|
mod iterable;
|
||||||
|
mod iterator;
|
||||||
mod long;
|
mod long;
|
||||||
mod object;
|
mod object;
|
||||||
mod string;
|
mod string;
|
||||||
@ -14,6 +16,8 @@ mod zval;
|
|||||||
pub use array::ZendHashTable;
|
pub use array::ZendHashTable;
|
||||||
pub use callable::ZendCallable;
|
pub use callable::ZendCallable;
|
||||||
pub use class_object::ZendClassObject;
|
pub use class_object::ZendClassObject;
|
||||||
|
pub use iterable::Iterable;
|
||||||
|
pub use iterator::ZendIterator;
|
||||||
pub use long::ZendLong;
|
pub use long::ZendLong;
|
||||||
pub use object::{PropertyQuery, ZendObject};
|
pub use object::{PropertyQuery, ZendObject};
|
||||||
pub use string::ZendStr;
|
pub use string::ZendStr;
|
||||||
|
@ -133,6 +133,15 @@ impl ZendObject {
|
|||||||
(self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _))
|
(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)]
|
#[inline(always)]
|
||||||
pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
|
pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result<Zval> {
|
||||||
let mut retval = Zval::new();
|
let mut retval = Zval::new();
|
||||||
@ -317,8 +326,8 @@ impl Debug for ZendObject {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if let Ok(props) = self.get_properties() {
|
if let Ok(props) = self.get_properties() {
|
||||||
for (id, key, val) in props.iter() {
|
for (key, val) in props.iter() {
|
||||||
dbg.field(key.unwrap_or_else(|| id.to_string()).as_str(), val);
|
dbg.field(key.to_string().as_str(), val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr};
|
use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr};
|
||||||
|
|
||||||
|
use crate::types::iterable::Iterable;
|
||||||
|
use crate::types::ZendIterator;
|
||||||
use crate::{
|
use crate::{
|
||||||
binary::Pack,
|
binary::Pack,
|
||||||
binary_slice::PackSlice,
|
binary_slice::PackSlice,
|
||||||
@ -12,7 +14,7 @@ use crate::{
|
|||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
ffi::{
|
ffi::{
|
||||||
_zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, zend_is_callable,
|
_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::DataType,
|
||||||
flags::ZvalTypeFlags,
|
flags::ZvalTypeFlags,
|
||||||
@ -256,6 +258,25 @@ impl Zval {
|
|||||||
ZendCallable::new(self).ok()
|
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.
|
/// Returns the value of the zval if it is a pointer.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
@ -371,6 +392,21 @@ impl Zval {
|
|||||||
unsafe { zend_is_identical(self_p as *mut Self, other_p as *mut Self) }
|
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.
|
/// Returns true if the zval contains a pointer, false otherwise.
|
||||||
pub fn is_ptr(&self) -> bool {
|
pub fn is_ptr(&self) -> bool {
|
||||||
self.get_type() == DataType::Ptr
|
self.get_type() == DataType::Ptr
|
||||||
@ -626,6 +662,7 @@ impl Debug for Zval {
|
|||||||
DataType::Void => field!(Option::<()>::None),
|
DataType::Void => field!(Option::<()>::None),
|
||||||
DataType::Bool => field!(self.bool()),
|
DataType::Bool => field!(self.bool()),
|
||||||
DataType::Indirect => field!(self.indirect()),
|
DataType::Indirect => field!(self.indirect()),
|
||||||
|
DataType::Iterable => field!(self.iterable()),
|
||||||
// SAFETY: We are not accessing the pointer.
|
// SAFETY: We are not accessing the pointer.
|
||||||
DataType::Ptr => field!(unsafe { self.ptr::<c_void>() }),
|
DataType::Ptr => field!(unsafe { self.ptr::<c_void>() }),
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
//! Builder and objects for creating classes in the PHP world.
|
//! Builder and objects for creating classes in the PHP world.
|
||||||
|
|
||||||
|
use crate::types::{ZendIterator, Zval};
|
||||||
use crate::{
|
use crate::{
|
||||||
boxed::ZBox,
|
boxed::ZBox,
|
||||||
ffi::zend_class_entry,
|
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> {
|
pub fn name(&self) -> Option<&str> {
|
||||||
unsafe { self.name.as_ref().and_then(|s| s.as_str().ok()) }
|
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 parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
|
|
||||||
use crate::boxed::ZBox;
|
use crate::boxed::ZBox;
|
||||||
|
use crate::exception::PhpResult;
|
||||||
#[cfg(php82)]
|
#[cfg(php82)]
|
||||||
use crate::ffi::zend_atomic_bool_store;
|
use crate::ffi::zend_atomic_bool_store;
|
||||||
use crate::ffi::{
|
use crate::ffi::{
|
||||||
@ -83,9 +84,8 @@ impl ExecutorGlobals {
|
|||||||
pub fn ini_values(&self) -> HashMap<String, Option<String>> {
|
pub fn ini_values(&self) -> HashMap<String, Option<String>> {
|
||||||
let hash_table = unsafe { &*self.ini_directives };
|
let hash_table = unsafe { &*self.ini_directives };
|
||||||
let mut ini_hash_map: HashMap<String, Option<String>> = HashMap::new();
|
let mut ini_hash_map: HashMap<String, Option<String>> = HashMap::new();
|
||||||
for (_index, key, value) in hash_table.iter() {
|
for (key, value) in hash_table.iter() {
|
||||||
if let Some(key) = key {
|
ini_hash_map.insert(key.to_string(), unsafe {
|
||||||
ini_hash_map.insert(key, unsafe {
|
|
||||||
let ini_entry = &*value.ptr::<zend_ini_entry>().expect("Invalid ini entry");
|
let ini_entry = &*value.ptr::<zend_ini_entry>().expect("Invalid ini entry");
|
||||||
if ini_entry.value.is_null() {
|
if ini_entry.value.is_null() {
|
||||||
None
|
None
|
||||||
@ -99,7 +99,6 @@ impl ExecutorGlobals {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ini_hash_map
|
ini_hash_map
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +114,13 @@ impl ExecutorGlobals {
|
|||||||
/// could lead to a deadlock if the globals are already borrowed immutably
|
/// could lead to a deadlock if the globals are already borrowed immutably
|
||||||
/// or mutably.
|
/// or mutably.
|
||||||
pub fn take_exception() -> Option<ZBox<ZendObject>> {
|
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 globals = Self::get_mut();
|
||||||
|
|
||||||
let mut exception_ptr = std::ptr::null_mut();
|
let mut exception_ptr = std::ptr::null_mut();
|
||||||
@ -124,6 +130,25 @@ impl ExecutorGlobals {
|
|||||||
Some(unsafe { ZBox::from_raw(exception_ptr.as_mut()?) })
|
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
|
/// Request an interrupt of the PHP VM. This will call the registered
|
||||||
/// interrupt handler function.
|
/// interrupt handler function.
|
||||||
/// set with [`crate::ffi::zend_interrupt_function`].
|
/// set with [`crate::ffi::zend_interrupt_function`].
|
||||||
|
Loading…
Reference in New Issue
Block a user