From 0bf6424b2995a46e827a5c2988b7184467788f90 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 17 Oct 2023 11:42:33 +0200 Subject: [PATCH 01/17] feat(iterator): add helper for zend_object_iterator --- src/types/iterator.rs | 106 ++++++++++++++++++++++++++++++++++++++++++ src/types/mod.rs | 1 + src/types/object.rs | 11 +++++ 3 files changed, 118 insertions(+) create mode 100644 src/types/iterator.rs diff --git a/src/types/iterator.rs b/src/types/iterator.rs new file mode 100644 index 0000000..7703f0e --- /dev/null +++ b/src/types/iterator.rs @@ -0,0 +1,106 @@ +use crate::convert::FromZvalMut; +use crate::ffi::zend_object_iterator; +use crate::flags::DataType; +use crate::types::{ZendLong, Zval}; +use std::convert::TryInto; + +pub type ZendIterator = zend_object_iterator; + +pub struct Iter<'a> { + zi: &'a mut ZendIterator, +} + +impl ZendIterator { + pub fn iter(&mut self) -> Iter { + self.index = 0; + self.rewind(); + + Iter { zi: self } + } + + pub fn valid(&mut self) -> bool { + if let Some(valid) = unsafe { (*self.funcs).valid } { + unsafe { valid(&mut *self) != 0 } + } else { + true + } + } + + pub fn rewind(&mut self) { + if let Some(rewind) = unsafe { (*self.funcs).rewind } { + unsafe { + rewind(&mut *self); + } + } + } + + pub fn move_forward(&mut self) { + if let Some(move_forward) = unsafe { (*self.funcs).move_forward } { + unsafe { + move_forward(&mut *self); + } + } + } + + 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) }; + + Some(value) + } + + pub fn get_current_key(&mut self) -> Option { + let get_current_key = unsafe { (*self.funcs).get_current_key }?; + let mut key = Zval::new(); + unsafe { + get_current_key(&mut *self, &mut key); + } + + Some(key) + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = (u64, Option, &'a Zval); + + fn next(&mut self) -> Option { + // 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(); + + if !self.zi.valid() { + return None; + } + } + + self.zi.index += 1; + + let key = self.zi.get_current_key(); + let value = self.zi.get_current_data()?; + let real_index = self.zi.index - 1; + + Some(match key { + Some(key) => match key.is_long() { + false => (real_index, key.try_into().ok(), value), + true => ( + key.long().unwrap_or(real_index as ZendLong) as u64, + None, + value, + ), + }, + None => (real_index, None, 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 { + let zend_object = zval.object()?; + let ce = zend_object.get_class_entry_mut(); + let iterator = unsafe { ce.get_iterator?(&mut *ce, &mut *zval, 0) }; + + unsafe { iterator.as_mut() } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 888e056..84785eb 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -6,6 +6,7 @@ mod array; mod callable; mod class_object; +mod iterator; mod long; mod object; mod string; diff --git a/src/types/object.rs b/src/types/object.rs index ca8e526..0b83796 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -103,6 +103,17 @@ impl ZendObject { unsafe { self.ce.as_ref() }.expect("Could not retrieve class entry.") } + /// Returns the [`ClassEntry`] associated with this object. + /// + /// # Panics + /// + /// Panics if the class entry is invalid. + pub fn get_class_entry_mut(&self) -> &'static mut ClassEntry { + // SAFETY: it is OK to panic here since PHP would segfault anyway + // when encountering an object with no class entry. + unsafe { self.ce.as_mut() }.expect("Could not retrieve class entry.") + } + /// Attempts to retrieve the class name of the object. pub fn get_class_name(&self) -> Result { unsafe { From 8f425fe5f1c5613f81d8d5c07b74364fc4c9b16f Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 17 Oct 2023 15:19:13 +0200 Subject: [PATCH 02/17] feat(iterator): use an iter key for both array and iterator --- allowed_bindings.rs | 1 + src/types/array.rs | 42 ++++++++++++++++++++++--------------- src/types/iterator.rs | 48 ++++++++++++++++++++++++++++++++----------- src/types/object.rs | 4 ++-- src/zend/globals.rs | 30 +++++++++++++-------------- 5 files changed, 79 insertions(+), 46 deletions(-) diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 49b1585..6d7ddd9 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -92,6 +92,7 @@ bind! { zend_internal_arg_info, zend_is_callable, zend_is_identical, + zend_is_iterable, zend_long, zend_lookup_class_ex, zend_module_entry, diff --git a/src/types/array.rs b/src/types/array.rs index c40b8f3..0920f8e 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -25,6 +25,8 @@ use crate::{ flags::DataType, types::Zval, }; +use crate::types::iterator::IterKey; +use crate::types::ZendLong; /// A PHP hashtable. /// @@ -463,7 +465,7 @@ impl ZendHashTable { /// assert!(!ht.has_numerical_keys()); /// ``` pub fn has_numerical_keys(&self) -> bool { - !self.iter().any(|(_, k, _)| k.is_some()) + !self.iter().any(|(k, _)| !k.is_numerical()) } /// Checks if the hashtable has numerical, sequential keys. @@ -492,7 +494,7 @@ impl ZendHashTable { !self .iter() .enumerate() - .any(|(i, (k, strk, _))| i as u64 != k || strk.is_some()) + .any(|(i, (k, _))| IterKey::Long(i as u64) != k) } /// Returns an iterator over the key(s) and value contained inside the @@ -505,7 +507,7 @@ impl ZendHashTable { /// /// let mut ht = ZendHashTable::new(); /// - /// for (idx, key, val) in ht.iter() { + /// for (key, val) in ht.iter() { /// // ^ Index if inserted at an index. /// // ^ Optional string key, if inserted like a hashtable. /// // ^ Inserted value. @@ -548,7 +550,7 @@ impl Debug for ZendHashTable { f.debug_map() .entries( self.iter() - .map(|(k, k2, v)| (k2.unwrap_or_else(|| k.to_string()), v)), + .map(|(k, v)| (k.to_string(), v)), ) .finish() } @@ -594,7 +596,7 @@ impl<'a> Iter<'a> { } impl<'a> Iterator for Iter<'a> { - type Item = (u64, Option, &'a Zval); + type Item = (IterKey, &'a Zval); fn next(&mut self) -> Option { let key_type = unsafe { @@ -622,9 +624,13 @@ impl<'a> Iterator for Iter<'a> { &mut self.pos as *mut HashPosition, ) }; - let r: (u64, Option, &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 r = match key.is_long() { + true => (IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), value), + false => match key.try_into() { + Ok(key) => (IterKey::String(key), value), + Err(_) => (IterKey::Long(self.current_num), value), + }, }; unsafe { @@ -679,9 +685,13 @@ impl<'a> DoubleEndedIterator for Iter<'a> { &mut self.pos as *mut HashPosition, ) }; - let r: (u64, Option, &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 r = match key.is_long() { + true => (IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), value), + false => match key.try_into() { + Ok(key) => (IterKey::String(key), value), + Err(_) => (IterKey::Long(self.current_num), value), + }, }; unsafe { @@ -715,7 +725,7 @@ impl<'a> Iterator for Values<'a> { type Item = &'a Zval; fn next(&mut self) -> Option { - self.0.next().map(|(_, _, zval)| zval) + self.0.next().map(|(_, zval)| zval) } fn count(self) -> usize @@ -734,7 +744,7 @@ impl<'a> ExactSizeIterator for Values<'a> { impl<'a> DoubleEndedIterator for Values<'a> { fn next_back(&mut self) -> Option { - self.0.next_back().map(|(_, _, zval)| zval) + self.0.next_back().map(|(_, zval)| zval) } } @@ -780,9 +790,9 @@ where fn try_from(value: &'a ZendHashTable) -> Result { let mut hm = HashMap::with_capacity(value.len()); - for (idx, key, val) in value.iter() { + for (key, val) in value.iter() { 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 +859,7 @@ where fn try_from(value: &'a ZendHashTable) -> Result { let mut vec = Vec::with_capacity(value.len()); - for (_, _, val) in value.iter() { + for (_, val) in value.iter() { vec.push(T::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?); } diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 7703f0e..de07ca8 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -3,13 +3,10 @@ use crate::ffi::zend_object_iterator; use crate::flags::DataType; use crate::types::{ZendLong, Zval}; use std::convert::TryInto; +use std::fmt::Display; pub type ZendIterator = zend_object_iterator; -pub struct Iter<'a> { - zi: &'a mut ZendIterator, -} - impl ZendIterator { pub fn iter(&mut self) -> Iter { self.index = 0; @@ -60,8 +57,36 @@ impl ZendIterator { } } +#[derive(PartialEq)] +pub enum IterKey { + Long(u64), + String(String), +} + +impl IterKey { + pub fn is_numerical(&self) -> bool { + match self { + IterKey::Long(_) => true, + IterKey::String(_) => false, + } + } +} + +impl Display for IterKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + IterKey::Long(key) => write!(f, "{}", key), + IterKey::String(key) => write!(f, "{}", key), + } + } +} + +pub struct Iter<'a> { + zi: &'a mut ZendIterator, +} + impl<'a> Iterator for Iter<'a> { - type Item = (u64, Option, &'a Zval); + type Item = (IterKey, &'a Zval); fn next(&mut self) -> Option { // Call next when index > 0, so next is really called at the start of each iteration, which allow to work better with generator iterator @@ -81,14 +106,13 @@ impl<'a> Iterator for Iter<'a> { Some(match key { Some(key) => match key.is_long() { - false => (real_index, key.try_into().ok(), value), - true => ( - key.long().unwrap_or(real_index as ZendLong) as u64, - None, - value, - ), + false => match key.try_into() { + Ok(key) => (IterKey::String(key), value), + Err(_) => (IterKey::Long(real_index), value), + }, + true => (IterKey::Long(key.long().unwrap_or(real_index as ZendLong) as u64), value), }, - None => (real_index, None, value), + None => (IterKey::Long(real_index), value), }) } } diff --git a/src/types/object.rs b/src/types/object.rs index 0b83796..ef1ccc6 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -328,8 +328,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); } } diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 9761ab9..d1d047f 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -57,22 +57,20 @@ impl ExecutorGlobals { pub fn ini_values(&self) -> HashMap> { let hash_table = unsafe { &*self.ini_directives }; let mut ini_hash_map: HashMap> = 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::().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::().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 } From e5b1ebd99eb17a532a9d7ae56e0ca676d9017411 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 17 Oct 2023 20:30:24 +0200 Subject: [PATCH 03/17] feat(iterator): add iterable, add method to get traversable or iterable --- allowed_bindings.rs | 1 + src/describe/stub.rs | 1 + src/flags.rs | 20 +++++++++------- src/types/array.rs | 21 +++++++++-------- src/types/iterable.rs | 53 +++++++++++++++++++++++++++++++++++++++++++ src/types/iterator.rs | 27 +++++++++++++++------- src/types/mod.rs | 2 ++ src/types/object.rs | 20 ++++++++-------- src/types/zval.rs | 35 +++++++++++++++++++++++++++- src/zend/class.rs | 19 ++++++++++++++++ 10 files changed, 162 insertions(+), 37 deletions(-) create mode 100644 src/types/iterable.rs diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 6d7ddd9..0ef1b33 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -160,6 +160,7 @@ bind! { IS_UNDEF, IS_VOID, IS_PTR, + IS_ITERABLE, MAY_BE_ANY, MAY_BE_BOOL, PHP_INI_USER, diff --git a/src/describe/stub.rs b/src/describe/stub.rs index 065b275..17cfbfd 100644 --- a/src/describe/stub.rs +++ b/src/describe/stub.rs @@ -172,6 +172,7 @@ impl ToStub for DataType { DataType::Reference => "reference", DataType::Callable => "callable", DataType::Bool => "bool", + DataType::Iterable => "iterable", _ => "mixed", } ) diff --git a/src/flags.rs b/src/flags.rs index 60897a0..473d823 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -8,14 +8,14 @@ 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_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, ZEND_ACC_HAS_RETURN_TYPE, - ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, + E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, 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, + ZEND_ACC_HAS_RETURN_TYPE, ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, ZEND_ACC_IMPLICIT_ABSTRACT_CLASS, ZEND_ACC_INTERFACE, ZEND_ACC_LINKED, ZEND_ACC_NEARLY_LINKED, ZEND_ACC_NEVER_CACHE, ZEND_ACC_NO_DYNAMIC_PROPERTIES, ZEND_ACC_PRELOADED, ZEND_ACC_PRIVATE, ZEND_ACC_PROMOTED, ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, ZEND_ACC_RESOLVED_INTERFACES, @@ -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, @@ -275,6 +277,7 @@ impl DataType { DataType::Mixed => IS_MIXED, DataType::Bool => _IS_BOOL, DataType::Ptr => IS_PTR, + DataType::Iterable => IS_ITERABLE, } } } @@ -379,6 +382,7 @@ impl Display for DataType { DataType::Bool => write!(f, "Bool"), DataType::Mixed => write!(f, "Mixed"), DataType::Ptr => write!(f, "Pointer"), + DataType::Iterable => write!(f, "Iterable"), } } } diff --git a/src/types/array.rs b/src/types/array.rs index 0920f8e..974272e 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -10,6 +10,8 @@ use std::{ u64, }; +use crate::types::iterator::IterKey; +use crate::types::ZendLong; use crate::{ boxed::{ZBox, ZBoxable}, convert::{FromZval, IntoZval}, @@ -25,8 +27,6 @@ use crate::{ flags::DataType, types::Zval, }; -use crate::types::iterator::IterKey; -use crate::types::ZendLong; /// A PHP hashtable. /// @@ -512,7 +512,7 @@ impl ZendHashTable { /// // ^ Optional string key, if inserted like a hashtable. /// // ^ Inserted value. /// - /// dbg!(idx, key, val); + /// dbg!(key, val); /// } #[inline] pub fn iter(&self) -> Iter { @@ -548,10 +548,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, v)| (k.to_string(), v)), - ) + .entries(self.iter().map(|(k, v)| (k.to_string(), v))) .finish() } } @@ -626,7 +623,10 @@ impl<'a> Iterator for Iter<'a> { }; let r = match key.is_long() { - true => (IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), value), + true => ( + IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), + value, + ), false => match key.try_into() { Ok(key) => (IterKey::String(key), value), Err(_) => (IterKey::Long(self.current_num), value), @@ -687,7 +687,10 @@ impl<'a> DoubleEndedIterator for Iter<'a> { }; let r = match key.is_long() { - true => (IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), value), + true => ( + IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), + value, + ), false => match key.try_into() { Ok(key) => (IterKey::String(key), value), Err(_) => (IterKey::Long(self.current_num), value), diff --git a/src/types/iterable.rs b/src/types/iterable.rs new file mode 100644 index 0000000..2333302 --- /dev/null +++ b/src/types/iterable.rs @@ -0,0 +1,53 @@ +use super::array::Iter as ZendHashTableIter; +use super::iterator::Iter as ZendIteratorIter; +use crate::convert::FromZval; +use crate::flags::DataType; +use crate::types::iterator::IterKey; +use crate::types::{ZendHashTable, ZendIterator, Zval}; + +#[derive(Debug)] +pub enum Iterable<'a> { + Array(&'a ZendHashTable), + Traversable(&'a mut ZendIterator), +} + +impl<'a> Iterable<'a> { + pub fn iter(&mut self) -> Iter { + match self { + Iterable::Array(array) => Iter::Array(array.iter()), + Iterable::Traversable(traversable) => Iter::Traversable(traversable.iter()), + } + } +} + +impl<'a> FromZval<'a> for Iterable<'a> { + const TYPE: DataType = DataType::Iterable; + + fn from_zval(zval: &'a Zval) -> Option { + if let Some(array) = zval.array() { + return Some(Iterable::Array(array)); + } + + if let Some(traversable) = zval.traversable() { + return Some(Iterable::Traversable(traversable)); + } + + None + } +} + +pub enum Iter<'a> { + Array(ZendHashTableIter<'a>), + Traversable(ZendIteratorIter<'a>), +} + +impl<'a> Iterator for Iter<'a> { + type Item = (IterKey, &'a Zval); + + fn next(&mut self) -> Option { + match self { + Iter::Array(array) => array.next(), + Iter::Traversable(traversable) => traversable.next(), + } + } +} diff --git a/src/types/iterator.rs b/src/types/iterator.rs index de07ca8..f3a4a29 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -3,8 +3,14 @@ use crate::ffi::zend_object_iterator; use crate::flags::DataType; use crate::types::{ZendLong, Zval}; use std::convert::TryInto; -use std::fmt::Display; +use std::fmt::{Debug, Display, Formatter}; +/// A PHP Iterator. +/// +/// In PHP, iterators are represented as zend_object_iterator. This allow user to iterate +/// over object implementing Traversable interface using foreach. +/// +/// ``` pub type ZendIterator = zend_object_iterator; impl ZendIterator { @@ -57,7 +63,13 @@ impl ZendIterator { } } -#[derive(PartialEq)] +impl Debug for ZendIterator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ZendIterator").finish() + } +} + +#[derive(Debug, PartialEq)] pub enum IterKey { Long(u64), String(String), @@ -110,7 +122,10 @@ impl<'a> Iterator for Iter<'a> { Ok(key) => (IterKey::String(key), value), Err(_) => (IterKey::Long(real_index), value), }, - true => (IterKey::Long(key.long().unwrap_or(real_index as ZendLong) as u64), value), + true => ( + IterKey::Long(key.long().unwrap_or(real_index as ZendLong) as u64), + value, + ), }, None => (IterKey::Long(real_index), value), }) @@ -121,10 +136,6 @@ impl<'a> FromZvalMut<'a> for &'a mut ZendIterator { const TYPE: DataType = DataType::Object(Some("Traversable")); fn from_zval_mut(zval: &'a mut Zval) -> Option { - let zend_object = zval.object()?; - let ce = zend_object.get_class_entry_mut(); - let iterator = unsafe { ce.get_iterator?(&mut *ce, &mut *zval, 0) }; - - unsafe { iterator.as_mut() } + zval.object()?.get_class_entry().get_iterator(zval, false) } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 84785eb..99bfdb3 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -6,6 +6,7 @@ mod array; mod callable; mod class_object; +mod iterable; mod iterator; mod long; mod object; @@ -15,6 +16,7 @@ mod zval; pub use array::ZendHashTable; pub use callable::ZendCallable; pub use class_object::ZendClassObject; +pub use iterator::ZendIterator; pub use long::ZendLong; pub use object::{PropertyQuery, ZendObject}; pub use string::ZendStr; diff --git a/src/types/object.rs b/src/types/object.rs index ef1ccc6..138f31a 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -103,17 +103,6 @@ impl ZendObject { unsafe { self.ce.as_ref() }.expect("Could not retrieve class entry.") } - /// Returns the [`ClassEntry`] associated with this object. - /// - /// # Panics - /// - /// Panics if the class entry is invalid. - pub fn get_class_entry_mut(&self) -> &'static mut ClassEntry { - // SAFETY: it is OK to panic here since PHP would segfault anyway - // when encountering an object with no class entry. - unsafe { self.ce.as_mut() }.expect("Could not retrieve class entry.") - } - /// Attempts to retrieve the class name of the object. pub fn get_class_name(&self) -> Result { unsafe { @@ -144,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 { let mut retval = Zval::new(); diff --git a/src/types/zval.rs b/src/types/zval.rs index 94b62aa..ce4e55d 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -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, @@ -237,6 +239,22 @@ impl Zval { ZendCallable::new(self).ok() } + pub fn traversable(&self) -> Option<&mut ZendIterator> { + if self.is_traversable() { + self.object()?.get_class_entry().get_iterator(self, false) + } else { + None + } + } + + pub fn iterable(&self) -> Option { + if self.is_iterable() { + Iterable::from_zval(self) + } else { + None + } + } + /// Returns the value of the zval if it is a pointer. /// /// # Safety @@ -347,6 +365,20 @@ 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 @@ -601,6 +633,7 @@ impl Debug for Zval { DataType::ConstantExpression => field!(Option::<()>::None), DataType::Void => field!(Option::<()>::None), DataType::Bool => field!(self.bool()), + DataType::Iterable => field!(self.iterable()), // SAFETY: We are not accessing the pointer. DataType::Ptr => field!(unsafe { self.ptr::() }), }; diff --git a/src/zend/class.rs b/src/zend/class.rs index 4c3c23d..d9765a3 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -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()) } } From 26e06ad90f68a00915f710bcb9155bf2a4287d7e Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 17 Oct 2023 20:45:23 +0200 Subject: [PATCH 04/17] reduce duplicated code for iter key --- src/types/array.rs | 25 ++++++------------------- src/types/iterator.rs | 28 ++++++++++++++++------------ 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/types/array.rs b/src/types/array.rs index 974272e..29e5c6e 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -11,7 +11,6 @@ use std::{ }; use crate::types::iterator::IterKey; -use crate::types::ZendLong; use crate::{ boxed::{ZBox, ZBoxable}, convert::{FromZval, IntoZval}, @@ -622,15 +621,9 @@ impl<'a> Iterator for Iter<'a> { ) }; - let r = match key.is_long() { - true => ( - IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), - value, - ), - false => match key.try_into() { - Ok(key) => (IterKey::String(key), value), - Err(_) => (IterKey::Long(self.current_num), value), - }, + let r = match IterKey::from_zval(&key) { + Some(key) => (key, value), + None => (IterKey::Long(self.current_num), value), }; unsafe { @@ -686,15 +679,9 @@ impl<'a> DoubleEndedIterator for Iter<'a> { ) }; - let r = match key.is_long() { - true => ( - IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), - value, - ), - false => match key.try_into() { - Ok(key) => (IterKey::String(key), value), - Err(_) => (IterKey::Long(self.current_num), value), - }, + let r = match IterKey::from_zval(&key) { + Some(key) => (key, value), + None => (IterKey::Long(self.current_num), value), }; unsafe { diff --git a/src/types/iterator.rs b/src/types/iterator.rs index f3a4a29..5598076 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -1,8 +1,7 @@ -use crate::convert::FromZvalMut; +use crate::convert::{FromZval, FromZvalMut}; use crate::ffi::zend_object_iterator; use crate::flags::DataType; -use crate::types::{ZendLong, Zval}; -use std::convert::TryInto; +use crate::types::Zval; use std::fmt::{Debug, Display, Formatter}; /// A PHP Iterator. @@ -93,6 +92,17 @@ impl Display for IterKey { } } +impl FromZval<'_> for IterKey { + const TYPE: DataType = DataType::String; + + fn from_zval(zval: &Zval) -> Option { + match zval.long() { + Some(key) => Some(IterKey::Long(key as u64)), + None => zval.string().map(|key| IterKey::String(key)), + } + } +} + pub struct Iter<'a> { zi: &'a mut ZendIterator, } @@ -117,15 +127,9 @@ impl<'a> Iterator for Iter<'a> { let real_index = self.zi.index - 1; Some(match key { - Some(key) => match key.is_long() { - false => match key.try_into() { - Ok(key) => (IterKey::String(key), value), - Err(_) => (IterKey::Long(real_index), value), - }, - true => ( - IterKey::Long(key.long().unwrap_or(real_index as ZendLong) as u64), - value, - ), + Some(key) => match IterKey::from_zval(&key) { + Some(key) => (key, value), + None => (IterKey::Long(real_index), value), }, None => (IterKey::Long(real_index), value), }) From b95bade2e2c95fec45be93b35db4b1c78bf280ed Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Wed, 18 Oct 2023 10:07:19 +0200 Subject: [PATCH 05/17] fix clippy feedback --- src/types/iterator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 5598076..03132f2 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -98,7 +98,7 @@ impl FromZval<'_> for IterKey { fn from_zval(zval: &Zval) -> Option { match zval.long() { Some(key) => Some(IterKey::Long(key as u64)), - None => zval.string().map(|key| IterKey::String(key)), + None => zval.string().map(IterKey::String), } } } From 524a18ddb584001beaf87333b4833b75458a7c20 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Wed, 18 Oct 2023 10:39:27 +0200 Subject: [PATCH 06/17] feat(iterator): add documentation --- guide/src/types/iterable.md | 59 +++++++++++++++++++++++++++++++++++++ guide/src/types/iterator.md | 50 +++++++++++++++++++++++++++++++ src/types/iterable.rs | 4 +++ src/types/iterator.rs | 36 ++++++++++++++++++++++ src/types/mod.rs | 1 + 5 files changed, 150 insertions(+) create mode 100644 guide/src/types/iterable.md create mode 100644 guide/src/types/iterator.md diff --git a/guide/src/types/iterable.md b/guide/src/types/iterable.md new file mode 100644 index 0000000..f79024f --- /dev/null +++ b/guide/src/types/iterable.md @@ -0,0 +1,59 @@ +# `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() { + println!("k: {} v: {}", k, v.string().unwrap()); + } +} +# fn main() {} +``` + +## PHP example + +```php + 'world'; + yield 'rust' => 'php'; + yield 'okk'; +}; + +$array = [ + 'hello' => 'world', + 'rust' => 'php', + 'okk', +]; + +test_iterable($generator()); +test_iterable($array); +``` + +Output: + +```text +k: hello v: world +k: rust v: php +k: 0 v: okk +k: hello v: world +k: rust v: php +k: 0 v: okk +``` diff --git a/guide/src/types/iterator.md b/guide/src/types/iterator.md new file mode 100644 index 0000000..fc187c1 --- /dev/null +++ b/guide/src/types/iterator.md @@ -0,0 +1,50 @@ +# `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`. + +## 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() { + println!("k: {} v: {}", k, v.string().unwrap()); + } +} +# fn main() {} +``` + +## PHP example + +```php + 'world'; + yield 'rust' => 'php'; + yield 'okk'; +}; + +test_iterator($generator()); +``` + +Output: + +```text +k: hello v: world +k: rust v: php +k: 0 v: okk +``` diff --git a/src/types/iterable.rs b/src/types/iterable.rs index 2333302..f4a0208 100644 --- a/src/types/iterable.rs +++ b/src/types/iterable.rs @@ -5,6 +5,8 @@ use crate::flags::DataType; use crate::types::iterator::IterKey; 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), @@ -12,6 +14,7 @@ pub enum Iterable<'a> { } impl<'a> Iterable<'a> { + /// Creates a new rust iterator from a PHP iterable. pub fn iter(&mut self) -> Iter { match self { Iterable::Array(array) => Iter::Array(array.iter()), @@ -36,6 +39,7 @@ impl<'a> FromZval<'a> for Iterable<'a> { } } +/// Rust iterator over a PHP iterable. pub enum Iter<'a> { Array(ZendHashTableIter<'a>), Traversable(ZendIteratorIter<'a>), diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 03132f2..3d3dc2d 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -13,6 +13,11 @@ use std::fmt::{Debug, Display, Formatter}; pub type ZendIterator = zend_object_iterator; impl ZendIterator { + /// Creates a new rust iterator from a zend_object_iterator. + /// + /// # Returns + /// + /// Returns a iterator over the zend_object_iterator. pub fn iter(&mut self) -> Iter { self.index = 0; self.rewind(); @@ -20,6 +25,10 @@ impl ZendIterator { Iter { zi: self } } + /// 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 } { unsafe { valid(&mut *self) != 0 } @@ -28,6 +37,10 @@ impl ZendIterator { } } + /// 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 pub fn rewind(&mut self) { if let Some(rewind) = unsafe { (*self.funcs).rewind } { unsafe { @@ -36,6 +49,10 @@ impl ZendIterator { } } + /// 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 pub fn move_forward(&mut self) { if let Some(move_forward) = unsafe { (*self.funcs).move_forward } { unsafe { @@ -44,6 +61,12 @@ impl ZendIterator { } } + /// 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) }; @@ -51,6 +74,12 @@ impl ZendIterator { 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 { let get_current_key = unsafe { (*self.funcs).get_current_key }?; let mut key = Zval::new(); @@ -74,7 +103,13 @@ pub enum IterKey { String(String), } +/// Represent the key of a PHP iterator, which can be either a long or a string. impl IterKey { + /// Check if the key is numerical. + /// + /// # Returns + /// + /// Returns true if the key is numerical, false otherwise. pub fn is_numerical(&self) -> bool { match self { IterKey::Long(_) => true, @@ -103,6 +138,7 @@ impl FromZval<'_> for IterKey { } } +/// Immutable iterator upon a reference to a PHP iterator. pub struct Iter<'a> { zi: &'a mut ZendIterator, } diff --git a/src/types/mod.rs b/src/types/mod.rs index 99bfdb3..846c8d1 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -16,6 +16,7 @@ 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}; From 57577b687c2747fca9b2d0ac8e1891d1825726b9 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Wed, 18 Oct 2023 12:11:53 +0200 Subject: [PATCH 07/17] fix doc --- src/types/iterator.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 3d3dc2d..6aa71a8 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -8,8 +8,6 @@ use std::fmt::{Debug, Display, Formatter}; /// /// In PHP, iterators are represented as zend_object_iterator. This allow user to iterate /// over object implementing Traversable interface using foreach. -/// -/// ``` pub type ZendIterator = zend_object_iterator; impl ZendIterator { @@ -28,7 +26,7 @@ impl ZendIterator { /// 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 + /// see pub fn valid(&mut self) -> bool { if let Some(valid) = unsafe { (*self.funcs).valid } { unsafe { valid(&mut *self) != 0 } @@ -40,7 +38,7 @@ impl ZendIterator { /// 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 + /// see pub fn rewind(&mut self) { if let Some(rewind) = unsafe { (*self.funcs).rewind } { unsafe { @@ -52,7 +50,7 @@ impl ZendIterator { /// 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 + /// see pub fn move_forward(&mut self) { if let Some(move_forward) = unsafe { (*self.funcs).move_forward } { unsafe { From a8f15d8f53e5672383ff684954d822bdb4a964d9 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Thu, 19 Oct 2023 08:54:22 +0200 Subject: [PATCH 08/17] feat(iterator): handle exception --- src/types/iterable.rs | 11 +++--- src/types/iterator.rs | 87 +++++++++++++++++++++++++++++-------------- src/zend/globals.rs | 22 +++++++++++ 3 files changed, 87 insertions(+), 33 deletions(-) diff --git a/src/types/iterable.rs b/src/types/iterable.rs index f4a0208..7eb81e6 100644 --- a/src/types/iterable.rs +++ b/src/types/iterable.rs @@ -1,6 +1,7 @@ use super::array::Iter as ZendHashTableIter; use super::iterator::Iter as ZendIteratorIter; use crate::convert::FromZval; +use crate::exception::PhpResult; use crate::flags::DataType; use crate::types::iterator::IterKey; use crate::types::{ZendHashTable, ZendIterator, Zval}; @@ -15,10 +16,10 @@ pub enum Iterable<'a> { impl<'a> Iterable<'a> { /// Creates a new rust iterator from a PHP iterable. - pub fn iter(&mut self) -> Iter { + pub fn iter(&mut self) -> PhpResult { match self { - Iterable::Array(array) => Iter::Array(array.iter()), - Iterable::Traversable(traversable) => Iter::Traversable(traversable.iter()), + Iterable::Array(array) => Ok(Iter::Array(array.iter())), + Iterable::Traversable(traversable) => Ok(Iter::Traversable(traversable.iter()?)), } } } @@ -46,11 +47,11 @@ pub enum Iter<'a> { } impl<'a> Iterator for Iter<'a> { - type Item = (IterKey, &'a Zval); + type Item = PhpResult<(IterKey, &'a Zval)>; fn next(&mut self) -> Option { match self { - Iter::Array(array) => array.next(), + Iter::Array(array) => array.next().map(Ok), Iter::Traversable(traversable) => traversable.next(), } } diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 6aa71a8..57d48bc 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -1,7 +1,9 @@ use crate::convert::{FromZval, FromZvalMut}; -use crate::ffi::zend_object_iterator; +use crate::ffi::{zend_object_iterator, ZEND_RESULT_CODE_SUCCESS}; use crate::flags::DataType; +use crate::prelude::PhpResult; use crate::types::Zval; +use crate::zend::ExecutorGlobals; use std::fmt::{Debug, Display, Formatter}; /// A PHP Iterator. @@ -16,22 +18,26 @@ impl ZendIterator { /// # Returns /// /// Returns a iterator over the zend_object_iterator. - pub fn iter(&mut self) -> Iter { + pub fn iter(&mut self) -> PhpResult { self.index = 0; - self.rewind(); + self.rewind()?; - Iter { zi: self } + Ok(Iter { zi: self }) } /// 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 - pub fn valid(&mut self) -> bool { + pub fn valid(&mut self) -> PhpResult { if let Some(valid) = unsafe { (*self.funcs).valid } { - unsafe { valid(&mut *self) != 0 } + let valid = unsafe { valid(&mut *self) == ZEND_RESULT_CODE_SUCCESS }; + + ExecutorGlobals::throw_if_exception()?; + + Ok(valid) } else { - true + Ok(true) } } @@ -39,24 +45,28 @@ impl ZendIterator { /// /// As an example this will call the user defined rewind method of the ['\Iterator'] interface. /// see - pub fn rewind(&mut self) { + pub fn rewind(&mut self) -> PhpResult<()> { if let Some(rewind) = unsafe { (*self.funcs).rewind } { unsafe { rewind(&mut *self); } } + + ExecutorGlobals::throw_if_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 - pub fn move_forward(&mut self) { + pub fn move_forward(&mut self) -> PhpResult<()> { if let Some(move_forward) = unsafe { (*self.funcs).move_forward } { unsafe { move_forward(&mut *self); } } + + ExecutorGlobals::throw_if_exception() } /// Get the current data of the iterator. @@ -65,11 +75,16 @@ impl ZendIterator { /// /// 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 }?; + pub fn get_current_data<'a>(&mut self) -> PhpResult> { + let get_current_data = match unsafe { (*self.funcs).get_current_data } { + Some(get_current_data) => get_current_data, + None => return Ok(None), + }; let value = unsafe { &*get_current_data(&mut *self) }; - Some(value) + ExecutorGlobals::throw_if_exception()?; + + Ok(Some(value)) } /// Get the current key of the iterator. @@ -78,14 +93,21 @@ impl ZendIterator { /// /// Returns a new ['Zval'] containing the current key of the iterator if available /// , ['None'] otherwise. - pub fn get_current_key(&mut self) -> Option { - let get_current_key = unsafe { (*self.funcs).get_current_key }?; + pub fn get_current_key(&mut self) -> PhpResult> { + let get_current_key = match unsafe { (*self.funcs).get_current_key } { + Some(get_current_key) => get_current_key, + None => return Ok(None), + }; + let mut key = Zval::new(); + unsafe { get_current_key(&mut *self, &mut key); } - Some(key) + ExecutorGlobals::throw_if_exception()?; + + Ok(Some(key)) } } @@ -142,31 +164,40 @@ pub struct Iter<'a> { } impl<'a> Iterator for Iter<'a> { - type Item = (IterKey, &'a Zval); + type Item = PhpResult<(IterKey, &'a Zval)>; fn next(&mut self) -> Option { // 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(); - - if !self.zi.valid() { - return None; + if let Err(err) = self.zi.move_forward() { + return Some(Err(err)); } } + match self.zi.valid() { + Err(err) => return Some(Err(err)), + Ok(false) => return None, + Ok(true) => (), + } + self.zi.index += 1; - let key = self.zi.get_current_key(); - let value = self.zi.get_current_data()?; let real_index = self.zi.index - 1; - Some(match key { - Some(key) => match IterKey::from_zval(&key) { - Some(key) => (key, value), - None => (IterKey::Long(real_index), value), + let key = match self.zi.get_current_key() { + Err(err) => return Some(Err(err)), + Ok(None) => IterKey::Long(real_index), + Ok(Some(key)) => match IterKey::from_zval(&key) { + Some(key) => key, + None => IterKey::Long(real_index), }, - None => (IterKey::Long(real_index), value), - }) + }; + + match self.zi.get_current_data() { + Err(err) => Some(Err(err)), + Ok(None) => None, + Ok(Some(value)) => Some(Ok((key, value))), + } } } diff --git a/src/zend/globals.rs b/src/zend/globals.rs index d1d047f..c3883fa 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -6,6 +6,7 @@ use std::ops::{Deref, DerefMut}; 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::{_zend_executor_globals, ext_php_rs_executor_globals, zend_ini_entry}; @@ -87,6 +88,13 @@ impl ExecutorGlobals { /// could lead to a deadlock if the globals are already borrowed immutably /// or mutably. pub fn take_exception() -> Option> { + { + // 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(); @@ -96,6 +104,20 @@ impl ExecutorGlobals { Some(unsafe { ZBox::from_raw(exception_ptr.as_mut()?) }) } + /// 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`]. From cf6c362c00ef5b02dcfb43b6fdf776bb1488bff0 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 20 Oct 2023 16:50:52 +0200 Subject: [PATCH 09/17] feat(iterator): simply stop when there is an exception, do not take it from EG to avoid segfault --- docsrs_bindings.rs | 4 + guide/src/types/iterable.md | 2 +- guide/src/types/iterator.md | 2 +- src/types/iterable.rs | 11 +- src/types/iterator.rs | 220 ++++++++++++++++++++++++++++-------- src/types/iterator.test.php | 49 ++++++++ src/types/zval.rs | 2 + src/zend/globals.rs | 5 + 8 files changed, 243 insertions(+), 52 deletions(-) create mode 100644 src/types/iterator.test.php diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 2d22490..8b77bbf 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -17,6 +17,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_PTR: u32 = 13; @@ -1455,6 +1456,9 @@ extern "C" { named_params: *mut HashTable, ); } +extern "C" { + pub fn zend_is_iterable(iterable: *mut 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; diff --git a/guide/src/types/iterable.md b/guide/src/types/iterable.md index f79024f..0e689c1 100644 --- a/guide/src/types/iterable.md +++ b/guide/src/types/iterable.md @@ -19,7 +19,7 @@ that implements the `Traversable` interface. This means that any value that can # use ext_php_rs::types::Iterable; #[php_function] pub fn test_iterable(mut iterable: Iterable) { - for (k, v) in iterable.iter() { + for (k, v) in iterable.iter().expect("cannot get iterable") { println!("k: {} v: {}", k, v.string().unwrap()); } } diff --git a/guide/src/types/iterator.md b/guide/src/types/iterator.md index fc187c1..af7d09f 100644 --- a/guide/src/types/iterator.md +++ b/guide/src/types/iterator.md @@ -20,7 +20,7 @@ used but also a the result of a `query` call with `PDO`. # use ext_php_rs::types::ZendIterator; #[php_function] pub fn test_iterator(iterator: &mut ZendIterator) { - for (k, v) in iterator.iter() { + for (k, v) in iterator.iter().expect("cannot get iterator") { println!("k: {} v: {}", k, v.string().unwrap()); } } diff --git a/src/types/iterable.rs b/src/types/iterable.rs index 7eb81e6..dd5c13e 100644 --- a/src/types/iterable.rs +++ b/src/types/iterable.rs @@ -1,7 +1,6 @@ use super::array::Iter as ZendHashTableIter; use super::iterator::Iter as ZendIteratorIter; use crate::convert::FromZval; -use crate::exception::PhpResult; use crate::flags::DataType; use crate::types::iterator::IterKey; use crate::types::{ZendHashTable, ZendIterator, Zval}; @@ -16,10 +15,10 @@ pub enum Iterable<'a> { impl<'a> Iterable<'a> { /// Creates a new rust iterator from a PHP iterable. - pub fn iter(&mut self) -> PhpResult { + pub fn iter(&mut self) -> Option { match self { - Iterable::Array(array) => Ok(Iter::Array(array.iter())), - Iterable::Traversable(traversable) => Ok(Iter::Traversable(traversable.iter()?)), + Iterable::Array(array) => Some(Iter::Array(array.iter())), + Iterable::Traversable(traversable) => Some(Iter::Traversable(traversable.iter()?)), } } } @@ -47,11 +46,11 @@ pub enum Iter<'a> { } impl<'a> Iterator for Iter<'a> { - type Item = PhpResult<(IterKey, &'a Zval)>; + type Item = (IterKey, &'a Zval); fn next(&mut self) -> Option { match self { - Iter::Array(array) => array.next().map(Ok), + Iter::Array(array) => array.next(), Iter::Traversable(traversable) => traversable.next(), } } diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 57d48bc..1dbfa0b 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -1,7 +1,6 @@ use crate::convert::{FromZval, FromZvalMut}; use crate::ffi::{zend_object_iterator, ZEND_RESULT_CODE_SUCCESS}; use crate::flags::DataType; -use crate::prelude::PhpResult; use crate::types::Zval; use crate::zend::ExecutorGlobals; use std::fmt::{Debug, Display, Formatter}; @@ -18,26 +17,31 @@ impl ZendIterator { /// # Returns /// /// Returns a iterator over the zend_object_iterator. - pub fn iter(&mut self) -> PhpResult { + pub fn iter(&mut self) -> Option { self.index = 0; - self.rewind()?; - Ok(Iter { zi: self }) + 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 - pub fn valid(&mut self) -> PhpResult { + pub fn valid(&mut self) -> bool { if let Some(valid) = unsafe { (*self.funcs).valid } { let valid = unsafe { valid(&mut *self) == ZEND_RESULT_CODE_SUCCESS }; - ExecutorGlobals::throw_if_exception()?; + if ExecutorGlobals::has_exception() { + return false; + } - Ok(valid) + valid } else { - Ok(true) + true } } @@ -45,28 +49,38 @@ impl ZendIterator { /// /// As an example this will call the user defined rewind method of the ['\Iterator'] interface. /// see - pub fn rewind(&mut self) -> PhpResult<()> { + /// + /// # 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::throw_if_exception() + !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 - pub fn move_forward(&mut self) -> PhpResult<()> { + /// + /// # 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::throw_if_exception() + !ExecutorGlobals::has_exception() } /// Get the current data of the iterator. @@ -75,16 +89,15 @@ impl ZendIterator { /// /// Returns a reference to the current data of the iterator if available /// , ['None'] otherwise. - pub fn get_current_data<'a>(&mut self) -> PhpResult> { - let get_current_data = match unsafe { (*self.funcs).get_current_data } { - Some(get_current_data) => get_current_data, - None => return Ok(None), - }; + 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) }; - ExecutorGlobals::throw_if_exception()?; + if ExecutorGlobals::has_exception() { + return None; + } - Ok(Some(value)) + Some(value) } /// Get the current key of the iterator. @@ -93,21 +106,19 @@ impl ZendIterator { /// /// Returns a new ['Zval'] containing the current key of the iterator if available /// , ['None'] otherwise. - pub fn get_current_key(&mut self) -> PhpResult> { - let get_current_key = match unsafe { (*self.funcs).get_current_key } { - Some(get_current_key) => get_current_key, - None => return Ok(None), - }; - + pub fn get_current_key(&mut self) -> Option { + let get_current_key = unsafe { (*self.funcs).get_current_key? }; let mut key = Zval::new(); unsafe { get_current_key(&mut *self, &mut key); } - ExecutorGlobals::throw_if_exception()?; + if ExecutorGlobals::has_exception() { + return None; + } - Ok(Some(key)) + Some(key) } } @@ -164,20 +175,16 @@ pub struct Iter<'a> { } impl<'a> Iterator for Iter<'a> { - type Item = PhpResult<(IterKey, &'a Zval)>; + type Item = (IterKey, &'a Zval); fn next(&mut self) -> Option { // 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 { - if let Err(err) = self.zi.move_forward() { - return Some(Err(err)); - } + if self.zi.index > 0 && !self.zi.move_forward() { + return None; } - match self.zi.valid() { - Err(err) => return Some(Err(err)), - Ok(false) => return None, - Ok(true) => (), + if !self.zi.valid() { + return None; } self.zi.index += 1; @@ -185,19 +192,14 @@ impl<'a> Iterator for Iter<'a> { let real_index = self.zi.index - 1; let key = match self.zi.get_current_key() { - Err(err) => return Some(Err(err)), - Ok(None) => IterKey::Long(real_index), - Ok(Some(key)) => match IterKey::from_zval(&key) { + None => IterKey::Long(real_index), + Some(key) => match IterKey::from_zval(&key) { Some(key) => key, None => IterKey::Long(real_index), }, }; - match self.zi.get_current_data() { - Err(err) => Some(Err(err)), - Ok(None) => None, - Ok(Some(value)) => Some(Ok((key, value))), - } + self.zi.get_current_data().map(|value| (key, value)) } } @@ -208,3 +210,133 @@ impl<'a> FromZvalMut<'a> for &'a mut ZendIterator { zval.object()?.get_class_entry().get_iterator(zval, false) } } + +#[cfg(test)] +#[cfg(feature = "embed")] +mod tests { + use crate::embed::Embed; + use crate::types::iterator::IterKey; + + #[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, IterKey::Long(0)); + assert!(value.is_long()); + assert_eq!(value.long().unwrap(), 1); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key, IterKey::Long(1)); + assert!(value.is_long()); + assert_eq!(value.long().unwrap(), 2); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key, IterKey::Long(2)); + assert!(value.is_long()); + assert_eq!(value.long().unwrap(), 3); + + 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_numerical()); + assert_eq!(key, IterKey::String("key".to_string())); + assert!(value.is_string()); + assert_eq!(value.string().unwrap(), "foo"); + + let (key, value) = iter.next().unwrap(); + + assert!(key.is_numerical()); + assert_eq!(key, IterKey::Long(10)); + assert!(value.is_string()); + assert_eq!(value.string().unwrap(), "bar"); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key, IterKey::Long(2)); + assert!(value.is_string()); + assert_eq!(value.string().unwrap(), "baz"); + + 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, IterKey::String("key".to_string())); + assert!(value.is_string()); + assert_eq!(value.string().unwrap(), "foo"); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key, IterKey::Long(10)); + assert!(value.is_string()); + assert_eq!(value.string().unwrap(), "bar"); + + let (key, value) = iter.next().unwrap(); + + assert_eq!(key, IterKey::Long(2)); + assert!(value.is_string()); + assert_eq!(value.string().unwrap(), "baz"); + + let next = iter.next(); + + assert!(next.is_none()); + } + }); + } +} diff --git a/src/types/iterator.test.php b/src/types/iterator.test.php new file mode 100644 index 0000000..216d663 --- /dev/null +++ b/src/types/iterator.test.php @@ -0,0 +1,49 @@ +count) { + 0 => 'foo', + 1 => 'bar', + 2 => 'baz', + default => null, + }; + } + + public function next() + { + $this->count++; + } + + public function key() + { + return match ($this->count) { + 0 => 'key', + 1 => 10, + 2 => 2, + default => null, + }; + } + + public function valid() + { + return $this->count < 3; + } + + public function rewind() + { + $this->count = 0; + } +} + +$generator = create_generator(); +$iterator = new TestIterator(); diff --git a/src/types/zval.rs b/src/types/zval.rs index ce4e55d..24f4565 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -239,6 +239,7 @@ 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) @@ -247,6 +248,7 @@ impl Zval { } } + /// Returns an iterable over the zval if it is an array or traversable. (is iterable) pub fn iterable(&self) -> Option { if self.is_iterable() { Iterable::from_zval(self) diff --git a/src/zend/globals.rs b/src/zend/globals.rs index c3883fa..93aff16 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -104,6 +104,11 @@ 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`]. /// From 678073a533bb19f0e87c5fc1bc84cb31c692256f Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 14:43:05 +0100 Subject: [PATCH 10/17] Cleanup --- guide/src/types/iterator.md | 2 ++ src/flags.rs | 9 ++++----- src/types/iterable.rs | 4 ++-- src/types/iterator.rs | 31 ++++++++++++++++--------------- src/types/zval.rs | 6 ++++-- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/guide/src/types/iterator.md b/guide/src/types/iterator.md index af7d09f..b113e41 100644 --- a/guide/src/types/iterator.md +++ b/guide/src/types/iterator.md @@ -11,6 +11,8 @@ the variable. This means that any value, at the exception of an `array`, that ca 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 diff --git a/src/flags.rs b/src/flags.rs index 6857a76..c91fc19 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -8,11 +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_ITERABLE, - 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, diff --git a/src/types/iterable.rs b/src/types/iterable.rs index dd5c13e..fb266c1 100644 --- a/src/types/iterable.rs +++ b/src/types/iterable.rs @@ -5,8 +5,8 @@ use crate::flags::DataType; use crate::types::iterator::IterKey; 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. +/// 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), diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 1dbfa0b..7d42625 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -7,8 +7,8 @@ use std::fmt::{Debug, Display, Formatter}; /// A PHP Iterator. /// -/// In PHP, iterators are represented as zend_object_iterator. This allow user to iterate -/// over object implementing Traversable interface using foreach. +/// In PHP, iterators are represented as zend_object_iterator. This allow user +/// to iterate over object implementing Traversable interface using foreach. pub type ZendIterator = zend_object_iterator; impl ZendIterator { @@ -29,8 +29,8 @@ impl ZendIterator { /// 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 + /// As an example this will call the user defined valid method of the + /// ['\Iterator'] interface. see pub fn valid(&mut self) -> bool { if let Some(valid) = unsafe { (*self.funcs).valid } { let valid = unsafe { valid(&mut *self) == ZEND_RESULT_CODE_SUCCESS }; @@ -47,13 +47,13 @@ impl ZendIterator { /// Rewind the iterator to the first element. /// - /// As an example this will call the user defined rewind method of the ['\Iterator'] interface. - /// see + /// As an example this will call the user defined rewind method of the + /// ['\Iterator'] interface. see /// /// # Returns /// - /// Returns true if the iterator was successfully rewind, false otherwise. (when there is - /// an exception during rewind) + /// 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 { @@ -66,13 +66,13 @@ impl ZendIterator { /// Move the iterator forward to the next element. /// - /// As an example this will call the user defined next method of the ['\Iterator'] interface. - /// see + /// As an example this will call the user defined next method of the + /// ['\Iterator'] interface. see /// /// # Returns /// - /// Returns true if the iterator was successfully move, false otherwise. (when there is - /// an exception during next) + /// 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 { @@ -104,8 +104,8 @@ impl ZendIterator { /// /// # Returns /// - /// Returns a new ['Zval'] containing the current key of the iterator if available - /// , ['None'] otherwise. + /// Returns a new ['Zval'] containing the current key of the iterator if + /// available , ['None'] otherwise. pub fn get_current_key(&mut self) -> Option { let get_current_key = unsafe { (*self.funcs).get_current_key? }; let mut key = Zval::new(); @@ -178,7 +178,8 @@ impl<'a> Iterator for Iter<'a> { type Item = (IterKey, &'a Zval); fn next(&mut self) -> Option { - // Call next when index > 0, so next is really called at the start of each iteration, which allow to work better with generator iterator + // 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; } diff --git a/src/types/zval.rs b/src/types/zval.rs index f9a3e7b..f031214 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -267,7 +267,8 @@ impl Zval { } } - /// Returns an iterable over the zval if it is an array or traversable. (is iterable) + /// Returns an iterable over the zval if it is an array or traversable. (is + /// iterable) pub fn iterable(&self) -> Option { if self.is_iterable() { Iterable::from_zval(self) @@ -399,7 +400,8 @@ impl Zval { } } - /// Returns true if the zval is iterable (array or traversable), false otherwise. + /// 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) } From 7614f3a4c2a8489435e82d151e626846b7b14a7a Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 14:45:20 +0100 Subject: [PATCH 11/17] Rename --- src/types/array.rs | 2 +- src/types/iterator.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/types/array.rs b/src/types/array.rs index 29e5c6e..958bc23 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -464,7 +464,7 @@ impl ZendHashTable { /// assert!(!ht.has_numerical_keys()); /// ``` pub fn has_numerical_keys(&self) -> bool { - !self.iter().any(|(k, _)| !k.is_numerical()) + !self.iter().any(|(k, _)| !k.is_long()) } /// Checks if the hashtable has numerical, sequential keys. diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 7d42625..c58b849 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -136,12 +136,12 @@ pub enum IterKey { /// Represent the key of a PHP iterator, which can be either a long or a string. impl IterKey { - /// Check if the key is numerical. + /// Check if the key is an integer. /// /// # Returns /// - /// Returns true if the key is numerical, false otherwise. - pub fn is_numerical(&self) -> bool { + /// Returns true if the key is an integer, false otherwise. + pub fn is_long(&self) -> bool { match self { IterKey::Long(_) => true, IterKey::String(_) => false, @@ -289,14 +289,14 @@ mod tests { let (key, value) = iter.next().unwrap(); - assert!(!key.is_numerical()); + assert!(!key.is_long()); assert_eq!(key, IterKey::String("key".to_string())); assert!(value.is_string()); assert_eq!(value.string().unwrap(), "foo"); let (key, value) = iter.next().unwrap(); - assert!(key.is_numerical()); + assert!(key.is_long()); assert_eq!(key, IterKey::Long(10)); assert!(value.is_string()); assert_eq!(value.string().unwrap(), "bar"); From 3418b045beeb0b06c0a9909055aa72c13679d4ef Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 15:38:01 +0100 Subject: [PATCH 12/17] Refactoring --- docsrs_bindings.rs | 2 +- src/types/array.rs | 146 +++++++++++++++++++++++++++++++----------- src/types/iterable.rs | 21 ++++-- src/types/iterator.rs | 63 +++++------------- 4 files changed, 142 insertions(+), 90 deletions(-) diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index ede47f1..c5cdd80 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1657,7 +1657,7 @@ extern "C" { ); } extern "C" { - pub fn zend_is_iterable(iterable: *mut zval) -> bool; + 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; diff --git a/src/types/array.rs b/src/types/array.rs index 958bc23..b8f14a9 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -5,12 +5,11 @@ use std::{ collections::HashMap, convert::{TryFrom, TryInto}, ffi::CString, - fmt::Debug, + fmt::{Debug, Display}, iter::FromIterator, u64, }; -use crate::types::iterator::IterKey; use crate::{ boxed::{ZBox, ZBoxable}, convert::{FromZval, IntoZval}, @@ -464,7 +463,7 @@ impl ZendHashTable { /// assert!(!ht.has_numerical_keys()); /// ``` pub fn has_numerical_keys(&self) -> bool { - !self.iter().any(|(k, _)| !k.is_long()) + !self.into_iter().any(|(k, _)| !k.is_long()) } /// Checks if the hashtable has numerical, sequential keys. @@ -491,31 +490,9 @@ impl ZendHashTable { /// ``` pub fn has_sequential_keys(&self) -> bool { !self - .iter() + .into_iter() .enumerate() - .any(|(i, (k, _))| IterKey::Long(i as u64) != 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 (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 { - Iter::new(self) + .any(|(i, (k, _))| ArrayKey::Long(i as i64) != k) } /// Returns an iterator over the values contained inside the hashtable, as @@ -535,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 { + /// // ^ 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 { @@ -547,7 +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, v)| (k.to_string(), v))) + .entries(self.into_iter().map(|(k, v)| (k.to_string(), v))) .finish() } } @@ -572,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<'a> { + Long(i64), + String(&'a str), +} + +/// Represent the key of a PHP array, which can be either a long or a string. +impl<'a> ArrayKey<'a> { + /// 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<'a> Display for ArrayKey<'a> { + 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<'_> for ArrayKey<'a> { + const TYPE: DataType = DataType::String; + + fn from_zval(zval: &Zval) -> Option { + if let Some(key) = zval.long() { + return Some(ArrayKey::Long(key)); + } + if let Some(key) = zval.str() { + return Some(ArrayKey::String(key)); + } + return None; + } +} + impl<'a> Iter<'a> { /// Creates a new iterator over a hashtable. /// @@ -591,8 +634,35 @@ impl<'a> Iter<'a> { } } +impl<'a> IntoIterator for &'a ZendHashTable { + type Item = (ArrayKey<'a>, &'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 { + /// // ^ 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 = (IterKey, &'a Zval); + type Item = (ArrayKey<'a>, &'a Zval); fn next(&mut self) -> Option { let key_type = unsafe { @@ -621,9 +691,9 @@ impl<'a> Iterator for Iter<'a> { ) }; - let r = match IterKey::from_zval(&key) { - Some(key) => (key, value), - None => (IterKey::Long(self.current_num), value), + let key = match ArrayKey::from_zval(&key) { + Some(key) => key, + None => ArrayKey::Long(self.current_num), }; unsafe { @@ -634,7 +704,7 @@ impl<'a> Iterator for Iter<'a> { }; self.current_num += 1; - Some(r) + Some((key, value)) } fn count(self) -> usize @@ -679,9 +749,9 @@ impl<'a> DoubleEndedIterator for Iter<'a> { ) }; - let r = match IterKey::from_zval(&key) { + let r = match ArrayKey::from_zval(&key) { Some(key) => (key, value), - None => (IterKey::Long(self.current_num), value), + None => (ArrayKey::Long(self.current_num), value), }; unsafe { @@ -780,7 +850,7 @@ where fn try_from(value: &'a ZendHashTable) -> Result { let mut hm = HashMap::with_capacity(value.len()); - for (key, val) in value.iter() { + for (key, val) in value { hm.insert( key.to_string(), V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?, @@ -849,7 +919,7 @@ where fn try_from(value: &'a ZendHashTable) -> Result { 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()))?); } diff --git a/src/types/iterable.rs b/src/types/iterable.rs index fb266c1..32eca2b 100644 --- a/src/types/iterable.rs +++ b/src/types/iterable.rs @@ -1,8 +1,7 @@ -use super::array::Iter as ZendHashTableIter; +use super::array::{ArrayKey, Iter as ZendHashTableIter}; use super::iterator::Iter as ZendIteratorIter; use crate::convert::FromZval; use crate::flags::DataType; -use crate::types::iterator::IterKey; use crate::types::{ZendHashTable, ZendIterator, Zval}; /// This type represents a PHP iterable, which can be either an array or an @@ -15,6 +14,7 @@ pub enum Iterable<'a> { 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 { match self { Iterable::Array(array) => Some(Iter::Array(array.iter())), @@ -23,6 +23,15 @@ impl<'a> Iterable<'a> { } } +impl<'a> IntoIterator for &'a mut Iterable<'a> { + type Item = (&'a 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; @@ -46,11 +55,15 @@ pub enum Iter<'a> { } impl<'a> Iterator for Iter<'a> { - type Item = (IterKey, &'a Zval); + type Item = (&'a Zval, &'a Zval); fn next(&mut self) -> Option { match self { - Iter::Array(array) => array.next(), + Iter::Array(array) => match array.next() { + Some((ArrayKey::Long(k), v)) => Zval::new().set_long(v), + Some((ArrayKey::String(k), v)) => Zval::new().set_string(v), + None => None, + }, Iter::Traversable(traversable) => traversable.next(), } } diff --git a/src/types/iterator.rs b/src/types/iterator.rs index c58b849..83e39b9 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -7,16 +7,17 @@ use std::fmt::{Debug, Display, Formatter}; /// A PHP Iterator. /// -/// In PHP, iterators are represented as zend_object_iterator. This allow user -/// to iterate over object implementing Traversable interface using foreach. +/// 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 - /// - /// Returns a iterator over the 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 { self.index = 0; @@ -122,60 +123,28 @@ impl ZendIterator { } } +impl<'a> IntoIterator for &'a mut ZendIterator { + type Item = (&'a 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() } } -#[derive(Debug, PartialEq)] -pub enum IterKey { - Long(u64), - String(String), -} - -/// Represent the key of a PHP iterator, which can be either a long or a string. -impl IterKey { - /// 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 { - IterKey::Long(_) => true, - IterKey::String(_) => false, - } - } -} - -impl Display for IterKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - IterKey::Long(key) => write!(f, "{}", key), - IterKey::String(key) => write!(f, "{}", key), - } - } -} - -impl FromZval<'_> for IterKey { - const TYPE: DataType = DataType::String; - - fn from_zval(zval: &Zval) -> Option { - match zval.long() { - Some(key) => Some(IterKey::Long(key as u64)), - None => zval.string().map(IterKey::String), - } - } -} - /// 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 = (IterKey, &'a Zval); + type Item = (&'a Zval, &'a Zval); fn next(&mut self) -> Option { // Call next when index > 0, so next is really called at the start of each From 91dcc38e50891725d5be6ad7c650a3e21a2f1389 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 16:50:58 +0100 Subject: [PATCH 13/17] Refactor --- src/types/array.rs | 115 ++++++++++++++++++++++-------------------- src/types/iterable.rs | 12 ++--- src/types/iterator.rs | 19 +++---- 3 files changed, 75 insertions(+), 71 deletions(-) diff --git a/src/types/array.rs b/src/types/array.rs index b8f14a9..4c5bc72 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -576,13 +576,13 @@ pub struct Iter<'a> { } #[derive(Debug, PartialEq)] -pub enum ArrayKey<'a> { +pub enum ArrayKey { Long(i64), - String(&'a str), + String(String), } /// Represent the key of a PHP array, which can be either a long or a string. -impl<'a> ArrayKey<'a> { +impl ArrayKey { /// Check if the key is an integer. /// /// # Returns @@ -596,7 +596,7 @@ impl<'a> ArrayKey<'a> { } } -impl<'a> Display for ArrayKey<'a> { +impl Display for ArrayKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ArrayKey::Long(key) => write!(f, "{}", key), @@ -605,17 +605,17 @@ impl<'a> Display for ArrayKey<'a> { } } -impl<'a> FromZval<'_> for ArrayKey<'a> { +impl<'a> FromZval<'a> for ArrayKey { const TYPE: DataType = DataType::String; - fn from_zval(zval: &Zval) -> Option { + fn from_zval(zval: &'a Zval) -> Option { if let Some(key) = zval.long() { return Some(ArrayKey::Long(key)); } - if let Some(key) = zval.str() { + if let Some(key) = zval.string() { return Some(ArrayKey::String(key)); } - return None; + None } } @@ -635,7 +635,7 @@ impl<'a> Iter<'a> { } impl<'a> IntoIterator for &'a ZendHashTable { - type Item = (ArrayKey<'a>, &'a Zval); + type Item = (ArrayKey, &'a Zval); type IntoIter = Iter<'a>; /// Returns an iterator over the key(s) and value contained inside the @@ -662,49 +662,10 @@ impl<'a> IntoIterator for &'a ZendHashTable { } impl<'a> Iterator for Iter<'a> { - type Item = (ArrayKey<'a>, &'a Zval); + type Item = (ArrayKey, &'a Zval); fn next(&mut self) -> Option { - 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 key = match ArrayKey::from_zval(&key) { - Some(key) => key, - None => ArrayKey::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)) + self.next_zval().map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v)) } fn count(self) -> usize @@ -735,6 +696,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, @@ -749,9 +711,9 @@ impl<'a> DoubleEndedIterator for Iter<'a> { ) }; - let r = match ArrayKey::from_zval(&key) { - Some(key) => (key, value), - None => (ArrayKey::Long(self.current_num), value), + let key = match ArrayKey::from_zval(&key) { + Some(key) => key, + None => ArrayKey::Long(self.current_num), }; unsafe { @@ -762,7 +724,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)) } } diff --git a/src/types/iterable.rs b/src/types/iterable.rs index 32eca2b..1440a67 100644 --- a/src/types/iterable.rs +++ b/src/types/iterable.rs @@ -1,4 +1,4 @@ -use super::array::{ArrayKey, Iter as ZendHashTableIter}; +use super::array::Iter as ZendHashTableIter; use super::iterator::Iter as ZendIteratorIter; use crate::convert::FromZval; use crate::flags::DataType; @@ -24,7 +24,7 @@ impl<'a> Iterable<'a> { } impl<'a> IntoIterator for &'a mut Iterable<'a> { - type Item = (&'a Zval, &'a Zval); + type Item = (Zval, &'a Zval); type IntoIter = Iter<'a>; fn into_iter(self) -> Self::IntoIter { @@ -55,15 +55,11 @@ pub enum Iter<'a> { } impl<'a> Iterator for Iter<'a> { - type Item = (&'a Zval, &'a Zval); + type Item = (Zval, &'a Zval); fn next(&mut self) -> Option { match self { - Iter::Array(array) => match array.next() { - Some((ArrayKey::Long(k), v)) => Zval::new().set_long(v), - Some((ArrayKey::String(k), v)) => Zval::new().set_string(v), - None => None, - }, + Iter::Array(array) => array.next_zval(), Iter::Traversable(traversable) => traversable.next(), } } diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 83e39b9..406194c 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -1,9 +1,9 @@ -use crate::convert::{FromZval, FromZvalMut}; +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, Display, Formatter}; +use std::fmt::{Debug, Formatter}; /// A PHP Iterator. /// @@ -124,7 +124,7 @@ impl ZendIterator { } impl<'a> IntoIterator for &'a mut ZendIterator { - type Item = (&'a Zval, &'a Zval); + type Item = (Zval, &'a Zval); type IntoIter = Iter<'a>; fn into_iter(self) -> Self::IntoIter { @@ -144,7 +144,7 @@ pub struct Iter<'a> { } impl<'a> Iterator for Iter<'a> { - type Item = (&'a Zval, &'a Zval); + type Item = (Zval, &'a Zval); fn next(&mut self) -> Option { // Call next when index > 0, so next is really called at the start of each @@ -162,11 +162,12 @@ impl<'a> Iterator for Iter<'a> { let real_index = self.zi.index - 1; let key = match self.zi.get_current_key() { - None => IterKey::Long(real_index), - Some(key) => match IterKey::from_zval(&key) { - Some(key) => key, - None => IterKey::Long(real_index), - }, + 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)) From e5d7a32a471073146ec6863956feebe585a2fa47 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 17:12:00 +0100 Subject: [PATCH 14/17] Fixup --- guide/src/types/iterable.md | 5 +++-- guide/src/types/iterator.md | 5 +++-- src/flags.rs | 2 +- src/types/array.rs | 4 ++-- src/types/iterator.test.php | 3 +++ 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/guide/src/types/iterable.md b/guide/src/types/iterable.md index 0e689c1..cda816d 100644 --- a/guide/src/types/iterable.md +++ b/guide/src/types/iterable.md @@ -19,8 +19,8 @@ that implements the `Traversable` interface. This means that any value that can # use ext_php_rs::types::Iterable; #[php_function] pub fn test_iterable(mut iterable: Iterable) { - for (k, v) in iterable.iter().expect("cannot get iterable") { - println!("k: {} v: {}", k, v.string().unwrap()); + for (k, v) in iterable.iter().expect("cannot rewind iterator") { + println!("k: {:?} v: {}", k, v.string().unwrap()); } } # fn main() {} @@ -35,6 +35,7 @@ $generator = function() { yield 'hello' => 'world'; yield 'rust' => 'php'; yield 'okk'; + yield new class {} => new class {}; }; $array = [ diff --git a/guide/src/types/iterator.md b/guide/src/types/iterator.md index b113e41..ce0e9cf 100644 --- a/guide/src/types/iterator.md +++ b/guide/src/types/iterator.md @@ -22,8 +22,8 @@ If you want a more universal `iterable` type that also supports arrays, see [Ite # use ext_php_rs::types::ZendIterator; #[php_function] pub fn test_iterator(iterator: &mut ZendIterator) { - for (k, v) in iterator.iter().expect("cannot get iterator") { - println!("k: {} v: {}", k, v.string().unwrap()); + for (k, v) in iterator.iter().expect("cannot rewind iterator") { + println!("k: {:?} v: {}", k, v.string().unwrap()); } } # fn main() {} @@ -38,6 +38,7 @@ $generator = function() { yield 'hello' => 'world'; yield 'rust' => 'php'; yield 'okk'; + yield new class {} => new class {}; }; test_iterator($generator()); diff --git a/src/flags.rs b/src/flags.rs index c91fc19..c78a26a 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -395,7 +395,7 @@ impl Display for DataType { mod tests { use super::DataType; use crate::ffi::{ - IS_ARRAY, IS_ARRAY_EX, IS_CALLABLE, IS_CONSTANT_AST, IS_CONSTANT_AST_EX, IS_DOUBLE, + IS_ARRAY, IS_ARRAY_EX, IS_CONSTANT_AST, IS_CONSTANT_AST_EX, IS_DOUBLE, IS_FALSE, IS_INDIRECT, IS_INTERNED_STRING_EX, IS_LONG, IS_NULL, IS_OBJECT, IS_OBJECT_EX, IS_PTR, IS_REFERENCE, IS_REFERENCE_EX, IS_RESOURCE, IS_RESOURCE_EX, IS_STRING, IS_STRING_EX, IS_TRUE, IS_UNDEF, IS_VOID, diff --git a/src/types/array.rs b/src/types/array.rs index 4c5bc72..aa32c42 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -523,7 +523,7 @@ impl ZendHashTable { /// /// let mut ht = ZendHashTable::new(); /// - /// for (key, val) in ht { + /// for (key, val) in ht.iter() { /// // ^ Index if inserted at an index. /// // ^ Optional string key, if inserted like a hashtable. /// // ^ Inserted value. @@ -648,7 +648,7 @@ impl<'a> IntoIterator for &'a ZendHashTable { /// /// let mut ht = ZendHashTable::new(); /// - /// for (key, val) in ht { + /// for (key, val) in ht.iter() { /// // ^ Index if inserted at an index. /// // ^ Optional string key, if inserted like a hashtable. /// // ^ Inserted value. diff --git a/src/types/iterator.test.php b/src/types/iterator.test.php index 216d663..e5463ec 100644 --- a/src/types/iterator.test.php +++ b/src/types/iterator.test.php @@ -4,6 +4,7 @@ function create_generator() { yield 1; yield 2; yield 3; + yield new class {}; } class TestIterator implements \Iterator { @@ -15,6 +16,7 @@ class TestIterator implements \Iterator { 0 => 'foo', 1 => 'bar', 2 => 'baz', + 3 => new class {}, default => null, }; } @@ -30,6 +32,7 @@ class TestIterator implements \Iterator { 0 => 'key', 1 => 10, 2 => 2, + 3 => new class {}, default => null, }; } From 050c017e261054d454acc2801b524f9a617c568c Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 18:39:59 +0100 Subject: [PATCH 15/17] Cleanup --- src/flags.rs | 8 ++++---- src/types/array.rs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/flags.rs b/src/flags.rs index c78a26a..da3a684 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -395,10 +395,10 @@ impl Display for DataType { mod tests { use super::DataType; use crate::ffi::{ - IS_ARRAY, IS_ARRAY_EX, IS_CONSTANT_AST, IS_CONSTANT_AST_EX, IS_DOUBLE, - IS_FALSE, IS_INDIRECT, IS_INTERNED_STRING_EX, IS_LONG, IS_NULL, IS_OBJECT, IS_OBJECT_EX, - IS_PTR, IS_REFERENCE, IS_REFERENCE_EX, IS_RESOURCE, IS_RESOURCE_EX, IS_STRING, - IS_STRING_EX, IS_TRUE, IS_UNDEF, IS_VOID, + IS_ARRAY, IS_ARRAY_EX, IS_CONSTANT_AST, IS_CONSTANT_AST_EX, IS_DOUBLE, IS_FALSE, + IS_INDIRECT, IS_INTERNED_STRING_EX, IS_LONG, IS_NULL, IS_OBJECT, IS_OBJECT_EX, IS_PTR, + IS_REFERENCE, IS_REFERENCE_EX, IS_RESOURCE, IS_RESOURCE_EX, IS_STRING, IS_STRING_EX, + IS_TRUE, IS_UNDEF, IS_VOID, }; use std::convert::TryFrom; diff --git a/src/types/array.rs b/src/types/array.rs index aa32c42..19768f3 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -665,7 +665,8 @@ impl<'a> Iterator for Iter<'a> { type Item = (ArrayKey, &'a Zval); fn next(&mut self) -> Option { - self.next_zval().map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v)) + self.next_zval() + .map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v)) } fn count(self) -> usize From 0f401865f74e11652fb317cd3080a5ee12486eab Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 18:46:19 +0100 Subject: [PATCH 16/17] Cleanup --- src/types/iterator.rs | 36 +++++++++++++++++++++++++----------- src/types/iterator.test.php | 14 +++++++------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 406194c..b6591ab 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -186,7 +186,6 @@ impl<'a> FromZvalMut<'a> for &'a mut ZendIterator { #[cfg(feature = "embed")] mod tests { use crate::embed::Embed; - use crate::types::iterator::IterKey; #[test] fn test_generator() { @@ -212,22 +211,27 @@ mod tests { let (key, value) = iter.next().unwrap(); - assert_eq!(key, IterKey::Long(0)); + 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, IterKey::Long(1)); + 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, IterKey::Long(2)); + 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()); @@ -260,23 +264,28 @@ mod tests { let (key, value) = iter.next().unwrap(); assert!(!key.is_long()); - assert_eq!(key, IterKey::String("key".to_string())); + assert_eq!(key.str(), Some("key")); assert!(value.is_string()); - assert_eq!(value.string().unwrap(), "foo"); + assert_eq!(value.str(), Some("foo")); let (key, value) = iter.next().unwrap(); assert!(key.is_long()); - assert_eq!(key, IterKey::Long(10)); + 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, IterKey::Long(2)); + 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()); @@ -288,22 +297,27 @@ mod tests { let (key, value) = iter.next().unwrap(); - assert_eq!(key, IterKey::String("key".to_string())); + 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, IterKey::Long(10)); + 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, IterKey::Long(2)); + 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()); diff --git a/src/types/iterator.test.php b/src/types/iterator.test.php index e5463ec..891f0a9 100644 --- a/src/types/iterator.test.php +++ b/src/types/iterator.test.php @@ -4,13 +4,13 @@ function create_generator() { yield 1; yield 2; yield 3; - yield new class {}; + yield new class {} => new class {}; } class TestIterator implements \Iterator { private $count = 0; - public function current() + public function current(): mixed { return match ($this->count) { 0 => 'foo', @@ -21,12 +21,12 @@ class TestIterator implements \Iterator { }; } - public function next() + public function next(): void { $this->count++; } - public function key() + public function key(): mixed { return match ($this->count) { 0 => 'key', @@ -37,12 +37,12 @@ class TestIterator implements \Iterator { }; } - public function valid() + public function valid(): bool { - return $this->count < 3; + return $this->count < 4; } - public function rewind() + public function rewind(): void { $this->count = 0; } From f449f4e4e119cdf4306cbaad495ecba50b6f32ce Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 18:48:13 +0100 Subject: [PATCH 17/17] Cleanup --- guide/src/types/iterable.md | 7 +------ guide/src/types/iterator.md | 7 +++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/guide/src/types/iterable.md b/guide/src/types/iterable.md index cda816d..fec8072 100644 --- a/guide/src/types/iterable.md +++ b/guide/src/types/iterable.md @@ -20,7 +20,7 @@ that implements the `Traversable` interface. This means that any value that can #[php_function] pub fn test_iterable(mut iterable: Iterable) { for (k, v) in iterable.iter().expect("cannot rewind iterator") { - println!("k: {:?} v: {}", k, v.string().unwrap()); + println!("k: {} v: {}", k.string().unwrap(), v.string().unwrap()); } } # fn main() {} @@ -34,14 +34,11 @@ pub fn test_iterable(mut iterable: Iterable) { $generator = function() { yield 'hello' => 'world'; yield 'rust' => 'php'; - yield 'okk'; - yield new class {} => new class {}; }; $array = [ 'hello' => 'world', 'rust' => 'php', - 'okk', ]; test_iterable($generator()); @@ -53,8 +50,6 @@ Output: ```text k: hello v: world k: rust v: php -k: 0 v: okk k: hello v: world k: rust v: php -k: 0 v: okk ``` diff --git a/guide/src/types/iterator.md b/guide/src/types/iterator.md index ce0e9cf..4c53e67 100644 --- a/guide/src/types/iterator.md +++ b/guide/src/types/iterator.md @@ -23,7 +23,9 @@ If you want a more universal `iterable` type that also supports arrays, see [Ite #[php_function] pub fn test_iterator(iterator: &mut ZendIterator) { for (k, v) in iterator.iter().expect("cannot rewind iterator") { - println!("k: {:?} v: {}", k, v.string().unwrap()); + // 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() {} @@ -37,8 +39,6 @@ pub fn test_iterator(iterator: &mut ZendIterator) { $generator = function() { yield 'hello' => 'world'; yield 'rust' => 'php'; - yield 'okk'; - yield new class {} => new class {}; }; test_iterator($generator()); @@ -49,5 +49,4 @@ Output: ```text k: hello v: world k: rust v: php -k: 0 v: okk ```