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

extphprs / ext-php-rs / 23743444734

30 Mar 2026 11:55AM UTC coverage: 65.794% (-0.3%) from 66.102%
23743444734

Pull #707

github

web-flow
Merge 87b7f8bab into 030380f14
Pull Request #707: fix(memory): eliminate module definition memory leak

6 of 62 new or added lines in 1 file covered. (9.68%)

494 existing lines in 15 files now uncovered.

8117 of 12337 relevant lines covered (65.79%)

32.73 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

627
    #[test]
628
    fn test_entry_or_insert() {
1✔
629
        Embed::run(|| {
1✔
630
            let mut ht = ZendHashTable::new();
1✔
631

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

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

644
    #[test]
645
    fn test_entry_or_insert_with() {
1✔
646
        Embed::run(|| {
1✔
647
            let mut ht = ZendHashTable::new();
1✔
648

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

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

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

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

675
    #[test]
676
    fn test_entry_and_modify_vacant() {
1✔
677
        Embed::run(|| {
1✔
678
            let mut ht = ZendHashTable::new();
1✔
679

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

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

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

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

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

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

721
    #[test]
722
    fn test_entry_with_numeric_key() {
1✔
723
        Embed::run(|| {
1✔
724
            let mut ht = ZendHashTable::new();
1✔
725

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

732
    #[test]
733
    fn test_vacant_entry_into_key() {
1✔
734
        Embed::run(|| {
1✔
735
            let mut ht = ZendHashTable::new();
1✔
736

737
            if let Entry::Vacant(entry) = ht.entry("my_key") {
1✔
738
                let key = entry.into_key();
1✔
739
                assert_eq!(key, ArrayKey::Str("my_key"));
1✔
UNCOV
740
            }
×
741
        });
1✔
742
    }
1✔
743

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