Skip to content

Commit eed2ac1

Browse files
committed
feat(array): allow negative indices (issues #453 and #454)
1 parent 688201a commit eed2ac1

File tree

3 files changed

+131
-31
lines changed

3 files changed

+131
-31
lines changed

src/types/array.rs

Lines changed: 125 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::{
99
iter::FromIterator,
1010
ptr,
1111
};
12-
12+
use std::str::FromStr;
1313
use crate::{
1414
boxed::{ZBox, ZBoxable},
1515
convert::{FromZval, IntoZval},
@@ -25,6 +25,7 @@ use crate::{
2525
flags::DataType,
2626
types::Zval,
2727
};
28+
use crate::ffi::zend_ulong;
2829

2930
/// A PHP hashtable.
3031
///
@@ -188,9 +189,26 @@ impl ZendHashTable {
188189
/// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world"));
189190
/// ```
190191
#[must_use]
191-
pub fn get(&self, key: &'_ str) -> Option<&Zval> {
192-
let str = CString::new(key).ok()?;
193-
unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_ref() }
192+
pub fn get<'a, K>(&self, key: K) -> Option<&Zval> where K: Into<ArrayKey<'a>>{
193+
match key.into() {
194+
ArrayKey::Long(index) => {
195+
unsafe { zend_hash_index_find(self, index as zend_ulong).as_ref() }
196+
}
197+
ArrayKey::String(key) => {
198+
if let Ok(index) = i64::from_str(key.as_str()) {
199+
unsafe { zend_hash_index_find(self, index as zend_ulong).as_ref() }
200+
} else {
201+
unsafe { zend_hash_str_find(self, CString::new(key.as_str()).ok()?.as_ptr(), key.len() as _).as_ref() }
202+
}
203+
}
204+
ArrayKey::Str(key) => {
205+
if let Ok(index) = i64::from_str(key) {
206+
unsafe { zend_hash_index_find(self, index as zend_ulong).as_ref() }
207+
} else {
208+
unsafe { zend_hash_str_find(self, CString::new(key).ok()?.as_ptr(), key.len() as _).as_ref() }
209+
}
210+
}
211+
}
194212
}
195213

196214
/// Attempts to retrieve a value from the hash table with a string key.
@@ -216,9 +234,26 @@ impl ZendHashTable {
216234
/// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world"));
217235
/// ```
218236
#[must_use]
219-
pub fn get_mut(&self, key: &'_ str) -> Option<&mut Zval> {
220-
let str = CString::new(key).ok()?;
221-
unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_mut() }
237+
pub fn get_mut<'a, K>(&self, key: K) -> Option<&mut Zval> where K: Into<ArrayKey<'a>>{
238+
match key.into() {
239+
ArrayKey::Long(index) => {
240+
unsafe { zend_hash_index_find(self, index as zend_ulong).as_mut() }
241+
}
242+
ArrayKey::String(key) => {
243+
if let Ok(index) = i64::from_str(key.as_str()) {
244+
unsafe { zend_hash_index_find(self, index as zend_ulong).as_mut() }
245+
} else {
246+
unsafe { zend_hash_str_find(self, CString::new(key.as_str()).ok()?.as_ptr(), key.len() as _).as_mut() }
247+
}
248+
}
249+
ArrayKey::Str(key) => {
250+
if let Ok(index) = i64::from_str(key) {
251+
unsafe { zend_hash_index_find(self, index as zend_ulong).as_mut() }
252+
} else {
253+
unsafe { zend_hash_str_find(self, CString::new(key).ok()?.as_ptr(), key.len() as _).as_mut() }
254+
}
255+
}
256+
}
222257
}
223258

224259
/// Attempts to retrieve a value from the hash table with an index.
@@ -244,8 +279,8 @@ impl ZendHashTable {
244279
/// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
245280
/// ```
246281
#[must_use]
247-
pub fn get_index(&self, key: u64) -> Option<&Zval> {
248-
unsafe { zend_hash_index_find(self, key).as_ref() }
282+
pub fn get_index(&self, key: i64) -> Option<&Zval> {
283+
unsafe { zend_hash_index_find(self, key as zend_ulong).as_ref() }
249284
}
250285

251286
/// Attempts to retrieve a value from the hash table with an index.
@@ -271,8 +306,8 @@ impl ZendHashTable {
271306
/// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100));
272307
/// ```
273308
#[must_use]
274-
pub fn get_index_mut(&self, key: u64) -> Option<&mut Zval> {
275-
unsafe { zend_hash_index_find(self, key).as_mut() }
309+
pub fn get_index_mut(&self, key: i64) -> Option<&mut Zval> {
310+
unsafe { zend_hash_index_find(self, key as zend_ulong).as_mut() }
276311
}
277312

278313
/// Attempts to remove a value from the hash table with a string key.
@@ -299,9 +334,26 @@ impl ZendHashTable {
299334
/// ht.remove("test");
300335
/// assert_eq!(ht.len(), 0);
301336
/// ```
302-
pub fn remove(&mut self, key: &str) -> Option<()> {
303-
let result =
304-
unsafe { zend_hash_str_del(self, CString::new(key).ok()?.as_ptr(), key.len() as _) };
337+
pub fn remove<'a, K>(&mut self, key: K) -> Option<()> where K: Into<ArrayKey<'a>> {
338+
let result = match key.into() {
339+
ArrayKey::Long(index) => {
340+
unsafe { zend_hash_index_del(self, index as zend_ulong) }
341+
}
342+
ArrayKey::String(key) => {
343+
if let Ok(index) = i64::from_str(key.as_str()) {
344+
unsafe { zend_hash_index_del(self, index as zend_ulong) }
345+
} else {
346+
unsafe { zend_hash_str_del(self, CString::new(key.as_str()).ok()?.as_ptr(), key.len() as _) }
347+
}
348+
}
349+
ArrayKey::Str(key) => {
350+
if let Ok(index) = i64::from_str(key) {
351+
unsafe { zend_hash_index_del(self, index as zend_ulong) }
352+
} else {
353+
unsafe { zend_hash_str_del(self, CString::new(key).ok()?.as_ptr(), key.len() as _) }
354+
}
355+
}
356+
};
305357

306358
if result < 0 {
307359
None
@@ -334,8 +386,8 @@ impl ZendHashTable {
334386
/// ht.remove_index(0);
335387
/// assert_eq!(ht.len(), 0);
336388
/// ```
337-
pub fn remove_index(&mut self, key: u64) -> Option<()> {
338-
let result = unsafe { zend_hash_index_del(self, key) };
389+
pub fn remove_index(&mut self, key: i64) -> Option<()> {
390+
let result = unsafe { zend_hash_index_del(self, key as zend_ulong) };
339391

340392
if result < 0 {
341393
None
@@ -373,12 +425,31 @@ impl ZendHashTable {
373425
/// ht.insert("c", "C");
374426
/// assert_eq!(ht.len(), 3);
375427
/// ```
376-
pub fn insert<V>(&mut self, key: &str, val: V) -> Result<()>
428+
pub fn insert<'a, K, V>(&mut self, key: K, val: V) -> Result<()>
377429
where
430+
K: Into<ArrayKey<'a>>,
378431
V: IntoZval,
379432
{
380433
let mut val = val.into_zval(false)?;
381-
unsafe { zend_hash_str_update(self, CString::new(key)?.as_ptr(), key.len(), &raw mut val) };
434+
match key.into() {
435+
ArrayKey::Long(index) => {
436+
unsafe { zend_hash_index_update(self, index as zend_ulong, &raw mut val) };
437+
}
438+
ArrayKey::String(key) => {
439+
if let Ok(idx) = i64::from_str(&key) {
440+
unsafe { zend_hash_index_update(self, idx as zend_ulong, &mut val) };
441+
} else {
442+
unsafe { zend_hash_str_update(self, CString::new(key.as_str())?.as_ptr(), key.len(), &raw mut val) };
443+
}
444+
}
445+
ArrayKey::Str(key) => {
446+
if let Ok(idx) = i64::from_str(key) {
447+
unsafe { zend_hash_index_update(self, idx as zend_ulong, &mut val) };
448+
} else {
449+
unsafe { zend_hash_str_update(self, CString::new(key)?.as_ptr(), key.len(), &raw mut val) };
450+
}
451+
}
452+
}
382453
val.release();
383454
Ok(())
384455
}
@@ -411,12 +482,12 @@ impl ZendHashTable {
411482
/// ht.insert_at_index(0, "C"); // notice overriding index 0
412483
/// assert_eq!(ht.len(), 2);
413484
/// ```
414-
pub fn insert_at_index<V>(&mut self, key: u64, val: V) -> Result<()>
485+
pub fn insert_at_index<V>(&mut self, key: i64, val: V) -> Result<()>
415486
where
416487
V: IntoZval,
417488
{
418489
let mut val = val.into_zval(false)?;
419-
unsafe { zend_hash_index_update(self, key, &raw mut val) };
490+
unsafe { zend_hash_index_update(self, key as zend_ulong, &raw mut val) };
420491
val.release();
421492
Ok(())
422493
}
@@ -554,6 +625,8 @@ impl ZendHashTable {
554625
/// }
555626
/// ArrayKey::String(key) => {
556627
/// }
628+
/// ArrayKey::Str(key) => {
629+
/// }
557630
/// }
558631
/// dbg!(key, val);
559632
/// }
@@ -606,15 +679,23 @@ pub struct Iter<'a> {
606679
}
607680

608681
/// Represents the key of a PHP array, which can be either a long or a string.
609-
#[derive(Debug, PartialEq)]
610-
pub enum ArrayKey {
682+
#[derive(Debug, Clone, PartialEq)]
683+
pub enum ArrayKey<'a> {
611684
/// A numerical key.
612685
Long(i64),
613686
/// A string key.
614687
String(String),
688+
/// A string key by reference.
689+
Str(&'a str),
690+
}
691+
692+
impl From<String> for ArrayKey<'_> {
693+
fn from(value: String) -> Self {
694+
Self::String(value)
695+
}
615696
}
616697

617-
impl ArrayKey {
698+
impl ArrayKey<'_> {
618699
/// Check if the key is an integer.
619700
///
620701
/// # Returns
@@ -625,20 +706,34 @@ impl ArrayKey {
625706
match self {
626707
ArrayKey::Long(_) => true,
627708
ArrayKey::String(_) => false,
709+
ArrayKey::Str(_) => false,
628710
}
629711
}
630712
}
631713

632-
impl Display for ArrayKey {
714+
impl Display for ArrayKey<'_> {
633715
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
634716
match self {
635717
ArrayKey::Long(key) => write!(f, "{key}"),
636718
ArrayKey::String(key) => write!(f, "{key}"),
719+
ArrayKey::Str(key) => write!(f, "{key}"),
637720
}
638721
}
639722
}
640723

641-
impl<'a> FromZval<'a> for ArrayKey {
724+
impl<'a> From<&'a str> for ArrayKey<'a> {
725+
fn from(key: &'a str) -> ArrayKey<'a> {
726+
ArrayKey::Str(key)
727+
}
728+
}
729+
730+
impl<'a> From<i64> for ArrayKey<'a> {
731+
fn from(index: i64) -> ArrayKey<'a> {
732+
ArrayKey::Long(index)
733+
}
734+
}
735+
736+
impl<'a> FromZval<'a> for ArrayKey<'_> {
642737
const TYPE: DataType = DataType::String;
643738

644739
fn from_zval(zval: &'a Zval) -> Option<Self> {
@@ -680,7 +775,7 @@ impl<'a> Iter<'a> {
680775
}
681776

682777
impl<'a> IntoIterator for &'a ZendHashTable {
683-
type Item = (ArrayKey, &'a Zval);
778+
type Item = (ArrayKey<'a>, &'a Zval);
684779
type IntoIter = Iter<'a>;
685780

686781
/// Returns an iterator over the key(s) and value contained inside the
@@ -707,7 +802,7 @@ impl<'a> IntoIterator for &'a ZendHashTable {
707802
}
708803

709804
impl<'a> Iterator for Iter<'a> {
710-
type Item = (ArrayKey, &'a Zval);
805+
type Item = (ArrayKey<'a>, &'a Zval);
711806

712807
fn next(&mut self) -> Option<Self::Item> {
713808
self.next_zval()
@@ -1046,8 +1141,8 @@ impl FromIterator<Zval> for ZBox<ZendHashTable> {
10461141
}
10471142
}
10481143

1049-
impl FromIterator<(u64, Zval)> for ZBox<ZendHashTable> {
1050-
fn from_iter<T: IntoIterator<Item = (u64, Zval)>>(iter: T) -> Self {
1144+
impl FromIterator<(i64, Zval)> for ZBox<ZendHashTable> {
1145+
fn from_iter<T: IntoIterator<Item = (i64, Zval)>>(iter: T) -> Self {
10511146
let mut ht = ZendHashTable::new();
10521147
for (key, val) in iter {
10531148
// Inserting a zval cannot fail, as `push` only returns `Err` if converting
@@ -1068,4 +1163,4 @@ impl<'a> FromIterator<(&'a str, Zval)> for ZBox<ZendHashTable> {
10681163
}
10691164
ht
10701165
}
1071-
}
1166+
}

src/zend/handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ impl ZendObjectHandlers {
181181
let self_ = &mut *obj;
182182
let struct_props = T::get_metadata().get_properties();
183183

184-
for (name, val) in struct_props {
184+
for (&name, val) in struct_props {
185185
let mut zv = Zval::new();
186186
if val.prop.get(self_, &mut zv).is_err() {
187187
continue;

tests/src/integration/iterator/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ fn key_to_zval(key: ArrayKey) -> Zval {
4848
let _ = zval.set_string(s.as_str(), false);
4949
zval
5050
}
51+
ArrayKey::Str(s) => {
52+
let mut zval = Zval::new();
53+
let _ = zval.set_string(s, false);
54+
zval
55+
}
5156
ArrayKey::Long(l) => {
5257
let mut zval = Zval::new();
5358
zval.set_long(l);

0 commit comments

Comments
 (0)