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

davidcole1340 / ext-php-rs / 15778794992

20 Jun 2025 12:19PM UTC coverage: 20.64% (-1.4%) from 22.034%
15778794992

Pull #463

github

web-flow
Merge b618ded48 into 660f308c0
Pull Request #463: feat(cargo-php): --features, --all-features, --no-default-features

0 of 11 new or added lines in 1 file covered. (0.0%)

52 existing lines in 10 files now uncovered.

761 of 3687 relevant lines covered (20.64%)

3.57 hits per line

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

55.81
/src/types/iterator.rs
1
use crate::convert::FromZvalMut;
2
use crate::ffi::{zend_object_iterator, ZEND_RESULT_CODE_SUCCESS};
3
use crate::flags::DataType;
4
use crate::types::Zval;
5
use crate::zend::ExecutorGlobals;
6
use std::fmt::{Debug, Formatter};
7

8
/// A PHP Iterator.
9
///
10
/// In PHP, iterators are represented as `zend_object_iterator`. This allows
11
/// user to iterate over objects implementing Traversable interface using
12
/// foreach.
13
///
14
/// Use [`Iterable`] to iterate over both iterators and arrays.
15
///
16
/// [`Iterable`]: crate::types::iterable::Iterable
17
pub type ZendIterator = zend_object_iterator;
18

19
impl ZendIterator {
20
    /// Creates a new rust iterator from a `zend_object_iterator`.
21
    ///
22
    /// Returns a iterator over the `zend_object_iterator`, or [`None`] if the
23
    /// iterator cannot be rewound.
24
    // TODO: Check iter not returning iterator
25
    #[allow(clippy::iter_not_returning_iterator)]
26
    pub fn iter(&mut self) -> Option<Iter> {
3✔
27
        self.index = 0;
3✔
28

29
        if self.rewind() {
6✔
30
            return Some(Iter { zi: self });
3✔
31
        }
32

33
        None
34
    }
35

36
    /// Check if the current position of the iterator is valid.
37
    ///
38
    /// As an example this will call the user defined valid method of the
39
    /// `\Iterator` interface. see <https://www.php.net/manual/en/iterator.valid.php>
40
    pub fn valid(&mut self) -> bool {
17✔
41
        if let Some(valid) = unsafe { (*self.funcs).valid } {
34✔
42
            let valid = unsafe { valid(&mut *self) == ZEND_RESULT_CODE_SUCCESS };
43

44
            if ExecutorGlobals::has_exception() {
45
                return false;
×
46
            }
47

48
            valid
49
        } else {
50
            true
×
51
        }
52
    }
53

54
    /// Rewind the iterator to the first element.
55
    ///
56
    /// As an example this will call the user defined rewind method of the
57
    /// `\Iterator` interface. see <https://www.php.net/manual/en/iterator.rewind.php>
58
    ///
59
    /// # Returns
60
    ///
61
    /// Returns true if the iterator was successfully rewind, false otherwise.
62
    /// (when there is an exception during rewind)
63
    pub fn rewind(&mut self) -> bool {
3✔
64
        if let Some(rewind) = unsafe { (*self.funcs).rewind } {
6✔
65
            unsafe {
66
                rewind(&mut *self);
67
            }
68
        }
69

70
        !ExecutorGlobals::has_exception()
3✔
71
    }
72

73
    /// Move the iterator forward to the next element.
74
    ///
75
    /// As an example this will call the user defined next method of the
76
    /// `\Iterator` interface. see <https://www.php.net/manual/en/iterator.next.php>
77
    ///
78
    /// # Returns
79
    ///
80
    /// Returns true if the iterator was successfully move, false otherwise.
81
    /// (when there is an exception during next)
82
    pub fn move_forward(&mut self) -> bool {
12✔
83
        if let Some(move_forward) = unsafe { (*self.funcs).move_forward } {
24✔
84
            unsafe {
85
                move_forward(&mut *self);
86
            }
87
        }
88

89
        !ExecutorGlobals::has_exception()
12✔
90
    }
91

92
    /// Get the current data of the iterator.
93
    ///
94
    /// # Returns
95
    ///
96
    /// Returns a reference to the current data of the iterator if available
97
    /// , [`None`] otherwise.
98
    pub fn get_current_data<'a>(&mut self) -> Option<&'a Zval> {
12✔
99
        let get_current_data = unsafe { (*self.funcs).get_current_data }?;
24✔
100
        let value = unsafe { &*get_current_data(&mut *self) };
×
101

102
        if ExecutorGlobals::has_exception() {
×
103
            return None;
×
104
        }
105

UNCOV
106
        Some(value)
×
107
    }
108

109
    /// Get the current key of the iterator.
110
    ///
111
    /// # Returns
112
    ///
113
    /// Returns a new [`Zval`] containing the current key of the iterator if
114
    /// available , [`None`] otherwise.
115
    pub fn get_current_key(&mut self) -> Option<Zval> {
12✔
116
        let get_current_key = unsafe { (*self.funcs).get_current_key? };
24✔
117
        let mut key = Zval::new();
118

119
        unsafe {
120
            get_current_key(&mut *self, &mut key);
121
        }
122

123
        if ExecutorGlobals::has_exception() {
124
            return None;
×
125
        }
126

127
        Some(key)
128
    }
129
}
130

131
// TODO: Implement `iter_mut`
132
#[allow(clippy::into_iter_without_iter)]
133
impl<'a> IntoIterator for &'a mut ZendIterator {
134
    type Item = (Zval, &'a Zval);
135
    type IntoIter = Iter<'a>;
136

137
    fn into_iter(self) -> Self::IntoIter {
×
138
        self.iter().expect("Could not rewind iterator!")
×
139
    }
140
}
141

142
impl Debug for ZendIterator {
143
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
×
144
        f.debug_struct("ZendIterator").finish()
×
145
    }
146
}
147

148
/// Immutable iterator upon a reference to a PHP iterator.
149
pub struct Iter<'a> {
150
    zi: &'a mut ZendIterator,
151
}
152

153
impl<'a> Iterator for Iter<'a> {
154
    type Item = (Zval, &'a Zval);
155

156
    fn next(&mut self) -> Option<Self::Item> {
15✔
157
        // Call next when index > 0, so next is really called at the start of each
158
        // iteration, which allow to work better with generator iterator
159
        if self.zi.index > 0 && !self.zi.move_forward() {
27✔
160
            return None;
×
161
        }
162

UNCOV
163
        if !self.zi.valid() {
×
164
            return None;
3✔
165
        }
166

167
        self.zi.index += 1;
12✔
168

169
        let real_index = i64::try_from(self.zi.index - 1).expect("index out of bounds");
60✔
170

171
        let key = match self.zi.get_current_key() {
24✔
172
            None => {
×
173
                let mut z = Zval::new();
×
174
                z.set_long(real_index);
×
175
                z
×
176
            }
177
            Some(key) => key,
24✔
178
        };
179

180
        self.zi.get_current_data().map(|value| (key, value))
60✔
181
    }
182
}
183

184
impl<'a> FromZvalMut<'a> for &'a mut ZendIterator {
185
    const TYPE: DataType = DataType::Object(Some("Traversable"));
186

187
    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
×
188
        zval.object()?.get_class_entry().get_iterator(zval, false)
×
189
    }
190
}
191

192
#[cfg(test)]
193
#[cfg(feature = "embed")]
194
mod tests {
195
    #![allow(clippy::unwrap_used)]
196
    use crate::embed::Embed;
197

198
    #[test]
199
    fn test_generator() {
200
        Embed::run(|| {
201
            let result = Embed::run_script("src/types/iterator.test.php");
202

203
            assert!(result.is_ok());
204

205
            let generator = Embed::eval("$generator;");
206

207
            assert!(generator.is_ok());
208

209
            let zval = generator.unwrap();
210

211
            assert!(zval.is_traversable());
212

213
            let iterator = zval.traversable().unwrap();
214

215
            assert!(iterator.valid());
216

217
            {
218
                let mut iter = iterator.iter().unwrap();
219

220
                let (key, value) = iter.next().unwrap();
221

222
                assert_eq!(key.long(), Some(0));
223
                assert!(value.is_long());
224
                assert_eq!(value.long().unwrap(), 1);
225

226
                let (key, value) = iter.next().unwrap();
227

228
                assert_eq!(key.long(), Some(1));
229
                assert!(value.is_long());
230
                assert_eq!(value.long().unwrap(), 2);
231

232
                let (key, value) = iter.next().unwrap();
233

234
                assert_eq!(key.long(), Some(2));
235
                assert!(value.is_long());
236
                assert_eq!(value.long().unwrap(), 3);
237

238
                let (key, value) = iter.next().unwrap();
239

240
                assert!(key.is_object());
241
                assert!(value.is_object());
242

243
                let next = iter.next();
244

245
                assert!(next.is_none());
246
            }
247
        });
248
    }
249

250
    #[test]
251
    fn test_iterator() {
252
        Embed::run(|| {
253
            let result = Embed::run_script("src/types/iterator.test.php");
254

255
            assert!(result.is_ok());
256

257
            let generator = Embed::eval("$iterator;");
258

259
            assert!(generator.is_ok());
260

261
            let zval = generator.unwrap();
262

263
            assert!(zval.is_traversable());
264

265
            let iterator = zval.traversable().unwrap();
266

267
            assert!(iterator.valid());
268

269
            {
270
                let mut iter = iterator.iter().unwrap();
271

272
                let (key, value) = iter.next().unwrap();
273

274
                assert!(!key.is_long());
275
                assert_eq!(key.str(), Some("key"));
276
                assert!(value.is_string());
277
                assert_eq!(value.str(), Some("foo"));
278

279
                let (key, value) = iter.next().unwrap();
280

281
                assert!(key.is_long());
282
                assert_eq!(key.long(), Some(10));
283
                assert!(value.is_string());
284
                assert_eq!(value.string().unwrap(), "bar");
285

286
                let (key, value) = iter.next().unwrap();
287

288
                assert_eq!(key.long(), Some(2));
289
                assert!(value.is_string());
290
                assert_eq!(value.string().unwrap(), "baz");
291

292
                let (key, value) = iter.next().unwrap();
293

294
                assert!(key.is_object());
295
                assert!(value.is_object());
296

297
                let next = iter.next();
298

299
                assert!(next.is_none());
300
            }
301

302
            // Test rewind
303
            {
304
                let mut iter = iterator.iter().unwrap();
305

306
                let (key, value) = iter.next().unwrap();
307

308
                assert_eq!(key.str(), Some("key"));
309
                assert!(value.is_string());
310
                assert_eq!(value.string().unwrap(), "foo");
311

312
                let (key, value) = iter.next().unwrap();
313

314
                assert_eq!(key.long(), Some(10));
315
                assert!(value.is_string());
316
                assert_eq!(value.string().unwrap(), "bar");
317

318
                let (key, value) = iter.next().unwrap();
319

320
                assert_eq!(key.long(), Some(2));
321
                assert!(value.is_string());
322
                assert_eq!(value.string().unwrap(), "baz");
323

324
                let (key, value) = iter.next().unwrap();
325

326
                assert!(key.is_object());
327
                assert!(value.is_object());
328

329
                let next = iter.next();
330

331
                assert!(next.is_none());
332
            }
333
        });
334
    }
335
}
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

© 2025 Coveralls, Inc