- Add normalizeCallableClass helper for dynamic callback normalization - Introduce FunctionContext::needsUserCodeCallableScope for user-code scope tracking - Replace MethodDef::needsUnpackedCallbackScope with per-context scope management - Update callback descriptors to include handling strategies (callable, dynamic, map) - Implement proper callable scope guard generation for closures and fibers - Add propertyNameToStr method for correct property name conversion - Refactor scope guard generation to use CallableScope with lexical scope - Update documentation for enhanced scope management system - Add tests for complex scoped callback scenarios - Fix parent class name normalization in property access resolver - Remove deprecated unpacked callback scope fallback mechanismpull/48/head
parent
8a3b5ae9a4
commit
e07b97d6cf
20 changed files with 328 additions and 81 deletions
@ -0,0 +1,44 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace TypePhp\Tests\Resolver; |
||||||
|
|
||||||
|
use PhpParser\NodeAbstract; |
||||||
|
use PHPUnit\Framework\TestCase; |
||||||
|
use TypePhp\Entity\ClassDef; |
||||||
|
use TypePhp\Resolver\PropertyAccessContext; |
||||||
|
use TypePhp\Resolver\PropertyAccessResolver; |
||||||
|
|
||||||
|
final class PropertyAccessResolverTest extends TestCase |
||||||
|
{ |
||||||
|
public function testSubclassLookupNormalizesEveryParentName(): void |
||||||
|
{ |
||||||
|
$parents = [ |
||||||
|
'app\\childexception' => 'RuntimeException', |
||||||
|
]; |
||||||
|
$context = new class($parents) implements PropertyAccessContext { |
||||||
|
public function __construct(private array $parents) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function getClassDef(string $name): ?ClassDef |
||||||
|
{ |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public function getParentClass(string $class): string |
||||||
|
{ |
||||||
|
return $this->parents[strtolower(ltrim($class, '\\'))] ?? ''; |
||||||
|
} |
||||||
|
|
||||||
|
public function fatalError(NodeAbstract $node, string $msg): never |
||||||
|
{ |
||||||
|
throw new \LogicException($msg); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
$resolver = new PropertyAccessResolver($context); |
||||||
|
|
||||||
|
$this->assertTrue($resolver->isSameOrSubclassOf('App\\ChildException', 'runtimeexception')); |
||||||
|
$this->assertTrue($resolver->canAccessProtectedProperty('App\\ChildException', 'RuntimeException')); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,128 @@ |
|||||||
|
--TEST-- |
||||||
|
Scoped callbacks preserve relative, inherited, delayed and nested call contexts |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class ComplexScopeBase |
||||||
|
{ |
||||||
|
private static function privateMap(int $value): string |
||||||
|
{ |
||||||
|
return static::class . ':private:' . $value; |
||||||
|
} |
||||||
|
|
||||||
|
protected static function protectedMap(int $value): string |
||||||
|
{ |
||||||
|
return static::class . ':protected:' . $value; |
||||||
|
} |
||||||
|
|
||||||
|
public function dynamicRelativeCallbacks(): void |
||||||
|
{ |
||||||
|
$self = 'self'; |
||||||
|
$static = 'static'; |
||||||
|
var_dump(array_map([$self, 'privateMap'], [1])); |
||||||
|
var_dump(array_map([$static, 'protectedMap'], [2])); |
||||||
|
} |
||||||
|
|
||||||
|
public function delayedUnpackedCallback(): Closure |
||||||
|
{ |
||||||
|
return function (int $value): string { |
||||||
|
$arguments = [['self', 'privateMap'], $value]; |
||||||
|
return call_user_func(...$arguments); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public function unpackedCallbackArray(int $value): string |
||||||
|
{ |
||||||
|
$static = 'static'; |
||||||
|
$arguments = [[$static, 'protectedMap'], [$value]]; |
||||||
|
return call_user_func_array(...$arguments); |
||||||
|
} |
||||||
|
|
||||||
|
public function nestedDynamicClosure(int $value): string |
||||||
|
{ |
||||||
|
$callback = function (int $innerValue): string { |
||||||
|
$arguments = [['self', 'privateMap'], $innerValue]; |
||||||
|
return call_user_func(...$arguments); |
||||||
|
}; |
||||||
|
return call_user_func($callback, $value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ComplexScopeChild extends ComplexScopeBase |
||||||
|
{ |
||||||
|
public function parentCallback(): void |
||||||
|
{ |
||||||
|
$parent = 'parent'; |
||||||
|
var_dump(array_map([$parent, 'protectedMap'], [3])); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class NestedScopeOuter |
||||||
|
{ |
||||||
|
private function invokeInner(int $value): string |
||||||
|
{ |
||||||
|
return (new NestedScopeInner())->run($value) . ':outer'; |
||||||
|
} |
||||||
|
|
||||||
|
public function callback(): array |
||||||
|
{ |
||||||
|
return [$this, 'invokeInner']; |
||||||
|
} |
||||||
|
|
||||||
|
public function run(int $value): string |
||||||
|
{ |
||||||
|
$arguments = [[$this, 'invokeInner'], $value]; |
||||||
|
return call_user_func(...$arguments); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class NestedScopeInner |
||||||
|
{ |
||||||
|
private function value(int $value): string |
||||||
|
{ |
||||||
|
return 'inner:' . $value; |
||||||
|
} |
||||||
|
|
||||||
|
public function run(int $value): string |
||||||
|
{ |
||||||
|
$arguments = [[$this, 'value'], $value]; |
||||||
|
return call_user_func(...$arguments); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$object = new ComplexScopeChild(); |
||||||
|
$object->dynamicRelativeCallbacks(); |
||||||
|
$object->parentCallback(); |
||||||
|
|
||||||
|
$delayed = $object->delayedUnpackedCallback(); |
||||||
|
var_dump($delayed(4)); |
||||||
|
var_dump($object->unpackedCallbackArray(5)); |
||||||
|
var_dump($object->nestedDynamicClosure(6)); |
||||||
|
|
||||||
|
$nested = new NestedScopeOuter(); |
||||||
|
$privateCallback = $nested->callback(); |
||||||
|
var_dump($nested->run(7)); |
||||||
|
var_dump(is_callable($privateCallback)); |
||||||
|
} |
||||||
|
|
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
string(27) "ComplexScopeChild:private:1" |
||||||
|
} |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
string(29) "ComplexScopeChild:protected:2" |
||||||
|
} |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
string(29) "ComplexScopeChild:protected:3" |
||||||
|
} |
||||||
|
string(26) "ComplexScopeBase:private:4" |
||||||
|
string(29) "ComplexScopeChild:protected:5" |
||||||
|
string(26) "ComplexScopeBase:private:6" |
||||||
|
string(13) "inner:7:outer" |
||||||
|
bool(false) |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
--TEST-- |
||||||
|
An instance property named static is not treated as late static binding |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class StaticPropertyValue |
||||||
|
{ |
||||||
|
public string $static = 'value'; |
||||||
|
} |
||||||
|
|
||||||
|
class StaticPropertyReader |
||||||
|
{ |
||||||
|
public function read(?StaticPropertyValue $value): void |
||||||
|
{ |
||||||
|
var_dump($value->static); |
||||||
|
var_dump($value?->static); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
(new StaticPropertyReader())->read(new StaticPropertyValue()); |
||||||
|
} |
||||||
|
|
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(5) "value" |
||||||
|
string(5) "value" |
||||||
Loading…
Reference in new issue