Skip to main content

ext_php_rs/types/
iterable.rs

1use super::array::Iter as ZendHashTableIter;
2use super::iterator::Iter as ZendIteratorIter;
3use crate::convert::FromZvalMut;
4use crate::flags::DataType;
5use crate::types::{ZendHashTable, ZendIterator, Zval};
6
7/// This type represents a PHP iterable, which can be either an array or an
8/// object implementing the Traversable interface.
9///
10/// An `Iterable` can only be extracted from a mutable [`Zval`], via
11/// [`FromZvalMut`]. Resolving the `Traversable` variant invokes the Zend
12/// `get_iterator` handler, which mutates the object, so a shared borrow is not
13/// enough:
14///
15/// ```compile_fail
16/// use ext_php_rs::{convert::FromZvalMut, types::{Iterable, Zval}};
17///
18/// fn extract(zval: &Zval) -> Option<Iterable<'_>> {
19///     Iterable::from_zval_mut(zval)
20/// }
21/// ```
22#[derive(Debug)]
23pub enum Iterable<'a> {
24    /// Iterable is an Array
25    Array(&'a ZendHashTable),
26    /// Iterable is a Traversable
27    Traversable(&'a mut ZendIterator),
28}
29
30impl Iterable<'_> {
31    /// Creates a new rust iterator from a PHP iterable.
32    /// May return None if a Traversable cannot be rewound.
33    // TODO: Check iter not returning iterator
34    #[allow(clippy::iter_not_returning_iterator)]
35    pub fn iter(&mut self) -> Option<Iter<'_>> {
36        match self {
37            Iterable::Array(array) => Some(Iter::Array(array.iter())),
38            Iterable::Traversable(traversable) => Some(Iter::Traversable(traversable.iter()?)),
39        }
40    }
41}
42
43// TODO: Implement `iter_mut`
44#[allow(clippy::into_iter_without_iter)]
45impl<'a> IntoIterator for &'a mut Iterable<'a> {
46    type Item = (Zval, Zval);
47    type IntoIter = Iter<'a>;
48
49    #[expect(
50        clippy::expect_used,
51        reason = "IntoIterator has no error channel, and a fresh iterator can always be rewound"
52    )]
53    fn into_iter(self) -> Self::IntoIter {
54        self.iter().expect("Could not rewind iterator!")
55    }
56}
57
58impl<'a> FromZvalMut<'a> for Iterable<'a> {
59    const TYPE: DataType = DataType::Iterable;
60
61    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
62        if zval.is_traversable() {
63            return zval.traversable().map(Iterable::Traversable);
64        }
65
66        zval.array().map(Iterable::Array)
67    }
68}
69
70/// Rust iterator over a PHP iterable.
71///
72/// Values are owned shallow clones so that both variants share one item type;
73/// see [`super::iterator::Iter`] for why the `Traversable` side cannot lend.
74pub enum Iter<'a> {
75    Array(ZendHashTableIter<'a>),
76    Traversable(ZendIteratorIter<'a>),
77}
78
79impl Iterator for Iter<'_> {
80    type Item = (Zval, Zval);
81
82    fn next(&mut self) -> Option<Self::Item> {
83        match self {
84            Iter::Array(array) => array
85                .next_zval()
86                .map(|(key, value)| (key, value.shallow_clone())),
87            Iter::Traversable(traversable) => traversable.next(),
88        }
89    }
90}