Skip to content

Commit 1f5b882

Browse files
committed
feat(array): Entry API (Issue #525)
1 parent 6b192e8 commit 1f5b882

File tree

6 files changed

+1092
-2
lines changed

6 files changed

+1092
-2
lines changed

guide/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- [`bool`](./types/bool.md)
1818
- [`Vec`](./types/vec.md)
1919
- [`HashMap`](./types/hashmap.md)
20+
- [`ZendHashTable`](./types/zend_hashtable.md)
2021
- [`Binary`](./types/binary.md)
2122
- [`BinarySlice`](./types/binary_slice.md)
2223
- [`Option`](./types/option.md)

guide/src/types/zend_hashtable.md

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
# `ZendHashTable`
2+
3+
`ZendHashTable` is the internal representation of PHP arrays. While you can use
4+
`Vec` and `HashMap` for most use cases (which are converted to/from
5+
`ZendHashTable` automatically), working directly with `ZendHashTable` gives you
6+
more control and avoids copying data when you need to manipulate PHP arrays
7+
in-place.
8+
9+
## When to use `ZendHashTable` directly
10+
11+
- When you need to modify a PHP array in place without copying
12+
- When working with arrays passed by reference
13+
- When you need fine-grained control over array operations
14+
- When implementing custom iterators or data structures
15+
16+
## Basic Operations
17+
18+
```rust,no_run
19+
# #![cfg_attr(windows, feature(abi_vectorcall))]
20+
# extern crate ext_php_rs;
21+
use ext_php_rs::prelude::*;
22+
use ext_php_rs::types::ZendHashTable;
23+
24+
#[php_function]
25+
pub fn create_array() -> ZBox<ZendHashTable> {
26+
let mut ht = ZendHashTable::new();
27+
28+
// Push values (auto-incrementing numeric keys)
29+
ht.push("first").unwrap();
30+
ht.push("second").unwrap();
31+
32+
// Insert with string keys
33+
ht.insert("name", "John").unwrap();
34+
ht.insert("age", 30i64).unwrap();
35+
36+
// Insert at specific numeric index
37+
ht.insert_at_index(100, "at index 100").unwrap();
38+
39+
ht
40+
}
41+
42+
#[php_function]
43+
pub fn read_array(arr: &ZendHashTable) {
44+
// Get by string key
45+
if let Some(name) = arr.get("name") {
46+
println!("Name: {:?}", name.str());
47+
}
48+
49+
// Get by numeric index
50+
if let Some(first) = arr.get_index(0) {
51+
println!("First: {:?}", first.str());
52+
}
53+
54+
// Check length
55+
println!("Length: {}", arr.len());
56+
println!("Is empty: {}", arr.is_empty());
57+
58+
// Iterate over key-value pairs
59+
for (key, value) in arr.iter() {
60+
println!("{}: {:?}", key, value);
61+
}
62+
}
63+
# fn main() {}
64+
```
65+
66+
## Entry API
67+
68+
The Entry API provides an ergonomic way to handle hash table operations where
69+
you need to conditionally insert or update values based on whether a key already
70+
exists. This is similar to Rust's `std::collections::hash_map::Entry` API.
71+
72+
### Basic Usage
73+
74+
```rust,no_run
75+
# #![cfg_attr(windows, feature(abi_vectorcall))]
76+
# extern crate ext_php_rs;
77+
use ext_php_rs::prelude::*;
78+
use ext_php_rs::types::ZendHashTable;
79+
80+
#[php_function]
81+
pub fn entry_example() -> ZBox<ZendHashTable> {
82+
let mut ht = ZendHashTable::new();
83+
84+
// Insert a default value if the key doesn't exist
85+
ht.entry("counter").or_insert(0i64).unwrap();
86+
87+
// Modify the value if it exists, using and_modify
88+
ht.entry("counter")
89+
.and_modify(|v| {
90+
if let Some(n) = v.long() {
91+
v.set_long(n + 1);
92+
}
93+
})
94+
.or_insert(0i64)
95+
.unwrap();
96+
97+
// Use or_insert_with for lazy initialization
98+
ht.entry("computed")
99+
.or_insert_with(|| "computed value")
100+
.unwrap();
101+
102+
// Works with numeric keys too
103+
ht.entry(42i64).or_insert("value at index 42").unwrap();
104+
105+
ht
106+
}
107+
# fn main() {}
108+
```
109+
110+
### Entry Variants
111+
112+
The `entry()` method returns an `Entry` enum with two variants:
113+
114+
- `Entry::Occupied` - The key exists in the hash table
115+
- `Entry::Vacant` - The key does not exist
116+
117+
```rust,no_run
118+
# #![cfg_attr(windows, feature(abi_vectorcall))]
119+
# extern crate ext_php_rs;
120+
use ext_php_rs::prelude::*;
121+
use ext_php_rs::types::{ZendHashTable, Entry};
122+
123+
#[php_function]
124+
pub fn match_entry() -> ZBox<ZendHashTable> {
125+
let mut ht = ZendHashTable::new();
126+
ht.insert("existing", "value").unwrap();
127+
128+
// Pattern match on the entry
129+
match ht.entry("existing") {
130+
Entry::Occupied(entry) => {
131+
println!("Key {:?} exists with value {:?}",
132+
entry.key(),
133+
entry.get().ok().and_then(|v| v.str()));
134+
}
135+
Entry::Vacant(entry) => {
136+
println!("Key {:?} is vacant", entry.key());
137+
entry.insert("new value").unwrap();
138+
}
139+
}
140+
141+
ht
142+
}
143+
# fn main() {}
144+
```
145+
146+
### Common Patterns
147+
148+
#### Counting occurrences
149+
150+
```rust,no_run
151+
# #![cfg_attr(windows, feature(abi_vectorcall))]
152+
# extern crate ext_php_rs;
153+
use ext_php_rs::prelude::*;
154+
use ext_php_rs::types::ZendHashTable;
155+
156+
#[php_function]
157+
pub fn count_words(words: Vec<String>) -> ZBox<ZendHashTable> {
158+
let mut counts = ZendHashTable::new();
159+
160+
for word in words {
161+
counts.entry(word.as_str())
162+
.and_modify(|v| {
163+
if let Some(n) = v.long() {
164+
v.set_long(n + 1);
165+
}
166+
})
167+
.or_insert(1i64)
168+
.unwrap();
169+
}
170+
171+
counts
172+
}
173+
# fn main() {}
174+
```
175+
176+
#### Caching computed values
177+
178+
```rust,no_run
179+
# #![cfg_attr(windows, feature(abi_vectorcall))]
180+
# extern crate ext_php_rs;
181+
use ext_php_rs::prelude::*;
182+
use ext_php_rs::types::ZendHashTable;
183+
184+
fn expensive_computation(key: &str) -> String {
185+
format!("computed_{}", key)
186+
}
187+
188+
#[php_function]
189+
pub fn get_or_compute(cache: &mut ZendHashTable, key: String) -> String {
190+
let value = cache.entry(key.as_str())
191+
.or_insert_with_key(|k| expensive_computation(&k.to_string()))
192+
.unwrap();
193+
194+
value.str().unwrap_or_default().to_string()
195+
}
196+
# fn main() {}
197+
```
198+
199+
#### Updating existing values
200+
201+
```rust,no_run
202+
# #![cfg_attr(windows, feature(abi_vectorcall))]
203+
# extern crate ext_php_rs;
204+
use ext_php_rs::prelude::*;
205+
use ext_php_rs::types::{ZendHashTable, Entry};
206+
207+
#[php_function]
208+
pub fn update_if_exists(ht: &mut ZendHashTable, key: String, new_value: String) -> bool {
209+
match ht.entry(key.as_str()) {
210+
Entry::Occupied(mut entry) => {
211+
entry.insert(new_value).unwrap();
212+
true
213+
}
214+
Entry::Vacant(_) => false,
215+
}
216+
}
217+
# fn main() {}
218+
```
219+
220+
### Entry Methods Reference
221+
222+
#### `Entry` methods
223+
224+
| Method | Description |
225+
|-------------------------|------------------------------------------------|
226+
| `or_insert(default)` | Insert `default` if vacant, return `&mut Zval` |
227+
| `or_insert_with(f)` | Insert result of `f()` if vacant |
228+
| `or_insert_with_key(f)` | Insert result of `f(&key)` if vacant |
229+
| `or_default()` | Insert default `Zval` (null) if vacant |
230+
| `key()` | Get reference to the key |
231+
| `and_modify(f)` | Modify value in place if occupied |
232+
233+
#### `OccupiedEntry` methods
234+
235+
| Method | Description |
236+
|------------------|----------------------------------------------------|
237+
| `key()` | Get reference to key |
238+
| `get()` | Get reference to value |
239+
| `get_mut()` | Get mutable reference to value |
240+
| `into_mut()` | Convert to mutable reference with entry's lifetime |
241+
| `insert(value)` | Replace value, returning old value |
242+
| `remove()` | Remove and return value |
243+
| `remove_entry()` | Remove and return key-value pair |
244+
245+
#### `VacantEntry` methods
246+
247+
| Method | Description |
248+
|-----------------|-------------------------------------|
249+
| `key()` | Get reference to key |
250+
| `into_key()` | Take ownership of key |
251+
| `insert(value)` | Insert value and return `&mut Zval` |
252+
253+
## PHP Example
254+
255+
```php
256+
<?php
257+
258+
// Using the create_array function
259+
$arr = create_array();
260+
var_dump($arr);
261+
// array(5) {
262+
// [0]=> string(5) "first"
263+
// [1]=> string(6) "second"
264+
// ["name"]=> string(4) "John"
265+
// ["age"]=> int(30)
266+
// [100]=> string(12) "at index 100"
267+
// }
268+
269+
// Count words
270+
$counts = count_words(['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']);
271+
var_dump($counts);
272+
// array(3) {
273+
// ["apple"]=> int(3)
274+
// ["banana"]=> int(2)
275+
// ["cherry"]=> int(1)
276+
// }
277+
```

0 commit comments

Comments
 (0)