• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

extphprs / ext-php-rs / 19768045016

28 Nov 2025 03:29PM UTC coverage: 35.332% (-0.03%) from 35.363%
19768045016

Pull #592

github

web-flow
Merge 910f4ac60 into 935a2d230
Pull Request #592: feat(php): Add PHP 8.5 support

1 of 22 new or added lines in 2 files covered. (4.55%)

1 existing line in 1 file now uncovered.

1603 of 4537 relevant lines covered (35.33%)

8.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

32.1
/src/types/array/iterators.rs
1
use std::{
2
    convert::TryInto,
3
    iter::{DoubleEndedIterator, ExactSizeIterator, Iterator},
4
    ptr,
5
};
6

7
use super::{ArrayKey, ZendHashTable};
8
use crate::boxed::ZBox;
9
use crate::{
10
    convert::FromZval,
11
    ffi::{
12
        HashPosition, zend_hash_get_current_data_ex, zend_hash_get_current_key_type_ex,
13
        zend_hash_get_current_key_zval_ex, zend_hash_move_backwards_ex, zend_hash_move_forward_ex,
14
    },
15
    types::Zval,
16
};
17

18
#[cfg(php85)]
19
use crate::ffi::zend_hash_key_type_HASH_KEY_NON_EXISTENT;
20

21
/// Immutable iterator upon a reference to a hashtable.
22
pub struct Iter<'a> {
23
    ht: &'a ZendHashTable,
24
    current_num: i64,
25
    end_num: i64,
26
    pos: HashPosition,
27
    end_pos: HashPosition,
28
}
29

30
impl<'a> Iter<'a> {
31
    /// Creates a new iterator over a hashtable.
32
    ///
33
    /// # Parameters
34
    ///
35
    /// * `ht` - The hashtable to iterate.
36
    pub fn new(ht: &'a ZendHashTable) -> Self {
18✔
37
        let end_num: i64 = ht
54✔
38
            .len()
39
            .try_into()
40
            .expect("Integer overflow in hashtable length");
41
        let end_pos = if ht.nNumOfElements > 0 {
36✔
42
            ht.nNumOfElements - 1
18✔
43
        } else {
44
            0
×
45
        };
46

47
        Self {
48
            ht,
49
            current_num: 0,
50
            end_num,
51
            pos: 0,
52
            end_pos,
53
        }
54
    }
55

56
    pub fn next_zval(&mut self) -> Option<(Zval, &'a Zval)> {
60✔
57
        if self.current_num >= self.end_num {
60✔
58
            return None;
16✔
59
        }
60

61
        let key_type = unsafe {
62
            zend_hash_get_current_key_type_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos)
220✔
63
        };
64

65
        // Key type `1` is string (HASH_KEY_IS_STRING)
66
        // Key type `2` is long (HASH_KEY_IS_LONG)
67
        // Key type `3` is null/non-existent (HASH_KEY_NON_EXISTENT)
68
        // Pre-PHP 8.5 returns int, PHP 8.5+ returns enum u32
NEW
69
        #[cfg(php85)]
×
NEW
70
        if key_type == zend_hash_key_type_HASH_KEY_NON_EXISTENT {
×
UNCOV
71
            return None;
×
72
        }
73
        #[cfg(not(php85))]
74
        {
75
            // Pre-PHP 8.5: function returns signed int (i32)
76
            // Check for -1 (defensive) and 3 (HASH_KEY_NON_EXISTENT)
77
            if key_type == -1 || key_type == 3 {
88✔
NEW
78
                return None;
×
79
            }
80
        }
81

82
        let mut key = Zval::new();
88✔
83

84
        unsafe {
85
            zend_hash_get_current_key_zval_ex(
86
                ptr::from_ref(self.ht).cast_mut(),
132✔
87
                (&raw const key).cast_mut(),
88✔
88
                &raw mut self.pos,
44✔
89
            );
90
        }
91
        let value = unsafe {
92
            let val_ptr =
44✔
93
                zend_hash_get_current_data_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos);
220✔
94

95
            if val_ptr.is_null() {
88✔
96
                return None;
×
97
            }
98

99
            &*val_ptr
44✔
100
        };
101

102
        if !key.is_long() && !key.is_string() {
62✔
103
            key.set_long(self.current_num);
×
104
        }
105

106
        unsafe { zend_hash_move_forward_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos) };
220✔
107
        self.current_num += 1;
44✔
108

109
        Some((key, value))
44✔
110
    }
111
}
112

113
impl<'a> IntoIterator for &'a ZendHashTable {
114
    type Item = (ArrayKey<'a>, &'a Zval);
115
    type IntoIter = Iter<'a>;
116

117
    /// Returns an iterator over the key(s) and value contained inside the
118
    /// hashtable.
119
    ///
120
    /// # Example
121
    ///
122
    /// ```no_run
123
    /// use ext_php_rs::types::ZendHashTable;
124
    ///
125
    /// let mut ht = ZendHashTable::new();
126
    ///
127
    /// for (key, val) in ht.iter() {
128
    /// //   ^ Index if inserted at an index.
129
    /// //        ^ Optional string key, if inserted like a hashtable.
130
    /// //             ^ Inserted value.
131
    ///
132
    ///     dbg!(key, val);
133
    /// }
134
    #[inline]
135
    fn into_iter(self) -> Self::IntoIter {
18✔
136
        Iter::new(self)
36✔
137
    }
138
}
139

140
impl<'a> Iterator for Iter<'a> {
141
    type Item = (ArrayKey<'a>, &'a Zval);
142

143
    fn next(&mut self) -> Option<Self::Item> {
60✔
144
        self.next_zval()
120✔
145
            .map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v))
280✔
146
    }
147

148
    fn count(self) -> usize
×
149
    where
150
        Self: Sized,
151
    {
152
        self.ht.len()
×
153
    }
154
}
155

156
impl ExactSizeIterator for Iter<'_> {
157
    fn len(&self) -> usize {
×
158
        self.ht.len()
×
159
    }
160
}
161

162
impl DoubleEndedIterator for Iter<'_> {
163
    fn next_back(&mut self) -> Option<Self::Item> {
×
164
        if self.end_num <= self.current_num {
×
165
            return None;
×
166
        }
167

168
        let key_type = unsafe {
169
            zend_hash_get_current_key_type_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos)
×
170
        };
171

172
        #[cfg(php85)]
173
        if key_type == zend_hash_key_type_HASH_KEY_NON_EXISTENT {
174
            return None;
175
        }
176
        #[cfg(not(php85))]
177
        {
178
            // Pre-PHP 8.5: function returns signed int (i32)
179
            // Check for -1 (defensive) and 3 (HASH_KEY_NON_EXISTENT)
NEW
180
            if key_type == -1 || key_type == 3 {
×
NEW
181
                return None;
×
182
            }
183
        }
184

185
        let key = Zval::new();
×
186

187
        unsafe {
188
            zend_hash_get_current_key_zval_ex(
189
                ptr::from_ref(self.ht).cast_mut(),
×
190
                (&raw const key).cast_mut(),
×
191
                &raw mut self.end_pos,
×
192
            );
193
        }
194
        let value = unsafe {
195
            &*zend_hash_get_current_data_ex(
×
196
                ptr::from_ref(self.ht).cast_mut(),
×
197
                &raw mut self.end_pos,
×
198
            )
199
        };
200

201
        let key = match ArrayKey::from_zval(&key) {
×
202
            Some(key) => key,
×
203
            None => ArrayKey::Long(self.end_num),
×
204
        };
205

206
        unsafe {
207
            zend_hash_move_backwards_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.end_pos)
×
208
        };
209
        self.end_num -= 1;
×
210

211
        Some((key, value))
×
212
    }
213
}
214

215
/// Immutable iterator which iterates over the values of the hashtable, as it
216
/// was a set or list.
217
pub struct Values<'a>(Iter<'a>);
218

219
impl<'a> Values<'a> {
220
    /// Creates a new iterator over a hashtables values.
221
    ///
222
    /// # Parameters
223
    ///
224
    /// * `ht` - The hashtable to iterate.
225
    pub fn new(ht: &'a ZendHashTable) -> Self {
×
226
        Self(Iter::new(ht))
×
227
    }
228
}
229

230
impl<'a> Iterator for Values<'a> {
231
    type Item = &'a Zval;
232

233
    fn next(&mut self) -> Option<Self::Item> {
×
234
        self.0.next().map(|(_, zval)| zval)
×
235
    }
236

237
    fn count(self) -> usize
×
238
    where
239
        Self: Sized,
240
    {
241
        self.0.count()
×
242
    }
243
}
244

245
impl ExactSizeIterator for Values<'_> {
246
    fn len(&self) -> usize {
×
247
        self.0.len()
×
248
    }
249
}
250

251
impl DoubleEndedIterator for Values<'_> {
252
    fn next_back(&mut self) -> Option<Self::Item> {
×
253
        self.0.next_back().map(|(_, zval)| zval)
×
254
    }
255
}
256

257
impl FromIterator<Zval> for ZBox<ZendHashTable> {
258
    fn from_iter<T: IntoIterator<Item = Zval>>(iter: T) -> Self {
×
259
        let mut ht = ZendHashTable::new();
×
260
        for item in iter {
×
261
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
262
            // `val` to a zval fails.
263
            let _ = ht.push(item);
×
264
        }
265
        ht
×
266
    }
267
}
268

269
impl FromIterator<(i64, Zval)> for ZBox<ZendHashTable> {
270
    fn from_iter<T: IntoIterator<Item = (i64, Zval)>>(iter: T) -> Self {
×
271
        let mut ht = ZendHashTable::new();
×
272
        for (key, val) in iter {
×
273
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
274
            // `val` to a zval fails.
275
            let _ = ht.insert_at_index(key, val);
×
276
        }
277
        ht
×
278
    }
279
}
280

281
impl<'a> FromIterator<(&'a str, Zval)> for ZBox<ZendHashTable> {
282
    fn from_iter<T: IntoIterator<Item = (&'a str, Zval)>>(iter: T) -> Self {
×
283
        let mut ht = ZendHashTable::new();
×
284
        for (key, val) in iter {
×
285
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
286
            // `val` to a zval fails.
287
            let _ = ht.insert(key, val);
×
288
        }
289
        ht
×
290
    }
291
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc