Skip to content

Commit 631f655

Browse files
committed
feat(core): 添加对象属性存在性检查功能
- 实现了 Object::propertyExists 方法用于检查属性是否存在 - 修复了 variant.cc 中的缩进问题以提高代码可读性 - 更新了对象属性访问的相关测试用例 - 添加了对不存在属性的各种操作测试 - 完善了属性存在性检查的逻辑实现
1 parent a24be75 commit 631f655

File tree

4 files changed

+68
-52
lines changed

4 files changed

+68
-52
lines changed

include/phpx.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,9 +1246,7 @@ class Object : public Variant {
12461246
bool methodExists(const String &name) const {
12471247
return zend_hash_exists(&ce()->function_table, name.str());
12481248
}
1249-
bool propertyExists(const String &name) const {
1250-
return zend_hash_exists(&ce()->properties_info, name.str());
1251-
}
1249+
bool propertyExists(const String &name) const;
12521250
bool instanceOf(const String &name) const;
12531251
bool instanceOf(const zend_class_entry *ce_) const {
12541252
return instanceof_function(ce(), ce_);

src/core/object.cc

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ zend_long Object::count() {
3535
}
3636
}
3737

38+
bool Object::propertyExists(const String &name) const {
39+
zend_string *property = name.str();
40+
auto property_info = (zend_property_info *) zend_hash_find_ptr(&ce()->properties_info, property);
41+
if (property_info != NULL && (!(property_info->flags & ZEND_ACC_PRIVATE) || property_info->ce == ce())) {
42+
return true;
43+
}
44+
if (object()->handlers->has_property(object(), property, 2, NULL)) {
45+
return true;
46+
}
47+
return false;
48+
}
49+
3850
Variant Object::exec(const Variant &fn, const ArgList &args) {
3951
if (UNEXPECTED(isNull())) {
4052
throwError("call method `%s` on null", fn.toCString());
@@ -112,10 +124,10 @@ Variant Object::attr(const Variant &name, bool update) const {
112124
auto member_p = zend_read_property_ex(ce(), object(), prop_name.str(), true, &rv);
113125

114126
if (zval_is_null(member_p) && update) {
115-
auto old_scope = EG(fake_scope);
116-
EG(fake_scope) = ce();
117-
member_p = object()->handlers->write_property(object(), prop_name.str(), undef(), NULL);
118-
EG(fake_scope) = old_scope;
127+
auto old_scope = EG(fake_scope);
128+
EG(fake_scope) = ce();
129+
member_p = object()->handlers->write_property(object(), prop_name.str(), undef(), NULL);
130+
EG(fake_scope) = old_scope;
119131
}
120132

121133
if (member_p == &rv) {

src/core/variant.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@ Variant Variant::item(zend_long offset, bool update) {
692692
ZVAL_LONG(&dim, offset);
693693
retval = obj->handlers->read_dimension(obj, &dim, BP_VAR_RW, &rv);
694694
if (UNEXPECTED(retval == NULL || retval == &EG(uninitialized_zval) || retval == &rv)) {
695-
throwErrorIfOccurred();
695+
throwErrorIfOccurred();
696696
return Variant{retval};
697697
}
698698
} else if (zval_is_string(zvar)) {
@@ -749,7 +749,7 @@ Variant Variant::item(const Variant &key, bool update) {
749749
auto dim = NO_CONST_V(key);
750750
retval = obj->handlers->read_dimension(obj, dim, BP_VAR_RW, &rv);
751751
if (UNEXPECTED(retval == NULL || retval == &EG(uninitialized_zval) || retval == &rv)) {
752-
throwErrorIfOccurred();
752+
throwErrorIfOccurred();
753753
return Variant{retval};
754754
}
755755
} else {
@@ -807,7 +807,7 @@ Variant Variant::newItem() {
807807
obj->handlers->write_dimension(obj, &key, undef());
808808
retval = obj->handlers->read_dimension(obj, &key, BP_VAR_RW, &rv);
809809
if (UNEXPECTED(retval == NULL || retval == &EG(uninitialized_zval) || retval == &rv)) {
810-
throwErrorIfOccurred();
810+
throwErrorIfOccurred();
811811
return Variant{retval};
812812
}
813813
} else if (zval_is_string(zvar)) {

tests/src/object.cpp

Lines changed: 48 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -440,79 +440,86 @@ TEST(object, non_existent_attr_operations) {
440440
ASSERT_TRUE(nonExistentAttr2.isNull());
441441

442442
// Test attr() with update=true for non-existent properties
443-
auto newAttr1 = obj.attr("new_prop", true);
444-
newAttr1 = 2003;
445-
ASSERT_EQ(obj.get("new_prop").toInt(), 2003);
443+
auto newAttr1 = obj.attr("new_prop", true);
444+
newAttr1 = 2003;
445+
ASSERT_EQ(obj.get("new_prop").toInt(), 2003);
446446

447447
// For attr chaining, we need to work with Object type
448-
try_call([&]() {
449-
Object tempObj = newObject("stdClass");
450-
auto chained = tempObj.attr("non_existent");
451-
ASSERT_TRUE(chained.isNull());
452-
}, "", true);
448+
try_call(
449+
[&]() {
450+
Object tempObj = newObject("stdClass");
451+
auto chained = tempObj.attr("non_existent");
452+
ASSERT_TRUE(chained.isNull());
453+
},
454+
"",
455+
true);
453456

454457
// Test attrRef with non-existent properties
455-
auto ref3 = obj.attrRef("missing_prop");
456-
// This should work but return a reference to null/undefined
457-
ASSERT_TRUE(ref3.isReference());
458-
ref3 = "assigned_value";
458+
auto ref3 = obj.attrRef("missing_prop");
459+
// This should work but return a reference to null/undefined
460+
ASSERT_TRUE(ref3.isReference());
461+
ref3 = "assigned_value";
459462
ASSERT_STREQ(obj.get("missing_prop").toCString(), "assigned_value");
460463

461-
try_call([&]() {
462-
null_object.attr("prop"); // Accessing attr on null object
463-
}, "read property `prop` on null");
464+
try_call(
465+
[&]() {
466+
null_object.attr("prop"); // Accessing attr on null object
467+
},
468+
"read property `prop` on null");
464469
}
465470

466471
TEST(object, array_object_non_existent_items) {
467472
// Test ArrayObject specific behavior with non-existent items
468473
auto arrayObj = newObject("ArrayObject");
469-
474+
470475
// Test offsetGet with non-existent indices
471476
auto result1 = arrayObj.offsetGet(999);
472477
ASSERT_TRUE(result1.isNull());
473-
478+
474479
auto result2 = arrayObj.offsetGet("non_existent_key");
475480
ASSERT_TRUE(result2.isNull());
476-
481+
477482
// Test exists methods
478483
ASSERT_FALSE(arrayObj.offsetExists(999));
479484
ASSERT_FALSE(arrayObj.offsetExists("non_existent_key"));
480-
485+
481486
// Test item method on ArrayObject
482487
auto item1 = arrayObj.item(500);
483488
ASSERT_TRUE(item1.isNull());
484-
489+
485490
auto item2 = arrayObj.item("missing_key");
486491
ASSERT_TRUE(item2.isNull());
487-
492+
488493
// Test that non-existent items can be assigned
489494
auto newItem = arrayObj.item("new_key", true);
490495
newItem = "assigned_value";
491-
496+
492497
auto retrieved = arrayObj.item("new_key");
493498
ASSERT_STREQ(retrieved.toCString(), "assigned_value");
494499
}
495500

496501
TEST(object, custom_class_non_existent_operations) {
497502
// Test non-existent operations on custom classes
498503
include(get_include_dir() + "/library.php", INCLUDE_ONCE);
499-
504+
500505
auto obj = newObject("TestClass2");
501-
506+
502507
// Test non-existent properties on custom class
503508
ASSERT_TRUE(obj.attr("non_existent_custom_prop").isNull());
504509
ASSERT_FALSE(obj.propertyExists("non_existent_custom_prop"));
505-
510+
506511
// Test non-existent methods
507512
ASSERT_FALSE(obj.methodExists("nonExistentMethod"));
508-
513+
509514
// Test chained operations
510-
try_call([&]() {
511-
Object tempObj = newObject("stdClass");
512-
auto firstLevel = tempObj.attr("missing_prop");
513-
ASSERT_TRUE(firstLevel.isNull());
514-
}, "");
515-
515+
try_call(
516+
[&]() {
517+
Object tempObj = newObject("stdClass");
518+
auto firstLevel = tempObj.attr("missing_prop");
519+
ASSERT_TRUE(firstLevel.isNull());
520+
},
521+
"");
522+
516523
// Test with update on custom class
517524
try {
518525
auto newAttr = obj.attr("dynamically_added_prop", true);
@@ -524,40 +531,39 @@ TEST(object, custom_class_non_existent_operations) {
524531

525532
TEST(object, special_object_types_non_existent) {
526533
// Test non-existent operations on special object types
527-
534+
528535
// DateTime object - test property access only
529536
auto dateTime = newObject("DateTime");
530537
ASSERT_TRUE(dateTime.attr("non_existent_datetime_prop").isNull());
531-
538+
532539
// Simple stdClass object
533540
auto simpleObj = newObject("stdClass");
534541
ASSERT_TRUE(simpleObj.attr("simple_missing_prop").isNull());
535542

536-
try_call([&](){
537-
ASSERT_TRUE(simpleObj.item("simple_missing_item").isNull());
538-
}, "Cannot use object of type stdClass as array");
543+
try_call([&]() { ASSERT_TRUE(simpleObj.item("simple_missing_item").isNull()); },
544+
"Cannot use object of type stdClass as array");
539545

540546
// Test that basic operations work without crashing
541547
SUCCEED() << "Special object types test completed";
542548
}
543549

544550
TEST(object, simple_property_access) {
545551
auto obj = newObject("stdClass");
546-
552+
547553
// Test basic property operations
548554
obj.set("test_prop", "test_value");
549555
ASSERT_STREQ(obj.get("test_prop").toCString(), "test_value");
550-
556+
551557
// Test non-existent property
552558
ASSERT_TRUE(obj.get("non_existent").isNull());
553-
559+
554560
// Test property existence
555561
ASSERT_TRUE(obj.propertyExists("test_prop"));
556562
ASSERT_FALSE(obj.propertyExists("missing_prop"));
557-
563+
558564
// Test attr access
559565
auto attrResult = obj.attr("test_prop");
560566
ASSERT_STREQ(attrResult.toCString(), "test_value");
561-
567+
562568
SUCCEED() << "Simple property access test completed";
563569
}

0 commit comments

Comments
 (0)