ext_php_rs/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
4use std::{
5    collections::HashMap,
6    convert::{TryFrom, TryInto},
7    ffi::CString,
8    fmt::{Debug, Display},
9    iter::FromIterator,
10};
11
12use crate::{
13    boxed::{ZBox, ZBoxable},
14    convert::{FromZval, IntoZval},
15    error::{Error, Result},
16    ffi::{
17        _zend_new_array, zend_array_count, zend_array_destroy, zend_array_dup, zend_hash_clean,
18        zend_hash_get_current_data_ex, zend_hash_get_current_key_type_ex,
19        zend_hash_get_current_key_zval_ex, zend_hash_index_del, zend_hash_index_find,
20        zend_hash_index_update, zend_hash_move_backwards_ex, zend_hash_move_forward_ex,
21        zend_hash_next_index_insert, zend_hash_str_del, zend_hash_str_find, zend_hash_str_update,
22        HashPosition, HT_MIN_SIZE,
23    },
24    flags::DataType,
25    types::Zval,
26};
27
28/// A PHP hashtable.
29///
30/// In PHP, arrays are represented as hashtables. This allows you to push values
31/// onto the end of the array like a vector, while also allowing you to insert
32/// at arbitrary string key indexes.
33///
34/// A PHP hashtable stores values as [`Zval`]s. This allows you to insert
35/// different types into the same hashtable. Types must implement [`IntoZval`]
36/// to be able to be inserted into the hashtable.
37///
38/// # Examples
39///
40/// ```no_run
41/// use ext_php_rs::types::ZendHashTable;
42///
43/// let mut ht = ZendHashTable::new();
44/// ht.push(1);
45/// ht.push("Hello, world!");
46/// ht.insert("Like", "Hashtable");
47///
48/// assert_eq!(ht.len(), 3);
49/// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(1));
50/// ```
51pub type ZendHashTable = crate::ffi::HashTable;
52
53// Clippy complains about there being no `is_empty` function when implementing
54// on the alias `ZendStr` :( <https://github.com/rust-lang/rust-clippy/issues/7702>
55#[allow(clippy::len_without_is_empty)]
56impl ZendHashTable {
57    /// Creates a new, empty, PHP hashtable, returned inside a [`ZBox`].
58    ///
59    /// # Example
60    ///
61    /// ```no_run
62    /// use ext_php_rs::types::ZendHashTable;
63    ///
64    /// let ht = ZendHashTable::new();
65    /// ```
66    ///
67    /// # Panics
68    ///
69    /// Panics if memory for the hashtable could not be allocated.
70    pub fn new() -> ZBox<Self> {
71        Self::with_capacity(HT_MIN_SIZE)
72    }
73
74    /// Creates a new, empty, PHP hashtable with an initial size, returned
75    /// inside a [`ZBox`].
76    ///
77    /// # Parameters
78    ///
79    /// * `size` - The size to initialize the array with.
80    ///
81    /// # Example
82    ///
83    /// ```no_run
84    /// use ext_php_rs::types::ZendHashTable;
85    ///
86    /// let ht = ZendHashTable::with_capacity(10);
87    /// ```
88    ///
89    /// # Panics
90    ///
91    /// Panics if memory for the hashtable could not be allocated.
92    pub fn with_capacity(size: u32) -> ZBox<Self> {
93        unsafe {
94            // SAFETY: PHP allocator handles the creation of the array.
95            let ptr = _zend_new_array(size);
96
97            // SAFETY: `as_mut()` checks if the pointer is null, and panics if it is not.
98            ZBox::from_raw(
99                ptr.as_mut()
100                    .expect("Failed to allocate memory for hashtable"),
101            )
102        }
103    }
104
105    /// Returns the current number of elements in the array.
106    ///
107    /// # Example
108    ///
109    /// ```no_run
110    /// use ext_php_rs::types::ZendHashTable;
111    ///
112    /// let mut ht = ZendHashTable::new();
113    ///
114    /// ht.push(1);
115    /// ht.push("Hello, world");
116    ///
117    /// assert_eq!(ht.len(), 2);
118    /// ```
119    pub fn len(&self) -> usize {
120        unsafe { zend_array_count(self as *const ZendHashTable as *mut ZendHashTable) as usize }
121    }
122
123    /// Returns whether the hash table is empty.
124    ///
125    /// # Example
126    ///
127    /// ```no_run
128    /// use ext_php_rs::types::ZendHashTable;
129    ///
130    /// let mut ht = ZendHashTable::new();
131    ///
132    /// assert_eq!(ht.is_empty(), true);
133    ///
134    /// ht.push(1);
135    /// ht.push("Hello, world");
136    ///
137    /// assert_eq!(ht.is_empty(), false);
138    /// ```
139    pub fn is_empty(&self) -> bool {
140        self.len() == 0
141    }
142
143    /// Clears the hash table, removing all values.
144    ///
145    /// # Example
146    ///
147    /// ```no_run
148    /// use ext_php_rs::types::ZendHashTable;
149    ///
150    /// let mut ht = ZendHashTable::new();
151    ///
152    /// ht.insert("test", "hello world");
153    /// assert_eq!(ht.is_empty(), false);
154    ///
155    /// ht.clear();
156    /// assert_eq!(ht.is_empty(), true);
157    /// ```
158    pub fn clear(&mut self) {
159        unsafe { zend_hash_clean(self) }
160    }
161
162    /// Attempts to retrieve a value from the hash table with a string key.
163    ///
164    /// # Parameters
165    ///
166    /// * `key` - The key to search for in the hash table.
167    ///
168    /// # Returns
169    ///
170    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
171    ///   table.
172    /// * `None` - No value at the given position was found.
173    ///
174    /// # Example
175    ///
176    /// ```no_run
177    /// use ext_php_rs::types::ZendHashTable;
178    ///
179    /// let mut ht = ZendHashTable::new();
180    ///
181    /// ht.insert("test", "hello world");
182    /// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world"));
183    /// ```
184    pub fn get(&self, key: &'_ str) -> Option<&Zval> {
185        let str = CString::new(key).ok()?;
186        unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_ref() }
187    }
188
189    /// Attempts to retrieve a value from the hash table with a string key.
190    ///
191    /// # Parameters
192    ///
193    /// * `key` - The key to search for in the hash table.
194    ///
195    /// # Returns
196    ///
197    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
198    ///   table.
199    /// * `None` - No value at the given position was found.
200    ///
201    /// # Example
202    ///
203    /// ```no_run
204    /// use ext_php_rs::types::ZendHashTable;
205    ///
206    /// let mut ht = ZendHashTable::new();
207    ///
208    /// ht.insert("test", "hello world");
209    /// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world"));
210    /// ```
211    pub fn get_mut(&self, key: &'_ str) -> Option<&mut Zval> {
212        let str = CString::new(key).ok()?;
213        unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_mut() }
214    }
215
216    /// Attempts to retrieve a value from the hash table with an index.
217    ///
218    /// # Parameters
219    ///
220    /// * `key` - The key to search for in the hash table.
221    ///
222    /// # Returns
223    ///
224    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
225    ///   table.
226    /// * `None` - No value at the given position was found.
227    ///
228    /// # Example
229    ///
230    /// ```no_run
231    /// use ext_php_rs::types::ZendHashTable;
232    ///
233    /// let mut ht = ZendHashTable::new();
234    ///
235    /// ht.push(100);
236    /// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
237    /// ```
238    pub fn get_index(&self, key: u64) -> Option<&Zval> {
239        unsafe { zend_hash_index_find(self, key).as_ref() }
240    }
241
242    /// Attempts to retrieve a value from the hash table with an index.
243    ///
244    /// # Parameters
245    ///
246    /// * `key` - The key to search for in the hash table.
247    ///
248    /// # Returns
249    ///
250    /// * `Some(&Zval)` - A reference to the zval at the position in the hash
251    ///   table.
252    /// * `None` - No value at the given position was found.
253    ///
254    /// # Example
255    ///
256    /// ```no_run
257    /// use ext_php_rs::types::ZendHashTable;
258    ///
259    /// let mut ht = ZendHashTable::new();
260    ///
261    /// ht.push(100);
262    /// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
263    /// ```
264    pub fn get_index_mut(&self, key: u64) -> Option<&mut Zval> {
265        unsafe { zend_hash_index_find(self, key).as_mut() }
266    }
267
268    /// Attempts to remove a value from the hash table with a string key.
269    ///
270    /// # Parameters
271    ///
272    /// * `key` - The key to remove from the hash table.
273    ///
274    /// # Returns
275    ///
276    /// * `Some(())` - Key was successfully removed.
277    /// * `None` - No key was removed, did not exist.
278    ///
279    /// # Example
280    ///
281    /// ```no_run
282    /// use ext_php_rs::types::ZendHashTable;
283    ///
284    /// let mut ht = ZendHashTable::new();
285    ///
286    /// ht.insert("test", "hello world");
287    /// assert_eq!(ht.len(), 1);
288    ///
289    /// ht.remove("test");
290    /// assert_eq!(ht.len(), 0);
291    /// ```
292    pub fn remove(&mut self, key: &str) -> Option<()> {
293        let result =
294            unsafe { zend_hash_str_del(self, CString::new(key).ok()?.as_ptr(), key.len() as _) };
295
296        if result < 0 {
297            None
298        } else {
299            Some(())
300        }
301    }
302
303    /// Attempts to remove a value from the hash table with a string key.
304    ///
305    /// # Parameters
306    ///
307    /// * `key` - The key to remove from the hash table.
308    ///
309    /// # Returns
310    ///
311    /// * `Ok(())` - Key was successfully removed.
312    /// * `None` - No key was removed, did not exist.
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("hello");
322    /// assert_eq!(ht.len(), 1);
323    ///
324    /// ht.remove_index(0);
325    /// assert_eq!(ht.len(), 0);
326    /// ```
327    pub fn remove_index(&mut self, key: u64) -> Option<()> {
328        let result = unsafe { zend_hash_index_del(self, key) };
329
330        if result < 0 {
331            None
332        } else {
333            Some(())
334        }
335    }
336
337    /// Attempts to insert an item into the hash table, or update if the key
338    /// already exists. Returns nothing in a result if successful.
339    ///
340    /// # Parameters
341    ///
342    /// * `key` - The key to insert the value at in the hash table.
343    /// * `value` - The value to insert into the hash table.
344    ///
345    /// # Returns
346    ///
347    /// Returns nothing in a result on success. Returns an error if the key
348    /// could not be converted into a [`CString`], or converting the value into
349    /// a [`Zval`] failed.
350    ///
351    /// # Example
352    ///
353    /// ```no_run
354    /// use ext_php_rs::types::ZendHashTable;
355    ///
356    /// let mut ht = ZendHashTable::new();
357    ///
358    /// ht.insert("a", "A");
359    /// ht.insert("b", "B");
360    /// ht.insert("c", "C");
361    /// assert_eq!(ht.len(), 3);
362    /// ```
363    pub fn insert<V>(&mut self, key: &str, val: V) -> Result<()>
364    where
365        V: IntoZval,
366    {
367        let mut val = val.into_zval(false)?;
368        unsafe { zend_hash_str_update(self, CString::new(key)?.as_ptr(), key.len(), &mut val) };
369        val.release();
370        Ok(())
371    }
372
373    /// Inserts an item into the hash table at a specified index, or updates if
374    /// the key already exists. Returns nothing in a result if successful.
375    ///
376    /// # Parameters
377    ///
378    /// * `key` - The index at which the value should be inserted.
379    /// * `val` - The value to insert into the hash table.
380    ///
381    /// # Returns
382    ///
383    /// Returns nothing in a result on success. Returns an error if converting
384    /// the value into a [`Zval`] failed.
385    ///
386    /// # Example
387    ///
388    /// ```no_run
389    /// use ext_php_rs::types::ZendHashTable;
390    ///
391    /// let mut ht = ZendHashTable::new();
392    ///
393    /// ht.insert_at_index(0, "A");
394    /// ht.insert_at_index(5, "B");
395    /// ht.insert_at_index(0, "C"); // notice overriding index 0
396    /// assert_eq!(ht.len(), 2);
397    /// ```
398    pub fn insert_at_index<V>(&mut self, key: u64, val: V) -> Result<()>
399    where
400        V: IntoZval,
401    {
402        let mut val = val.into_zval(false)?;
403        unsafe { zend_hash_index_update(self, key, &mut val) };
404        val.release();
405        Ok(())
406    }
407
408    /// Pushes an item onto the end of the hash table. Returns a result
409    /// containing nothing if the element was successfully inserted.
410    ///
411    /// # Parameters
412    ///
413    /// * `val` - The value to insert into the hash table.
414    ///
415    /// # Returns
416    ///
417    /// Returns nothing in a result on success. Returns an error if converting
418    /// the value into a [`Zval`] failed.
419    ///
420    /// # Example
421    ///
422    /// ```no_run
423    /// use ext_php_rs::types::ZendHashTable;
424    ///
425    /// let mut ht = ZendHashTable::new();
426    ///
427    /// ht.push("a");
428    /// ht.push("b");
429    /// ht.push("c");
430    /// assert_eq!(ht.len(), 3);
431    /// ```
432    pub fn push<V>(&mut self, val: V) -> Result<()>
433    where
434        V: IntoZval,
435    {
436        let mut val = val.into_zval(false)?;
437        unsafe { zend_hash_next_index_insert(self, &mut val) };
438        val.release();
439
440        Ok(())
441    }
442
443    /// Checks if the hashtable only contains numerical keys.
444    ///
445    /// # Returns
446    ///
447    /// True if all keys on the hashtable are numerical.
448    ///
449    /// # Example
450    ///
451    /// ```no_run
452    /// use ext_php_rs::types::ZendHashTable;
453    ///
454    /// let mut ht = ZendHashTable::new();
455    ///
456    /// ht.push(0);
457    /// ht.push(3);
458    /// ht.push(9);
459    /// assert!(ht.has_numerical_keys());
460    ///
461    /// ht.insert("obviously not numerical", 10);
462    /// assert!(!ht.has_numerical_keys());
463    /// ```
464    pub fn has_numerical_keys(&self) -> bool {
465        !self.into_iter().any(|(k, _)| !k.is_long())
466    }
467
468    /// Checks if the hashtable has numerical, sequential keys.
469    ///
470    /// # Returns
471    ///
472    /// True if all keys on the hashtable are numerical and are in sequential
473    /// order (i.e. starting at 0 and not skipping any keys).
474    ///
475    /// # Example
476    ///
477    /// ```no_run
478    /// use ext_php_rs::types::ZendHashTable;
479    ///
480    /// let mut ht = ZendHashTable::new();
481    ///
482    /// ht.push(0);
483    /// ht.push(3);
484    /// ht.push(9);
485    /// assert!(ht.has_sequential_keys());
486    ///
487    /// ht.insert_at_index(90, 10);
488    /// assert!(!ht.has_sequential_keys());
489    /// ```
490    pub fn has_sequential_keys(&self) -> bool {
491        !self
492            .into_iter()
493            .enumerate()
494            .any(|(i, (k, _))| ArrayKey::Long(i as i64) != k)
495    }
496
497    /// Returns an iterator over the values contained inside the hashtable, as
498    /// if it was a set or list.
499    ///
500    /// # Example
501    ///
502    /// ```no_run
503    /// use ext_php_rs::types::ZendHashTable;
504    ///
505    /// let mut ht = ZendHashTable::new();
506    ///
507    /// for val in ht.values() {
508    ///     dbg!(val);
509    /// }
510    #[inline]
511    pub fn values(&self) -> Values {
512        Values::new(self)
513    }
514
515    /// Returns an iterator over the key(s) and value contained inside the
516    /// hashtable.
517    ///
518    /// # Example
519    ///
520    /// ```no_run
521    /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
522    ///
523    /// let mut ht = ZendHashTable::new();
524    ///
525    /// for (key, val) in ht.iter() {
526    ///     match &key {
527    ///         ArrayKey::Long(index) => {
528    ///         }
529    ///         ArrayKey::String(key) => {
530    ///         }
531    ///     }
532    ///     dbg!(key, val);
533    /// }
534    #[inline]
535    pub fn iter(&self) -> Iter {
536        self.into_iter()
537    }
538}
539
540unsafe impl ZBoxable for ZendHashTable {
541    fn free(&mut self) {
542        // SAFETY: ZBox has immutable access to `self`.
543        unsafe { zend_array_destroy(self) }
544    }
545}
546
547impl Debug for ZendHashTable {
548    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549        f.debug_map()
550            .entries(self.into_iter().map(|(k, v)| (k.to_string(), v)))
551            .finish()
552    }
553}
554
555impl ToOwned for ZendHashTable {
556    type Owned = ZBox<ZendHashTable>;
557
558    fn to_owned(&self) -> Self::Owned {
559        unsafe {
560            // SAFETY: FFI call does not modify `self`, returns a new hashtable.
561            let ptr = zend_array_dup(self as *const ZendHashTable as *mut ZendHashTable);
562
563            // SAFETY: `as_mut()` checks if the pointer is null, and panics if it is not.
564            ZBox::from_raw(
565                ptr.as_mut()
566                    .expect("Failed to allocate memory for hashtable"),
567            )
568        }
569    }
570}
571
572/// Immutable iterator upon a reference to a hashtable.
573pub struct Iter<'a> {
574    ht: &'a ZendHashTable,
575    current_num: i64,
576    end_num: i64,
577    pos: HashPosition,
578    end_pos: HashPosition,
579}
580
581#[derive(Debug, PartialEq)]
582pub enum ArrayKey {
583    Long(i64),
584    String(String),
585}
586
587/// Represent the key of a PHP array, which can be either a long or a string.
588impl ArrayKey {
589    /// Check if the key is an integer.
590    ///
591    /// # Returns
592    ///
593    /// Returns true if the key is an integer, false otherwise.
594    pub fn is_long(&self) -> bool {
595        match self {
596            ArrayKey::Long(_) => true,
597            ArrayKey::String(_) => false,
598        }
599    }
600}
601
602impl Display for ArrayKey {
603    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
604        match self {
605            ArrayKey::Long(key) => write!(f, "{}", key),
606            ArrayKey::String(key) => write!(f, "{}", key),
607        }
608    }
609}
610
611impl<'a> FromZval<'a> for ArrayKey {
612    const TYPE: DataType = DataType::String;
613
614    fn from_zval(zval: &'a Zval) -> Option<Self> {
615        if let Some(key) = zval.long() {
616            return Some(ArrayKey::Long(key));
617        }
618        if let Some(key) = zval.string() {
619            return Some(ArrayKey::String(key));
620        }
621        None
622    }
623}
624
625impl<'a> Iter<'a> {
626    /// Creates a new iterator over a hashtable.
627    ///
628    /// # Parameters
629    ///
630    /// * `ht` - The hashtable to iterate.
631    pub fn new(ht: &'a ZendHashTable) -> Self {
632        let end_num: i64 = ht
633            .len()
634            .try_into()
635            .expect("Integer overflow in hashtable length");
636        let end_pos = if ht.nNumOfElements > 0 {
637            ht.nNumOfElements - 1
638        } else {
639            0
640        };
641
642        Self {
643            ht,
644            current_num: 0,
645            end_num,
646            pos: 0,
647            end_pos,
648        }
649    }
650}
651
652impl<'a> IntoIterator for &'a ZendHashTable {
653    type Item = (ArrayKey, &'a Zval);
654    type IntoIter = Iter<'a>;
655
656    /// Returns an iterator over the key(s) and value contained inside the
657    /// hashtable.
658    ///
659    /// # Example
660    ///
661    /// ```no_run
662    /// use ext_php_rs::types::ZendHashTable;
663    ///
664    /// let mut ht = ZendHashTable::new();
665    ///
666    /// for (key, val) in ht.iter() {
667    /// //   ^ Index if inserted at an index.
668    /// //        ^ Optional string key, if inserted like a hashtable.
669    /// //             ^ Inserted value.
670    ///
671    ///     dbg!(key, val);
672    /// }
673    #[inline]
674    fn into_iter(self) -> Self::IntoIter {
675        Iter::new(self)
676    }
677}
678
679impl<'a> Iterator for Iter<'a> {
680    type Item = (ArrayKey, &'a Zval);
681
682    fn next(&mut self) -> Option<Self::Item> {
683        self.next_zval()
684            .map(|(k, v)| (ArrayKey::from_zval(&k).expect("Invalid array key!"), v))
685    }
686
687    fn count(self) -> usize
688    where
689        Self: Sized,
690    {
691        self.ht.len()
692    }
693}
694
695impl ExactSizeIterator for Iter<'_> {
696    fn len(&self) -> usize {
697        self.ht.len()
698    }
699}
700
701impl DoubleEndedIterator for Iter<'_> {
702    fn next_back(&mut self) -> Option<Self::Item> {
703        if self.end_num <= self.current_num {
704            return None;
705        }
706
707        let key_type = unsafe {
708            zend_hash_get_current_key_type_ex(
709                self.ht as *const ZendHashTable as *mut ZendHashTable,
710                &mut self.pos as *mut HashPosition,
711            )
712        };
713
714        if key_type == -1 {
715            return None;
716        }
717
718        let key = Zval::new();
719
720        unsafe {
721            zend_hash_get_current_key_zval_ex(
722                self.ht as *const ZendHashTable as *mut ZendHashTable,
723                &key as *const Zval as *mut Zval,
724                &mut self.end_pos as *mut HashPosition,
725            );
726        }
727        let value = unsafe {
728            &*zend_hash_get_current_data_ex(
729                self.ht as *const ZendHashTable as *mut ZendHashTable,
730                &mut self.end_pos as *mut HashPosition,
731            )
732        };
733
734        let key = match ArrayKey::from_zval(&key) {
735            Some(key) => key,
736            None => ArrayKey::Long(self.end_num),
737        };
738
739        unsafe {
740            zend_hash_move_backwards_ex(
741                self.ht as *const ZendHashTable as *mut ZendHashTable,
742                &mut self.end_pos as *mut HashPosition,
743            )
744        };
745        self.end_num -= 1;
746
747        Some((key, value))
748    }
749}
750
751impl<'a> Iter<'a> {
752    pub fn next_zval(&mut self) -> Option<(Zval, &'a Zval)> {
753        if self.current_num >= self.end_num {
754            return None;
755        }
756
757        let key_type = unsafe {
758            zend_hash_get_current_key_type_ex(
759                self.ht as *const ZendHashTable as *mut ZendHashTable,
760                &mut self.pos as *mut HashPosition,
761            )
762        };
763
764        // Key type `-1` is ???
765        // Key type `1` is string
766        // Key type `2` is long
767        // Key type `3` is null meaning the end of the array
768        if key_type == -1 || key_type == 3 {
769            return None;
770        }
771
772        let mut key = Zval::new();
773
774        unsafe {
775            zend_hash_get_current_key_zval_ex(
776                self.ht as *const ZendHashTable as *mut ZendHashTable,
777                &key as *const Zval as *mut Zval,
778                &mut self.pos as *mut HashPosition,
779            );
780        }
781        let value = unsafe {
782            let val_ptr = zend_hash_get_current_data_ex(
783                self.ht as *const ZendHashTable as *mut ZendHashTable,
784                &mut self.pos as *mut HashPosition,
785            );
786
787            if val_ptr.is_null() {
788                return None;
789            }
790
791            &*val_ptr
792        };
793
794        if !key.is_long() && !key.is_string() {
795            key.set_long(self.current_num)
796        }
797
798        unsafe {
799            zend_hash_move_forward_ex(
800                self.ht as *const ZendHashTable as *mut ZendHashTable,
801                &mut self.pos as *mut HashPosition,
802            )
803        };
804        self.current_num += 1;
805
806        Some((key, value))
807    }
808}
809
810/// Immutable iterator which iterates over the values of the hashtable, as it
811/// was a set or list.
812pub struct Values<'a>(Iter<'a>);
813
814impl<'a> Values<'a> {
815    /// Creates a new iterator over a hashtables values.
816    ///
817    /// # Parameters
818    ///
819    /// * `ht` - The hashtable to iterate.
820    pub fn new(ht: &'a ZendHashTable) -> Self {
821        Self(Iter::new(ht))
822    }
823}
824
825impl<'a> Iterator for Values<'a> {
826    type Item = &'a Zval;
827
828    fn next(&mut self) -> Option<Self::Item> {
829        self.0.next().map(|(_, zval)| zval)
830    }
831
832    fn count(self) -> usize
833    where
834        Self: Sized,
835    {
836        self.0.count()
837    }
838}
839
840impl ExactSizeIterator for Values<'_> {
841    fn len(&self) -> usize {
842        self.0.len()
843    }
844}
845
846impl DoubleEndedIterator for Values<'_> {
847    fn next_back(&mut self) -> Option<Self::Item> {
848        self.0.next_back().map(|(_, zval)| zval)
849    }
850}
851
852impl Default for ZBox<ZendHashTable> {
853    fn default() -> Self {
854        ZendHashTable::new()
855    }
856}
857
858impl Clone for ZBox<ZendHashTable> {
859    fn clone(&self) -> Self {
860        (**self).to_owned()
861    }
862}
863
864impl IntoZval for ZBox<ZendHashTable> {
865    const TYPE: DataType = DataType::Array;
866
867    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
868        zv.set_hashtable(self);
869        Ok(())
870    }
871}
872
873impl<'a> FromZval<'a> for &'a ZendHashTable {
874    const TYPE: DataType = DataType::Array;
875
876    fn from_zval(zval: &'a Zval) -> Option<Self> {
877        zval.array()
878    }
879}
880
881///////////////////////////////////////////
882// HashMap
883///////////////////////////////////////////
884
885impl<'a, V> TryFrom<&'a ZendHashTable> for HashMap<String, V>
886where
887    V: FromZval<'a>,
888{
889    type Error = Error;
890
891    fn try_from(value: &'a ZendHashTable) -> Result<Self> {
892        let mut hm = HashMap::with_capacity(value.len());
893
894        for (key, val) in value {
895            hm.insert(
896                key.to_string(),
897                V::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?,
898            );
899        }
900
901        Ok(hm)
902    }
903}
904
905impl<K, V> TryFrom<HashMap<K, V>> for ZBox<ZendHashTable>
906where
907    K: AsRef<str>,
908    V: IntoZval,
909{
910    type Error = Error;
911
912    fn try_from(value: HashMap<K, V>) -> Result<Self> {
913        let mut ht = ZendHashTable::with_capacity(
914            value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
915        );
916
917        for (k, v) in value.into_iter() {
918            ht.insert(k.as_ref(), v)?;
919        }
920
921        Ok(ht)
922    }
923}
924
925impl<K, V> IntoZval for HashMap<K, V>
926where
927    K: AsRef<str>,
928    V: IntoZval,
929{
930    const TYPE: DataType = DataType::Array;
931
932    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
933        let arr = self.try_into()?;
934        zv.set_hashtable(arr);
935        Ok(())
936    }
937}
938
939impl<'a, T> FromZval<'a> for HashMap<String, T>
940where
941    T: FromZval<'a>,
942{
943    const TYPE: DataType = DataType::Array;
944
945    fn from_zval(zval: &'a Zval) -> Option<Self> {
946        zval.array().and_then(|arr| arr.try_into().ok())
947    }
948}
949
950///////////////////////////////////////////
951// Vec
952///////////////////////////////////////////
953
954impl<'a, T> TryFrom<&'a ZendHashTable> for Vec<T>
955where
956    T: FromZval<'a>,
957{
958    type Error = Error;
959
960    fn try_from(value: &'a ZendHashTable) -> Result<Self> {
961        let mut vec = Vec::with_capacity(value.len());
962
963        for (_, val) in value {
964            vec.push(T::from_zval(val).ok_or_else(|| Error::ZvalConversion(val.get_type()))?);
965        }
966
967        Ok(vec)
968    }
969}
970
971impl<T> TryFrom<Vec<T>> for ZBox<ZendHashTable>
972where
973    T: IntoZval,
974{
975    type Error = Error;
976
977    fn try_from(value: Vec<T>) -> Result<Self> {
978        let mut ht = ZendHashTable::with_capacity(
979            value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
980        );
981
982        for val in value.into_iter() {
983            ht.push(val)?;
984        }
985
986        Ok(ht)
987    }
988}
989
990impl<T> IntoZval for Vec<T>
991where
992    T: IntoZval,
993{
994    const TYPE: DataType = DataType::Array;
995
996    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
997        let arr = self.try_into()?;
998        zv.set_hashtable(arr);
999        Ok(())
1000    }
1001}
1002
1003impl<'a, T> FromZval<'a> for Vec<T>
1004where
1005    T: FromZval<'a>,
1006{
1007    const TYPE: DataType = DataType::Array;
1008
1009    fn from_zval(zval: &'a Zval) -> Option<Self> {
1010        zval.array().and_then(|arr| arr.try_into().ok())
1011    }
1012}
1013
1014impl FromIterator<Zval> for ZBox<ZendHashTable> {
1015    fn from_iter<T: IntoIterator<Item = Zval>>(iter: T) -> Self {
1016        let mut ht = ZendHashTable::new();
1017        for item in iter.into_iter() {
1018            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
1019            // `val` to a zval fails.
1020            let _ = ht.push(item);
1021        }
1022        ht
1023    }
1024}
1025
1026impl FromIterator<(u64, Zval)> for ZBox<ZendHashTable> {
1027    fn from_iter<T: IntoIterator<Item = (u64, Zval)>>(iter: T) -> Self {
1028        let mut ht = ZendHashTable::new();
1029        for (key, val) in iter.into_iter() {
1030            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
1031            // `val` to a zval fails.
1032            let _ = ht.insert_at_index(key, val);
1033        }
1034        ht
1035    }
1036}
1037
1038impl<'a> FromIterator<(&'a str, Zval)> for ZBox<ZendHashTable> {
1039    fn from_iter<T: IntoIterator<Item = (&'a str, Zval)>>(iter: T) -> Self {
1040        let mut ht = ZendHashTable::new();
1041        for (key, val) in iter.into_iter() {
1042            // Inserting a zval cannot fail, as `push` only returns `Err` if converting
1043            // `val` to a zval fails.
1044            let _ = ht.insert(key, val);
1045        }
1046        ht
1047    }
1048}