@@ -693,12 +693,56 @@ impl ZendHashTable {
693693 K : Into < ArrayKey < ' k > > ,
694694 {
695695 let key = key. into ( ) ;
696- if self . get ( key. clone ( ) ) . is_some ( ) {
696+ if self . has_key ( & key) {
697697 Entry :: Occupied ( entry:: OccupiedEntry :: new ( self , key) )
698698 } else {
699699 Entry :: Vacant ( entry:: VacantEntry :: new ( self , key) )
700700 }
701701 }
702+
703+ /// Checks if a key exists in the hash table.
704+ ///
705+ /// # Parameters
706+ ///
707+ /// * `key` - The key to check for in the hash table.
708+ ///
709+ /// # Returns
710+ ///
711+ /// * `true` - The key exists in the hash table.
712+ /// * `false` - The key does not exist in the hash table.
713+ ///
714+ /// # Example
715+ ///
716+ /// ```no_run
717+ /// use ext_php_rs::types::{ZendHashTable, ArrayKey};
718+ ///
719+ /// let mut ht = ZendHashTable::new();
720+ ///
721+ /// ht.insert("test", "hello world");
722+ /// assert!(ht.has_key(&ArrayKey::from("test")));
723+ /// assert!(!ht.has_key(&ArrayKey::from("missing")));
724+ /// ```
725+ #[ must_use]
726+ pub fn has_key ( & self , key : & ArrayKey < ' _ > ) -> bool {
727+ match key {
728+ ArrayKey :: Long ( index) => unsafe {
729+ #[ allow( clippy:: cast_sign_loss) ]
730+ !zend_hash_index_find ( self , * index as zend_ulong ) . is_null ( )
731+ } ,
732+ ArrayKey :: String ( key) => {
733+ let Ok ( cstr) = CString :: new ( key. as_str ( ) ) else {
734+ return false ;
735+ } ;
736+ unsafe { !zend_hash_str_find ( self , cstr. as_ptr ( ) , key. len ( ) as _ ) . is_null ( ) }
737+ }
738+ ArrayKey :: Str ( key) => {
739+ let Ok ( cstr) = CString :: new ( * key) else {
740+ return false ;
741+ } ;
742+ unsafe { !zend_hash_str_find ( self , cstr. as_ptr ( ) , key. len ( ) as _ ) . is_null ( ) }
743+ }
744+ }
745+ }
702746}
703747
704748unsafe impl ZBoxable for ZendHashTable {
@@ -762,3 +806,47 @@ impl<'a> FromZval<'a> for &'a ZendHashTable {
762806 zval. array ( )
763807 }
764808}
809+
810+ #[ cfg( test) ]
811+ #[ cfg( feature = "embed" ) ]
812+ mod tests {
813+ use super :: * ;
814+ use crate :: embed:: Embed ;
815+
816+ #[ test]
817+ fn test_has_key_string ( ) {
818+ Embed :: run ( || {
819+ let mut ht = ZendHashTable :: new ( ) ;
820+ let _ = ht. insert ( "test" , "value" ) ;
821+
822+ assert ! ( ht. has_key( & ArrayKey :: from( "test" ) ) ) ;
823+ assert ! ( !ht. has_key( & ArrayKey :: from( "missing" ) ) ) ;
824+ } ) ;
825+ }
826+
827+ #[ test]
828+ fn test_has_key_long ( ) {
829+ Embed :: run ( || {
830+ let mut ht = ZendHashTable :: new ( ) ;
831+ let _ = ht. push ( 42i64 ) ;
832+
833+ assert ! ( ht. has_key( & ArrayKey :: Long ( 0 ) ) ) ;
834+ assert ! ( !ht. has_key( & ArrayKey :: Long ( 1 ) ) ) ;
835+ } ) ;
836+ }
837+
838+ #[ test]
839+ fn test_has_key_str_ref ( ) {
840+ Embed :: run ( || {
841+ let mut ht = ZendHashTable :: new ( ) ;
842+ let _ = ht. insert ( "hello" , "world" ) ;
843+
844+ let key = ArrayKey :: Str ( "hello" ) ;
845+ assert ! ( ht. has_key( & key) ) ;
846+ // Key is still usable after has_key (no clone needed)
847+ assert ! ( ht. has_key( & key) ) ;
848+
849+ assert ! ( !ht. has_key( & ArrayKey :: Str ( "missing" ) ) ) ;
850+ } ) ;
851+ }
852+ }
0 commit comments