Skip to content

Commit 8d77b9e

Browse files
committed
Merge remote-tracking branch 'joel/feat/iterator'
2 parents 5de7c7f + cf6c362 commit 8d77b9e

File tree

15 files changed

+700
-39
lines changed

15 files changed

+700
-39
lines changed

allowed_bindings.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ bind! {
9494
zend_internal_arg_info,
9595
zend_is_callable,
9696
zend_is_identical,
97+
zend_is_iterable,
9798
zend_long,
9899
zend_lookup_class_ex,
99100
zend_module_entry,
@@ -163,6 +164,7 @@ bind! {
163164
IS_UNDEF,
164165
IS_VOID,
165166
IS_PTR,
167+
IS_ITERABLE,
166168
MAY_BE_ANY,
167169
MAY_BE_BOOL,
168170
PHP_INI_USER,

docsrs_bindings.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub const IS_RESOURCE: u32 = 9;
9797
pub const IS_REFERENCE: u32 = 10;
9898
pub const IS_CONSTANT_AST: u32 = 11;
9999
pub const IS_CALLABLE: u32 = 12;
100+
pub const IS_ITERABLE: u32 = 13;
100101
pub const IS_VOID: u32 = 14;
101102
pub const IS_MIXED: u32 = 16;
102103
pub const IS_INDIRECT: u32 = 12;
@@ -1655,6 +1656,9 @@ extern "C" {
16551656
named_params: *mut HashTable,
16561657
);
16571658
}
1659+
extern "C" {
1660+
pub fn zend_is_iterable(iterable: *mut zval) -> bool;
1661+
}
16581662
pub const _zend_expected_type_Z_EXPECTED_LONG: _zend_expected_type = 0;
16591663
pub const _zend_expected_type_Z_EXPECTED_LONG_OR_NULL: _zend_expected_type = 1;
16601664
pub const _zend_expected_type_Z_EXPECTED_BOOL: _zend_expected_type = 2;

guide/src/types/iterable.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# `Iterable`
2+
3+
`Iterable`s are represented either by an `array` or `Traversable` type.
4+
5+
| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation |
6+
|---------------|----------------|-----------------| ---------------- |----------------------------------|
7+
| Yes | No | No | No | `ZendHashTable` or `ZendIterator` |
8+
9+
Converting from a zval to a `Iterable` is valid when the value is either an array or an object
10+
that implements the `Traversable` interface. This means that any value that can be used in a
11+
`foreach` loop can be converted into a `Iterable`.
12+
13+
## Rust example
14+
15+
```rust,no_run
16+
# #![cfg_attr(windows, feature(abi_vectorcall))]
17+
# extern crate ext_php_rs;
18+
# use ext_php_rs::prelude::*;
19+
# use ext_php_rs::types::Iterable;
20+
#[php_function]
21+
pub fn test_iterable(mut iterable: Iterable) {
22+
for (k, v) in iterable.iter().expect("cannot get iterable") {
23+
println!("k: {} v: {}", k, v.string().unwrap());
24+
}
25+
}
26+
# fn main() {}
27+
```
28+
29+
## PHP example
30+
31+
```php
32+
<?php
33+
34+
$generator = function() {
35+
yield 'hello' => 'world';
36+
yield 'rust' => 'php';
37+
yield 'okk';
38+
};
39+
40+
$array = [
41+
'hello' => 'world',
42+
'rust' => 'php',
43+
'okk',
44+
];
45+
46+
test_iterable($generator());
47+
test_iterable($array);
48+
```
49+
50+
Output:
51+
52+
```text
53+
k: hello v: world
54+
k: rust v: php
55+
k: 0 v: okk
56+
k: hello v: world
57+
k: rust v: php
58+
k: 0 v: okk
59+
```

guide/src/types/iterator.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# `ZendIterator`
2+
3+
`ZendIterator`s are represented by the `Traversable` type.
4+
5+
| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation |
6+
|---------------| -------------- |-----------------| ---------------- | ------------------ |
7+
| No | Yes | No | No | `ZendIterator` |
8+
9+
Converting from a zval to a `ZendIterator` is valid when there is an associated iterator to
10+
the variable. This means that any value, at the exception of an `array`, that can be used in
11+
a `foreach` loop can be converted into a `ZendIterator`. As an example, a `Generator` can be
12+
used but also a the result of a `query` call with `PDO`.
13+
14+
## Rust example
15+
16+
```rust,no_run
17+
# #![cfg_attr(windows, feature(abi_vectorcall))]
18+
# extern crate ext_php_rs;
19+
# use ext_php_rs::prelude::*;
20+
# use ext_php_rs::types::ZendIterator;
21+
#[php_function]
22+
pub fn test_iterator(iterator: &mut ZendIterator) {
23+
for (k, v) in iterator.iter().expect("cannot get iterator") {
24+
println!("k: {} v: {}", k, v.string().unwrap());
25+
}
26+
}
27+
# fn main() {}
28+
```
29+
30+
## PHP example
31+
32+
```php
33+
<?php
34+
35+
$generator = function() {
36+
yield 'hello' => 'world';
37+
yield 'rust' => 'php';
38+
yield 'okk';
39+
};
40+
41+
test_iterator($generator());
42+
```
43+
44+
Output:
45+
46+
```text
47+
k: hello v: world
48+
k: rust v: php
49+
k: 0 v: okk
50+
```

src/describe/stub.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ impl ToStub for DataType {
172172
DataType::Reference => "reference",
173173
DataType::Callable => "callable",
174174
DataType::Bool => "bool",
175+
DataType::Iterable => "iterable",
175176
_ => "mixed",
176177
}
177178
)

src/flags.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::ffi::{
99
E_COMPILE_WARNING, E_CORE_ERROR, E_CORE_WARNING, E_DEPRECATED, E_ERROR, E_NOTICE, E_PARSE,
1010
E_RECOVERABLE_ERROR, E_STRICT, E_USER_DEPRECATED, E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING,
1111
E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_INDIRECT, IS_LONG,
12+
IS_ITERABLE,
1213
IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE,
1314
IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR,
1415
PHP_INI_SYSTEM, PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS,
@@ -49,6 +50,7 @@ bitflags! {
4950
const ConstantExpression = IS_CONSTANT_AST;
5051
const Void = IS_VOID;
5152
const Ptr = IS_PTR;
53+
const Iterable = IS_ITERABLE;
5254

5355
const InternedStringEx = Self::String.bits();
5456
const StringEx = Self::String.bits() | Self::RefCounted.bits();
@@ -237,6 +239,7 @@ pub enum DataType {
237239
Double,
238240
String,
239241
Array,
242+
Iterable,
240243
Object(Option<&'static str>),
241244
Resource,
242245
Reference,
@@ -277,6 +280,7 @@ impl DataType {
277280
DataType::Mixed => IS_MIXED,
278281
DataType::Bool => _IS_BOOL,
279282
DataType::Ptr => IS_PTR,
283+
DataType::Iterable => IS_ITERABLE,
280284
}
281285
}
282286
}
@@ -383,6 +387,7 @@ impl Display for DataType {
383387
DataType::Mixed => write!(f, "Mixed"),
384388
DataType::Ptr => write!(f, "Pointer"),
385389
DataType::Indirect => write!(f, "Indirect"),
390+
DataType::Iterable => write!(f, "Iterable"),
386391
}
387392
}
388393
}

src/types/array.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{
1010
u64,
1111
};
1212

13+
use crate::types::iterator::IterKey;
1314
use crate::{
1415
boxed::{ZBox, ZBoxable},
1516
convert::{FromZval, IntoZval},
@@ -463,7 +464,7 @@ impl ZendHashTable {
463464
/// assert!(!ht.has_numerical_keys());
464465
/// ```
465466
pub fn has_numerical_keys(&self) -> bool {
466-
!self.iter().any(|(_, k, _)| k.is_some())
467+
!self.iter().any(|(k, _)| !k.is_numerical())
467468
}
468469

469470
/// Checks if the hashtable has numerical, sequential keys.
@@ -492,7 +493,7 @@ impl ZendHashTable {
492493
!self
493494
.iter()
494495
.enumerate()
495-
.any(|(i, (k, strk, _))| i as u64 != k || strk.is_some())
496+
.any(|(i, (k, _))| IterKey::Long(i as u64) != k)
496497
}
497498

498499
/// Returns an iterator over the key(s) and value contained inside the
@@ -505,12 +506,12 @@ impl ZendHashTable {
505506
///
506507
/// let mut ht = ZendHashTable::new();
507508
///
508-
/// for (idx, key, val) in ht.iter() {
509+
/// for (key, val) in ht.iter() {
509510
/// // ^ Index if inserted at an index.
510511
/// // ^ Optional string key, if inserted like a hashtable.
511512
/// // ^ Inserted value.
512513
///
513-
/// dbg!(idx, key, val);
514+
/// dbg!(key, val);
514515
/// }
515516
#[inline]
516517
pub fn iter(&self) -> Iter {
@@ -546,10 +547,7 @@ unsafe impl ZBoxable for ZendHashTable {
546547
impl Debug for ZendHashTable {
547548
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548549
f.debug_map()
549-
.entries(
550-
self.iter()
551-
.map(|(k, k2, v)| (k2.unwrap_or_else(|| k.to_string()), v)),
552-
)
550+
.entries(self.iter().map(|(k, v)| (k.to_string(), v)))
553551
.finish()
554552
}
555553
}
@@ -594,7 +592,7 @@ impl<'a> Iter<'a> {
594592
}
595593

596594
impl<'a> Iterator for Iter<'a> {
597-
type Item = (u64, Option<String>, &'a Zval);
595+
type Item = (IterKey, &'a Zval);
598596

599597
fn next(&mut self) -> Option<Self::Item> {
600598
let key_type = unsafe {
@@ -622,9 +620,10 @@ impl<'a> Iterator for Iter<'a> {
622620
&mut self.pos as *mut HashPosition,
623621
)
624622
};
625-
let r: (u64, Option<String>, &Zval) = match key.is_long() {
626-
true => (key.long().unwrap_or(0) as u64, None, value),
627-
false => (self.current_num, key.try_into().ok(), value),
623+
624+
let r = match IterKey::from_zval(&key) {
625+
Some(key) => (key, value),
626+
None => (IterKey::Long(self.current_num), value),
628627
};
629628

630629
unsafe {
@@ -679,9 +678,10 @@ impl<'a> DoubleEndedIterator for Iter<'a> {
679678
&mut self.pos as *mut HashPosition,
680679
)
681680
};
682-
let r: (u64, Option<String>, &Zval) = match key.is_long() {
683-
true => (key.long().unwrap_or(0) as u64, None, value),
684-
false => (self.current_num, key.try_into().ok(), value),
681+
682+
let r = match IterKey::from_zval(&key) {
683+
Some(key) => (key, value),
684+
None => (IterKey::Long(self.current_num), value),
685685
};
686686

687687
unsafe {
@@ -715,7 +715,7 @@ impl<'a> Iterator for Values<'a> {
715715
type Item = &'a Zval;
716716

717717
fn next(&mut self) -> Option<Self::Item> {
718-
self.0.next().map(|(_, _, zval)| zval)
718+
self.0.next().map(|(_, zval)| zval)
719719
}
720720

721721
fn count(self) -> usize
@@ -734,7 +734,7 @@ impl<'a> ExactSizeIterator for Values<'a> {
734734

735735
impl<'a> DoubleEndedIterator for Values<'a> {
736736
fn next_back(&mut self) -> Option<Self::Item> {
737-
self.0.next_back().map(|(_, _, zval)| zval)
737+
self.0.next_back().map(|(_, zval)| zval)
738738
}
739739
}
740740

@@ -780,9 +780,9 @@ where
780780
fn try_from(value: &'a ZendHashTable) -> Result<Self> {
781781
let mut hm = HashMap::with_capacity(value.len());
782782

783-
for (idx, key, val) in value.iter() {
783+
for (key, val) in value.iter() {
784784
hm.insert(
785-
key.unwrap_or_else(|| idx.to_string()),
785+
key.to_string(),
786786
V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
787787
);
788788
}
@@ -849,7 +849,7 @@ where
849849
fn try_from(value: &'a ZendHashTable) -> Result<Self> {
850850
let mut vec = Vec::with_capacity(value.len());
851851

852-
for (_, _, val) in value.iter() {
852+
for (_, val) in value.iter() {
853853
vec.push(T::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?);
854854
}
855855

src/types/iterable.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use super::array::Iter as ZendHashTableIter;
2+
use super::iterator::Iter as ZendIteratorIter;
3+
use crate::convert::FromZval;
4+
use crate::flags::DataType;
5+
use crate::types::iterator::IterKey;
6+
use crate::types::{ZendHashTable, ZendIterator, Zval};
7+
8+
/// This type represents a PHP iterable, which can be either an array or an object implementing
9+
/// the Traversable interface.
10+
#[derive(Debug)]
11+
pub enum Iterable<'a> {
12+
Array(&'a ZendHashTable),
13+
Traversable(&'a mut ZendIterator),
14+
}
15+
16+
impl<'a> Iterable<'a> {
17+
/// Creates a new rust iterator from a PHP iterable.
18+
pub fn iter(&mut self) -> Option<Iter> {
19+
match self {
20+
Iterable::Array(array) => Some(Iter::Array(array.iter())),
21+
Iterable::Traversable(traversable) => Some(Iter::Traversable(traversable.iter()?)),
22+
}
23+
}
24+
}
25+
26+
impl<'a> FromZval<'a> for Iterable<'a> {
27+
const TYPE: DataType = DataType::Iterable;
28+
29+
fn from_zval(zval: &'a Zval) -> Option<Self> {
30+
if let Some(array) = zval.array() {
31+
return Some(Iterable::Array(array));
32+
}
33+
34+
if let Some(traversable) = zval.traversable() {
35+
return Some(Iterable::Traversable(traversable));
36+
}
37+
38+
None
39+
}
40+
}
41+
42+
/// Rust iterator over a PHP iterable.
43+
pub enum Iter<'a> {
44+
Array(ZendHashTableIter<'a>),
45+
Traversable(ZendIteratorIter<'a>),
46+
}
47+
48+
impl<'a> Iterator for Iter<'a> {
49+
type Item = (IterKey, &'a Zval);
50+
51+
fn next(&mut self) -> Option<Self::Item> {
52+
match self {
53+
Iter::Array(array) => array.next(),
54+
Iter::Traversable(traversable) => traversable.next(),
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)