Эх сурвалжийг харах

PHP: Added `==` operators for Map and Array. (#7900)

Joshua Haberman 5 жил өмнө
parent
commit
cae85a9f11

+ 43 - 0
php/ext/google/protobuf/array.c

@@ -41,6 +41,7 @@
 #include "arena.h"
 #include "convert.h"
 #include "def.h"
+#include "message.h"
 #include "php-upb.h"
 #include "protobuf.h"
 
@@ -94,6 +95,26 @@ static void RepeatedField_destructor(zend_object* obj) {
   zend_object_std_dtor(&intern->std);
 }
 
+/**
+ * RepeatedField_compare_objects()
+ *
+ * Object handler for comparing two repeated field objects. Called whenever PHP
+ * code does:
+ *
+ *   $rf1 == $rf2
+ */
+static int RepeatedField_compare_objects(zval *rf1, zval *rf2) {
+  RepeatedField* intern1 = (RepeatedField*)Z_OBJ_P(rf1);
+  RepeatedField* intern2 = (RepeatedField*)Z_OBJ_P(rf2);
+  upb_fieldtype_t type = intern1->type;
+  const upb_msgdef *m = intern1->desc ? intern1->desc->msgdef : NULL;
+
+  if (type != intern2->type) return 1;
+  if (intern1->desc != intern2->desc) return 1;
+
+  return ArrayEq(intern1->array, intern2->array, type, m) ? 0 : 1;
+}
+
 static HashTable *RepeatedField_GetProperties(PROTO_VAL *object) {
   return NULL;  // We do not have a properties table.
 }
@@ -177,6 +198,27 @@ upb_array *RepeatedField_GetUpbArray(zval *val, const upb_fielddef *f,
   }
 }
 
+bool ArrayEq(const upb_array *a1, const upb_array *a2, upb_fieldtype_t type,
+             const upb_msgdef *m) {
+  size_t i;
+  size_t n;
+
+  if ((a1 == NULL) != (a2 == NULL)) return false;
+  if (a1 == NULL) return true;
+
+  n = upb_array_size(a1);
+  if (n != upb_array_size(a2)) return false;
+
+  for (i = 0; i < n; i++) {
+    upb_msgval val1 = upb_array_get(a1, i);
+    upb_msgval val2 = upb_array_get(a2, i);
+    if (!ValueEq(val1, val2, type, m)) return false;
+  }
+
+  return true;
+}
+
+
 // RepeatedField PHP methods ///////////////////////////////////////////////////
 
 /**
@@ -594,6 +636,7 @@ void Array_ModuleInit() {
   h = &RepeatedField_object_handlers;
   memcpy(h, &std_object_handlers, sizeof(zend_object_handlers));
   h->dtor_obj = RepeatedField_destructor;
+  h->compare_objects = RepeatedField_compare_objects;
   h->get_properties = RepeatedField_GetProperties;
   h->get_property_ptr_ptr = RepeatedField_GetPropertyPtrPtr;
 

+ 5 - 0
php/ext/google/protobuf/array.h

@@ -58,4 +58,9 @@ upb_array *RepeatedField_GetUpbArray(zval *val, const upb_fielddef *f, upb_arena
 void RepeatedField_GetPhpWrapper(zval *val, upb_array *arr,
                                  const upb_fielddef *f, zval *arena);
 
+// Returns true if the given arrays are equal. Both arrays must be of this
+// |type| and, if the type is |UPB_TYPE_MESSAGE|, must have the same |m|.
+bool ArrayEq(const upb_array *a1, const upb_array *a2, upb_fieldtype_t type,
+             const upb_msgdef *m);
+
 #endif  // PHP_PROTOBUF_ARRAY_H_

+ 45 - 0
php/ext/google/protobuf/map.c

@@ -37,6 +37,7 @@
 
 #include "arena.h"
 #include "convert.h"
+#include "message.h"
 #include "php-upb.h"
 #include "protobuf.h"
 
@@ -90,6 +91,28 @@ static void MapField_destructor(zend_object* obj) {
   zend_object_std_dtor(&intern->std);
 }
 
+/**
+ * MapField_compare_objects()
+ *
+ * Object handler for comparing two repeated field objects. Called whenever PHP
+ * code does:
+ *
+ *   $map1 == $map2
+ */
+static int MapField_compare_objects(zval *map1, zval *map2) {
+  MapField* intern1 = (MapField*)Z_OBJ_P(map1);
+  MapField* intern2 = (MapField*)Z_OBJ_P(map2);
+  const upb_msgdef *m = intern1->desc ? intern1->desc->msgdef : NULL;
+  upb_fieldtype_t key_type = intern1->key_type;
+  upb_fieldtype_t val_type = intern1->val_type;
+
+  if (key_type != intern2->key_type) return 1;
+  if (val_type != intern2->val_type) return 1;
+  if (intern1->desc != intern2->desc) return 1;
+
+  return MapEq(intern1->map, intern2->map, key_type, val_type, m) ? 0 : 1;
+}
+
 static zval *Map_GetPropertyPtrPtr(PROTO_VAL *object, PROTO_STR *member,
                                    int type, void **cache_slot) {
   return NULL;  // We don't offer direct references to our properties.
@@ -185,6 +208,27 @@ upb_map *MapField_GetUpbMap(zval *val, const upb_fielddef *f, upb_arena *arena)
   }
 }
 
+bool MapEq(const upb_map *m1, const upb_map *m2, upb_fieldtype_t key_type,
+           upb_fieldtype_t val_type, const upb_msgdef *m) {
+  size_t iter = UPB_MAP_BEGIN;
+
+  if ((m1 == NULL) != (m2 == NULL)) return false;
+  if (m1 == NULL) return true;
+  if (upb_map_size(m1) != upb_map_size(m2)) return false;
+
+  while (upb_mapiter_next(m1, &iter)) {
+    upb_msgval key = upb_mapiter_key(m1, iter);
+    upb_msgval val1 = upb_mapiter_value(m1, iter);
+    upb_msgval val2;
+
+    if (!upb_map_get(m2, key, &val2)) return false;
+    if (!ValueEq(val1, val2, val_type, m)) return false;
+  }
+
+  return true;
+}
+
+
 // MapField PHP methods ////////////////////////////////////////////////////////
 
 /**
@@ -578,6 +622,7 @@ void Map_ModuleInit() {
   h = &MapField_object_handlers;
   memcpy(h, &std_object_handlers, sizeof(zend_object_handlers));
   h->dtor_obj = MapField_destructor;
+  h->compare_objects = MapField_compare_objects;
   h->get_properties = Map_GetProperties;
   h->get_property_ptr_ptr = Map_GetPropertyPtrPtr;
 

+ 3 - 0
php/ext/google/protobuf/map.h

@@ -57,4 +57,7 @@ upb_map *MapField_GetUpbMap(zval *val, const upb_fielddef *f, upb_arena *arena);
 void MapField_GetPhpWrapper(zval *val, upb_map *arr, const upb_fielddef *f,
                             zval *arena);
 
+bool MapEq(const upb_map *m1, const upb_map *m2, upb_fieldtype_t key_type,
+           upb_fieldtype_t val_type, const upb_msgdef *m);
+
 #endif  // PHP_PROTOBUF_MAP_H_

+ 2 - 49
php/ext/google/protobuf/message.c

@@ -113,8 +113,8 @@ static bool MessageEq(const upb_msg *m1, const upb_msg *m2, const upb_msgdef *m)
 /**
  * ValueEq()()
  */
-static bool ValueEq(upb_msgval val1, upb_msgval val2, upb_fieldtype_t type,
-                    const upb_msgdef *m) {
+bool ValueEq(upb_msgval val1, upb_msgval val2, upb_fieldtype_t type,
+             const upb_msgdef *m) {
   switch (type) {
     case UPB_TYPE_BOOL:
       return val1.bool_val == val2.bool_val;
@@ -140,53 +140,6 @@ static bool ValueEq(upb_msgval val1, upb_msgval val2, upb_fieldtype_t type,
   }
 }
 
-/**
- * MapEq()
- */
-static bool MapEq(const upb_map *m1, const upb_map *m2,
-                  upb_fieldtype_t key_type, upb_fieldtype_t val_type,
-                  const upb_msgdef *m) {
-  size_t iter = UPB_MAP_BEGIN;
-
-  if ((m1 == NULL) != (m2 == NULL)) return false;
-  if (m1 == NULL) return true;
-  if (upb_map_size(m1) != upb_map_size(m2)) return false;
-
-  while (upb_mapiter_next(m1, &iter)) {
-    upb_msgval key = upb_mapiter_key(m1, iter);
-    upb_msgval val1 = upb_mapiter_value(m1, iter);
-    upb_msgval val2;
-
-    if (!upb_map_get(m2, key, &val2)) return false;
-    if (!ValueEq(val1, val2, val_type, m)) return false;
-  }
-
-  return true;
-}
-
-/**
- * ArrayEq()
- */
-static bool ArrayEq(const upb_array *a1, const upb_array *a2,
-                    upb_fieldtype_t type, const upb_msgdef *m) {
-  size_t i;
-  size_t n;
-
-  if ((a1 == NULL) != (a2 == NULL)) return false;
-  if (a1 == NULL) return true;
-
-  n = upb_array_size(a1);
-  if (n != upb_array_size(a2)) return false;
-
-  for (i = 0; i < n; i++) {
-    upb_msgval val1 = upb_array_get(a1, i);
-    upb_msgval val2 = upb_array_get(a2, i);
-    if (!ValueEq(val1, val2, type, m)) return false;
-  }
-
-  return true;
-}
-
 /**
  * MessageEq()
  */

+ 3 - 0
php/ext/google/protobuf/message.h

@@ -56,4 +56,7 @@ bool Message_GetUpbMessage(zval *val, const Descriptor *desc, upb_arena *arena,
 void Message_GetPhpWrapper(zval *val, const Descriptor *desc, upb_msg *msg,
                            zval *arena);
 
+bool ValueEq(upb_msgval val1, upb_msgval val2, upb_fieldtype_t type,
+             const upb_msgdef *m);
+
 #endif  // PHP_PROTOBUF_MESSAGE_H_

+ 1 - 0
php/ext/google/protobuf/php-upb.c

@@ -407,6 +407,7 @@ static const char *decode_varint64(upb_decstate *d, const char *ptr,
   }
 }
 
+UPB_FORCEINLINE
 static const char *decode_varint32(upb_decstate *d, const char *ptr,
                                    const char *limit, uint32_t *val) {
   uint64_t u64;

+ 31 - 0
php/tests/ArrayTest.php

@@ -590,4 +590,35 @@ class ArrayTest extends \PHPUnit\Framework\TestCase
         $end = memory_get_usage();
         $this->assertLessThan($start, $end);
     }
+
+    #########################################################
+    # Test equality
+    #########################################################
+
+    public function testEquality()
+    {
+        $arr = new RepeatedField(GPBType::INT32);
+        $arr2 = new RepeatedField(GPBType::INT32);
+
+        $this->assertTrue($arr == $arr2);
+
+        $arr[] = 0;
+        $arr[] = 1;
+        $arr[] = 2;
+
+        $this->assertFalse($arr == $arr2);
+
+        $arr2[] = 0;
+        $arr2[] = 1;
+        $arr2[] = 2;
+
+        $this->assertTrue($arr == $arr2);
+
+        // Arrays of different types always compare false.
+        $this->assertFalse(new RepeatedField(GPBType::INT32) ==
+                           new RepeatedField(GPBType::INT64));
+        $this->assertFalse(
+            new RepeatedField(GPBType::MESSAGE, TestMessage::class) ==
+            new RepeatedField(GPBType::MESSAGE, Sub::class));
+    }
 }

+ 29 - 0
php/tests/MapFieldTest.php

@@ -479,6 +479,35 @@ class MapFieldTest extends \PHPUnit\Framework\TestCase {
         $m->setMapInt32Message($values);
     }
 
+    #########################################################
+    # Test equality
+    #########################################################
+
+    public function testEquality()
+    {
+        $map = new MapField(GPBType::INT32, GPBType::INT32);
+        $map2 = new MapField(GPBType::INT32, GPBType::INT32);
+
+        $this->assertTrue($map == $map2);
+
+        $map[1] = 2;
+
+        $this->assertFalse($map == $map2);
+
+        $map2[1] = 2;
+
+        $this->assertTrue($map == $map2);
+
+        // Arrays of different types always compare false.
+        $this->assertFalse(new MapField(GPBType::INT32, GPBType::INT32) ==
+                           new MapField(GPBType::INT32, GPBType::INT64));
+        $this->assertFalse(new MapField(GPBType::INT32, GPBType::INT32) ==
+                           new MapField(GPBType::INT64, GPBType::INT32));
+        $this->assertFalse(
+            new MapField(GPBType::INT32, GPBType::MESSAGE, TestMessage::class) ==
+            new MapField(GPBType::INT32, GPBType::MESSAGE, Sub::class));
+    }
+
     #########################################################
     # Test memory leak
     #########################################################