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

davidcole1340 / ext-php-rs / 16001688552

01 Jul 2025 02:07PM UTC coverage: 20.545% (+0.3%) from 20.197%
16001688552

Pull #475

github

Xenira
chore: add git hooks and `CONTRIBUTING.md`
Pull Request #475: chore: add git hooks and `CONTRIBUTING.md`

761 of 3704 relevant lines covered (20.55%)

3.56 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
    // TODO: Verify if this is safe to use, as it allows mutating the
219
    // hashtable while only having a reference to it. #461
220
    #[allow(clippy::mut_from_ref)]
221
    #[must_use]
222
    pub fn get_mut(&self, key: &'_ str) -> Option<&mut Zval> {
×
223
        let str = CString::new(key).ok()?;
×
224
        unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_mut() }
×
225
    }
226

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

254
    /// Attempts to retrieve a value from the hash table with an index.
255
    ///
256
    /// # Parameters
257
    ///
258
    /// * `key` - The key to search for in the hash table.
259
    ///
260
    /// # Returns
261
    ///
262
    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
263
    ///   table.
264
    /// * `None` - No value at the given position was found.
265
    ///
266
    /// # Example
267
    ///
268
    /// ```no_run
269
    /// use ext_php_rs::types::ZendHashTable;
270
    ///
271
    /// let mut ht = ZendHashTable::new();
272
    ///
273
    /// ht.push(100);
274
    /// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
275
    /// ```
276
    // TODO: Verify if this is safe to use, as it allows mutating the
277
    // hashtable while only having a reference to it. #461
278
    #[allow(clippy::mut_from_ref)]
279
    #[must_use]
280
    pub fn get_index_mut(&self, key: u64) -> Option<&mut Zval> {
×
281
        unsafe { zend_hash_index_find(self, key).as_mut() }
×
282
    }
283

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

312
        if result < 0 {
×
313
            None
×
314
        } else {
315
            Some(())
×
316
        }
317
    }
318

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

346
        if result < 0 {
×
347
            None
×
348
        } else {
349
            Some(())
×
350
        }
351
    }
352

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

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

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

465
        Ok(())
×
466
    }
467

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

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

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

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

573
unsafe impl ZBoxable for ZendHashTable {
574
    fn free(&mut self) {
×
575
        // SAFETY: ZBox has immutable access to `self`.
576
        unsafe { zend_array_destroy(self) }
×
577
    }
578
}
579

580
impl Debug for ZendHashTable {
581
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
582
        f.debug_map()
×
583
            .entries(self.into_iter().map(|(k, v)| (k.to_string(), v)))
×
584
            .finish()
585
    }
586
}
587

588
impl ToOwned for ZendHashTable {
589
    type Owned = ZBox<ZendHashTable>;
590

591
    fn to_owned(&self) -> Self::Owned {
×
592
        unsafe {
593
            // SAFETY: FFI call does not modify `self`, returns a new hashtable.
594
            let ptr = zend_array_dup(ptr::from_ref(self).cast_mut());
×
595

596
            // SAFETY: `as_mut()` checks if the pointer is null, and panics if it is not.
597
            ZBox::from_raw(
598
                ptr.as_mut()
×
599
                    .expect("Failed to allocate memory for hashtable"),
×
600
            )
601
        }
602
    }
603
}
604

605
/// Immutable iterator upon a reference to a hashtable.
606
pub struct Iter<'a> {
607
    ht: &'a ZendHashTable,
608
    current_num: i64,
609
    end_num: i64,
610
    pos: HashPosition,
611
    end_pos: HashPosition,
612
}
613

614
/// Represents the key of a PHP array, which can be either a long or a string.
615
#[derive(Debug, PartialEq)]
616
pub enum ArrayKey {
617
    /// A numerical key.
618
    Long(i64),
619
    /// A string key.
620
    String(String),
621
}
622

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

638
impl Display for ArrayKey {
639
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
640
        match self {
×
641
            ArrayKey::Long(key) => write!(f, "{key}"),
×
642
            ArrayKey::String(key) => write!(f, "{key}"),
×
643
        }
644
    }
645
}
646

647
impl<'a> FromZval<'a> for ArrayKey {
648
    const TYPE: DataType = DataType::String;
649

650
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
651
        if let Some(key) = zval.long() {
×
652
            return Some(ArrayKey::Long(key));
×
653
        }
654
        if let Some(key) = zval.string() {
×
655
            return Some(ArrayKey::String(key));
×
656
        }
657
        None
×
658
    }
659
}
660

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

678
        Self {
679
            ht,
680
            current_num: 0,
681
            end_num,
682
            pos: 0,
683
            end_pos,
684
        }
685
    }
686
}
687

688
impl<'a> IntoIterator for &'a ZendHashTable {
689
    type Item = (ArrayKey, &'a Zval);
690
    type IntoIter = Iter<'a>;
691

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

715
impl<'a> Iterator for Iter<'a> {
716
    type Item = (ArrayKey, &'a Zval);
717

718
    fn next(&mut self) -> Option<Self::Item> {
×
719
        self.next_zval()
×
720
            .map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v))
×
721
    }
722

723
    fn count(self) -> usize
×
724
    where
725
        Self: Sized,
726
    {
727
        self.ht.len()
×
728
    }
729
}
730

731
impl ExactSizeIterator for Iter<'_> {
732
    fn len(&self) -> usize {
×
733
        self.ht.len()
×
734
    }
735
}
736

737
impl DoubleEndedIterator for Iter<'_> {
738
    fn next_back(&mut self) -> Option<Self::Item> {
×
739
        if self.end_num <= self.current_num {
×
740
            return None;
×
741
        }
742

743
        let key_type = unsafe {
744
            zend_hash_get_current_key_type_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos)
×
745
        };
746

747
        if key_type == -1 {
×
748
            return None;
×
749
        }
750

751
        let key = Zval::new();
×
752

753
        unsafe {
754
            zend_hash_get_current_key_zval_ex(
755
                ptr::from_ref(self.ht).cast_mut(),
×
756
                (&raw const key).cast_mut(),
×
757
                &raw mut self.end_pos,
×
758
            );
759
        }
760
        let value = unsafe {
761
            &*zend_hash_get_current_data_ex(
×
762
                ptr::from_ref(self.ht).cast_mut(),
×
763
                &raw mut self.end_pos,
×
764
            )
765
        };
766

767
        let key = match ArrayKey::from_zval(&key) {
×
768
            Some(key) => key,
×
769
            None => ArrayKey::Long(self.end_num),
×
770
        };
771

772
        unsafe {
773
            zend_hash_move_backwards_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.end_pos)
×
774
        };
775
        self.end_num -= 1;
×
776

777
        Some((key, value))
×
778
    }
779
}
780

781
impl<'a> Iter<'a> {
782
    pub fn next_zval(&mut self) -> Option<(Zval, &'a Zval)> {
×
783
        if self.current_num >= self.end_num {
×
784
            return None;
×
785
        }
786

787
        let key_type = unsafe {
788
            zend_hash_get_current_key_type_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos)
×
789
        };
790

791
        // Key type `-1` is ???
792
        // Key type `1` is string
793
        // Key type `2` is long
794
        // Key type `3` is null meaning the end of the array
795
        if key_type == -1 || key_type == 3 {
×
796
            return None;
×
797
        }
798

799
        let mut key = Zval::new();
×
800

801
        unsafe {
802
            zend_hash_get_current_key_zval_ex(
803
                ptr::from_ref(self.ht).cast_mut(),
×
804
                (&raw const key).cast_mut(),
×
805
                &raw mut self.pos,
×
806
            );
807
        }
808
        let value = unsafe {
809
            let val_ptr =
×
810
                zend_hash_get_current_data_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos);
×
811

812
            if val_ptr.is_null() {
×
813
                return None;
×
814
            }
815

816
            &*val_ptr
×
817
        };
818

819
        if !key.is_long() && !key.is_string() {
×
820
            key.set_long(self.current_num);
×
821
        }
822

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

826
        Some((key, value))
×
827
    }
828
}
829

830
/// Immutable iterator which iterates over the values of the hashtable, as it
831
/// was a set or list.
832
pub struct Values<'a>(Iter<'a>);
833

834
impl<'a> Values<'a> {
835
    /// Creates a new iterator over a hashtables values.
836
    ///
837
    /// # Parameters
838
    ///
839
    /// * `ht` - The hashtable to iterate.
840
    pub fn new(ht: &'a ZendHashTable) -> Self {
×
841
        Self(Iter::new(ht))
×
842
    }
843
}
844

845
impl<'a> Iterator for Values<'a> {
846
    type Item = &'a Zval;
847

848
    fn next(&mut self) -> Option<Self::Item> {
×
849
        self.0.next().map(|(_, zval)| zval)
×
850
    }
851

852
    fn count(self) -> usize
×
853
    where
854
        Self: Sized,
855
    {
856
        self.0.count()
×
857
    }
858
}
859

860
impl ExactSizeIterator for Values<'_> {
861
    fn len(&self) -> usize {
×
862
        self.0.len()
×
863
    }
864
}
865

866
impl DoubleEndedIterator for Values<'_> {
867
    fn next_back(&mut self) -> Option<Self::Item> {
×
868
        self.0.next_back().map(|(_, zval)| zval)
×
869
    }
870
}
871

872
impl Default for ZBox<ZendHashTable> {
873
    fn default() -> Self {
×
874
        ZendHashTable::new()
×
875
    }
876
}
877

878
impl Clone for ZBox<ZendHashTable> {
879
    fn clone(&self) -> Self {
×
880
        (**self).to_owned()
×
881
    }
882
}
883

884
impl IntoZval for ZBox<ZendHashTable> {
885
    const TYPE: DataType = DataType::Array;
886
    const NULLABLE: bool = false;
887

888
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
889
        zv.set_hashtable(self);
×
890
        Ok(())
×
891
    }
892
}
893

894
impl<'a> FromZval<'a> for &'a ZendHashTable {
895
    const TYPE: DataType = DataType::Array;
896

897
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
898
        zval.array()
×
899
    }
900
}
901

902
///////////////////////////////////////////
903
// HashMap
904
///////////////////////////////////////////
905

906
// TODO: Generalize hasher
907
#[allow(clippy::implicit_hasher)]
908
impl<'a, V> TryFrom<&'a ZendHashTable> for HashMap<String, V>
909
where
910
    V: FromZval<'a>,
911
{
912
    type Error = Error;
913

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

917
        for (key, val) in value {
×
918
            hm.insert(
×
919
                key.to_string(),
×
920
                V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
×
921
            );
922
        }
923

924
        Ok(hm)
×
925
    }
926
}
927

928
impl<K, V> TryFrom<HashMap<K, V>> for ZBox<ZendHashTable>
929
where
930
    K: AsRef<str>,
931
    V: IntoZval,
932
{
933
    type Error = Error;
934

935
    fn try_from(value: HashMap<K, V>) -> Result<Self> {
×
936
        let mut ht = ZendHashTable::with_capacity(
937
            value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
×
938
        );
939

940
        for (k, v) in value {
×
941
            ht.insert(k.as_ref(), v)?;
×
942
        }
943

944
        Ok(ht)
×
945
    }
946
}
947

948
// TODO: Generalize hasher
949
#[allow(clippy::implicit_hasher)]
950
impl<K, V> IntoZval for HashMap<K, V>
951
where
952
    K: AsRef<str>,
953
    V: IntoZval,
954
{
955
    const TYPE: DataType = DataType::Array;
956
    const NULLABLE: bool = false;
957

958
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
959
        let arr = self.try_into()?;
×
960
        zv.set_hashtable(arr);
×
961
        Ok(())
×
962
    }
963
}
964

965
// TODO: Generalize hasher
966
#[allow(clippy::implicit_hasher)]
967
impl<'a, T> FromZval<'a> for HashMap<String, T>
968
where
969
    T: FromZval<'a>,
970
{
971
    const TYPE: DataType = DataType::Array;
972

973
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
974
        zval.array().and_then(|arr| arr.try_into().ok())
×
975
    }
976
}
977

978
///////////////////////////////////////////
979
// Vec
980
///////////////////////////////////////////
981

982
impl<'a, T> TryFrom<&'a ZendHashTable> for Vec<T>
983
where
984
    T: FromZval<'a>,
985
{
986
    type Error = Error;
987

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

991
        for (_, val) in value {
×
992
            vec.push(T::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?);
×
993
        }
994

995
        Ok(vec)
×
996
    }
997
}
998

999
impl<T> TryFrom<Vec<T>> for ZBox<ZendHashTable>
1000
where
1001
    T: IntoZval,
1002
{
1003
    type Error = Error;
1004

1005
    fn try_from(value: Vec<T>) -> Result<Self> {
×
1006
        let mut ht = ZendHashTable::with_capacity(
1007
            value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
×
1008
        );
1009

1010
        for val in value {
×
1011
            ht.push(val)?;
×
1012
        }
1013

1014
        Ok(ht)
×
1015
    }
1016
}
1017

1018
impl<T> IntoZval for Vec<T>
1019
where
1020
    T: IntoZval,
1021
{
1022
    const TYPE: DataType = DataType::Array;
1023
    const NULLABLE: bool = false;
1024

1025
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
1026
        let arr = self.try_into()?;
×
1027
        zv.set_hashtable(arr);
×
1028
        Ok(())
×
1029
    }
1030
}
1031

1032
impl<'a, T> FromZval<'a> for Vec<T>
1033
where
1034
    T: FromZval<'a>,
1035
{
1036
    const TYPE: DataType = DataType::Array;
1037

1038
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
1039
        zval.array().and_then(|arr| arr.try_into().ok())
×
1040
    }
1041
}
1042

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

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

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