- Add ATTR_SCOPED_CALLBACK attribute to track callback scope requirements - Introduce getCallableScopeExpr method to generate proper scope expressions - Modify call argument generation to wrap callbacks with scope information - Update dynamic method call logic to preserve declaring class scope - Replace legacy markRuntimeObjectMethodCall with markUnpackedScopedCallbackCall - Add support for scoped callbacks in array functions like array_map - Implement proper scope handling for nullsafe method calls - Generate scoped callable wrappers in placeholder generator - Add comprehensive tests for dynamic method lexical scope behavior - Add tests for internal callback functions preserving method scopepull/48/head
parent
f5ca997e65
commit
8e48b9d7fe
7 changed files with 209 additions and 83 deletions
@ -0,0 +1,53 @@ |
||||
--TEST-- |
||||
Dynamic method calls use the declaring class scope |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class DynamicScopeBase |
||||
{ |
||||
private function privateValue(): string |
||||
{ |
||||
return 'base-private'; |
||||
} |
||||
|
||||
public function callPrivate(): string |
||||
{ |
||||
$method = 'privateValue'; |
||||
return $this->$method(); |
||||
} |
||||
|
||||
public function callSubclassHook(): string |
||||
{ |
||||
return $this->subclassHook(); |
||||
} |
||||
|
||||
public function callPrivateCallback(): array |
||||
{ |
||||
return array_map([$this, 'privateValue'], [null]); |
||||
} |
||||
} |
||||
|
||||
class DynamicScopeChild extends DynamicScopeBase |
||||
{ |
||||
protected function subclassHook(): string |
||||
{ |
||||
return 'child-protected'; |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$object = new DynamicScopeChild(); |
||||
var_dump($object->callPrivate()); |
||||
var_dump($object->callSubclassHook()); |
||||
var_dump($object->callPrivateCallback()); |
||||
} |
||||
|
||||
?> |
||||
--EXPECT-- |
||||
string(12) "base-private" |
||||
string(15) "child-protected" |
||||
array(1) { |
||||
[0]=> |
||||
string(12) "base-private" |
||||
} |
||||
@ -0,0 +1,73 @@ |
||||
--TEST-- |
||||
Internal callback functions preserve the declaring method scope |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class ScopedInternalCallbacks |
||||
{ |
||||
private function triple(int $value): int |
||||
{ |
||||
return $value * 3; |
||||
} |
||||
|
||||
private static function replace(array $match): string |
||||
{ |
||||
return strtoupper($match[0]); |
||||
} |
||||
|
||||
private static function compareValue(mixed $left, mixed $right): int |
||||
{ |
||||
return $left <=> $right; |
||||
} |
||||
|
||||
private static function compareKey(mixed $left, mixed $right): int |
||||
{ |
||||
return $left <=> $right; |
||||
} |
||||
|
||||
public function run(): void |
||||
{ |
||||
var_dump(array_map([$this, 'triple'], [1, 2, 3])); |
||||
var_dump(array_map(array: [4, 5], callback: [$this, 'triple'])); |
||||
var_dump(preg_replace_callback_array([ |
||||
'/a+/' => [self::class, 'replace'], |
||||
], 'caaab')); |
||||
var_dump(array_udiff_uassoc( |
||||
['a' => 1, 'b' => 2], |
||||
['a' => 1, 'c' => 2], |
||||
[self::class, 'compareValue'], |
||||
[self::class, 'compareKey'], |
||||
)); |
||||
|
||||
$unpacked = [[$this, 'triple'], 6]; |
||||
var_dump(call_user_func(...$unpacked)); |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
(new ScopedInternalCallbacks())->run(); |
||||
} |
||||
|
||||
?> |
||||
--EXPECT-- |
||||
array(3) { |
||||
[0]=> |
||||
int(3) |
||||
[1]=> |
||||
int(6) |
||||
[2]=> |
||||
int(9) |
||||
} |
||||
array(2) { |
||||
[0]=> |
||||
int(12) |
||||
[1]=> |
||||
int(15) |
||||
} |
||||
string(5) "cAAAb" |
||||
array(1) { |
||||
["b"]=> |
||||
int(2) |
||||
} |
||||
int(18) |
||||
Loading…
Reference in new issue