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

davidcole1340 / ext-php-rs / 15788182852

20 Jun 2025 09:32PM UTC coverage: 22.034%. Remained the same
15788182852

Pull #460

github

web-flow
Merge d4fc01bb5 into 660f308c0
Pull Request #460: chore(clippy): flowing lifetimes warning and clippy::mut_from_ref

6 of 56 new or added lines in 13 files covered. (10.71%)

3 existing lines in 2 files now uncovered.

871 of 3953 relevant lines covered (22.03%)

2.35 hits per line

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

0.0
/src/types/array.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::{
5
    collections::HashMap,
6
    convert::{TryFrom, TryInto},
7
    ffi::CString,
8
    fmt::{Debug, Display},
9
    iter::FromIterator,
10
    ptr,
11
};
12

13
use crate::{
14
    boxed::{ZBox, ZBoxable},
15
    convert::{FromZval, IntoZval},
16
    error::{Error, Result},
17
    ffi::{
18
        _zend_new_array, zend_array_count, zend_array_destroy, zend_array_dup, zend_hash_clean,
19
        zend_hash_get_current_data_ex, zend_hash_get_current_key_type_ex,
20
        zend_hash_get_current_key_zval_ex, zend_hash_index_del, zend_hash_index_find,
21
        zend_hash_index_update, zend_hash_move_backwards_ex, zend_hash_move_forward_ex,
22
        zend_hash_next_index_insert, zend_hash_str_del, zend_hash_str_find, zend_hash_str_update,
23
        HashPosition, HT_MIN_SIZE,
24
    },
25
    flags::DataType,
26
    types::Zval,
27
};
28

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

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

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

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

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

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

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

168
    /// Attempts to retrieve a value from the hash table with a string key.
169
    ///
170
    /// # Parameters
171
    ///
172
    /// * `key` - The key to search for in the hash table.
173
    ///
174
    /// # Returns
175
    ///
176
    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
177
    ///   table.
178
    /// * `None` - No value at the given position was found.
179
    ///
180
    /// # Example
181
    ///
182
    /// ```no_run
183
    /// use ext_php_rs::types::ZendHashTable;
184
    ///
185
    /// let mut ht = ZendHashTable::new();
186
    ///
187
    /// ht.insert("test", "hello world");
188
    /// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world"));
189
    /// ```
190
    #[must_use]
191
    pub fn get(&self, key: &'_ str) -> Option<&Zval> {
×
192
        let str = CString::new(key).ok()?;
×
193
        unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_ref() }
×
194
    }
195

196
    /// Attempts to retrieve a value from the hash table with a string key.
197
    ///
198
    /// # Parameters
199
    ///
200
    /// * `key` - The key to search for in the hash table.
201
    ///
202
    /// # Returns
203
    ///
204
    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
205
    ///   table.
206
    /// * `None` - No value at the given position was found.
207
    ///
208
    /// # Example
209
    ///
210
    /// ```no_run
211
    /// use ext_php_rs::types::ZendHashTable;
212
    ///
213
    /// let mut ht = ZendHashTable::new();
214
    ///
215
    /// ht.insert("test", "hello world");
216
    /// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world"));
217
    /// ```
218
    #[must_use]
219
    pub fn get_mut(&self, key: &'_ str) -> Option<&mut Zval> {
×
220
        let str = CString::new(key).ok()?;
×
221
        unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_mut() }
×
222
    }
223

224
    /// Attempts to retrieve a value from the hash table with an index.
225
    ///
226
    /// # Parameters
227
    ///
228
    /// * `key` - The key to search for in the hash table.
229
    ///
230
    /// # Returns
231
    ///
232
    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
233
    ///   table.
234
    /// * `None` - No value at the given position was found.
235
    ///
236
    /// # Example
237
    ///
238
    /// ```no_run
239
    /// use ext_php_rs::types::ZendHashTable;
240
    ///
241
    /// let mut ht = ZendHashTable::new();
242
    ///
243
    /// ht.push(100);
244
    /// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
245
    /// ```
246
    #[must_use]
247
    pub fn get_index(&self, key: u64) -> Option<&Zval> {
×
248
        unsafe { zend_hash_index_find(self, key).as_ref() }
×
249
    }
250

251
    /// Attempts to retrieve a value from the hash table with an index.
252
    ///
253
    /// # Parameters
254
    ///
255
    /// * `key` - The key to search for in the hash table.
256
    ///
257
    /// # Returns
258
    ///
259
    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
260
    ///   table.
261
    /// * `None` - No value at the given position was found.
262
    ///
263
    /// # Example
264
    ///
265
    /// ```no_run
266
    /// use ext_php_rs::types::ZendHashTable;
267
    ///
268
    /// let mut ht = ZendHashTable::new();
269
    ///
270
    /// ht.push(100);
271
    /// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
272
    /// ```
273
    #[must_use]
274
    pub fn get_index_mut(&self, key: u64) -> Option<&mut Zval> {
×
275
        unsafe { zend_hash_index_find(self, key).as_mut() }
×
276
    }
277

278
    /// Attempts to remove a value from the hash table with a string key.
279
    ///
280
    /// # Parameters
281
    ///
282
    /// * `key` - The key to remove from the hash table.
283
    ///
284
    /// # Returns
285
    ///
286
    /// * `Some(())` - Key was successfully removed.
287
    /// * `None` - No key was removed, did not exist.
288
    ///
289
    /// # Example
290
    ///
291
    /// ```no_run
292
    /// use ext_php_rs::types::ZendHashTable;
293
    ///
294
    /// let mut ht = ZendHashTable::new();
295
    ///
296
    /// ht.insert("test", "hello world");
297
    /// assert_eq!(ht.len(), 1);
298
    ///
299
    /// ht.remove("test");
300
    /// assert_eq!(ht.len(), 0);
301
    /// ```
302
    pub fn remove(&mut self, key: &str) -> Option<()> {
×
303
        let result =
×
304
            unsafe { zend_hash_str_del(self, CString::new(key).ok()?.as_ptr(), key.len() as _) };
×
305

306
        if result < 0 {
×
307
            None
×
308
        } else {
309
            Some(())
×
310
        }
311
    }
312

313
    /// Attempts to remove a value from the hash table with a string key.
314
    ///
315
    /// # Parameters
316
    ///
317
    /// * `key` - The key to remove from the hash table.
318
    ///
319
    /// # Returns
320
    ///
321
    /// * `Ok(())` - Key was successfully removed.
322
    /// * `None` - No key was removed, did not exist.
323
    ///
324
    /// # Example
325
    ///
326
    /// ```no_run
327
    /// use ext_php_rs::types::ZendHashTable;
328
    ///
329
    /// let mut ht = ZendHashTable::new();
330
    ///
331
    /// ht.push("hello");
332
    /// assert_eq!(ht.len(), 1);
333
    ///
334
    /// ht.remove_index(0);
335
    /// assert_eq!(ht.len(), 0);
336
    /// ```
337
    pub fn remove_index(&mut self, key: u64) -> Option<()> {
×
338
        let result = unsafe { zend_hash_index_del(self, key) };
×
339

340
        if result < 0 {
×
341
            None
×
342
        } else {
343
            Some(())
×
344
        }
345
    }
346

347
    /// Attempts to insert an item into the hash table, or update if the key
348
    /// already exists. Returns nothing in a result if successful.
349
    ///
350
    /// # Parameters
351
    ///
352
    /// * `key` - The key to insert the value at in the hash table.
353
    /// * `value` - The value to insert into the hash table.
354
    ///
355
    /// # Returns
356
    ///
357
    /// Returns nothing in a result on success.
358
    ///
359
    /// # Errors
360
    ///
361
    /// Returns an error if the key could not be converted into a [`CString`],
362
    /// or converting the value into a [`Zval`] failed.
363
    ///
364
    /// # Example
365
    ///
366
    /// ```no_run
367
    /// use ext_php_rs::types::ZendHashTable;
368
    ///
369
    /// let mut ht = ZendHashTable::new();
370
    ///
371
    /// ht.insert("a", "A");
372
    /// ht.insert("b", "B");
373
    /// ht.insert("c", "C");
374
    /// assert_eq!(ht.len(), 3);
375
    /// ```
376
    pub fn insert<V>(&mut self, key: &str, val: V) -> Result<()>
×
377
    where
378
        V: IntoZval,
379
    {
380
        let mut val = val.into_zval(false)?;
×
NEW
381
        unsafe { zend_hash_str_update(self, CString::new(key)?.as_ptr(), key.len(), &raw mut val) };
×
382
        val.release();
×
383
        Ok(())
×
384
    }
385

386
    /// Inserts an item into the hash table at a specified index, or updates if
387
    /// the key already exists. Returns nothing in a result if successful.
388
    ///
389
    /// # Parameters
390
    ///
391
    /// * `key` - The index at which the value should be inserted.
392
    /// * `val` - The value to insert into the hash table.
393
    ///
394
    /// # Returns
395
    ///
396
    /// Returns nothing in a result on success.
397
    ///
398
    /// # Errors
399
    ///
400
    /// Returns an error if converting the value into a [`Zval`] failed.
401
    ///
402
    /// # Example
403
    ///
404
    /// ```no_run
405
    /// use ext_php_rs::types::ZendHashTable;
406
    ///
407
    /// let mut ht = ZendHashTable::new();
408
    ///
409
    /// ht.insert_at_index(0, "A");
410
    /// ht.insert_at_index(5, "B");
411
    /// ht.insert_at_index(0, "C"); // notice overriding index 0
412
    /// assert_eq!(ht.len(), 2);
413
    /// ```
414
    pub fn insert_at_index<V>(&mut self, key: u64, val: V) -> Result<()>
×
415
    where
416
        V: IntoZval,
417
    {
418
        let mut val = val.into_zval(false)?;
×
NEW
419
        unsafe { zend_hash_index_update(self, key, &raw mut val) };
×
420
        val.release();
×
421
        Ok(())
×
422
    }
423

424
    /// Pushes an item onto the end of the hash table. Returns a result
425
    /// containing nothing if the element was successfully inserted.
426
    ///
427
    /// # Parameters
428
    ///
429
    /// * `val` - The value to insert into the hash table.
430
    ///
431
    /// # Returns
432
    ///
433
    /// Returns nothing in a result on success.
434
    ///
435
    /// # Errors
436
    ///
437
    /// Returns an error if converting the value into a [`Zval`] failed.
438
    ///
439
    /// # Example
440
    ///
441
    /// ```no_run
442
    /// use ext_php_rs::types::ZendHashTable;
443
    ///
444
    /// let mut ht = ZendHashTable::new();
445
    ///
446
    /// ht.push("a");
447
    /// ht.push("b");
448
    /// ht.push("c");
449
    /// assert_eq!(ht.len(), 3);
450
    /// ```
451
    pub fn push<V>(&mut self, val: V) -> Result<()>
×
452
    where
453
        V: IntoZval,
454
    {
455
        let mut val = val.into_zval(false)?;
×
NEW
456
        unsafe { zend_hash_next_index_insert(self, &raw mut val) };
×
457
        val.release();
×
458

459
        Ok(())
×
460
    }
461

462
    /// Checks if the hashtable only contains numerical keys.
463
    ///
464
    /// # Returns
465
    ///
466
    /// True if all keys on the hashtable are numerical.
467
    ///
468
    /// # Example
469
    ///
470
    /// ```no_run
471
    /// use ext_php_rs::types::ZendHashTable;
472
    ///
473
    /// let mut ht = ZendHashTable::new();
474
    ///
475
    /// ht.push(0);
476
    /// ht.push(3);
477
    /// ht.push(9);
478
    /// assert!(ht.has_numerical_keys());
479
    ///
480
    /// ht.insert("obviously not numerical", 10);
481
    /// assert!(!ht.has_numerical_keys());
482
    /// ```
483
    #[must_use]
484
    pub fn has_numerical_keys(&self) -> bool {
×
485
        !self.into_iter().any(|(k, _)| !k.is_long())
×
486
    }
487

488
    /// Checks if the hashtable has numerical, sequential keys.
489
    ///
490
    /// # Returns
491
    ///
492
    /// True if all keys on the hashtable are numerical and are in sequential
493
    /// order (i.e. starting at 0 and not skipping any keys).
494
    ///
495
    /// # Panics
496
    ///
497
    /// Panics if the number of elements in the hashtable exceeds `i64::MAX`.
498
    ///
499
    /// # Example
500
    ///
501
    /// ```no_run
502
    /// use ext_php_rs::types::ZendHashTable;
503
    ///
504
    /// let mut ht = ZendHashTable::new();
505
    ///
506
    /// ht.push(0);
507
    /// ht.push(3);
508
    /// ht.push(9);
509
    /// assert!(ht.has_sequential_keys());
510
    ///
511
    /// ht.insert_at_index(90, 10);
512
    /// assert!(!ht.has_sequential_keys());
513
    /// ```
514
    #[must_use]
515
    pub fn has_sequential_keys(&self) -> bool {
×
516
        !self
×
517
            .into_iter()
×
518
            .enumerate()
×
519
            .any(|(i, (k, _))| ArrayKey::Long(i64::try_from(i).expect("Integer overflow")) != k)
×
520
    }
521

522
    /// Returns an iterator over the values contained inside the hashtable, as
523
    /// if it was a set or list.
524
    ///
525
    /// # Example
526
    ///
527
    /// ```no_run
528
    /// use ext_php_rs::types::ZendHashTable;
529
    ///
530
    /// let mut ht = ZendHashTable::new();
531
    ///
532
    /// for val in ht.values() {
533
    ///     dbg!(val);
534
    /// }
535
    #[inline]
536
    #[must_use]
NEW
537
    pub fn values(&self) -> Values<'_> {
×
538
        Values::new(self)
×
539
    }
540

541
    /// Returns an iterator over the key(s) and value contained inside the
542
    /// hashtable.
543
    ///
544
    /// # Example
545
    ///
546
    /// ```no_run
547
    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
548
    ///
549
    /// let mut ht = ZendHashTable::new();
550
    ///
551
    /// for (key, val) in ht.iter() {
552
    ///     match &key {
553
    ///         ArrayKey::Long(index) => {
554
    ///         }
555
    ///         ArrayKey::String(key) => {
556
    ///         }
557
    ///     }
558
    ///     dbg!(key, val);
559
    /// }
560
    #[inline]
561
    #[must_use]
NEW
562
    pub fn iter(&self) -> Iter<'_> {
×
563
        self.into_iter()
×
564
    }
565
}
566

567
unsafe impl ZBoxable for ZendHashTable {
568
    fn free(&mut self) {
×
569
        // SAFETY: ZBox has immutable access to `self`.
570
        unsafe { zend_array_destroy(self) }
×
571
    }
572
}
573

574
impl Debug for ZendHashTable {
575
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
576
        f.debug_map()
×
577
            .entries(self.into_iter().map(|(k, v)| (k.to_string(), v)))
×
578
            .finish()
579
    }
580
}
581

582
impl ToOwned for ZendHashTable {
583
    type Owned = ZBox<ZendHashTable>;
584

585
    fn to_owned(&self) -> Self::Owned {
×
586
        unsafe {
587
            // SAFETY: FFI call does not modify `self`, returns a new hashtable.
588
            let ptr = zend_array_dup(ptr::from_ref(self).cast_mut());
×
589

590
            // SAFETY: `as_mut()` checks if the pointer is null, and panics if it is not.
591
            ZBox::from_raw(
592
                ptr.as_mut()
×
593
                    .expect("Failed to allocate memory for hashtable"),
×
594
            )
595
        }
596
    }
597
}
598

599
/// Immutable iterator upon a reference to a hashtable.
600
pub struct Iter<'a> {
601
    ht: &'a ZendHashTable,
602
    current_num: i64,
603
    end_num: i64,
604
    pos: HashPosition,
605
    end_pos: HashPosition,
606
}
607

608
/// Represents the key of a PHP array, which can be either a long or a string.
609
#[derive(Debug, PartialEq)]
610
pub enum ArrayKey {
611
    /// A numerical key.
612
    Long(i64),
613
    /// A string key.
614
    String(String),
615
}
616

617
impl ArrayKey {
618
    /// Check if the key is an integer.
619
    ///
620
    /// # Returns
621
    ///
622
    /// Returns true if the key is an integer, false otherwise.
623
    #[must_use]
624
    pub fn is_long(&self) -> bool {
×
625
        match self {
×
626
            ArrayKey::Long(_) => true,
×
627
            ArrayKey::String(_) => false,
×
628
        }
629
    }
630
}
631

632
impl Display for ArrayKey {
633
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
634
        match self {
×
635
            ArrayKey::Long(key) => write!(f, "{key}"),
×
636
            ArrayKey::String(key) => write!(f, "{key}"),
×
637
        }
638
    }
639
}
640

641
impl<'a> FromZval<'a> for ArrayKey {
642
    const TYPE: DataType = DataType::String;
643

644
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
645
        if let Some(key) = zval.long() {
×
646
            return Some(ArrayKey::Long(key));
×
647
        }
648
        if let Some(key) = zval.string() {
×
649
            return Some(ArrayKey::String(key));
×
650
        }
651
        None
×
652
    }
653
}
654

655
impl<'a> Iter<'a> {
656
    /// Creates a new iterator over a hashtable.
657
    ///
658
    /// # Parameters
659
    ///
660
    /// * `ht` - The hashtable to iterate.
661
    pub fn new(ht: &'a ZendHashTable) -> Self {
×
662
        let end_num: i64 = ht
×
663
            .len()
664
            .try_into()
665
            .expect("Integer overflow in hashtable length");
666
        let end_pos = if ht.nNumOfElements > 0 {
×
667
            ht.nNumOfElements - 1
×
668
        } else {
669
            0
×
670
        };
671

672
        Self {
673
            ht,
674
            current_num: 0,
675
            end_num,
676
            pos: 0,
677
            end_pos,
678
        }
679
    }
680
}
681

682
impl<'a> IntoIterator for &'a ZendHashTable {
683
    type Item = (ArrayKey, &'a Zval);
684
    type IntoIter = Iter<'a>;
685

686
    /// Returns an iterator over the key(s) and value contained inside the
687
    /// hashtable.
688
    ///
689
    /// # Example
690
    ///
691
    /// ```no_run
692
    /// use ext_php_rs::types::ZendHashTable;
693
    ///
694
    /// let mut ht = ZendHashTable::new();
695
    ///
696
    /// for (key, val) in ht.iter() {
697
    /// //   ^ Index if inserted at an index.
698
    /// //        ^ Optional string key, if inserted like a hashtable.
699
    /// //             ^ Inserted value.
700
    ///
701
    ///     dbg!(key, val);
702
    /// }
703
    #[inline]
704
    fn into_iter(self) -> Self::IntoIter {
×
705
        Iter::new(self)
×
706
    }
707
}
708

709
impl<'a> Iterator for Iter<'a> {
710
    type Item = (ArrayKey, &'a Zval);
711

712
    fn next(&mut self) -> Option<Self::Item> {
×
713
        self.next_zval()
×
714
            .map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v))
×
715
    }
716

717
    fn count(self) -> usize
×
718
    where
719
        Self: Sized,
720
    {
721
        self.ht.len()
×
722
    }
723
}
724

725
impl ExactSizeIterator for Iter<'_> {
726
    fn len(&self) -> usize {
×
727
        self.ht.len()
×
728
    }
729
}
730

731
impl DoubleEndedIterator for Iter<'_> {
732
    fn next_back(&mut self) -> Option<Self::Item> {
×
733
        if self.end_num <= self.current_num {
×
734
            return None;
×
735
        }
736

737
        let key_type = unsafe {
738
            zend_hash_get_current_key_type_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos)
×
739
        };
740

741
        if key_type == -1 {
×
742
            return None;
×
743
        }
744

745
        let key = Zval::new();
×
746

747
        unsafe {
748
            zend_hash_get_current_key_zval_ex(
749
                ptr::from_ref(self.ht).cast_mut(),
×
750
                (&raw const key).cast_mut(),
×
751
                &raw mut self.end_pos,
×
752
            );
753
        }
754
        let value = unsafe {
755
            &*zend_hash_get_current_data_ex(
×
756
                ptr::from_ref(self.ht).cast_mut(),
×
757
                &raw mut self.end_pos,
×
758
            )
759
        };
760

761
        let key = match ArrayKey::from_zval(&key) {
×
762
            Some(key) => key,
×
763
            None => ArrayKey::Long(self.end_num),
×
764
        };
765

766
        unsafe {
767
            zend_hash_move_backwards_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.end_pos)
×
768
        };
769
        self.end_num -= 1;
×
770

771
        Some((key, value))
×
772
    }
773
}
774

775
impl<'a> Iter<'a> {
776
    pub fn next_zval(&mut self) -> Option<(Zval, &'a Zval)> {
×
777
        if self.current_num >= self.end_num {
×
778
            return None;
×
779
        }
780

781
        let key_type = unsafe {
782
            zend_hash_get_current_key_type_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos)
×
783
        };
784

785
        // Key type `-1` is ???
786
        // Key type `1` is string
787
        // Key type `2` is long
788
        // Key type `3` is null meaning the end of the array
789
        if key_type == -1 || key_type == 3 {
×
790
            return None;
×
791
        }
792

793
        let mut key = Zval::new();
×
794

795
        unsafe {
796
            zend_hash_get_current_key_zval_ex(
797
                ptr::from_ref(self.ht).cast_mut(),
×
798
                (&raw const key).cast_mut(),
×
799
                &raw mut self.pos,
×
800
            );
801
        }
802
        let value = unsafe {
803
            let val_ptr =
×
804
                zend_hash_get_current_data_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos);
×
805

806
            if val_ptr.is_null() {
×
807
                return None;
×
808
            }
809

810
            &*val_ptr
×
811
        };
812

813
        if !key.is_long() && !key.is_string() {
×
814
            key.set_long(self.current_num);
×
815
        }
816

817
        unsafe { zend_hash_move_forward_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos) };
×
818
        self.current_num += 1;
×
819

820
        Some((key, value))
×
821
    }
822
}
823

824
/// Immutable iterator which iterates over the values of the hashtable, as it
825
/// was a set or list.
826
pub struct Values<'a>(Iter<'a>);
827

828
impl<'a> Values<'a> {
829
    /// Creates a new iterator over a hashtables values.
830
    ///
831
    /// # Parameters
832
    ///
833
    /// * `ht` - The hashtable to iterate.
834
    pub fn new(ht: &'a ZendHashTable) -> Self {
×
835
        Self(Iter::new(ht))
×
836
    }
837
}
838

839
impl<'a> Iterator for Values<'a> {
840
    type Item = &'a Zval;
841

842
    fn next(&mut self) -> Option<Self::Item> {
×
843
        self.0.next().map(|(_, zval)| zval)
×
844
    }
845

846
    fn count(self) -> usize
×
847
    where
848
        Self: Sized,
849
    {
850
        self.0.count()
×
851
    }
852
}
853

854
impl ExactSizeIterator for Values<'_> {
855
    fn len(&self) -> usize {
×
856
        self.0.len()
×
857
    }
858
}
859

860
impl DoubleEndedIterator for Values<'_> {
861
    fn next_back(&mut self) -> Option<Self::Item> {
×
862
        self.0.next_back().map(|(_, zval)| zval)
×
863
    }
864
}
865

866
impl Default for ZBox<ZendHashTable> {
867
    fn default() -> Self {
×
868
        ZendHashTable::new()
×
869
    }
870
}
871

872
impl Clone for ZBox<ZendHashTable> {
873
    fn clone(&self) -> Self {
×
874
        (**self).to_owned()
×
875
    }
876
}
877

878
impl IntoZval for ZBox<ZendHashTable> {
879
    const TYPE: DataType = DataType::Array;
880
    const NULLABLE: bool = false;
881

882
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
883
        zv.set_hashtable(self);
×
884
        Ok(())
×
885
    }
886
}
887

888
impl<'a> FromZval<'a> for &'a ZendHashTable {
889
    const TYPE: DataType = DataType::Array;
890

891
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
892
        zval.array()
×
893
    }
894
}
895

896
///////////////////////////////////////////
897
// HashMap
898
///////////////////////////////////////////
899

900
// TODO: Generalize hasher
901
#[allow(clippy::implicit_hasher)]
902
impl<'a, V> TryFrom<&'a ZendHashTable> for HashMap<String, V>
903
where
904
    V: FromZval<'a>,
905
{
906
    type Error = Error;
907

908
    fn try_from(value: &'a ZendHashTable) -> Result<Self> {
×
909
        let mut hm = HashMap::with_capacity(value.len());
×
910

911
        for (key, val) in value {
×
912
            hm.insert(
×
913
                key.to_string(),
×
914
                V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
×
915
            );
916
        }
917

918
        Ok(hm)
×
919
    }
920
}
921

922
impl<K, V> TryFrom<HashMap<K, V>> for ZBox<ZendHashTable>
923
where
924
    K: AsRef<str>,
925
    V: IntoZval,
926
{
927
    type Error = Error;
928

929
    fn try_from(value: HashMap<K, V>) -> Result<Self> {
×
930
        let mut ht = ZendHashTable::with_capacity(
931
            value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
×
932
        );
933

934
        for (k, v) in value {
×
935
            ht.insert(k.as_ref(), v)?;
×
936
        }
937

938
        Ok(ht)
×
939
    }
940
}
941

942
// TODO: Generalize hasher
943
#[allow(clippy::implicit_hasher)]
944
impl<K, V> IntoZval for HashMap<K, V>
945
where
946
    K: AsRef<str>,
947
    V: IntoZval,
948
{
949
    const TYPE: DataType = DataType::Array;
950
    const NULLABLE: bool = false;
951

952
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
953
        let arr = self.try_into()?;
×
954
        zv.set_hashtable(arr);
×
955
        Ok(())
×
956
    }
957
}
958

959
// TODO: Generalize hasher
960
#[allow(clippy::implicit_hasher)]
961
impl<'a, T> FromZval<'a> for HashMap<String, T>
962
where
963
    T: FromZval<'a>,
964
{
965
    const TYPE: DataType = DataType::Array;
966

967
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
968
        zval.array().and_then(|arr| arr.try_into().ok())
×
969
    }
970
}
971

972
///////////////////////////////////////////
973
// Vec
974
///////////////////////////////////////////
975

976
impl<'a, T> TryFrom<&'a ZendHashTable> for Vec<T>
977
where
978
    T: FromZval<'a>,
979
{
980
    type Error = Error;
981

982
    fn try_from(value: &'a ZendHashTable) -> Result<Self> {
×
983
        let mut vec = Vec::with_capacity(value.len());
×
984

985
        for (_, val) in value {
×
986
            vec.push(T::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?);
×
987
        }
988

989
        Ok(vec)
×
990
    }
991
}
992

993
impl<T> TryFrom<Vec<T>> for ZBox<ZendHashTable>
994
where
995
    T: IntoZval,
996
{
997
    type Error = Error;
998

999
    fn try_from(value: Vec<T>) -> Result<Self> {
×
1000
        let mut ht = ZendHashTable::with_capacity(
1001
            value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
×
1002
        );
1003

1004
        for val in value {
×
1005
            ht.push(val)?;
×
1006
        }
1007

1008
        Ok(ht)
×
1009
    }
1010
}
1011

1012
impl<T> IntoZval for Vec<T>
1013
where
1014
    T: IntoZval,
1015
{
1016
    const TYPE: DataType = DataType::Array;
1017
    const NULLABLE: bool = false;
1018

1019
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
1020
        let arr = self.try_into()?;
×
1021
        zv.set_hashtable(arr);
×
1022
        Ok(())
×
1023
    }
1024
}
1025

1026
impl<'a, T> FromZval<'a> for Vec<T>
1027
where
1028
    T: FromZval<'a>,
1029
{
1030
    const TYPE: DataType = DataType::Array;
1031

1032
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
1033
        zval.array().and_then(|arr| arr.try_into().ok())
×
1034
    }
1035
}
1036

1037
impl FromIterator<Zval> for ZBox<ZendHashTable> {
1038
    fn from_iter<T: IntoIterator<Item = Zval>>(iter: T) -> Self {
×
1039
        let mut ht = ZendHashTable::new();
×
1040
        for item in iter {
×
1041
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
1042
            // `val` to a zval fails.
1043
            let _ = ht.push(item);
×
1044
        }
1045
        ht
×
1046
    }
1047
}
1048

1049
impl FromIterator<(u64, Zval)> for ZBox<ZendHashTable> {
1050
    fn from_iter<T: IntoIterator<Item = (u64, Zval)>>(iter: T) -> Self {
×
1051
        let mut ht = ZendHashTable::new();
×
1052
        for (key, val) in iter {
×
1053
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
1054
            // `val` to a zval fails.
1055
            let _ = ht.insert_at_index(key, val);
×
1056
        }
1057
        ht
×
1058
    }
1059
}
1060

1061
impl<'a> FromIterator<(&'a str, Zval)> for ZBox<ZendHashTable> {
1062
    fn from_iter<T: IntoIterator<Item = (&'a str, Zval)>>(iter: T) -> Self {
×
1063
        let mut ht = ZendHashTable::new();
×
1064
        for (key, val) in iter {
×
1065
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
1066
            // `val` to a zval fails.
1067
            let _ = ht.insert(key, val);
×
1068
        }
1069
        ht
×
1070
    }
1071
}
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

© 2025 Coveralls, Inc