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

extphprs / ext-php-rs / 19771651930

28 Nov 2025 06:41PM UTC coverage: 35.308% (-0.06%) from 35.363%
19771651930

Pull #592

github

ptondereau
feat(php): Add PHP 8.5 support
Pull Request #592: feat(php): Add PHP 8.5 support

1 of 26 new or added lines in 2 files covered. (3.85%)

1603 of 4540 relevant lines covered (35.31%)

8.52 hits per line

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

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

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

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

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

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

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

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

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

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

84
        let mut key = Zval::new();
88✔
85

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

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

101
            &*val_ptr
44✔
102
        };
103

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

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

111
        Some((key, value))
44✔
112
    }
113
}
114

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

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

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

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

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

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

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

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

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

188
        let key = Zval::new();
×
189

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

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

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

214
        Some((key, value))
×
215
    }
216
}
217

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

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

233
impl<'a> Iterator for Values<'a> {
234
    type Item = &'a Zval;
235

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

240
    fn count(self) -> usize
×
241
    where
242
        Self: Sized,
243
    {
244
        self.0.count()
×
245
    }
246
}
247

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

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

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

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

284
impl<'a> FromIterator<(&'a str, Zval)> for ZBox<ZendHashTable> {
285
    fn from_iter<T: IntoIterator<Item = (&'a str, Zval)>>(iter: T) -> Self {
×
286
        let mut ht = ZendHashTable::new();
×
287
        for (key, val) in iter {
×
288
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
289
            // `val` to a zval fails.
290
            let _ = ht.insert(key, val);
×
291
        }
292
        ht
×
293
    }
294
}
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