|
|
|
|
@ -0,0 +1,253 @@ |
|
|
|
|
--TEST-- |
|
|
|
|
Property |
|
|
|
|
--FILE-- |
|
|
|
|
<?php |
|
|
|
|
class Worker { |
|
|
|
|
public $context; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 原始代码(有缺陷) |
|
|
|
|
*/ |
|
|
|
|
function originalLogic($worker, $prop) { |
|
|
|
|
// ⚠️ 有缺陷的写法:缺少 $worker->context 存在性检查 |
|
|
|
|
!isset($worker->$prop) && !isset($worker->context->$prop) && $worker->context->$prop = 'NNNN'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 改进后的安全写法 |
|
|
|
|
*/ |
|
|
|
|
function safeLogic($worker, $prop) { |
|
|
|
|
// ✅ 安全写法:先确保 context 存在,再使用空合并赋值 |
|
|
|
|
if (!isset($worker->context)) { |
|
|
|
|
$worker->context = new stdClass(); |
|
|
|
|
} |
|
|
|
|
$worker->context->$prop ??= 'NNNN'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 测试工具函数 |
|
|
|
|
*/ |
|
|
|
|
function dumpState($label, $worker, $prop) { |
|
|
|
|
echo "\n[$label]\n"; |
|
|
|
|
echo " worker->$prop: " . (isset($worker->$prop) ? "'" . $worker->$prop . "'" : 'UNSET') . "\n"; |
|
|
|
|
echo " context->$prop: " . ( |
|
|
|
|
isset($worker->context) && isset($worker->context->$prop) |
|
|
|
|
? "'" . $worker->context->$prop . "'" |
|
|
|
|
: (isset($worker->context) ? 'UNSET' : 'CONTEXT_NULL') |
|
|
|
|
) . "\n"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function main() { |
|
|
|
|
echo "=".str_repeat("=", 70) . "\n"; |
|
|
|
|
echo "TEST SUITE: isset + assignment short-circuit logic\n"; |
|
|
|
|
echo "=".str_repeat("=", 70) . "\n"; |
|
|
|
|
|
|
|
|
|
$prop = 'name'; |
|
|
|
|
|
|
|
|
|
// ======================================================================== |
|
|
|
|
// 测试 1: 两个属性都不存在 → 应触发赋值 |
|
|
|
|
// ======================================================================== |
|
|
|
|
echo "\n【TEST 1】两个属性都不存在 → 应触发赋值 'NNNN'\n"; |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
$worker->context = new stdClass(); |
|
|
|
|
|
|
|
|
|
dumpState('BEFORE', $worker, $prop); |
|
|
|
|
originalLogic($worker, $prop); |
|
|
|
|
dumpState('AFTER (original)', $worker, $prop); |
|
|
|
|
|
|
|
|
|
// 验证 |
|
|
|
|
assert(isset($worker->context->name), "TEST 1 FAILED: context->name should be set"); |
|
|
|
|
assert($worker->context->name === 'NNNN', "TEST 1 FAILED: value should be 'NNNN'"); |
|
|
|
|
echo "✅ TEST 1 PASSED\n"; |
|
|
|
|
|
|
|
|
|
// ======================================================================== |
|
|
|
|
// 测试 2: $worker->$prop 存在 → 不应触发赋值 |
|
|
|
|
// ======================================================================== |
|
|
|
|
echo "\n【TEST 2】\$worker->\$prop 存在 → 不应触发赋值\n"; |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
$worker->context = new stdClass(); |
|
|
|
|
$worker->name = 'Alice'; // worker 有值 |
|
|
|
|
|
|
|
|
|
dumpState('BEFORE', $worker, $prop); |
|
|
|
|
originalLogic($worker, $prop); |
|
|
|
|
dumpState('AFTER (original)', $worker, $prop); |
|
|
|
|
|
|
|
|
|
// 验证:context->name 应该仍不存在 |
|
|
|
|
assert(!isset($worker->context->name), "TEST 2 FAILED: context->name should NOT be set"); |
|
|
|
|
echo "✅ TEST 2 PASSED\n"; |
|
|
|
|
|
|
|
|
|
// ======================================================================== |
|
|
|
|
// 测试 3: $worker->context->$prop 存在 → 不应触发赋值 |
|
|
|
|
// ======================================================================== |
|
|
|
|
echo "\n【TEST 3】\$worker->context->\$prop 存在 → 不应触发赋值\n"; |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
$worker->context = new stdClass(); |
|
|
|
|
$worker->context->name = 'Bob'; // context 有值 |
|
|
|
|
|
|
|
|
|
dumpState('BEFORE', $worker, $prop); |
|
|
|
|
originalLogic($worker, $prop); |
|
|
|
|
dumpState('AFTER (original)', $worker, $prop); |
|
|
|
|
|
|
|
|
|
// 验证:context->name 应保持原值 |
|
|
|
|
assert($worker->context->name === 'Bob', "TEST 3 FAILED: value should remain 'Bob'"); |
|
|
|
|
echo "✅ TEST 3 PASSED\n"; |
|
|
|
|
|
|
|
|
|
// ======================================================================== |
|
|
|
|
// 测试 4: 两个属性都存在 → 不应触发赋值 |
|
|
|
|
// ======================================================================== |
|
|
|
|
echo "\n【TEST 4】两个属性都存在 → 不应触发赋值\n"; |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
$worker->context = new stdClass(); |
|
|
|
|
$worker->name = 'Charlie'; |
|
|
|
|
$worker->context->name = 'David'; |
|
|
|
|
|
|
|
|
|
dumpState('BEFORE', $worker, $prop); |
|
|
|
|
originalLogic($worker, $prop); |
|
|
|
|
dumpState('AFTER (original)', $worker, $prop); |
|
|
|
|
|
|
|
|
|
// 验证:context->name 应保持原值 |
|
|
|
|
assert($worker->context->name === 'David', "TEST 4 FAILED: value should remain 'David'"); |
|
|
|
|
echo "✅ TEST 4 PASSED\n"; |
|
|
|
|
|
|
|
|
|
// ======================================================================== |
|
|
|
|
// 测试 5: $worker->context 为 null → 原始代码会触发错误! |
|
|
|
|
// ======================================================================== |
|
|
|
|
echo "\n【TEST 5】\$worker->context 为 null → 原始代码会触发 FATAL ERROR\n"; |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
$worker->context = null; // context 为 null |
|
|
|
|
|
|
|
|
|
dumpState('BEFORE', $worker, $prop); |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
originalLogic($worker, $prop); |
|
|
|
|
echo "❌ TEST 5 FAILED: Should have thrown error!\n"; |
|
|
|
|
} catch (Error $e) { |
|
|
|
|
echo "✅ TEST 5 PASSED: Caught expected error: " . $e->getMessage() . "\n"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 安全写法测试 |
|
|
|
|
$worker2 = new Worker(); |
|
|
|
|
$worker2->context = null; |
|
|
|
|
safeLogic($worker2, $prop); |
|
|
|
|
dumpState('AFTER (safe)', $worker2, $prop); |
|
|
|
|
assert(isset($worker2->context->name), "TEST 5 SAFE FAILED: context should be created"); |
|
|
|
|
echo "✅ TEST 5 SAFE PASSED: Safe logic handled null context\n"; |
|
|
|
|
|
|
|
|
|
// ======================================================================== |
|
|
|
|
// 测试 6: $worker->context 完全不存在(未定义)→ 原始代码会触发错误! |
|
|
|
|
// ======================================================================== |
|
|
|
|
echo "\n【TEST 6】\$worker->context 未定义 → 原始代码会触发 NOTICE\n"; |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
unset($worker->context); // 完全移除 context 属性 |
|
|
|
|
|
|
|
|
|
dumpState('BEFORE', $worker, $prop); |
|
|
|
|
|
|
|
|
|
// PHP 8.0+ 会抛出 Error,PHP 7.x 会触发 Notice |
|
|
|
|
try { |
|
|
|
|
originalLogic($worker, $prop); |
|
|
|
|
echo "❌ TEST 6 FAILED: Should have thrown error!\n"; |
|
|
|
|
} catch (Error $e) { |
|
|
|
|
echo "✅ TEST 6 PASSED: Caught expected error: " . $e->getMessage() . "\n"; |
|
|
|
|
} catch (Exception $e) { |
|
|
|
|
echo "⚠️ TEST 6: Notice triggered (PHP 7.x behavior)\n"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 安全写法测试 |
|
|
|
|
$worker2 = new Worker(); |
|
|
|
|
unset($worker2->context); |
|
|
|
|
safeLogic($worker2, $prop); |
|
|
|
|
dumpState('AFTER (safe)', $worker2, $prop); |
|
|
|
|
assert(isset($worker2->context->name), "TEST 6 SAFE FAILED: context should be created"); |
|
|
|
|
echo "✅ TEST 6 SAFE PASSED: Safe logic handled undefined context\n"; |
|
|
|
|
|
|
|
|
|
// ======================================================================== |
|
|
|
|
// 测试 7: 属性值为 null(isset 返回 false) |
|
|
|
|
// ======================================================================== |
|
|
|
|
echo "\n【TEST 7】属性值为 null → isset 返回 false,应触发赋值\n"; |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
$worker->context = new stdClass(); |
|
|
|
|
$worker->name = null; // null 值 |
|
|
|
|
$worker->context->name = null; // null 值 |
|
|
|
|
|
|
|
|
|
dumpState('BEFORE', $worker, $prop); |
|
|
|
|
originalLogic($worker, $prop); |
|
|
|
|
dumpState('AFTER (original)', $worker, $prop); |
|
|
|
|
|
|
|
|
|
// 验证:null 被视为"不存在",应触发赋值 |
|
|
|
|
assert($worker->context->name === 'NNNN', "TEST 7 FAILED: null should trigger assignment"); |
|
|
|
|
echo "✅ TEST 7 PASSED\n"; |
|
|
|
|
|
|
|
|
|
// ======================================================================== |
|
|
|
|
// 测试 8: 属性值为假值(0, '', false)→ isset 返回 true,不应触发赋值 |
|
|
|
|
// ======================================================================== |
|
|
|
|
echo "\n【TEST 8】属性值为假值(0, '', false)→ isset 返回 true\n"; |
|
|
|
|
|
|
|
|
|
// 子测试 8a: 空字符串 |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
$worker->context = new stdClass(); |
|
|
|
|
$worker->context->name = ''; |
|
|
|
|
originalLogic($worker, $prop); |
|
|
|
|
assert($worker->context->name === '', "TEST 8a FAILED: empty string should NOT trigger assignment"); |
|
|
|
|
echo "✅ TEST 8a PASSED: empty string preserved\n"; |
|
|
|
|
|
|
|
|
|
// 子测试 8b: 数字 0 |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
$worker->context = new stdClass(); |
|
|
|
|
$worker->context->name = 0; |
|
|
|
|
originalLogic($worker, $prop); |
|
|
|
|
assert($worker->context->name === 0, "TEST 8b FAILED: zero should NOT trigger assignment"); |
|
|
|
|
echo "✅ TEST 8b PASSED: zero preserved\n"; |
|
|
|
|
|
|
|
|
|
// 子测试 8c: false |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
$worker->context = new stdClass(); |
|
|
|
|
$worker->context->name = false; |
|
|
|
|
originalLogic($worker, $prop); |
|
|
|
|
assert($worker->context->name === false, "TEST 8c FAILED: false should NOT trigger assignment"); |
|
|
|
|
echo "✅ TEST 8c PASSED: false preserved\n"; |
|
|
|
|
|
|
|
|
|
// ======================================================================== |
|
|
|
|
// 测试 9: 动态属性名(变量) |
|
|
|
|
// ======================================================================== |
|
|
|
|
echo "\n【TEST 9】动态属性名(变量)\n"; |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
$worker->context = new stdClass(); |
|
|
|
|
$dynamicProp = 'email'; |
|
|
|
|
|
|
|
|
|
originalLogic($worker, $dynamicProp); |
|
|
|
|
assert(isset($worker->context->email), "TEST 9 FAILED: dynamic property should be set"); |
|
|
|
|
assert($worker->context->email === 'NNNN', "TEST 9 FAILED: value should be 'NNNN'"); |
|
|
|
|
echo "✅ TEST 9 PASSED\n"; |
|
|
|
|
|
|
|
|
|
// ======================================================================== |
|
|
|
|
// 测试 10: 多次调用(幂等性) |
|
|
|
|
// ======================================================================== |
|
|
|
|
echo "\n【TEST 10】多次调用(幂等性)\n"; |
|
|
|
|
$worker = new Worker(); |
|
|
|
|
$worker->context = new stdClass(); |
|
|
|
|
|
|
|
|
|
originalLogic($worker, $prop); // 第一次:赋值 |
|
|
|
|
$firstValue = $worker->context->name; |
|
|
|
|
|
|
|
|
|
originalLogic($worker, $prop); // 第二次:不应覆盖 |
|
|
|
|
$secondValue = $worker->context->name; |
|
|
|
|
|
|
|
|
|
assert($firstValue === 'NNNN', "TEST 10 FAILED: first call should set value"); |
|
|
|
|
assert($secondValue === 'NNNN', "TEST 10 FAILED: second call should not change value"); |
|
|
|
|
assert($firstValue === $secondValue, "TEST 10 FAILED: value should be idempotent"); |
|
|
|
|
echo "✅ TEST 10 PASSED: Idempotent behavior confirmed\n"; |
|
|
|
|
|
|
|
|
|
// ======================================================================== |
|
|
|
|
// 总结 |
|
|
|
|
// ======================================================================== |
|
|
|
|
echo "\n".str_repeat("=", 70); |
|
|
|
|
echo "\n✅ ALL TESTS COMPLETED SUCCESSFULLY\n"; |
|
|
|
|
echo "=".str_repeat("=", 70) . "\n"; |
|
|
|
|
} |
|
|
|
|
?> |
|
|
|
|
--EXPECT-- |
|
|
|
|
string(10) "Processing" |
|
|
|
|
string(7) "unknown" |
|
|
|
|
string(2) "OK" |
|
|
|
|
string(2) "OK" |