diff --git a/src/types/array.rs b/src/types/array.rs index 3bf6f41132..0570ad4d2c 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -573,7 +573,9 @@ impl ToOwned for ZendHashTable { pub struct Iter<'a> { ht: &'a ZendHashTable, current_num: i64, + end_num: i64, pos: HashPosition, + end_pos: HashPosition, } #[derive(Debug, PartialEq)] @@ -627,10 +629,22 @@ impl<'a> Iter<'a> { /// /// * `ht` - The hashtable to iterate. pub fn new(ht: &'a ZendHashTable) -> Self { + let end_num: i64 = ht + .len() + .try_into() + .expect("Integer overflow in hashtable length"); + let end_pos = if ht.nNumOfElements > 0 { + ht.nNumOfElements - 1 + } else { + 0 + }; + Self { ht, current_num: 0, + end_num, pos: 0, + end_pos, } } } @@ -686,6 +700,10 @@ impl ExactSizeIterator for Iter<'_> { impl DoubleEndedIterator for Iter<'_> { fn next_back(&mut self) -> Option { + if self.end_num <= self.current_num { + return None; + } + let key_type = unsafe { zend_hash_get_current_key_type_ex( self.ht as *const ZendHashTable as *mut ZendHashTable, @@ -703,28 +721,28 @@ impl DoubleEndedIterator for Iter<'_> { 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, + &mut self.end_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, + &mut self.end_pos as *mut HashPosition, ) }; let key = match ArrayKey::from_zval(&key) { Some(key) => key, - None => ArrayKey::Long(self.current_num), + None => ArrayKey::Long(self.end_num), }; unsafe { zend_hash_move_backwards_ex( self.ht as *const ZendHashTable as *mut ZendHashTable, - &mut self.pos as *mut HashPosition, + &mut self.end_pos as *mut HashPosition, ) }; - self.current_num -= 1; + self.end_num -= 1; Some((key, value)) } @@ -732,6 +750,10 @@ impl DoubleEndedIterator for Iter<'_> { impl<'a> Iter<'a> { pub fn next_zval(&mut self) -> Option<(Zval, &'a Zval)> { + if self.current_num >= self.end_num { + return None; + } + let key_type = unsafe { zend_hash_get_current_key_type_ex( self.ht as *const ZendHashTable as *mut ZendHashTable, diff --git a/tests/src/integration/iterator.php b/tests/src/integration/iterator.php new file mode 100644 index 0000000000..d8942a82ab --- /dev/null +++ b/tests/src/integration/iterator.php @@ -0,0 +1,11 @@ + Zval { call.try_call(vec![&a]).expect("Failed to call function") } +#[php_function] +pub fn iter_next(ht: &ZendHashTable) -> Vec { + ht.iter() + .flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()]) + .collect() +} + +#[php_function] +pub fn iter_back(ht: &ZendHashTable) -> Vec { + ht.iter() + .rev() + .flat_map(|(k, v)| [key_to_zval(k), v.shallow_clone()]) + .collect() +} + +#[php_function] +pub fn iter_next_back(ht: &ZendHashTable, modulus: usize) -> Vec> { + let mut result = Vec::with_capacity(ht.len()); + let mut iter = ht.iter(); + + for i in 0..ht.len() + modulus { + let entry = if i % modulus == 0 { + iter.next_back() + } else { + iter.next() + }; + + if let Some((k, v)) = entry { + result.push(Some(key_to_zval(k))); + result.push(Some(v.shallow_clone())); + } else { + result.push(None); + } + } + + result +} + +fn key_to_zval(key: ArrayKey) -> Zval { + match key { + ArrayKey::String(s) => { + let mut zval = Zval::new(); + let _ = zval.set_string(s.as_str(), false); + zval + } + ArrayKey::Long(l) => { + let mut zval = Zval::new(); + zval.set_long(l); + zval + } + } +} + #[php_class] pub struct TestClass { string: String, @@ -220,6 +273,7 @@ mod integration { mod class; mod closure; mod globals; + mod iterator; mod nullable; mod number; mod object;