修复读取动态属性多调用一次 __isset 魔术方法的问题

pull/45/head
韩天峰 3 weeks ago
parent 78135d75ef
commit c6c04d7e96
  1. 5
      src/Generator/Utils.php
  2. 2
      src/Optimizer/FuncCallOptimizer.php
  3. 4
      src/Optimizer/SsaPropOptimizer.php
  4. 2
      src/Parser/NullsafeAccessTrait.php
  5. 8
      src/Parser/PropertyAccessTrait.php
  6. 47
      tests/compiler/magic_methods/read-skips-isset.phpt

@ -75,6 +75,11 @@ trait Utils
return $bool ? 'true' : 'false';
}
protected function escapeAttrMode(bool $update): string
{
return $update ? 'php::AttrMode::Update' : 'php::AttrMode::Get';
}
protected function escapeVarName(string $name): string
{
if ($name === 'this') {

@ -371,7 +371,7 @@ trait FuncCallOptimizer
if ($this->isPropertyFetch($arg) and $this->isVarExpr($arg->var)) {
$obj = $this->parseIdentifier($arg->var);
$tmpRef = $this->genTmpVarName();
$this->context->beforeStmtLines[] = 'auto&& ' . $tmpRef . ' = ' . $obj . '.attr(' . $this->identifierToStr($arg->name) . ', true);';
$this->context->beforeStmtLines[] = 'auto&& ' . $tmpRef . ' = ' . $obj . '.attr(' . $this->identifierToStr($arg->name) . ', php::AttrMode::Update);';
return $tmpRef;
}
return $this->getArg($expr, $i);

@ -823,7 +823,7 @@ trait SsaPropOptimizer
}
if ($this->context->inLoop || $this->context->scopeLevel > 1) {
$refGetter = $objName . '.attr(' . $id . ', true)';
$refGetter = $objName . '.attr(' . $id . ', php::AttrMode::Update)';
$zvalMacro = $this->getZvalValueMacroForPropType($cType);
if ($zvalMacro !== null) {
return $zvalMacro . '(' . $refGetter . '.unwrap_ptr())';
@ -831,7 +831,7 @@ trait SsaPropOptimizer
return $refGetter;
}
$refGetter = $objName . '.attr(' . $id . ', true)';
$refGetter = $objName . '.attr(' . $id . ', php::AttrMode::Update)';
$zvalMacro = $this->getZvalValueMacroForPropType($cType);
if ($zvalMacro !== null) {
$this->context->beforeStmtLines[] = $cType . ' &' . $propVar . ' = ' . $zvalMacro . '(' . $refGetter . '.unwrap_ptr());';

@ -84,7 +84,7 @@ trait NullsafeAccessTrait
$code .= "if ({$object}.isNull()) { return " . self::VALUE_NULL . '; }';
}
if ($item[0] == 'property') {
$update = $this->escapeBool($this->isPropertyFetchUpdate($item[2]));
$update = $this->escapeAttrMode($this->isPropertyFetchUpdate($item[2]));
$code .= $this->getIndent() . "{$tmpVar} = {$object}.attr({$item[1]}, {$update});";
} else {
$beforeStmtCount = count($this->context->beforeStmtLines);

@ -163,12 +163,12 @@ trait PropertyAccessTrait
protected function emitDynamicPropertyAppendArray(string $object, string $property, string $value): string
{
return "{$object}.attr({$property}, true).newItem() = {$value}";
return "{$object}.attr({$property}, php::AttrMode::Update).newItem() = {$value}";
}
protected function emitDynamicPropertyUpdateArray(string $object, string $property, string $dim, string $value): string
{
return "{$object}.attr({$property}, true).item({$dim}, true) = {$value}";
return "{$object}.attr({$property}, php::AttrMode::Update).item({$dim}, true) = {$value}";
}
protected function assertDynamicPropertyTarget(PropertyWriteTarget $target): void
@ -729,7 +729,7 @@ trait PropertyAccessTrait
if ($this->hasObjectPropVar($propVar)) {
$lines[] = $propVar . ' = ' . $restoreDefault . ';';
} else {
$lines[] = $object . '.attr(' . $propertyId . ', true) = ' . $restoreDefault . ';';
$lines[] = $object . '.attr(' . $propertyId . ', php::AttrMode::Update) = ' . $restoreDefault . ';';
}
}
}
@ -839,7 +839,7 @@ trait PropertyAccessTrait
$this->errorUndefinedVariable($object);
}
$objectVar = $objectName;
$getProperty = $objectVar . '.attr(' . $id . ', ' . $this->escapeBool($update) . ')';
$getProperty = $objectVar . '.attr(' . $id . ', ' . $this->escapeAttrMode($update) . ')';
$def = $this->getNativePropertyDef($expr);
if ($def and $this->nativeTypes) {
$propName = $this->parseIdentifier($property);

@ -0,0 +1,47 @@
--TEST--
Magic Methods - a plain property read invokes __get, never __isset
--FILE--
<?php
declare(strict_types=1);
class MagicReadTest
{
private array $data = ['name' => 'TypePHP'];
public function __get(string $name)
{
echo "__get($name)\n";
return $this->data[$name] ?? null;
}
public function __isset(string $name)
{
echo "__isset($name)\n";
return isset($this->data[$name]);
}
}
function main(): void
{
$obj = new MagicReadTest();
echo "--- plain read ---\n";
var_dump($obj->aaa);
echo "--- isset ---\n";
var_dump(isset($obj->aaa));
echo "--- empty ---\n";
var_dump(empty($obj->aaa));
}
?>
--EXPECTF--
--- plain read ---
__get(aaa)
NULL
--- isset ---
__isset(aaa)
bool(false)
--- empty ---
__isset(aaa)
bool(true)
Loading…
Cancel
Save