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}