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

extphprs / ext-php-rs / 21334611714

25 Jan 2026 03:01PM UTC coverage: 35.853% (+0.9%) from 34.976%
21334611714

Pull #611

github

web-flow
Merge 56ba33bb5 into 6013c2e62
Pull Request #611: feat(array): Entry API (Issue #525)

57 of 90 new or added lines in 2 files covered. (63.33%)

56 existing lines in 1 file now uncovered.

1902 of 5305 relevant lines covered (35.85%)

14.42 hits per line

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

60.81
/src/types/array/entry.rs
1
//! Entry API for [`ZendHashTable`], similar to Rust's `std::collections::hash_map::Entry`.
2
//!
3
//! This module provides an ergonomic API for working with entries in a PHP
4
//! hashtable, allowing conditional insertion or modification based on whether
5
//! a key already exists.
6
//!
7
//! # Examples
8
//!
9
//! ```no_run
10
//! use ext_php_rs::types::ZendHashTable;
11
//!
12
//! let mut ht = ZendHashTable::new();
13
//!
14
//! // Insert a default value if the key doesn't exist
15
//! ht.entry("counter").or_insert(0i64);
16
//!
17
//! // Modify the value if it exists
18
//! ht.entry("counter").and_modify(|v| {
19
//!     if let Some(n) = v.long() {
20
//!         v.set_long(n + 1);
21
//!     }
22
//! });
23
//!
24
//! // Or use or_insert_with for lazy initialization
25
//! ht.entry("computed").or_insert_with(|| "computed value");
26
//! ```
27

28
use super::{ArrayKey, ZendHashTable};
29
use crate::error::Error;
30
use crate::{
31
    convert::IntoZval,
32
    error::Result,
33
    ffi::{
34
        zend_hash_index_find, zend_hash_index_update, zend_hash_str_find, zend_hash_str_update,
35
        zend_ulong,
36
    },
37
    types::Zval,
38
};
39
use std::mem::ManuallyDrop;
40

41
/// A view into a single entry in a [`ZendHashTable`], which may either be vacant or
42
/// occupied.
43
///
44
/// This enum is constructed from the [`entry`] method on [`ZendHashTable`].
45
///
46
/// [`entry`]: ZendHashTable::entry
47
pub enum Entry<'a, 'k> {
48
    /// An occupied entry.
49
    Occupied(OccupiedEntry<'a, 'k>),
50
    /// A vacant entry.
51
    Vacant(VacantEntry<'a, 'k>),
52
}
53

54
/// A view into an occupied entry in a [`ZendHashTable`].
55
///
56
/// It is part of the [`Entry`] enum.
57
pub struct OccupiedEntry<'a, 'k> {
58
    ht: &'a mut ZendHashTable,
59
    key: ArrayKey<'k>,
60
}
61

62
/// A view into a vacant entry in a [`ZendHashTable`].
63
///
64
/// It is part of the [`Entry`] enum.
65
pub struct VacantEntry<'a, 'k> {
66
    ht: &'a mut ZendHashTable,
67
    key: ArrayKey<'k>,
68
}
69

70
impl<'a, 'k> Entry<'a, 'k> {
71
    /// Ensures a value is in the entry by inserting the default if empty, and
72
    /// returns a mutable reference to the value in the entry.
73
    ///
74
    /// # Parameters
75
    ///
76
    /// * `default` - The default value to insert if the entry is vacant.
77
    ///
78
    /// # Returns
79
    ///
80
    /// A result containing a mutable reference to the value, or an error if
81
    /// the insertion failed.
82
    ///
83
    /// # Errors
84
    ///
85
    /// Returns an error if the value conversion to [`Zval`] fails or if
86
    /// the key contains a null byte (for string keys).
87
    ///
88
    /// # Example
89
    ///
90
    /// ```no_run
91
    /// use ext_php_rs::types::ZendHashTable;
92
    ///
93
    /// let mut ht = ZendHashTable::new();
94
    /// ht.entry("key").or_insert("default value");
95
    /// assert_eq!(ht.get("key").and_then(|v| v.str()), Some("default value"));
96
    /// ```
97
    pub fn or_insert<V: IntoZval>(self, default: V) -> Result<&'a mut Zval> {
6✔
98
        match self {
6✔
99
            Entry::Occupied(entry) => entry.into_mut().ok_or(Error::InvalidPointer),
10✔
100
            Entry::Vacant(entry) => entry.insert(default),
16✔
101
        }
102
    }
103

104
    /// Ensures a value is in the entry by inserting the result of the default
105
    /// function if empty, and returns a mutable reference to the value in the
106
    /// entry.
107
    ///
108
    /// # Parameters
109
    ///
110
    /// * `default` - A function that returns the default value to insert.
111
    ///
112
    /// # Returns
113
    ///
114
    /// A result containing a mutable reference to the value, or an error if
115
    /// the insertion failed.
116
    ///
117
    /// # Errors
118
    ///
119
    /// Returns an error if the value conversion to [`Zval`] fails or if
120
    /// the key contains a null byte (for string keys).
121
    ///
122
    /// # Example
123
    ///
124
    /// ```no_run
125
    /// use ext_php_rs::types::ZendHashTable;
126
    ///
127
    /// let mut ht = ZendHashTable::new();
128
    /// ht.entry("key").or_insert_with(|| "computed value");
129
    /// ```
130
    pub fn or_insert_with<V: IntoZval, F: FnOnce() -> V>(self, default: F) -> Result<&'a mut Zval> {
1✔
131
        match self {
1✔
NEW
132
            Entry::Occupied(entry) => entry.into_mut().ok_or(Error::InvalidPointer),
×
133
            Entry::Vacant(entry) => entry.insert(default()),
4✔
134
        }
135
    }
136

137
    /// Ensures a value is in the entry by inserting the result of the default
138
    /// function if empty. The function receives a reference to the key.
139
    ///
140
    /// # Parameters
141
    ///
142
    /// * `default` - A function that takes the key and returns the default value.
143
    ///
144
    /// # Returns
145
    ///
146
    /// A result containing a mutable reference to the value, or an error if
147
    /// the insertion failed.
148
    ///
149
    /// # Errors
150
    ///
151
    /// Returns an error if the value conversion to [`Zval`] fails or if
152
    /// the key contains a null byte (for string keys).
153
    ///
154
    /// # Example
155
    ///
156
    /// ```no_run
157
    /// use ext_php_rs::types::ZendHashTable;
158
    ///
159
    /// let mut ht = ZendHashTable::new();
160
    /// ht.entry("key").or_insert_with_key(|k| format!("value for {}", k));
161
    /// ```
NEW
162
    pub fn or_insert_with_key<V: IntoZval, F: FnOnce(&ArrayKey<'k>) -> V>(
×
163
        self,
164
        default: F,
165
    ) -> Result<&'a mut Zval> {
NEW
166
        match self {
×
NEW
167
            Entry::Occupied(entry) => entry.into_mut().ok_or(Error::InvalidPointer),
×
NEW
168
            Entry::Vacant(entry) => {
×
NEW
169
                let value = default(entry.key());
×
NEW
170
                entry.insert(value)
×
171
            }
172
        }
173
    }
174

175
    /// Returns a reference to this entry's key.
176
    ///
177
    /// # Example
178
    ///
179
    /// ```no_run
180
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
181
    ///
182
    /// let mut ht = ZendHashTable::new();
183
    /// assert_eq!(ht.entry("key").key(), &ArrayKey::Str("key"));
184
    /// ```
185
    #[must_use]
NEW
186
    pub fn key(&self) -> &ArrayKey<'k> {
×
NEW
187
        match self {
×
NEW
188
            Entry::Occupied(entry) => entry.key(),
×
NEW
189
            Entry::Vacant(entry) => entry.key(),
×
190
        }
191
    }
192

193
    /// Provides in-place mutable access to an occupied entry before any
194
    /// potential inserts into the map.
195
    ///
196
    /// # Parameters
197
    ///
198
    /// * `f` - A function that modifies the value in place.
199
    ///
200
    /// # Returns
201
    ///
202
    /// The entry, allowing for method chaining.
203
    ///
204
    /// # Example
205
    ///
206
    /// ```no_run
207
    /// use ext_php_rs::types::ZendHashTable;
208
    ///
209
    /// let mut ht = ZendHashTable::new();
210
    /// ht.insert("counter", 0i64);
211
    ///
212
    /// ht.entry("counter")
213
    ///     .and_modify(|v| {
214
    ///         if let Some(n) = v.long() {
215
    ///             v.set_long(n + 1);
216
    ///         }
217
    ///     })
218
    ///     .or_insert(0i64);
219
    /// ```
220
    #[must_use]
221
    pub fn and_modify<F: FnOnce(&mut Zval)>(self, f: F) -> Self {
2✔
222
        match self {
2✔
223
            Entry::Occupied(mut entry) => {
1✔
224
                if let Some(value) = entry.get_mut() {
3✔
225
                    f(value);
1✔
226
                }
227
                Entry::Occupied(entry)
1✔
228
            }
229
            Entry::Vacant(entry) => Entry::Vacant(entry),
2✔
230
        }
231
    }
232
}
233

234
impl<'a, 'k> Entry<'a, 'k>
235
where
236
    'k: 'a,
237
{
238
    /// Ensures a value is in the entry by inserting the default value if empty,
239
    /// and returns a mutable reference to the value in the entry.
240
    ///
241
    /// This is a convenience method that uses `Default::default()` as the
242
    /// default value.
243
    ///
244
    /// # Returns
245
    ///
246
    /// A result containing a mutable reference to the value, or an error if
247
    /// the insertion failed.
248
    ///
249
    /// # Errors
250
    ///
251
    /// Returns an error if the key contains a null byte (for string keys).
252
    ///
253
    /// # Example
254
    ///
255
    /// ```no_run
256
    /// use ext_php_rs::types::ZendHashTable;
257
    ///
258
    /// let mut ht = ZendHashTable::new();
259
    /// // Inserts a null Zval if the key doesn't exist
260
    /// ht.entry("key").or_default();
261
    /// ```
NEW
262
    pub fn or_default(self) -> Result<&'a mut Zval> {
×
NEW
263
        match self {
×
NEW
264
            Entry::Occupied(entry) => entry.into_mut().ok_or(Error::InvalidPointer),
×
NEW
265
            Entry::Vacant(entry) => entry.insert(Zval::new()),
×
266
        }
267
    }
268
}
269

270
impl<'a, 'k> OccupiedEntry<'a, 'k> {
271
    /// Creates a new occupied entry.
272
    pub(super) fn new(ht: &'a mut ZendHashTable, key: ArrayKey<'k>) -> Self {
4✔
273
        Self { ht, key }
274
    }
275

276
    /// Gets a reference to the key in the entry.
277
    ///
278
    /// # Example
279
    ///
280
    /// ```no_run
281
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
282
    ///
283
    /// let mut ht = ZendHashTable::new();
284
    /// ht.insert("key", "value");
285
    ///
286
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
287
    ///     assert_eq!(entry.key(), &ArrayKey::Str("key"));
288
    /// }
289
    /// ```
290
    #[must_use]
NEW
291
    pub fn key(&self) -> &ArrayKey<'k> {
×
NEW
292
        &self.key
×
293
    }
294

295
    /// Gets a reference to the value in the entry.
296
    ///
297
    /// # Returns
298
    ///
299
    /// A result containing a reference to the value, or an error if
300
    /// the key conversion failed.
301
    ///
302
    /// # Errors
303
    ///
304
    /// Returns an error if the key contains a null byte (for string keys).
305
    ///
306
    /// # Example
307
    ///
308
    /// ```no_run
309
    /// use ext_php_rs::types::{ZendHashTable, Zval};
310
    ///
311
    /// let mut ht = ZendHashTable::new();
312
    /// ht.insert("key", "value");
313
    ///
314
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
315
    ///     assert_eq!(entry.get().and_then(Zval::str), Some("value"));
316
    /// }
317
    /// ```
318
    #[must_use]
319
    pub fn get(&self) -> Option<&Zval> {
2✔
320
        match &self.key {
2✔
321
            ArrayKey::Long(index) => unsafe {
322
                #[allow(clippy::cast_sign_loss)]
NEW
323
                zend_hash_index_find(self.ht, *index as zend_ulong).as_ref()
×
324
            },
325
            ArrayKey::String(key) => unsafe {
NEW
326
                zend_hash_str_find(self.ht, key.as_ptr().cast(), key.len()).as_ref()
×
327
            },
328
            ArrayKey::Str(key) => unsafe {
329
                zend_hash_str_find(self.ht, key.as_ptr().cast(), key.len()).as_ref()
16✔
330
            },
331
        }
332
    }
333

334
    /// Gets a mutable reference to the value in the entry.
335
    ///
336
    /// # Returns
337
    ///
338
    /// A result containing a mutable reference to the value, or an error if
339
    /// the key conversion failed.
340
    ///
341
    /// # Errors
342
    ///
343
    /// Returns an error if the key contains a null byte (for string keys).
344
    ///
345
    /// # Example
346
    ///
347
    /// ```no_run
348
    /// use ext_php_rs::types::ZendHashTable;
349
    ///
350
    /// let mut ht = ZendHashTable::new();
351
    /// ht.insert("counter", 0i64);
352
    ///
353
    /// if let ext_php_rs::types::array::Entry::Occupied(mut entry) = ht.entry("counter") {
354
    ///     if let Some(v) = entry.get_mut() {
355
    ///         if let Some(n) = v.long() {
356
    ///             v.set_long(n + 1);
357
    ///         }
358
    ///     }
359
    /// }
360
    /// ```
361
    pub fn get_mut(&mut self) -> Option<&mut Zval> {
1✔
362
        get_value_mut(&self.key, self.ht)
3✔
363
    }
364

365
    /// Converts the entry into a mutable reference to the value.
366
    ///
367
    /// If you need multiple references to the `OccupiedEntry`, see [`get_mut`].
368
    ///
369
    /// [`get_mut`]: OccupiedEntry::get_mut
370
    ///
371
    /// # Returns
372
    ///
373
    /// A result containing a mutable reference to the value with the entry's
374
    /// lifetime, or an error if the key conversion failed.
375
    ///
376
    /// # Errors
377
    ///
378
    /// Returns an error if the key contains a null byte (for string keys).
379
    ///
380
    /// # Example
381
    ///
382
    /// ```no_run
383
    /// use ext_php_rs::types::ZendHashTable;
384
    ///
385
    /// let mut ht = ZendHashTable::new();
386
    /// ht.insert("key", "value");
387
    ///
388
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
389
    ///     let value = entry.into_mut().unwrap();
390
    ///     // value has the lifetime of the hashtable borrow
391
    /// }
392
    /// ```
393
    #[must_use]
394
    pub fn into_mut(self) -> Option<&'a mut Zval> {
2✔
395
        get_value_mut(&self.key, self.ht)
6✔
396
    }
397

398
    /// Sets the value of the entry, returning the old value.
399
    ///
400
    /// # Parameters
401
    ///
402
    /// * `value` - The new value to set.
403
    ///
404
    /// # Returns
405
    ///
406
    /// A result containing the old value (shallow cloned), or an error if the
407
    /// insertion failed.
408
    ///
409
    /// # Errors
410
    ///
411
    /// Returns an error if the value conversion to [`Zval`] fails or if
412
    /// the key contains a null byte (for string keys).
413
    ///
414
    /// # Example
415
    ///
416
    /// ```no_run
417
    /// use ext_php_rs::types::{ZendHashTable, Zval};
418
    ///
419
    /// let mut ht = ZendHashTable::new();
420
    /// ht.insert("key", "old");
421
    ///
422
    /// if let ext_php_rs::types::array::Entry::Occupied(mut entry) = ht.entry("key") {
423
    ///     let old = entry.insert("new").unwrap();
424
    ///     assert_eq!(old.as_ref().and_then(Zval::str), Some("old"));
425
    /// }
426
    /// ```
427
    pub fn insert<V: IntoZval>(&mut self, value: V) -> Result<Option<Zval>> {
1✔
428
        let old = self.get().map(Zval::shallow_clone);
4✔
429
        insert_value(&self.key, self.ht, value)?;
4✔
430
        Ok(old)
1✔
431
    }
432

433
    /// Takes ownership of the key and value from the entry, removing it from
434
    /// the hashtable.
435
    ///
436
    /// # Returns
437
    ///
438
    /// A result containing a tuple of the key and the removed value (shallow
439
    /// cloned), or an error if the removal failed.
440
    ///
441
    /// # Errors
442
    ///
443
    /// Returns an error if the key contains a null byte (for string keys).
444
    ///
445
    /// # Example
446
    ///
447
    /// ```no_run
448
    /// use ext_php_rs::types::ZendHashTable;
449
    ///
450
    /// let mut ht = ZendHashTable::new();
451
    /// ht.insert("key", "value");
452
    ///
453
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
454
    ///     let (key, value) = entry.remove_entry();
455
    ///     assert!(ht.get("key").is_none());
456
    /// }
457
    /// ```
NEW
458
    pub fn remove_entry(self) -> (ArrayKey<'k>, Option<Zval>) {
×
NEW
459
        let value = self.get().map(Zval::shallow_clone);
×
NEW
460
        self.ht.remove(self.key.clone());
×
NEW
461
        (self.key, value)
×
462
    }
463

464
    /// Removes the value from the entry, returning it.
465
    ///
466
    /// # Returns
467
    ///
468
    /// A result containing the removed value (shallow cloned), or an error if
469
    /// the removal failed.
470
    ///
471
    /// # Errors
472
    ///
473
    /// Returns an error if the key contains a null byte (for string keys).
474
    ///
475
    /// # Example
476
    ///
477
    /// ```no_run
478
    /// use ext_php_rs::types::ZendHashTable;
479
    ///
480
    /// let mut ht = ZendHashTable::new();
481
    /// ht.insert("key", "value");
482
    ///
483
    /// if let ext_php_rs::types::array::Entry::Occupied(entry) = ht.entry("key") {
484
    ///     let value = entry.remove().unwrap();
485
    ///     assert_eq!(value.str(), Some("value"));
486
    /// }
487
    /// ```
488
    pub fn remove(self) -> Option<Zval> {
1✔
489
        let value = self.get().map(Zval::shallow_clone);
4✔
490
        self.ht.remove(self.key);
3✔
491
        value
1✔
492
    }
493
}
494

495
impl<'a, 'k> VacantEntry<'a, 'k> {
496
    /// Creates a new vacant entry.
497
    pub(super) fn new(ht: &'a mut ZendHashTable, key: ArrayKey<'k>) -> Self {
6✔
498
        Self { ht, key }
499
    }
500

501
    /// Gets a reference to the key that would be used when inserting a value
502
    /// through the `VacantEntry`.
503
    ///
504
    /// # Example
505
    ///
506
    /// ```no_run
507
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
508
    ///
509
    /// let mut ht = ZendHashTable::new();
510
    ///
511
    /// if let ext_php_rs::types::array::Entry::Vacant(entry) = ht.entry("key") {
512
    ///     assert_eq!(entry.key(), &ArrayKey::Str("key"));
513
    /// }
514
    /// ```
515
    #[must_use]
NEW
516
    pub fn key(&self) -> &ArrayKey<'k> {
×
NEW
517
        &self.key
×
518
    }
519

520
    /// Take ownership of the key.
521
    ///
522
    /// # Example
523
    ///
524
    /// ```no_run
525
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
526
    ///
527
    /// let mut ht = ZendHashTable::new();
528
    ///
529
    /// if let ext_php_rs::types::array::Entry::Vacant(entry) = ht.entry("key") {
530
    ///     let key = entry.into_key();
531
    ///     assert_eq!(key, ArrayKey::Str("key"));
532
    /// }
533
    /// ```
534
    #[must_use]
535
    pub fn into_key(self) -> ArrayKey<'k> {
1✔
536
        self.key
1✔
537
    }
538

539
    /// Sets the value of the entry with the `VacantEntry`'s key, and returns
540
    /// a mutable reference to it.
541
    ///
542
    /// # Parameters
543
    ///
544
    /// * `value` - The value to insert.
545
    ///
546
    /// # Returns
547
    ///
548
    /// A result containing a mutable reference to the inserted value, or an
549
    /// error if the insertion failed.
550
    ///
551
    /// # Errors
552
    ///
553
    /// Returns an error if the value conversion to [`Zval`] fails or if
554
    /// the key contains a null byte (for string keys).
555
    ///
556
    /// # Example
557
    ///
558
    /// ```no_run
559
    /// use ext_php_rs::types::ZendHashTable;
560
    ///
561
    /// let mut ht = ZendHashTable::new();
562
    ///
563
    /// if let ext_php_rs::types::array::Entry::Vacant(entry) = ht.entry("key") {
564
    ///     entry.insert("value");
565
    /// }
566
    /// assert_eq!(ht.get("key").and_then(|v| v.str()), Some("value"));
567
    /// ```
568
    pub fn insert<V: IntoZval>(self, value: V) -> Result<&'a mut Zval> {
5✔
569
        insert_value(&self.key, self.ht, value)
20✔
570
    }
571
}
572

573
/// Helper function to get a mutable value from the hashtable by key.
574
#[inline]
575
fn get_value_mut<'a>(key: &ArrayKey<'_>, ht: &'a mut ZendHashTable) -> Option<&'a mut Zval> {
3✔
576
    match key {
3✔
577
        ArrayKey::Long(index) => unsafe {
578
            #[allow(clippy::cast_sign_loss)]
NEW
579
            zend_hash_index_find(ht, *index as zend_ulong).as_mut()
×
580
        },
581
        ArrayKey::String(key) => unsafe {
NEW
582
            zend_hash_str_find(ht, key.as_ptr().cast(), key.len()).as_mut()
×
583
        },
584
        ArrayKey::Str(key) => unsafe {
585
            zend_hash_str_find(ht, key.as_ptr().cast(), key.len()).as_mut()
24✔
586
        },
587
    }
588
}
589

590
/// Helper function to insert a value into the hashtable by key.
591
fn insert_value<'a, V: IntoZval>(
6✔
592
    key: &ArrayKey<'_>,
593
    ht: &'a mut ZendHashTable,
594
    value: V,
595
) -> Result<&'a mut Zval> {
596
    // Wrap in ManuallyDrop to prevent drop from freeing the underlying data -
597
    // ownership is transferred to the hash table via shallow copy
598
    let mut val = ManuallyDrop::new(value.into_zval(false)?);
24✔
599
    match key {
6✔
600
        ArrayKey::Long(index) => unsafe {
601
            #[allow(clippy::cast_sign_loss)]
602
            zend_hash_index_update(ht, *index as zend_ulong, &raw mut *val)
4✔
603
                .as_mut()
604
                .ok_or(Error::InvalidPointer)
2✔
605
        },
606
        ArrayKey::String(key) => unsafe {
NEW
607
            zend_hash_str_update(ht, key.as_ptr().cast(), key.len(), &raw mut *val)
×
608
                .as_mut()
NEW
609
                .ok_or(Error::InvalidPointer)
×
610
        },
611
        ArrayKey::Str(key) => unsafe {
612
            zend_hash_str_update(ht, key.as_ptr().cast(), key.len(), &raw mut *val)
40✔
613
                .as_mut()
614
                .ok_or(Error::InvalidPointer)
10✔
615
        },
616
    }
617
}
618

619
#[cfg(test)]
620
#[cfg(feature = "embed")]
621
mod tests {
622
    use super::*;
623
    use crate::embed::Embed;
624

625
    #[test]
626
    fn test_entry_or_insert() {
627
        Embed::run(|| {
628
            let mut ht = ZendHashTable::new();
629

630
            // Insert into vacant entry
631
            let result = ht.entry("key").or_insert("value");
632
            assert!(result.is_ok());
633
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("value"));
634

635
            // Entry already exists, should return existing value
636
            let result = ht.entry("key").or_insert("other");
637
            assert!(result.is_ok());
638
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("value"));
639
        });
640
    }
641

642
    #[test]
643
    fn test_entry_or_insert_with() {
644
        Embed::run(|| {
645
            let mut ht = ZendHashTable::new();
646

647
            let result = ht.entry("key").or_insert_with(|| "computed");
648
            assert!(result.is_ok());
649
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("computed"));
650
        });
651
    }
652

653
    #[test]
654
    fn test_entry_and_modify() {
655
        Embed::run(|| {
656
            let mut ht = ZendHashTable::new();
657
            let _ = ht.insert("counter", 5i64);
658

659
            let result = ht
660
                .entry("counter")
661
                .and_modify(|v| {
662
                    if let Some(n) = v.long() {
663
                        v.set_long(n + 1);
664
                    }
665
                })
666
                .or_insert(0i64);
667

668
            assert!(result.is_ok());
669
            assert_eq!(ht.get("counter").and_then(Zval::long), Some(6));
670
        });
671
    }
672

673
    #[test]
674
    fn test_entry_and_modify_vacant() {
675
        Embed::run(|| {
676
            let mut ht = ZendHashTable::new();
677

678
            // and_modify on vacant entry should be a no-op
679
            let result = ht
680
                .entry("key")
681
                .and_modify(|v| {
682
                    v.set_long(100);
683
                })
684
                .or_insert(42i64);
685

686
            assert!(result.is_ok());
687
            assert_eq!(ht.get("key").and_then(Zval::long), Some(42));
688
        });
689
    }
690

691
    #[test]
692
    fn test_occupied_entry_insert() {
693
        Embed::run(|| {
694
            let mut ht = ZendHashTable::new();
695
            let _ = ht.insert("key", "old");
696

697
            if let Entry::Occupied(mut entry) = ht.entry("key") {
698
                let old = entry.insert("new").expect("insert should succeed");
699
                assert_eq!(old.as_ref().and_then(Zval::str), Some("old"));
700
            }
701
            assert_eq!(ht.get("key").and_then(|v| v.str()), Some("new"));
702
        });
703
    }
704

705
    #[test]
706
    fn test_occupied_entry_remove() {
707
        Embed::run(|| {
708
            let mut ht = ZendHashTable::new();
709
            let _ = ht.insert("key", "value");
710

711
            if let Entry::Occupied(entry) = ht.entry("key") {
712
                let value = entry.remove().expect("remove should succeed");
713
                assert_eq!(value.str(), Some("value"));
714
            }
715
            assert!(ht.get("key").is_none());
716
        });
717
    }
718

719
    #[test]
720
    fn test_entry_with_numeric_key() {
721
        Embed::run(|| {
722
            let mut ht = ZendHashTable::new();
723

724
            let result = ht.entry(42i64).or_insert("value");
725
            assert!(result.is_ok());
726
            assert_eq!(ht.get_index(42).and_then(|v| v.str()), Some("value"));
727
        });
728
    }
729

730
    #[test]
731
    fn test_vacant_entry_into_key() {
732
        Embed::run(|| {
733
            let mut ht = ZendHashTable::new();
734

735
            if let Entry::Vacant(entry) = ht.entry("my_key") {
736
                let key = entry.into_key();
737
                assert_eq!(key, ArrayKey::Str("my_key"));
738
            }
739
        });
740
    }
741

742
    #[test]
743
    fn test_entry_with_binary_key() {
744
        Embed::run(|| {
745
            let mut ht = ZendHashTable::new();
746
            let key = "\0MyClass\0myProp";
747
            let result = ht.entry(key).or_insert("value");
748
            assert!(result.is_ok());
749
        });
750
    }
751
}
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