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

davidcole1340 / ext-php-rs / 16353578993

17 Jul 2025 06:58PM UTC coverage: 24.494% (-0.1%) from 24.606%
16353578993

Pull #520

github

web-flow
Merge 83cb3def7 into 2a0d615ec
Pull Request #520: Array btreemap conversion

0 of 18 new or added lines in 1 file covered. (0.0%)

968 of 3952 relevant lines covered (24.49%)

5.56 hits per line

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

24.92
/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 crate::{
5
    boxed::{ZBox, ZBoxable},
6
    convert::{FromZval, IntoZval},
7
    error::{Error, Result},
8
    ffi::zend_ulong,
9
    ffi::{
10
        _zend_new_array, zend_array_count, zend_array_destroy, zend_array_dup, zend_hash_clean,
11
        zend_hash_get_current_data_ex, zend_hash_get_current_key_type_ex,
12
        zend_hash_get_current_key_zval_ex, zend_hash_index_del, zend_hash_index_find,
13
        zend_hash_index_update, zend_hash_move_backwards_ex, zend_hash_move_forward_ex,
14
        zend_hash_next_index_insert, zend_hash_str_del, zend_hash_str_find, zend_hash_str_update,
15
        HashPosition, HT_MIN_SIZE,
16
    },
17
    flags::DataType,
18
    types::Zval,
19
};
20
use std::{
21
    collections::{BTreeMap, HashMap},
22
    convert::{TryFrom, TryInto},
23
    ffi::CString,
24
    fmt::{Debug, Display},
25
    iter::FromIterator,
26
    ptr,
27
    str::FromStr,
28
};
29

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

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

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

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

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

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

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

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

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

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

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

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

428
        if result < 0 {
×
429
            None
×
430
        } else {
431
            Some(())
×
432
        }
433
    }
434

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

465
        if result < 0 {
×
466
            None
×
467
        } else {
468
            Some(())
×
469
        }
470
    }
471

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

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

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

629
        Ok(())
×
630
    }
631

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

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

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

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

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

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

754
impl ToOwned for ZendHashTable {
755
    type Owned = ZBox<ZendHashTable>;
756

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

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

771
/// Immutable iterator upon a reference to a hashtable.
772
pub struct Iter<'a> {
773
    ht: &'a ZendHashTable,
774
    current_num: i64,
775
    end_num: i64,
776
    pos: HashPosition,
777
    end_pos: HashPosition,
778
}
779

780
/// Represents the key of a PHP array, which can be either a long or a string.
781
#[derive(Debug, Clone, PartialEq)]
782
pub enum ArrayKey<'a> {
783
    /// A numerical key.
784
    /// In Zend API it's represented by `u64` (`zend_ulong`), so the value needs
785
    /// to be cast to `zend_ulong` before passing into Zend functions.
786
    Long(i64),
787
    /// A string key.
788
    String(String),
789
    /// A string key by reference.
790
    Str(&'a str),
791
}
792

793
impl From<String> for ArrayKey<'_> {
794
    fn from(value: String) -> Self {
×
795
        Self::String(value)
×
796
    }
797
}
798

799
impl TryFrom<ArrayKey<'_>> for String {
800
    type Error = Error;
801

802
    fn try_from(value: ArrayKey<'_>) -> std::result::Result<Self, Self::Error> {
12✔
803
        match value {
12✔
804
            ArrayKey::String(s) => Ok(s),
16✔
805
            ArrayKey::Str(s) => Ok(s.to_string()),
2✔
806
            ArrayKey::Long(_) => Err(Error::InvalidProperty),
3✔
807
        }
808
    }
809
}
810

811
impl TryFrom<ArrayKey<'_>> for i64 {
812
    type Error = Error;
813

814
    fn try_from(value: ArrayKey<'_>) -> std::result::Result<Self, Self::Error> {
11✔
815
        match value {
11✔
816
            ArrayKey::Long(i) => Ok(i),
6✔
817
            ArrayKey::String(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
9✔
818
            ArrayKey::Str(s) => s.parse::<i64>().map_err(|_| Error::InvalidProperty),
8✔
819
        }
820
    }
821
}
822

823
impl ArrayKey<'_> {
824
    /// Check if the key is an integer.
825
    ///
826
    /// # Returns
827
    ///
828
    /// Returns true if the key is an integer, false otherwise.
829
    #[must_use]
830
    pub fn is_long(&self) -> bool {
×
831
        match self {
×
832
            ArrayKey::Long(_) => true,
×
833
            ArrayKey::String(_) | ArrayKey::Str(_) => false,
×
834
        }
835
    }
836
}
837

838
impl Display for ArrayKey<'_> {
839
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
840
        match self {
×
841
            ArrayKey::Long(key) => write!(f, "{key}"),
×
842
            ArrayKey::String(key) => write!(f, "{key}"),
×
843
            ArrayKey::Str(key) => write!(f, "{key}"),
×
844
        }
845
    }
846
}
847

848
impl<'a> From<&'a str> for ArrayKey<'a> {
849
    fn from(key: &'a str) -> ArrayKey<'a> {
27✔
850
        ArrayKey::Str(key)
27✔
851
    }
852
}
853

854
impl<'a> From<i64> for ArrayKey<'a> {
855
    fn from(index: i64) -> ArrayKey<'a> {
20✔
856
        ArrayKey::Long(index)
20✔
857
    }
858
}
859

860
impl<'a> FromZval<'a> for ArrayKey<'_> {
861
    const TYPE: DataType = DataType::String;
862

863
    fn from_zval(zval: &'a Zval) -> Option<Self> {
20✔
864
        if let Some(key) = zval.long() {
31✔
865
            return Some(ArrayKey::Long(key));
×
866
        }
867
        if let Some(key) = zval.string() {
18✔
868
            return Some(ArrayKey::String(key));
×
869
        }
870
        None
×
871
    }
872
}
873

874
impl<'a> Iter<'a> {
875
    /// Creates a new iterator over a hashtable.
876
    ///
877
    /// # Parameters
878
    ///
879
    /// * `ht` - The hashtable to iterate.
880
    pub fn new(ht: &'a ZendHashTable) -> Self {
9✔
881
        let end_num: i64 = ht
27✔
882
            .len()
883
            .try_into()
884
            .expect("Integer overflow in hashtable length");
885
        let end_pos = if ht.nNumOfElements > 0 {
18✔
886
            ht.nNumOfElements - 1
9✔
887
        } else {
888
            0
×
889
        };
890

891
        Self {
892
            ht,
893
            current_num: 0,
894
            end_num,
895
            pos: 0,
896
            end_pos,
897
        }
898
    }
899
}
900

901
impl<'a> IntoIterator for &'a ZendHashTable {
902
    type Item = (ArrayKey<'a>, &'a Zval);
903
    type IntoIter = Iter<'a>;
904

905
    /// Returns an iterator over the key(s) and value contained inside the
906
    /// hashtable.
907
    ///
908
    /// # Example
909
    ///
910
    /// ```no_run
911
    /// use ext_php_rs::types::ZendHashTable;
912
    ///
913
    /// let mut ht = ZendHashTable::new();
914
    ///
915
    /// for (key, val) in ht.iter() {
916
    /// //   ^ Index if inserted at an index.
917
    /// //        ^ Optional string key, if inserted like a hashtable.
918
    /// //             ^ Inserted value.
919
    ///
920
    ///     dbg!(key, val);
921
    /// }
922
    #[inline]
923
    fn into_iter(self) -> Self::IntoIter {
9✔
924
        Iter::new(self)
18✔
925
    }
926
}
927

928
impl<'a> Iterator for Iter<'a> {
929
    type Item = (ArrayKey<'a>, &'a Zval);
930

931
    fn next(&mut self) -> Option<Self::Item> {
26✔
932
        self.next_zval()
52✔
933
            .map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v))
126✔
934
    }
935

936
    fn count(self) -> usize
×
937
    where
938
        Self: Sized,
939
    {
940
        self.ht.len()
×
941
    }
942
}
943

944
impl ExactSizeIterator for Iter<'_> {
945
    fn len(&self) -> usize {
×
946
        self.ht.len()
×
947
    }
948
}
949

950
impl DoubleEndedIterator for Iter<'_> {
951
    fn next_back(&mut self) -> Option<Self::Item> {
×
952
        if self.end_num <= self.current_num {
×
953
            return None;
×
954
        }
955

956
        let key_type = unsafe {
957
            zend_hash_get_current_key_type_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos)
×
958
        };
959

960
        if key_type == -1 {
×
961
            return None;
×
962
        }
963

964
        let key = Zval::new();
×
965

966
        unsafe {
967
            zend_hash_get_current_key_zval_ex(
968
                ptr::from_ref(self.ht).cast_mut(),
×
969
                (&raw const key).cast_mut(),
×
970
                &raw mut self.end_pos,
×
971
            );
972
        }
973
        let value = unsafe {
974
            &*zend_hash_get_current_data_ex(
×
975
                ptr::from_ref(self.ht).cast_mut(),
×
976
                &raw mut self.end_pos,
×
977
            )
978
        };
979

980
        let key = match ArrayKey::from_zval(&key) {
×
981
            Some(key) => key,
×
982
            None => ArrayKey::Long(self.end_num),
×
983
        };
984

985
        unsafe {
986
            zend_hash_move_backwards_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.end_pos)
×
987
        };
988
        self.end_num -= 1;
×
989

990
        Some((key, value))
×
991
    }
992
}
993

994
impl<'a> Iter<'a> {
995
    pub fn next_zval(&mut self) -> Option<(Zval, &'a Zval)> {
26✔
996
        if self.current_num >= self.end_num {
26✔
997
            return None;
6✔
998
        }
999

1000
        let key_type = unsafe {
1001
            zend_hash_get_current_key_type_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos)
×
1002
        };
1003

1004
        // Key type `-1` is ???
1005
        // Key type `1` is string
1006
        // Key type `2` is long
1007
        // Key type `3` is null meaning the end of the array
1008
        if key_type == -1 || key_type == 3 {
20✔
1009
            return None;
×
1010
        }
1011

1012
        let mut key = Zval::new();
×
1013

1014
        unsafe {
1015
            zend_hash_get_current_key_zval_ex(
1016
                ptr::from_ref(self.ht).cast_mut(),
×
1017
                (&raw const key).cast_mut(),
×
1018
                &raw mut self.pos,
×
1019
            );
1020
        }
1021
        let value = unsafe {
1022
            let val_ptr =
×
1023
                zend_hash_get_current_data_ex(ptr::from_ref(self.ht).cast_mut(), &raw mut self.pos);
×
1024

1025
            if val_ptr.is_null() {
×
1026
                return None;
×
1027
            }
1028

1029
            &*val_ptr
×
1030
        };
1031

1032
        if !key.is_long() && !key.is_string() {
9✔
1033
            key.set_long(self.current_num);
×
1034
        }
1035

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

1039
        Some((key, value))
×
1040
    }
1041
}
1042

1043
/// Immutable iterator which iterates over the values of the hashtable, as it
1044
/// was a set or list.
1045
pub struct Values<'a>(Iter<'a>);
1046

1047
impl<'a> Values<'a> {
1048
    /// Creates a new iterator over a hashtables values.
1049
    ///
1050
    /// # Parameters
1051
    ///
1052
    /// * `ht` - The hashtable to iterate.
1053
    pub fn new(ht: &'a ZendHashTable) -> Self {
×
1054
        Self(Iter::new(ht))
×
1055
    }
1056
}
1057

1058
impl<'a> Iterator for Values<'a> {
1059
    type Item = &'a Zval;
1060

1061
    fn next(&mut self) -> Option<Self::Item> {
×
1062
        self.0.next().map(|(_, zval)| zval)
×
1063
    }
1064

1065
    fn count(self) -> usize
×
1066
    where
1067
        Self: Sized,
1068
    {
1069
        self.0.count()
×
1070
    }
1071
}
1072

1073
impl ExactSizeIterator for Values<'_> {
1074
    fn len(&self) -> usize {
×
1075
        self.0.len()
×
1076
    }
1077
}
1078

1079
impl DoubleEndedIterator for Values<'_> {
1080
    fn next_back(&mut self) -> Option<Self::Item> {
×
1081
        self.0.next_back().map(|(_, zval)| zval)
×
1082
    }
1083
}
1084

1085
impl Default for ZBox<ZendHashTable> {
1086
    fn default() -> Self {
×
1087
        ZendHashTable::new()
×
1088
    }
1089
}
1090

1091
impl Clone for ZBox<ZendHashTable> {
1092
    fn clone(&self) -> Self {
×
1093
        (**self).to_owned()
×
1094
    }
1095
}
1096

1097
impl IntoZval for ZBox<ZendHashTable> {
1098
    const TYPE: DataType = DataType::Array;
1099
    const NULLABLE: bool = false;
1100

1101
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
1102
        zv.set_hashtable(self);
×
1103
        Ok(())
×
1104
    }
1105
}
1106

1107
impl<'a> FromZval<'a> for &'a ZendHashTable {
1108
    const TYPE: DataType = DataType::Array;
1109

1110
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
1111
        zval.array()
×
1112
    }
1113
}
1114

1115
///////////////////////////////////////////
1116
// HashMap
1117
///////////////////////////////////////////
1118

1119
// TODO: Generalize hasher
1120
#[allow(clippy::implicit_hasher)]
1121
impl<'a, V> TryFrom<&'a ZendHashTable> for HashMap<String, V>
1122
where
1123
    V: FromZval<'a>,
1124
{
1125
    type Error = Error;
1126

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

1130
        for (key, val) in value {
×
1131
            hm.insert(
×
1132
                key.to_string(),
×
1133
                V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
×
1134
            );
1135
        }
1136

1137
        Ok(hm)
×
1138
    }
1139
}
1140

1141
impl<K, V> TryFrom<HashMap<K, V>> for ZBox<ZendHashTable>
1142
where
1143
    K: AsRef<str>,
1144
    V: IntoZval,
1145
{
1146
    type Error = Error;
1147

1148
    fn try_from(value: HashMap<K, V>) -> Result<Self> {
×
1149
        let mut ht = ZendHashTable::with_capacity(
1150
            value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
×
1151
        );
1152

1153
        for (k, v) in value {
×
1154
            ht.insert(k.as_ref(), v)?;
×
1155
        }
1156

1157
        Ok(ht)
×
1158
    }
1159
}
1160

1161
impl<'a, K, V> TryFrom<&'a ZendHashTable> for Vec<(K, V)>
1162
where
1163
    K: TryFrom<ArrayKey<'a>, Error = Error>,
1164
    V: FromZval<'a>,
1165
{
1166
    type Error = Error;
1167

1168
    fn try_from(value: &'a ZendHashTable) -> Result<Self> {
7✔
1169
        let mut vec = Vec::with_capacity(value.len());
28✔
1170

1171
        for (key, val) in value {
46✔
1172
            vec.push((
25✔
1173
                key.try_into()?,
31✔
1174
                V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
11✔
1175
            ));
1176
        }
1177

1178
        Ok(vec)
4✔
1179
    }
1180
}
1181

1182
impl<'a, V> TryFrom<&'a ZendHashTable> for Vec<(ArrayKey<'a>, V)>
1183
where
1184
    V: FromZval<'a>,
1185
{
1186
    type Error = Error;
1187

1188
    fn try_from(value: &'a ZendHashTable) -> Result<Self> {
2✔
1189
        let mut vec = Vec::with_capacity(value.len());
8✔
1190

1191
        for (key, val) in value {
20✔
1192
            vec.push((
12✔
1193
                key,
6✔
1194
                V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
18✔
1195
            ));
1196
        }
1197

1198
        Ok(vec)
2✔
1199
    }
1200
}
1201

1202
impl<'a, K, V> TryFrom<Vec<(K, V)>> for ZBox<ZendHashTable>
1203
where
1204
    K: Into<ArrayKey<'a>>,
1205
    V: IntoZval,
1206
{
1207
    type Error = Error;
1208

1209
    fn try_from(value: Vec<(K, V)>) -> Result<Self> {
4✔
1210
        let mut ht = ZendHashTable::with_capacity(
1211
            value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
16✔
1212
        );
1213

1214
        for (k, v) in value {
40✔
1215
            ht.insert(k, v)?;
36✔
1216
        }
1217

1218
        Ok(ht)
4✔
1219
    }
1220
}
1221

1222
// TODO: Generalize hasher
1223
#[allow(clippy::implicit_hasher)]
1224
impl<K, V> IntoZval for HashMap<K, V>
1225
where
1226
    K: AsRef<str>,
1227
    V: IntoZval,
1228
{
1229
    const TYPE: DataType = DataType::Array;
1230
    const NULLABLE: bool = false;
1231

1232
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
1233
        let arr = self.try_into()?;
×
1234
        zv.set_hashtable(arr);
×
1235
        Ok(())
×
1236
    }
1237
}
1238

1239
// TODO: Generalize hasher
1240
#[allow(clippy::implicit_hasher)]
1241
impl<'a, T> FromZval<'a> for HashMap<String, T>
1242
where
1243
    T: FromZval<'a>,
1244
{
1245
    const TYPE: DataType = DataType::Array;
1246

1247
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
1248
        zval.array().and_then(|arr| arr.try_into().ok())
×
1249
    }
1250
}
1251

1252
impl<'a, K, V> IntoZval for Vec<(K, V)>
1253
where
1254
    K: Into<ArrayKey<'a>>,
1255
    V: IntoZval,
1256
{
1257
    const TYPE: DataType = DataType::Array;
1258
    const NULLABLE: bool = false;
1259

1260
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
2✔
1261
        let arr = self.try_into()?;
6✔
1262
        zv.set_hashtable(arr);
×
1263
        Ok(())
×
1264
    }
1265
}
1266

1267
impl<'a, K, V> FromZval<'a> for Vec<(K, V)>
1268
where
1269
    K: TryFrom<ArrayKey<'a>, Error = Error>,
1270
    V: FromZval<'a>,
1271
{
1272
    const TYPE: DataType = DataType::Array;
1273

1274
    fn from_zval(zval: &'a Zval) -> Option<Self> {
3✔
1275
        zval.array().and_then(|arr| arr.try_into().ok())
18✔
1276
    }
1277
}
1278

1279
impl<'a, V> FromZval<'a> for Vec<(ArrayKey<'a>, V)>
1280
where
1281
    V: FromZval<'a>,
1282
{
1283
    const TYPE: DataType = DataType::Array;
1284

1285
    fn from_zval(zval: &'a Zval) -> Option<Self> {
1✔
1286
        zval.array().and_then(|arr| arr.try_into().ok())
6✔
1287
    }
1288
}
1289

1290
///////////////////////////////////////////
1291
// BTreeMap
1292
///////////////////////////////////////////
1293

1294
impl<'a, V> TryFrom<&'a ZendHashTable> for BTreeMap<String, V>
1295
where
1296
    V: FromZval<'a>,
1297
{
1298
    type Error = Error;
1299

NEW
1300
    fn try_from(value: &'a ZendHashTable) -> Result<Self> {
×
NEW
1301
        let mut hm = BTreeMap::new();
×
1302

NEW
1303
        for (key, val) in value {
×
NEW
1304
            hm.insert(
×
NEW
1305
                key.to_string(),
×
NEW
1306
                V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
×
1307
            );
1308
        }
1309

NEW
1310
        Ok(hm)
×
1311
    }
1312
}
1313

1314
impl<K, V> TryFrom<BTreeMap<K, V>> for ZBox<ZendHashTable>
1315
where
1316
    K: AsRef<str>,
1317
    V: IntoZval,
1318
{
1319
    type Error = Error;
1320

NEW
1321
    fn try_from(value: BTreeMap<K, V>) -> Result<Self> {
×
1322
        let mut ht = ZendHashTable::with_capacity(
NEW
1323
            value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
×
1324
        );
1325

NEW
1326
        for (k, v) in value {
×
NEW
1327
            ht.insert(k.as_ref(), v)?;
×
1328
        }
1329

NEW
1330
        Ok(ht)
×
1331
    }
1332
}
1333

1334
impl<K, V> IntoZval for BTreeMap<K, V>
1335
where
1336
    K: AsRef<str>,
1337
    V: IntoZval,
1338
{
1339
    const TYPE: DataType = DataType::Array;
1340
    const NULLABLE: bool = false;
1341

NEW
1342
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
NEW
1343
        let arr = self.try_into()?;
×
NEW
1344
        zv.set_hashtable(arr);
×
NEW
1345
        Ok(())
×
1346
    }
1347
}
1348

1349
impl<'a, T> FromZval<'a> for BTreeMap<String, T>
1350
where
1351
    T: FromZval<'a>,
1352
{
1353
    const TYPE: DataType = DataType::Array;
1354

NEW
1355
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
NEW
1356
        zval.array().and_then(|arr| arr.try_into().ok())
×
1357
    }
1358
}
1359

1360
///////////////////////////////////////////
1361
// Vec
1362
///////////////////////////////////////////
1363

1364
impl<'a, T> TryFrom<&'a ZendHashTable> for Vec<T>
1365
where
1366
    T: FromZval<'a>,
1367
{
1368
    type Error = Error;
1369

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

1373
        for (_, val) in value {
×
1374
            vec.push(T::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?);
×
1375
        }
1376

1377
        Ok(vec)
×
1378
    }
1379
}
1380

1381
impl<T> TryFrom<Vec<T>> for ZBox<ZendHashTable>
1382
where
1383
    T: IntoZval,
1384
{
1385
    type Error = Error;
1386

1387
    fn try_from(value: Vec<T>) -> Result<Self> {
×
1388
        let mut ht = ZendHashTable::with_capacity(
1389
            value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
×
1390
        );
1391

1392
        for val in value {
×
1393
            ht.push(val)?;
×
1394
        }
1395

1396
        Ok(ht)
×
1397
    }
1398
}
1399

1400
impl<T> IntoZval for Vec<T>
1401
where
1402
    T: IntoZval,
1403
{
1404
    const TYPE: DataType = DataType::Array;
1405
    const NULLABLE: bool = false;
1406

1407
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
1408
        let arr = self.try_into()?;
×
1409
        zv.set_hashtable(arr);
×
1410
        Ok(())
×
1411
    }
1412
}
1413

1414
impl<'a, T> FromZval<'a> for Vec<T>
1415
where
1416
    T: FromZval<'a>,
1417
{
1418
    const TYPE: DataType = DataType::Array;
1419

1420
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
1421
        zval.array().and_then(|arr| arr.try_into().ok())
×
1422
    }
1423
}
1424

1425
impl FromIterator<Zval> for ZBox<ZendHashTable> {
1426
    fn from_iter<T: IntoIterator<Item = Zval>>(iter: T) -> Self {
×
1427
        let mut ht = ZendHashTable::new();
×
1428
        for item in iter {
×
1429
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
1430
            // `val` to a zval fails.
1431
            let _ = ht.push(item);
×
1432
        }
1433
        ht
×
1434
    }
1435
}
1436

1437
impl FromIterator<(i64, Zval)> for ZBox<ZendHashTable> {
1438
    fn from_iter<T: IntoIterator<Item = (i64, Zval)>>(iter: T) -> Self {
×
1439
        let mut ht = ZendHashTable::new();
×
1440
        for (key, val) in iter {
×
1441
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
1442
            // `val` to a zval fails.
1443
            let _ = ht.insert_at_index(key, val);
×
1444
        }
1445
        ht
×
1446
    }
1447
}
1448

1449
impl<'a> FromIterator<(&'a str, Zval)> for ZBox<ZendHashTable> {
1450
    fn from_iter<T: IntoIterator<Item = (&'a str, Zval)>>(iter: T) -> Self {
×
1451
        let mut ht = ZendHashTable::new();
×
1452
        for (key, val) in iter {
×
1453
            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
1454
            // `val` to a zval fails.
1455
            let _ = ht.insert(key, val);
×
1456
        }
1457
        ht
×
1458
    }
1459
}
1460

1461
#[cfg(test)]
1462
#[cfg(feature = "embed")]
1463
#[allow(clippy::unwrap_used)]
1464
mod tests {
1465
    use super::*;
1466
    use crate::embed::Embed;
1467

1468
    #[test]
1469
    fn test_string_try_from_array_key() {
1470
        let key = ArrayKey::String("test".to_string());
1471
        let result: Result<String, _> = key.try_into();
1472
        assert!(result.is_ok());
1473
        assert_eq!(result.unwrap(), "test".to_string());
1474

1475
        let key = ArrayKey::Str("test");
1476
        let result: Result<String, _> = key.try_into();
1477
        assert!(result.is_ok());
1478
        assert_eq!(result.unwrap(), "test".to_string());
1479

1480
        let key = ArrayKey::Long(42);
1481
        let result: Result<String, _> = key.try_into();
1482
        assert!(result.is_err());
1483
        assert!(matches!(result.unwrap_err(), Error::InvalidProperty));
1484

1485
        let key = ArrayKey::String("42".to_string());
1486
        let result: Result<String, _> = key.try_into();
1487
        assert!(result.is_ok());
1488
        assert_eq!(result.unwrap(), "42".to_string());
1489

1490
        let key = ArrayKey::Str("123");
1491
        let result: Result<i64, _> = key.try_into();
1492
        assert!(result.is_ok());
1493
        assert_eq!(result.unwrap(), 123);
1494
    }
1495

1496
    #[test]
1497
    fn test_i64_try_from_array_key() {
1498
        let key = ArrayKey::Long(42);
1499
        let result: Result<i64, _> = key.try_into();
1500
        assert!(result.is_ok());
1501
        assert_eq!(result.unwrap(), 42);
1502

1503
        let key = ArrayKey::String("42".to_string());
1504
        let result: Result<i64, _> = key.try_into();
1505
        assert!(result.is_ok());
1506
        assert_eq!(result.unwrap(), 42);
1507

1508
        let key = ArrayKey::Str("123");
1509
        let result: Result<i64, _> = key.try_into();
1510
        assert!(result.is_ok());
1511
        assert_eq!(result.unwrap(), 123);
1512

1513
        let key = ArrayKey::String("not a number".to_string());
1514
        let result: Result<i64, _> = key.try_into();
1515
        assert!(result.is_err());
1516
        assert!(matches!(result.unwrap_err(), Error::InvalidProperty));
1517
    }
1518

1519
    #[test]
1520
    fn test_vec_string_v_try_from_hash_table() {
1521
        Embed::run(|| {
1522
            let mut ht = ZendHashTable::new();
1523
            ht.insert("key1", "value1").unwrap();
1524
            ht.insert("key2", "value2").unwrap();
1525

1526
            let vec: Vec<(String, String)> = ht.as_ref().try_into().unwrap();
1527
            assert_eq!(vec.len(), 2);
1528
            assert_eq!(vec[0].0, "key1");
1529
            assert_eq!(vec[0].1, "value1");
1530
            assert_eq!(vec[1].0, "key2");
1531
            assert_eq!(vec[1].1, "value2");
1532

1533
            let mut ht2 = ZendHashTable::new();
1534
            ht2.insert(1, "value1").unwrap();
1535
            ht2.insert(2, "value2").unwrap();
1536

1537
            let vec2: Result<Vec<(String, String)>> = ht2.as_ref().try_into();
1538
            assert!(vec2.is_err());
1539
            assert!(matches!(vec2.unwrap_err(), Error::InvalidProperty));
1540
        });
1541
    }
1542

1543
    #[test]
1544
    fn test_vec_i64_v_try_from_hash_table() {
1545
        Embed::run(|| {
1546
            let mut ht = ZendHashTable::new();
1547
            ht.insert(1, "value1").unwrap();
1548
            ht.insert("2", "value2").unwrap();
1549

1550
            let vec: Vec<(i64, String)> = ht.as_ref().try_into().unwrap();
1551
            assert_eq!(vec.len(), 2);
1552
            assert_eq!(vec[0].0, 1);
1553
            assert_eq!(vec[0].1, "value1");
1554
            assert_eq!(vec[1].0, 2);
1555
            assert_eq!(vec[1].1, "value2");
1556

1557
            let mut ht2 = ZendHashTable::new();
1558
            ht2.insert("key1", "value1").unwrap();
1559
            ht2.insert("key2", "value2").unwrap();
1560

1561
            let vec2: Result<Vec<(i64, String)>> = ht2.as_ref().try_into();
1562
            assert!(vec2.is_err());
1563
            assert!(matches!(vec2.unwrap_err(), Error::InvalidProperty));
1564
        });
1565
    }
1566

1567
    #[test]
1568
    fn test_vec_array_key_v_try_from_hash_table() {
1569
        Embed::run(|| {
1570
            let mut ht = ZendHashTable::new();
1571
            ht.insert("key1", "value1").unwrap();
1572
            ht.insert(2, "value2").unwrap();
1573
            ht.insert("3", "value3").unwrap();
1574

1575
            let vec: Vec<(ArrayKey, String)> = ht.as_ref().try_into().unwrap();
1576
            assert_eq!(vec.len(), 3);
1577
            assert_eq!(vec[0].0, ArrayKey::String("key1".to_string()));
1578
            assert_eq!(vec[0].1, "value1");
1579
            assert_eq!(vec[1].0, ArrayKey::Long(2));
1580
            assert_eq!(vec[1].1, "value2");
1581
            assert_eq!(vec[2].0, ArrayKey::Long(3));
1582
            assert_eq!(vec[2].1, "value3");
1583
        });
1584
    }
1585

1586
    #[test]
1587
    fn test_hash_table_try_from_vec() {
1588
        Embed::run(|| {
1589
            let vec = vec![("key1", "value1"), ("key2", "value2"), ("key3", "value3")];
1590

1591
            let ht: ZBox<ZendHashTable> = vec.try_into().unwrap();
1592
            assert_eq!(ht.len(), 3);
1593
            assert_eq!(ht.get("key1").unwrap().string().unwrap(), "value1");
1594
            assert_eq!(ht.get("key2").unwrap().string().unwrap(), "value2");
1595
            assert_eq!(ht.get("key3").unwrap().string().unwrap(), "value3");
1596

1597
            let vec_i64 = vec![(1, "value1"), (2, "value2"), (3, "value3")];
1598

1599
            let ht_i64: ZBox<ZendHashTable> = vec_i64.try_into().unwrap();
1600
            assert_eq!(ht_i64.len(), 3);
1601
            assert_eq!(ht_i64.get(1).unwrap().string().unwrap(), "value1");
1602
            assert_eq!(ht_i64.get(2).unwrap().string().unwrap(), "value2");
1603
            assert_eq!(ht_i64.get(3).unwrap().string().unwrap(), "value3");
1604
        });
1605
    }
1606

1607
    #[test]
1608
    fn test_vec_k_v_into_zval() {
1609
        Embed::run(|| {
1610
            let vec = vec![("key1", "value1"), ("key2", "value2"), ("key3", "value3")];
1611

1612
            let zval = vec.into_zval(false).unwrap();
1613
            assert!(zval.is_array());
1614
            let ht: &ZendHashTable = zval.array().unwrap();
1615
            assert_eq!(ht.len(), 3);
1616
            assert_eq!(ht.get("key1").unwrap().string().unwrap(), "value1");
1617
            assert_eq!(ht.get("key2").unwrap().string().unwrap(), "value2");
1618
            assert_eq!(ht.get("key3").unwrap().string().unwrap(), "value3");
1619

1620
            let vec_i64 = vec![(1, "value1"), (2, "value2"), (3, "value3")];
1621
            let zval_i64 = vec_i64.into_zval(false).unwrap();
1622
            assert!(zval_i64.is_array());
1623
            let ht_i64: &ZendHashTable = zval_i64.array().unwrap();
1624
            assert_eq!(ht_i64.len(), 3);
1625
            assert_eq!(ht_i64.get(1).unwrap().string().unwrap(), "value1");
1626
            assert_eq!(ht_i64.get(2).unwrap().string().unwrap(), "value2");
1627
            assert_eq!(ht_i64.get(3).unwrap().string().unwrap(), "value3");
1628
        });
1629
    }
1630

1631
    #[test]
1632
    fn test_vec_k_v_from_zval() {
1633
        Embed::run(|| {
1634
            let mut ht = ZendHashTable::new();
1635
            ht.insert("key1", "value1").unwrap();
1636
            ht.insert("key2", "value2").unwrap();
1637
            ht.insert("key3", "value3").unwrap();
1638
            let mut zval = Zval::new();
1639
            zval.set_hashtable(ht);
1640

1641
            let vec: Vec<(String, String)> = Vec::<(String, String)>::from_zval(&zval).unwrap();
1642
            assert_eq!(vec.len(), 3);
1643
            assert_eq!(vec[0].0, "key1");
1644
            assert_eq!(vec[0].1, "value1");
1645
            assert_eq!(vec[1].0, "key2");
1646
            assert_eq!(vec[1].1, "value2");
1647
            assert_eq!(vec[2].0, "key3");
1648
            assert_eq!(vec[2].1, "value3");
1649

1650
            let mut ht_i64 = ZendHashTable::new();
1651
            ht_i64.insert(1, "value1").unwrap();
1652
            ht_i64.insert("2", "value2").unwrap();
1653
            ht_i64.insert(3, "value3").unwrap();
1654
            let mut zval_i64 = Zval::new();
1655
            zval_i64.set_hashtable(ht_i64);
1656

1657
            let vec_i64: Vec<(i64, String)> = Vec::<(i64, String)>::from_zval(&zval_i64).unwrap();
1658
            assert_eq!(vec_i64.len(), 3);
1659
            assert_eq!(vec_i64[0].0, 1);
1660
            assert_eq!(vec_i64[0].1, "value1");
1661
            assert_eq!(vec_i64[1].0, 2);
1662
            assert_eq!(vec_i64[1].1, "value2");
1663
            assert_eq!(vec_i64[2].0, 3);
1664
            assert_eq!(vec_i64[2].1, "value3");
1665

1666
            let mut ht_mixed = ZendHashTable::new();
1667
            ht_mixed.insert("key1", "value1").unwrap();
1668
            ht_mixed.insert(2, "value2").unwrap();
1669
            ht_mixed.insert("3", "value3").unwrap();
1670
            let mut zval_mixed = Zval::new();
1671
            zval_mixed.set_hashtable(ht_mixed);
1672

1673
            let vec_mixed: Option<Vec<(String, String)>> =
1674
                Vec::<(String, String)>::from_zval(&zval_mixed);
1675
            assert!(vec_mixed.is_none());
1676
        });
1677
    }
1678

1679
    #[test]
1680
    fn test_vec_array_key_v_from_zval() {
1681
        Embed::run(|| {
1682
            let mut ht = ZendHashTable::new();
1683
            ht.insert("key1", "value1").unwrap();
1684
            ht.insert(2, "value2").unwrap();
1685
            ht.insert("3", "value3").unwrap();
1686
            let mut zval = Zval::new();
1687
            zval.set_hashtable(ht);
1688

1689
            let vec: Vec<(ArrayKey, String)> = Vec::<(ArrayKey, String)>::from_zval(&zval).unwrap();
1690
            assert_eq!(vec.len(), 3);
1691
            assert_eq!(vec[0].0, ArrayKey::String("key1".to_string()));
1692
            assert_eq!(vec[0].1, "value1");
1693
            assert_eq!(vec[1].0, ArrayKey::Long(2));
1694
            assert_eq!(vec[1].1, "value2");
1695
            assert_eq!(vec[2].0, ArrayKey::Long(3));
1696
            assert_eq!(vec[2].1, "value3");
1697
        });
1698
    }
1699
}
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