TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
4.7 KiB
194 lines
4.7 KiB
--TEST--
|
|
Named magic property cache preserves polymorphism, dynamic names, and receiver evaluation
|
|
--FILE--
|
|
<?php
|
|
|
|
class FirstMagicProperty
|
|
{
|
|
private array $values = [];
|
|
|
|
public function __get(string $name): mixed
|
|
{
|
|
echo "first:get:$name\n";
|
|
return $this->values[$name] ?? null;
|
|
}
|
|
|
|
public function __set(string $name, mixed $value): void
|
|
{
|
|
echo "first:set:$name=$value\n";
|
|
$this->values[$name] = $value;
|
|
}
|
|
}
|
|
|
|
class SecondMagicProperty
|
|
{
|
|
private array $values = [];
|
|
|
|
public function __get(string $name): mixed
|
|
{
|
|
echo "second:get:$name\n";
|
|
return $this->values[$name] ?? null;
|
|
}
|
|
|
|
public function __set(string $name, mixed $value): void
|
|
{
|
|
echo "second:set:$name=$value\n";
|
|
$this->values[$name] = $value;
|
|
}
|
|
}
|
|
|
|
#[AllowDynamicProperties]
|
|
class MaterializingMagicProperty
|
|
{
|
|
public int $setCalls = 0;
|
|
|
|
public function __set(string $name, mixed $value): void
|
|
{
|
|
$this->setCalls++;
|
|
$this->{$name} = $value;
|
|
}
|
|
}
|
|
|
|
final class RecursiveMagicProperty
|
|
{
|
|
public int $getCalls = 0;
|
|
|
|
public function __get(string $name): mixed
|
|
{
|
|
$this->getCalls++;
|
|
return @$this->{$name};
|
|
}
|
|
}
|
|
|
|
final class ThrowingMagicProperty
|
|
{
|
|
public int $getCalls = 0;
|
|
public int $setCalls = 0;
|
|
|
|
public function __get(string $name): mixed
|
|
{
|
|
$this->getCalls++;
|
|
if ($this->getCalls === 1) {
|
|
throw new RuntimeException('get failed');
|
|
}
|
|
return 77;
|
|
}
|
|
|
|
public function __set(string $name, mixed $value): void
|
|
{
|
|
$this->setCalls++;
|
|
if ($this->setCalls === 1) {
|
|
throw new RuntimeException('set failed');
|
|
}
|
|
}
|
|
}
|
|
|
|
function readNamedProperty(object $object): mixed
|
|
{
|
|
return $object->value;
|
|
}
|
|
|
|
function writeNamedProperty(object $object, mixed $value): void
|
|
{
|
|
$object->value = $value;
|
|
}
|
|
|
|
function readDynamicProperty(object $object, string $name): mixed
|
|
{
|
|
return $object->{$name};
|
|
}
|
|
|
|
function writeDynamicProperty(object $object, string $name, mixed $value): void
|
|
{
|
|
$object->{$name} = $value;
|
|
}
|
|
|
|
function namedPropertyReceiver(object $object, int &$calls): object
|
|
{
|
|
$calls++;
|
|
return $object;
|
|
}
|
|
|
|
function main(): void
|
|
{
|
|
$first = new FirstMagicProperty();
|
|
$second = new SecondMagicProperty();
|
|
|
|
// One generated access site sees different runtime class entries. Zend
|
|
// must invalidate and refill the polymorphic cache rather than reusing the
|
|
// first class's magic-property result.
|
|
writeNamedProperty($first, 10);
|
|
writeNamedProperty($second, 20);
|
|
writeNamedProperty($first, 30);
|
|
var_dump(readNamedProperty($first));
|
|
var_dump(readNamedProperty($second));
|
|
var_dump(readNamedProperty($first));
|
|
|
|
// A dynamic property-name expression deliberately remains uncached.
|
|
writeDynamicProperty($second, 'other', 40);
|
|
var_dump(readDynamicProperty($second, 'other'));
|
|
|
|
// __set() materializes a real dynamic property on its first invocation.
|
|
// The cached dynamic-property sentinel must continue to dispatch through
|
|
// Zend so the second write reaches the newly created property directly.
|
|
$materialized = new MaterializingMagicProperty();
|
|
writeNamedProperty($materialized, 1);
|
|
writeNamedProperty($materialized, 2);
|
|
var_dump($materialized->setCalls, readNamedProperty($materialized));
|
|
|
|
// The direct TypePHP path owns the same per-name Zend recursion guard.
|
|
// Re-reading the same missing property from __get() must not recurse.
|
|
$recursive = new RecursiveMagicProperty();
|
|
var_dump(readNamedProperty($recursive), $recursive->getCalls);
|
|
|
|
// A direct magic method may throw a C++ exception. Its RAII guard must be
|
|
// released before the next access, just as Zend clears its guard after a
|
|
// VM-level exception.
|
|
$throwing = new ThrowingMagicProperty();
|
|
try {
|
|
writeNamedProperty($throwing, 1);
|
|
} catch (RuntimeException $e) {
|
|
echo "set caught\n";
|
|
}
|
|
writeNamedProperty($throwing, 2);
|
|
var_dump($throwing->setCalls);
|
|
try {
|
|
readNamedProperty($throwing);
|
|
} catch (RuntimeException $e) {
|
|
echo "get caught\n";
|
|
}
|
|
var_dump(readNamedProperty($throwing), $throwing->getCalls);
|
|
|
|
// A parenthesized receiver is cacheable, but is still evaluated once.
|
|
$calls = std::any(0);
|
|
namedPropertyReceiver($first, $calls)->value = 50;
|
|
var_dump($calls, namedPropertyReceiver($first, $calls)->value, $calls);
|
|
}
|
|
?>
|
|
--EXPECT--
|
|
first:set:value=10
|
|
second:set:value=20
|
|
first:set:value=30
|
|
first:get:value
|
|
int(30)
|
|
second:get:value
|
|
int(20)
|
|
first:get:value
|
|
int(30)
|
|
second:set:other=40
|
|
second:get:other
|
|
int(40)
|
|
int(1)
|
|
int(2)
|
|
NULL
|
|
int(1)
|
|
set caught
|
|
int(2)
|
|
get caught
|
|
int(77)
|
|
int(2)
|
|
first:set:value=50
|
|
first:get:value
|
|
int(1)
|
|
int(50)
|
|
int(2)
|
|
|