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

davidcole1340 / ext-php-rs / 16403230036

20 Jul 2025 07:03PM UTC coverage: 27.026% (-0.1%) from 27.144%
16403230036

Pull #535

github

web-flow
Merge 08bfe3962 into 6869625f4
Pull Request #535: feat(array): BTreeMap conversion

0 of 20 new or added lines in 2 files covered. (0.0%)

97 existing lines in 3 files now uncovered.

1127 of 4170 relevant lines covered (27.03%)

5.64 hits per line

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

17.83
/src/types/array/mod.rs
1
//! Represents an array in PHP. As all arrays in PHP are associative arrays,
2
//! they are represented by hash tables.
3

4
use std::{convert::TryFrom, ffi::CString, fmt::Debug, ptr, str::FromStr};
5

6
use crate::{
7
    boxed::{ZBox, ZBoxable},
8
    convert::{FromZval, IntoZval},
9
    error::Result,
10
    ffi::zend_ulong,
11
    ffi::{
12
        _zend_new_array, zend_array_count, zend_array_destroy, zend_array_dup, zend_hash_clean,
13
        zend_hash_index_del, zend_hash_index_find, zend_hash_index_update,
14
        zend_hash_next_index_insert, zend_hash_str_del, zend_hash_str_find, zend_hash_str_update,
15
        HT_MIN_SIZE,
16
    },
17
    flags::DataType,
18
    types::Zval,
19
};
20

21
mod array_key;
22
mod conversions;
23
mod iterators;
24

25
pub use array_key::ArrayKey;
26
pub use iterators::{Iter, Values};
27

28
/// A PHP hashtable.
29
///
30
/// In PHP, arrays are represented as hashtables. This allows you to push values
31
/// onto the end of the array like a vector, while also allowing you to insert
32
/// at arbitrary string key indexes.
33
///
34
/// A PHP hashtable stores values as [`Zval`]s. This allows you to insert
35
/// different types into the same hashtable. Types must implement [`IntoZval`]
36
/// to be able to be inserted into the hashtable.
37
///
38
/// # Examples
39
///
40
/// ```no_run
41
/// use ext_php_rs::types::ZendHashTable;
42
///
43
/// let mut ht = ZendHashTable::new();
44
/// ht.push(1);
45
/// ht.push("Hello, world!");
46
/// ht.insert("Like", "Hashtable");
47
///
48
/// assert_eq!(ht.len(), 3);
49
/// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(1));
50
/// ```
51
pub type ZendHashTable = crate::ffi::HashTable;
52

53
// Clippy complains about there being no `is_empty` function when implementing
54
// on the alias `ZendStr` :( <https://github.com/rust-lang/rust-clippy/issues/7702>
55
#[allow(clippy::len_without_is_empty)]
56
impl ZendHashTable {
57
    /// Creates a new, empty, PHP hashtable, returned inside a [`ZBox`].
58
    ///
59
    /// # Example
60
    ///
61
    /// ```no_run
62
    /// use ext_php_rs::types::ZendHashTable;
63
    ///
64
    /// let ht = ZendHashTable::new();
65
    /// ```
66
    ///
67
    /// # Panics
68
    ///
69
    /// Panics if memory for the hashtable could not be allocated.
70
    #[must_use]
71
    pub fn new() -> ZBox<Self> {
9✔
72
        Self::with_capacity(HT_MIN_SIZE)
9✔
73
    }
74

75
    /// Creates a new, empty, PHP hashtable with an initial size, returned
76
    /// inside a [`ZBox`].
77
    ///
78
    /// # Parameters
79
    ///
80
    /// * `size` - The size to initialize the array with.
81
    ///
82
    /// # Example
83
    ///
84
    /// ```no_run
85
    /// use ext_php_rs::types::ZendHashTable;
86
    ///
87
    /// let ht = ZendHashTable::with_capacity(10);
88
    /// ```
89
    ///
90
    /// # Panics
91
    ///
92
    /// Panics if memory for the hashtable could not be allocated.
93
    #[must_use]
94
    pub fn with_capacity(size: u32) -> ZBox<Self> {
13✔
95
        unsafe {
96
            // SAFETY: PHP allocator handles the creation of the array.
97
            #[allow(clippy::used_underscore_items)]
98
            let ptr = _zend_new_array(size);
39✔
99

100
            // SAFETY: `as_mut()` checks if the pointer is null, and panics if it is not.
101
            ZBox::from_raw(
102
                ptr.as_mut()
39✔
103
                    .expect("Failed to allocate memory for hashtable"),
13✔
104
            )
105
        }
106
    }
107

108
    /// Returns the current number of elements in the array.
109
    ///
110
    /// # Example
111
    ///
112
    /// ```no_run
113
    /// use ext_php_rs::types::ZendHashTable;
114
    ///
115
    /// let mut ht = ZendHashTable::new();
116
    ///
117
    /// ht.push(1);
118
    /// ht.push("Hello, world");
119
    ///
120
    /// assert_eq!(ht.len(), 2);
121
    /// ```
122
    #[must_use]
123
    pub fn len(&self) -> usize {
22✔
124
        unsafe { zend_array_count(ptr::from_ref(self).cast_mut()) as usize }
66✔
125
    }
126

127
    /// Returns whether the hash table is empty.
128
    ///
129
    /// # Example
130
    ///
131
    /// ```no_run
132
    /// use ext_php_rs::types::ZendHashTable;
133
    ///
134
    /// let mut ht = ZendHashTable::new();
135
    ///
136
    /// assert_eq!(ht.is_empty(), true);
137
    ///
138
    /// ht.push(1);
139
    /// ht.push("Hello, world");
140
    ///
141
    /// assert_eq!(ht.is_empty(), false);
142
    /// ```
143
    #[must_use]
144
    pub fn is_empty(&self) -> bool {
×
145
        self.len() == 0
×
146
    }
147

148
    /// Clears the hash table, removing all values.
149
    ///
150
    /// # Example
151
    ///
152
    /// ```no_run
153
    /// use ext_php_rs::types::ZendHashTable;
154
    ///
155
    /// let mut ht = ZendHashTable::new();
156
    ///
157
    /// ht.insert("test", "hello world");
158
    /// assert_eq!(ht.is_empty(), false);
159
    ///
160
    /// ht.clear();
161
    /// assert_eq!(ht.is_empty(), true);
162
    /// ```
163
    pub fn clear(&mut self) {
×
164
        unsafe { zend_hash_clean(self) }
×
165
    }
166

167
    /// Attempts to retrieve a value from the hash table with a string key.
168
    ///
169
    /// # Parameters
170
    ///
171
    /// * `key` - The key to search for in the hash table.
172
    ///
173
    /// # Returns
174
    ///
175
    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
176
    ///   table.
177
    /// * `None` - No value at the given position was found.
178
    ///
179
    /// # Example
180
    ///
181
    /// ```no_run
182
    /// use ext_php_rs::types::ZendHashTable;
183
    ///
184
    /// let mut ht = ZendHashTable::new();
185
    ///
186
    /// ht.insert("test", "hello world");
187
    /// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world"));
188
    /// ```
189
    #[must_use]
190
    pub fn get<'a, K>(&self, key: K) -> Option<&Zval>
12✔
191
    where
192
        K: Into<ArrayKey<'a>>,
193
    {
194
        match key.into() {
12✔
195
            ArrayKey::Long(index) => unsafe {
196
                #[allow(clippy::cast_sign_loss)]
197
                zend_hash_index_find(self, index as zend_ulong).as_ref()
×
198
            },
199
            ArrayKey::String(key) => {
×
200
                if let Ok(index) = i64::from_str(key.as_str()) {
×
201
                    #[allow(clippy::cast_sign_loss)]
×
202
                    unsafe {
203
                        zend_hash_index_find(self, index as zend_ulong).as_ref()
×
204
                    }
205
                } else {
206
                    unsafe {
207
                        zend_hash_str_find(
208
                            self,
×
209
                            CString::new(key.as_str()).ok()?.as_ptr(),
×
UNCOV
210
                            key.len() as _,
×
211
                        )
212
                        .as_ref()
213
                    }
214
                }
215
            }
216
            ArrayKey::Str(key) => {
6✔
217
                if let Ok(index) = i64::from_str(key) {
6✔
UNCOV
218
                    #[allow(clippy::cast_sign_loss)]
×
219
                    unsafe {
UNCOV
220
                        zend_hash_index_find(self, index as zend_ulong).as_ref()
×
221
                    }
222
                } else {
223
                    unsafe {
224
                        zend_hash_str_find(self, CString::new(key).ok()?.as_ptr(), key.len() as _)
30✔
225
                            .as_ref()
226
                    }
227
                }
228
            }
229
        }
230
    }
231

232
    /// Attempts to retrieve a value from the hash table with a string key.
233
    ///
234
    /// # Parameters
235
    ///
236
    /// * `key` - The key to search for in the hash table.
237
    ///
238
    /// # Returns
239
    ///
240
    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
241
    ///   table.
242
    /// * `None` - No value at the given position was found.
243
    ///
244
    /// # Example
245
    ///
246
    /// ```no_run
247
    /// use ext_php_rs::types::ZendHashTable;
248
    ///
249
    /// let mut ht = ZendHashTable::new();
250
    ///
251
    /// ht.insert("test", "hello world");
252
    /// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world"));
253
    /// ```
254
    // TODO: Verify if this is safe to use, as it allows mutating the
255
    // hashtable while only having a reference to it. #461
256
    #[allow(clippy::mut_from_ref)]
257
    #[must_use]
258
    pub fn get_mut<'a, K>(&self, key: K) -> Option<&mut Zval>
×
259
    where
260
        K: Into<ArrayKey<'a>>,
261
    {
UNCOV
262
        match key.into() {
×
263
            ArrayKey::Long(index) => unsafe {
264
                #[allow(clippy::cast_sign_loss)]
UNCOV
265
                zend_hash_index_find(self, index as zend_ulong).as_mut()
×
266
            },
UNCOV
267
            ArrayKey::String(key) => {
×
UNCOV
268
                if let Ok(index) = i64::from_str(key.as_str()) {
×
UNCOV
269
                    #[allow(clippy::cast_sign_loss)]
×
270
                    unsafe {
UNCOV
271
                        zend_hash_index_find(self, index as zend_ulong).as_mut()
×
272
                    }
273
                } else {
274
                    unsafe {
275
                        zend_hash_str_find(
UNCOV
276
                            self,
×
UNCOV
277
                            CString::new(key.as_str()).ok()?.as_ptr(),
×
UNCOV
278
                            key.len() as _,
×
279
                        )
280
                        .as_mut()
281
                    }
282
                }
283
            }
UNCOV
284
            ArrayKey::Str(key) => {
×
285
                if let Ok(index) = i64::from_str(key) {
×
UNCOV
286
                    #[allow(clippy::cast_sign_loss)]
×
287
                    unsafe {
288
                        zend_hash_index_find(self, index as zend_ulong).as_mut()
×
289
                    }
290
                } else {
291
                    unsafe {
UNCOV
292
                        zend_hash_str_find(self, CString::new(key).ok()?.as_ptr(), key.len() as _)
×
293
                            .as_mut()
294
                    }
295
                }
296
            }
297
        }
298
    }
299

300
    /// Attempts to retrieve a value from the hash table with an index.
301
    ///
302
    /// # Parameters
303
    ///
304
    /// * `key` - The key to search for in the hash table.
305
    ///
306
    /// # Returns
307
    ///
308
    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
309
    ///   table.
310
    /// * `None` - No value at the given position was found.
311
    ///
312
    /// # Example
313
    ///
314
    /// ```no_run
315
    /// use ext_php_rs::types::ZendHashTable;
316
    ///
317
    /// let mut ht = ZendHashTable::new();
318
    ///
319
    /// ht.push(100);
320
    /// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
321
    /// ```
322
    #[must_use]
UNCOV
323
    pub fn get_index(&self, key: i64) -> Option<&Zval> {
×
324
        #[allow(clippy::cast_sign_loss)]
325
        unsafe {
UNCOV
326
            zend_hash_index_find(self, key as zend_ulong).as_ref()
×
327
        }
328
    }
329

330
    /// Attempts to retrieve a value from the hash table with an index.
331
    ///
332
    /// # Parameters
333
    ///
334
    /// * `key` - The key to search for in the hash table.
335
    ///
336
    /// # Returns
337
    ///
338
    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
339
    ///   table.
340
    /// * `None` - No value at the given position was found.
341
    ///
342
    /// # Example
343
    ///
344
    /// ```no_run
345
    /// use ext_php_rs::types::ZendHashTable;
346
    ///
347
    /// let mut ht = ZendHashTable::new();
348
    ///
349
    /// ht.push(100);
350
    /// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
351
    /// ```
352
    // TODO: Verify if this is safe to use, as it allows mutating the
353
    // hashtable while only having a reference to it. #461
354
    #[allow(clippy::mut_from_ref)]
355
    #[must_use]
356
    pub fn get_index_mut(&self, key: i64) -> Option<&mut Zval> {
×
357
        unsafe {
358
            #[allow(clippy::cast_sign_loss)]
359
            zend_hash_index_find(self, key as zend_ulong).as_mut()
×
360
        }
361
    }
362

363
    /// Attempts to remove a value from the hash table with a string key.
364
    ///
365
    /// # Parameters
366
    ///
367
    /// * `key` - The key to remove from the hash table.
368
    ///
369
    /// # Returns
370
    ///
371
    /// * `Some(())` - Key was successfully removed.
372
    /// * `None` - No key was removed, did not exist.
373
    ///
374
    /// # Example
375
    ///
376
    /// ```no_run
377
    /// use ext_php_rs::types::ZendHashTable;
378
    ///
379
    /// let mut ht = ZendHashTable::new();
380
    ///
381
    /// ht.insert("test", "hello world");
382
    /// assert_eq!(ht.len(), 1);
383
    ///
384
    /// ht.remove("test");
385
    /// assert_eq!(ht.len(), 0);
386
    /// ```
UNCOV
387
    pub fn remove<'a, K>(&mut self, key: K) -> Option<()>
×
388
    where
389
        K: Into<ArrayKey<'a>>,
390
    {
UNCOV
391
        let result = match key.into() {
×
392
            ArrayKey::Long(index) => unsafe {
393
                #[allow(clippy::cast_sign_loss)]
UNCOV
394
                zend_hash_index_del(self, index as zend_ulong)
×
395
            },
UNCOV
396
            ArrayKey::String(key) => {
×
UNCOV
397
                if let Ok(index) = i64::from_str(key.as_str()) {
×
UNCOV
398
                    #[allow(clippy::cast_sign_loss)]
×
399
                    unsafe {
UNCOV
400
                        zend_hash_index_del(self, index as zend_ulong)
×
401
                    }
402
                } else {
403
                    unsafe {
404
                        zend_hash_str_del(
UNCOV
405
                            self,
×
UNCOV
406
                            CString::new(key.as_str()).ok()?.as_ptr(),
×
407
                            key.len() as _,
×
408
                        )
409
                    }
410
                }
411
            }
UNCOV
412
            ArrayKey::Str(key) => {
×
UNCOV
413
                if let Ok(index) = i64::from_str(key) {
×
UNCOV
414
                    #[allow(clippy::cast_sign_loss)]
×
415
                    unsafe {
UNCOV
416
                        zend_hash_index_del(self, index as zend_ulong)
×
417
                    }
418
                } else {
419
                    unsafe {
UNCOV
420
                        zend_hash_str_del(self, CString::new(key).ok()?.as_ptr(), key.len() as _)
×
421
                    }
422
                }
423
            }
424
        };
425

UNCOV
426
        if result < 0 {
×
UNCOV
427
            None
×
428
        } else {
UNCOV
429
            Some(())
×
430
        }
431
    }
432

433
    /// Attempts to remove a value from the hash table with a string key.
434
    ///
435
    /// # Parameters
436
    ///
437
    /// * `key` - The key to remove from the hash table.
438
    ///
439
    /// # Returns
440
    ///
441
    /// * `Ok(())` - Key was successfully removed.
442
    /// * `None` - No key was removed, did not exist.
443
    ///
444
    /// # Example
445
    ///
446
    /// ```no_run
447
    /// use ext_php_rs::types::ZendHashTable;
448
    ///
449
    /// let mut ht = ZendHashTable::new();
450
    ///
451
    /// ht.push("hello");
452
    /// assert_eq!(ht.len(), 1);
453
    ///
454
    /// ht.remove_index(0);
455
    /// assert_eq!(ht.len(), 0);
456
    /// ```
457
    pub fn remove_index(&mut self, key: i64) -> Option<()> {
×
458
        let result = unsafe {
459
            #[allow(clippy::cast_sign_loss)]
460
            zend_hash_index_del(self, key as zend_ulong)
×
461
        };
462

463
        if result < 0 {
×
464
            None
×
465
        } else {
UNCOV
466
            Some(())
×
467
        }
468
    }
469

470
    /// Attempts to insert an item into the hash table, or update if the key
471
    /// already exists. Returns nothing in a result if successful.
472
    ///
473
    /// # Parameters
474
    ///
475
    /// * `key` - The key to insert the value at in the hash table.
476
    /// * `value` - The value to insert into the hash table.
477
    ///
478
    /// # Returns
479
    ///
480
    /// Returns nothing in a result on success.
481
    ///
482
    /// # Errors
483
    ///
484
    /// Returns an error if the key could not be converted into a [`CString`],
485
    /// or converting the value into a [`Zval`] failed.
486
    ///
487
    /// # Example
488
    ///
489
    /// ```no_run
490
    /// use ext_php_rs::types::ZendHashTable;
491
    ///
492
    /// let mut ht = ZendHashTable::new();
493
    ///
494
    /// ht.insert("a", "A");
495
    /// ht.insert("b", "B");
496
    /// ht.insert("c", "C");
497
    /// assert_eq!(ht.len(), 3);
498
    /// ```
499
    pub fn insert<'a, K, V>(&mut self, key: K, val: V) -> Result<()>
35✔
500
    where
501
        K: Into<ArrayKey<'a>>,
502
        V: IntoZval,
503
    {
504
        let mut val = val.into_zval(false)?;
105✔
UNCOV
505
        match key.into() {
×
506
            ArrayKey::Long(index) => {
14✔
507
                unsafe {
508
                    #[allow(clippy::cast_sign_loss)]
UNCOV
509
                    zend_hash_index_update(self, index as zend_ulong, &raw mut val)
×
510
                };
511
            }
UNCOV
512
            ArrayKey::String(key) => {
×
513
                if let Ok(index) = i64::from_str(&key) {
×
514
                    unsafe {
515
                        #[allow(clippy::cast_sign_loss)]
UNCOV
516
                        zend_hash_index_update(self, index as zend_ulong, &raw mut val)
×
517
                    };
518
                } else {
519
                    unsafe {
520
                        zend_hash_str_update(
UNCOV
521
                            self,
×
UNCOV
522
                            CString::new(key.as_str())?.as_ptr(),
×
UNCOV
523
                            key.len(),
×
UNCOV
524
                            &raw mut val,
×
525
                        )
526
                    };
527
                }
528
            }
529
            ArrayKey::Str(key) => {
21✔
530
                if let Ok(index) = i64::from_str(key) {
26✔
531
                    unsafe {
532
                        #[allow(clippy::cast_sign_loss)]
UNCOV
533
                        zend_hash_index_update(self, index as zend_ulong, &raw mut val)
×
534
                    };
535
                } else {
536
                    unsafe {
537
                        zend_hash_str_update(
538
                            self,
16✔
539
                            CString::new(key)?.as_ptr(),
32✔
UNCOV
540
                            key.len(),
×
UNCOV
541
                            &raw mut val,
×
542
                        )
543
                    };
544
                }
545
            }
546
        }
547
        val.release();
35✔
548
        Ok(())
×
549
    }
550

551
    /// Inserts an item into the hash table at a specified index, or updates if
552
    /// the key already exists. Returns nothing in a result if successful.
553
    ///
554
    /// # Parameters
555
    ///
556
    /// * `key` - The index at which the value should be inserted.
557
    /// * `val` - The value to insert into the hash table.
558
    ///
559
    /// # Returns
560
    ///
561
    /// Returns nothing in a result on success.
562
    ///
563
    /// # Errors
564
    ///
565
    /// Returns an error if converting the value into a [`Zval`] failed.
566
    ///
567
    /// # Example
568
    ///
569
    /// ```no_run
570
    /// use ext_php_rs::types::ZendHashTable;
571
    ///
572
    /// let mut ht = ZendHashTable::new();
573
    ///
574
    /// ht.insert_at_index(0, "A");
575
    /// ht.insert_at_index(5, "B");
576
    /// ht.insert_at_index(0, "C"); // notice overriding index 0
577
    /// assert_eq!(ht.len(), 2);
578
    /// ```
UNCOV
579
    pub fn insert_at_index<V>(&mut self, key: i64, val: V) -> Result<()>
×
580
    where
581
        V: IntoZval,
582
    {
UNCOV
583
        let mut val = val.into_zval(false)?;
×
584
        unsafe {
585
            #[allow(clippy::cast_sign_loss)]
UNCOV
586
            zend_hash_index_update(self, key as zend_ulong, &raw mut val)
×
587
        };
UNCOV
588
        val.release();
×
UNCOV
589
        Ok(())
×
590
    }
591

592
    /// Pushes an item onto the end of the hash table. Returns a result
593
    /// containing nothing if the element was successfully inserted.
594
    ///
595
    /// # Parameters
596
    ///
597
    /// * `val` - The value to insert into the hash table.
598
    ///
599
    /// # Returns
600
    ///
601
    /// Returns nothing in a result on success.
602
    ///
603
    /// # Errors
604
    ///
605
    /// Returns an error if converting the value into a [`Zval`] failed.
606
    ///
607
    /// # Example
608
    ///
609
    /// ```no_run
610
    /// use ext_php_rs::types::ZendHashTable;
611
    ///
612
    /// let mut ht = ZendHashTable::new();
613
    ///
614
    /// ht.push("a");
615
    /// ht.push("b");
616
    /// ht.push("c");
617
    /// assert_eq!(ht.len(), 3);
618
    /// ```
UNCOV
619
    pub fn push<V>(&mut self, val: V) -> Result<()>
×
620
    where
621
        V: IntoZval,
622
    {
UNCOV
623
        let mut val = val.into_zval(false)?;
×
UNCOV
624
        unsafe { zend_hash_next_index_insert(self, &raw mut val) };
×
UNCOV
625
        val.release();
×
626

UNCOV
627
        Ok(())
×
628
    }
629

630
    /// Checks if the hashtable only contains numerical keys.
631
    ///
632
    /// # Returns
633
    ///
634
    /// True if all keys on the hashtable are numerical.
635
    ///
636
    /// # Example
637
    ///
638
    /// ```no_run
639
    /// use ext_php_rs::types::ZendHashTable;
640
    ///
641
    /// let mut ht = ZendHashTable::new();
642
    ///
643
    /// ht.push(0);
644
    /// ht.push(3);
645
    /// ht.push(9);
646
    /// assert!(ht.has_numerical_keys());
647
    ///
648
    /// ht.insert("obviously not numerical", 10);
649
    /// assert!(!ht.has_numerical_keys());
650
    /// ```
651
    #[must_use]
UNCOV
652
    pub fn has_numerical_keys(&self) -> bool {
×
UNCOV
653
        !self.into_iter().any(|(k, _)| !k.is_long())
×
654
    }
655

656
    /// Checks if the hashtable has numerical, sequential keys.
657
    ///
658
    /// # Returns
659
    ///
660
    /// True if all keys on the hashtable are numerical and are in sequential
661
    /// order (i.e. starting at 0 and not skipping any keys).
662
    ///
663
    /// # Panics
664
    ///
665
    /// Panics if the number of elements in the hashtable exceeds `i64::MAX`.
666
    ///
667
    /// # Example
668
    ///
669
    /// ```no_run
670
    /// use ext_php_rs::types::ZendHashTable;
671
    ///
672
    /// let mut ht = ZendHashTable::new();
673
    ///
674
    /// ht.push(0);
675
    /// ht.push(3);
676
    /// ht.push(9);
677
    /// assert!(ht.has_sequential_keys());
678
    ///
679
    /// ht.insert_at_index(90, 10);
680
    /// assert!(!ht.has_sequential_keys());
681
    /// ```
682
    #[must_use]
683
    pub fn has_sequential_keys(&self) -> bool {
×
UNCOV
684
        !self
×
UNCOV
685
            .into_iter()
×
UNCOV
686
            .enumerate()
×
687
            .any(|(i, (k, _))| ArrayKey::Long(i64::try_from(i).expect("Integer overflow")) != k)
×
688
    }
689

690
    /// Returns an iterator over the values contained inside the hashtable, as
691
    /// if it was a set or list.
692
    ///
693
    /// # Example
694
    ///
695
    /// ```no_run
696
    /// use ext_php_rs::types::ZendHashTable;
697
    ///
698
    /// let mut ht = ZendHashTable::new();
699
    ///
700
    /// for val in ht.values() {
701
    ///     dbg!(val);
702
    /// }
703
    #[inline]
704
    #[must_use]
UNCOV
705
    pub fn values(&self) -> Values<'_> {
×
UNCOV
706
        Values::new(self)
×
707
    }
708

709
    /// Returns an iterator over the key(s) and value contained inside the
710
    /// hashtable.
711
    ///
712
    /// # Example
713
    ///
714
    /// ```no_run
715
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
716
    ///
717
    /// let mut ht = ZendHashTable::new();
718
    ///
719
    /// for (key, val) in ht.iter() {
720
    ///     match &key {
721
    ///         ArrayKey::Long(index) => {
722
    ///         }
723
    ///         ArrayKey::String(key) => {
724
    ///         }
725
    ///         ArrayKey::Str(key) => {
726
    ///         }
727
    ///     }
728
    ///     dbg!(key, val);
729
    /// }
730
    #[inline]
731
    #[must_use]
UNCOV
732
    pub fn iter(&self) -> Iter<'_> {
×
UNCOV
733
        self.into_iter()
×
734
    }
735
}
736

737
unsafe impl ZBoxable for ZendHashTable {
738
    fn free(&mut self) {
7✔
739
        // SAFETY: ZBox has immutable access to `self`.
740
        unsafe { zend_array_destroy(self) }
14✔
741
    }
742
}
743

744
impl Debug for ZendHashTable {
UNCOV
745
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
UNCOV
746
        f.debug_map()
×
UNCOV
747
            .entries(self.into_iter().map(|(k, v)| (k.to_string(), v)))
×
748
            .finish()
749
    }
750
}
751

752
impl ToOwned for ZendHashTable {
753
    type Owned = ZBox<ZendHashTable>;
754

UNCOV
755
    fn to_owned(&self) -> Self::Owned {
×
756
        unsafe {
757
            // SAFETY: FFI call does not modify `self`, returns a new hashtable.
UNCOV
758
            let ptr = zend_array_dup(ptr::from_ref(self).cast_mut());
×
759

760
            // SAFETY: `as_mut()` checks if the pointer is null, and panics if it is not.
761
            ZBox::from_raw(
UNCOV
762
                ptr.as_mut()
×
UNCOV
763
                    .expect("Failed to allocate memory for hashtable"),
×
764
            )
765
        }
766
    }
767
}
768

769
impl Default for ZBox<ZendHashTable> {
UNCOV
770
    fn default() -> Self {
×
UNCOV
771
        ZendHashTable::new()
×
772
    }
773
}
774

775
impl Clone for ZBox<ZendHashTable> {
UNCOV
776
    fn clone(&self) -> Self {
×
UNCOV
777
        (**self).to_owned()
×
778
    }
779
}
780

781
impl IntoZval for ZBox<ZendHashTable> {
782
    const TYPE: DataType = DataType::Array;
783
    const NULLABLE: bool = false;
784

UNCOV
785
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
UNCOV
786
        zv.set_hashtable(self);
×
UNCOV
787
        Ok(())
×
788
    }
789
}
790

791
impl<'a> FromZval<'a> for &'a ZendHashTable {
792
    const TYPE: DataType = DataType::Array;
793

UNCOV
794
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
UNCOV
795
        zval.array()
×
796
    }
797
}
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