parent
b6d0488ea6
commit
c94be9c6a9
14 changed files with 478 additions and 30 deletions
@ -0,0 +1,32 @@ |
|||||||
|
--TEST-- |
||||||
|
anonymous class stores constructor state and uses it in methods |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
abstract class AnonStateVisitor |
||||||
|
{ |
||||||
|
abstract public function visit(string $name): string; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$prefixes = ['node' => 'Node', 'leaf' => 'Leaf']; |
||||||
|
|
||||||
|
$visitor = new class($prefixes) extends AnonStateVisitor { |
||||||
|
public function __construct(private array $prefixes) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function visit(string $name): string |
||||||
|
{ |
||||||
|
return ($this->prefixes[$name] ?? 'Unknown') . ':' . $name; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
var_dump($visitor->visit('node')); |
||||||
|
var_dump($visitor->visit('missing')); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(9) "Node:node" |
||||||
|
string(15) "Unknown:missing" |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
--TEST-- |
||||||
|
function attributes are available through reflection |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_FUNCTION)] |
||||||
|
class AotFunctionMeta |
||||||
|
{ |
||||||
|
public function __construct(public string $name, public int $priority = 0) |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[AotFunctionMeta('handler', 10)] |
||||||
|
function attributed_function(): string |
||||||
|
{ |
||||||
|
return 'ok'; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$func = new ReflectionFunction('attributed_function'); |
||||||
|
$attrs = $func->getAttributes(AotFunctionMeta::class); |
||||||
|
|
||||||
|
var_dump($attrs[0]->getName()); |
||||||
|
var_dump($attrs[0]->getArguments()); |
||||||
|
var_dump($attrs[0]->newInstance()->name); |
||||||
|
var_dump(attributed_function()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(15) "AotFunctionMeta" |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(7) "handler" |
||||||
|
[1]=> |
||||||
|
int(10) |
||||||
|
} |
||||||
|
string(7) "handler" |
||||||
|
string(2) "ok" |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
--TEST-- |
||||||
|
function parameter and property attributes are available through reflection |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PARAMETER | Attribute::TARGET_PROPERTY)] |
||||||
|
class AotMeta |
||||||
|
{ |
||||||
|
public function __construct(public string $name, public int $order = 0) |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class AttributeHolder |
||||||
|
{ |
||||||
|
#[AotMeta('property', 1)] |
||||||
|
public string $value = 'ok'; |
||||||
|
} |
||||||
|
|
||||||
|
function attributed_parameter(#[AotMeta('parameter', 2)] string $value): string |
||||||
|
{ |
||||||
|
return $value; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$func = new ReflectionFunction('attributed_parameter'); |
||||||
|
$paramAttrs = $func->getParameters()[0]->getAttributes(AotMeta::class); |
||||||
|
var_dump($paramAttrs[0]->getName()); |
||||||
|
var_dump($paramAttrs[0]->getArguments()); |
||||||
|
|
||||||
|
$prop = new ReflectionProperty(AttributeHolder::class, 'value'); |
||||||
|
$propAttrs = $prop->getAttributes(AotMeta::class); |
||||||
|
var_dump($propAttrs[0]->getName()); |
||||||
|
var_dump($propAttrs[0]->getArguments()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(7) "AotMeta" |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(9) "parameter" |
||||||
|
[1]=> |
||||||
|
int(2) |
||||||
|
} |
||||||
|
string(7) "AotMeta" |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(8) "property" |
||||||
|
[1]=> |
||||||
|
int(1) |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
--TEST-- |
||||||
|
unset evaluates array dimension expressions left to right |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function unset_key(string $key): string |
||||||
|
{ |
||||||
|
echo "unset-key:$key\n"; |
||||||
|
return $key; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$items = ['a' => 1, 'b' => 2, 'c' => 3]; |
||||||
|
|
||||||
|
unset($items[unset_key('a')], $items[unset_key('c')]); |
||||||
|
|
||||||
|
var_dump($items); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
unset-key:a |
||||||
|
unset-key:c |
||||||
|
array(1) { |
||||||
|
["b"]=> |
||||||
|
int(2) |
||||||
|
} |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
--TEST-- |
||||||
|
dynamic new with unpacked constructor args then method call preserves order |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class DynamicNewOrder |
||||||
|
{ |
||||||
|
public function __construct(public string $name) |
||||||
|
{ |
||||||
|
echo "ctor:$name\n"; |
||||||
|
} |
||||||
|
|
||||||
|
public function run(string $suffix): string |
||||||
|
{ |
||||||
|
echo "run:$suffix\n"; |
||||||
|
return $this->name . ':' . $suffix; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function make_new_args(): array |
||||||
|
{ |
||||||
|
echo "new-args\n"; |
||||||
|
return ['object']; |
||||||
|
} |
||||||
|
|
||||||
|
function make_call_arg(): string |
||||||
|
{ |
||||||
|
echo "call-arg\n"; |
||||||
|
return 'method'; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$class = DynamicNewOrder::class; |
||||||
|
var_dump((new $class(...make_new_args()))->run(make_call_arg())); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
new-args |
||||||
|
ctor:object |
||||||
|
call-arg |
||||||
|
run:method |
||||||
|
string(13) "object:method" |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
--TEST-- |
||||||
|
isset and empty evaluate array dimension expressions in order |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function dim_key(string $key): string |
||||||
|
{ |
||||||
|
echo "key:$key\n"; |
||||||
|
return $key; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$data = [ |
||||||
|
'user' => ['name' => 'Alice'], |
||||||
|
'zero' => 0, |
||||||
|
]; |
||||||
|
|
||||||
|
var_dump(isset($data[dim_key('user')][dim_key('name')])); |
||||||
|
var_dump(isset($data[dim_key('missing')][dim_key('nested')])); |
||||||
|
var_dump(empty($data[dim_key('zero')])); |
||||||
|
var_dump(empty($data[dim_key('missing')][dim_key('empty-nested')])); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
key:user |
||||||
|
key:name |
||||||
|
bool(true) |
||||||
|
key:missing |
||||||
|
key:nested |
||||||
|
bool(false) |
||||||
|
key:zero |
||||||
|
bool(true) |
||||||
|
key:missing |
||||||
|
key:empty-nested |
||||||
|
bool(true) |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
--TEST-- |
||||||
|
finally runs for return inside catch without changing return value |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class FinallyCatchReturnException extends Exception {} |
||||||
|
|
||||||
|
function catch_finally_return(): string |
||||||
|
{ |
||||||
|
$state = "before"; |
||||||
|
try { |
||||||
|
throw new FinallyCatchReturnException("failure"); |
||||||
|
} catch (FinallyCatchReturnException $e) { |
||||||
|
$state = "catch:" . $e->getMessage(); |
||||||
|
return $state; |
||||||
|
} finally { |
||||||
|
echo "finally:$state\n"; |
||||||
|
$state = "finally"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(catch_finally_return()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
finally:catch:failure |
||||||
|
string(13) "catch:failure" |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
--TEST-- |
||||||
|
finally runs for return nested in if/else branches without changing return value |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function nested_finally_return(int $value): string |
||||||
|
{ |
||||||
|
$state = "start"; |
||||||
|
try { |
||||||
|
if ($value > 0) { |
||||||
|
$state .= ":positive"; |
||||||
|
return $state; |
||||||
|
} else { |
||||||
|
$state .= ":negative"; |
||||||
|
return $state; |
||||||
|
} |
||||||
|
} finally { |
||||||
|
echo "finally:$state\n"; |
||||||
|
$state .= ":finally"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(nested_finally_return(1)); |
||||||
|
var_dump(nested_finally_return(-1)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
finally:start:positive |
||||||
|
string(14) "start:positive" |
||||||
|
finally:start:negative |
||||||
|
string(14) "start:negative" |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
--TEST-- |
||||||
|
finally side effects run before return and throw leave the frame |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class FinallySideEffectException extends Exception {} |
||||||
|
|
||||||
|
function finally_return_case(): string |
||||||
|
{ |
||||||
|
try { |
||||||
|
echo "try-return\n"; |
||||||
|
return "returned"; |
||||||
|
} finally { |
||||||
|
echo "finally-return\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function finally_throw_case(): void |
||||||
|
{ |
||||||
|
try { |
||||||
|
echo "try-throw\n"; |
||||||
|
throw new FinallySideEffectException("thrown"); |
||||||
|
} finally { |
||||||
|
echo "finally-throw\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(finally_return_case()); |
||||||
|
|
||||||
|
try { |
||||||
|
finally_throw_case(); |
||||||
|
} catch (FinallySideEffectException $e) { |
||||||
|
var_dump($e->getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
try-return |
||||||
|
finally-return |
||||||
|
string(8) "returned" |
||||||
|
try-throw |
||||||
|
finally-throw |
||||||
|
string(6) "thrown" |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
--TEST-- |
||||||
|
array_push with unpacked values mutates first argument once |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function make_push_values(): array |
||||||
|
{ |
||||||
|
echo "make-values\n"; |
||||||
|
return [2, 3]; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$items = [1]; |
||||||
|
$count = array_push($items, ...make_push_values()); |
||||||
|
|
||||||
|
var_dump($count); |
||||||
|
var_dump($items); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
make-values |
||||||
|
int(3) |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
int(1) |
||||||
|
[1]=> |
||||||
|
int(2) |
||||||
|
[2]=> |
||||||
|
int(3) |
||||||
|
} |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
--TEST-- |
||||||
|
preg_match output array can be reused through references |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
preg_match('/(foo)(bar)/', 'foobar', $matches); |
||||||
|
|
||||||
|
$first =& $matches[1]; |
||||||
|
$first = strtoupper($first); |
||||||
|
|
||||||
|
var_dump($matches); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
string(6) "foobar" |
||||||
|
[1]=> |
||||||
|
&string(3) "FOO" |
||||||
|
[2]=> |
||||||
|
string(3) "bar" |
||||||
|
} |
||||||
Loading…
Reference in new issue