Permalink
Please sign in to comment.
Browse files
Add Closure::fromCallable().
Add the ability to create closures from callable as part of RFC: https://wiki.php.net/rfc/closurefromcallable
- Loading branch information...
Showing
with
659 additions
and 14 deletions.
187
Zend/tests/closures/closureFunction.inc
| @@ -0,0 +1,187 @@ | ||
| +<?php | ||
| + | ||
| +function bar($param1) | ||
| +{ | ||
| + return $param1; | ||
| +} | ||
| + | ||
| + | ||
| +$closure = function($param1) { | ||
| + return $param1; | ||
| +}; | ||
| + | ||
| +function test($fn) | ||
| +{ | ||
| + static $count = 0; | ||
| + $input = "foo".$count; | ||
| + $count++; | ||
| + | ||
| + $output = $fn($input); | ||
| + return $input === $output; | ||
| +} | ||
| + | ||
| +class Foo | ||
| +{ | ||
| + public static function publicStaticFunction($param1) | ||
| + { | ||
| + return $param1; | ||
| + } | ||
| + | ||
| + private static function privateStaticFunction($param1) | ||
| + { | ||
| + return $param1; | ||
| + } | ||
| + | ||
| + protected static function protectedStaticFunction($param1) | ||
| + { | ||
| + return $param1; | ||
| + } | ||
| + | ||
| + private function privateInstanceFunc($param1) | ||
| + { | ||
| + return $param1; | ||
| + } | ||
| + | ||
| + protected function protectedInstanceFunc($param1) | ||
| + { | ||
| + return $param1; | ||
| + } | ||
| + | ||
| + | ||
| + public function publicInstanceFunc($param1) | ||
| + { | ||
| + return $param1; | ||
| + } | ||
| + | ||
| + public function closePrivateValid() | ||
| + { | ||
| + return Closure::fromCallable([$this, 'privateInstanceFunc']); | ||
| + } | ||
| + | ||
| + public function closePrivateStatic() | ||
| + { | ||
| + return Closure::fromCallable([__CLASS__, 'privateStaticFunction']); | ||
| + } | ||
| + | ||
| + public function bar($param1) | ||
| + { | ||
| + echo "this is bar\n"; | ||
| + } | ||
| + | ||
| + public function getCallable() | ||
| + { | ||
| + return Closure::fromCallable([$this, 'publicInstanceFunc']); | ||
| + } | ||
| + | ||
| + public function getSelfPublicInstance() | ||
| + { | ||
| + return Closure::fromCallable([$this, 'publicInstanceFunc']); | ||
| + } | ||
| + | ||
| + public function getSelfColonPublicInstanceMethod() | ||
| + { | ||
| + return Closure::fromCallable('self::publicInstanceFunc'); | ||
| + } | ||
| +} | ||
| + | ||
| + | ||
| + | ||
| +class SubFoo extends Foo { | ||
| + | ||
| + public function closePrivateStaticInvalid() | ||
| + { | ||
| + return Closure::fromCallable([__CLASS__, 'privateStaticFunction']); | ||
| + } | ||
| + | ||
| + | ||
| + public function closePrivateInvalid() | ||
| + { | ||
| + return Closure::fromCallable([$this, 'privateInstanceFunc']); | ||
| + } | ||
| + | ||
| + public function closeProtectedStaticMethod() | ||
| + { | ||
| + return Closure::fromCallable([__CLASS__, 'protectedStaticFunction']); | ||
| + } | ||
| + | ||
| + public function closeProtectedValid() | ||
| + { | ||
| + return Closure::fromCallable([$this, 'protectedInstanceFunc']); | ||
| + } | ||
| + | ||
| + public function getParentPublicInstanceMethod() | ||
| + { | ||
| + return Closure::fromCallable('parent::publicInstanceFunc'); | ||
| + } | ||
| + | ||
| + public function getSelfColonParentPublicInstanceMethod() | ||
| + { | ||
| + return Closure::fromCallable('self::publicInstanceFunc'); | ||
| + } | ||
| + | ||
| + | ||
| + public function getSelfColonParentProtectedInstanceMethod() | ||
| + { | ||
| + return Closure::fromCallable('self::protectedInstanceFunc'); | ||
| + } | ||
| + | ||
| + public function getSelfColonParentPrivateInstanceMethod() | ||
| + { | ||
| + return Closure::fromCallable('self::privateInstanceFunc'); | ||
| + } | ||
| +} | ||
| + | ||
| + | ||
| +class MagicCall | ||
| +{ | ||
| + public function __call($name, $arguments) | ||
| + { | ||
| + $info = ['__call']; | ||
| + $info[] = $name; | ||
| + $info = array_merge($info, $arguments); | ||
| + return implode(',', $info); | ||
| + } | ||
| + | ||
| + public static function __callStatic($name, $arguments) | ||
| + { | ||
| + $info = ['__callStatic']; | ||
| + $info[] = $name; | ||
| + $info = array_merge($info, $arguments); | ||
| + return implode(',', $info); | ||
| + } | ||
| +} | ||
| + | ||
| + | ||
| + | ||
| +class PublicInvokable | ||
| +{ | ||
| + public function __invoke($param1) | ||
| + { | ||
| + return $param1; | ||
| + } | ||
| +} | ||
| + | ||
| + | ||
| +function functionAccessProtected() | ||
| +{ | ||
| + $foo = new Foo; | ||
| + | ||
| + return Closure::fromCallable([$foo, 'protectedStaticFunction']); | ||
| +} | ||
| + | ||
| +function functionAccessPrivate() | ||
| +{ | ||
| + $foo = new Foo; | ||
| + | ||
| + return Closure::fromCallable([$foo, 'privateStaticFunction']); | ||
| +} | ||
| + | ||
| + | ||
| +function functionAccessMethodDoesntExist() | ||
| +{ | ||
| + $foo = new Foo; | ||
| + | ||
| + return Closure::fromCallable([$foo, 'thisDoesNotExist']); | ||
| +} | ||
| + | ||
| +?> |
122
Zend/tests/closures/closureFunction_basic.phpt
| @@ -0,0 +1,122 @@ | ||
| +--TEST-- | ||
| +Testing closure() functionality | ||
| +--FILE-- | ||
| +<?php | ||
| + | ||
| +include('closureFunction.inc'); | ||
| + | ||
| +echo 'Access public static function'; | ||
| +$fn = Closure::fromCallable(['Foo', 'publicStaticFunction']); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Access public static function with different case'; | ||
| +$fn = Closure::fromCallable(['fOo', 'publicStaticfUNCTION']); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Access public static function with colon scheme'; | ||
| +$fn = Closure::fromCallable('Foo::publicStaticFunction'); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Access public instance method of object'; | ||
| +$fn = Closure::fromCallable([new Foo, 'publicInstanceFunc']); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Access public instance method of parent object through parent:: '; | ||
| +$fn = Closure::fromCallable([new Foo, 'publicInstanceFunc']); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Function that exists'; | ||
| +$fn = Closure::fromCallable('bar'); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Function that exists with different spelling'; | ||
| +$fn = Closure::fromCallable('BAR'); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Closure is already a closure'; | ||
| +$fn = Closure::fromCallable($closure); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Class with public invokable'; | ||
| +$fn = Closure::fromCallable(new PublicInvokable); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo "Instance return private method as callable"; | ||
| +$foo = new Foo; | ||
| +$fn = $foo->closePrivateValid(); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo "Instance return private static method as callable"; | ||
| +$foo = new Foo; | ||
| +$fn = $foo->closePrivateStatic(); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Instance return protected static method as callable'; | ||
| +$subFoo = new SubFoo; | ||
| +$fn = $subFoo->closeProtectedStaticMethod(); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Subclass closure over parent class protected method'; | ||
| +$subFoo = new SubFoo; | ||
| +$fn = $subFoo->closeProtectedValid(); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Subclass closure over parent class static protected method'; | ||
| +$subFoo = new SubFoo; | ||
| +$fn = $subFoo->closeProtectedStaticMethod(); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Access public instance method of parent object through "parent::" '; | ||
| +$subFoo = new SubFoo; | ||
| +$fn = $subFoo->getParentPublicInstanceMethod(); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Access public instance method of self object through "self::" '; | ||
| +$foo = new Foo; | ||
| +$fn = $foo->getSelfColonPublicInstanceMethod(); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Access public instance method of parent object through "self::" to parent method'; | ||
| +$foo = new SubFoo; | ||
| +$fn = $foo->getSelfColonParentPublicInstanceMethod(); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'Access proteced instance method of parent object through "self::" to parent method'; | ||
| +$foo = new SubFoo; | ||
| +$fn = $foo->getSelfColonParentProtectedInstanceMethod(); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'MagicCall __call instance method '; | ||
| +$fn = Closure::fromCallable([new MagicCall, 'nonExistentMethod']); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| +echo 'MagicCall __callStatic static method '; | ||
| +$fn = Closure::fromCallable(['MagicCall', 'nonExistentMethod']); | ||
| +echo $fn(" OK".PHP_EOL); | ||
| + | ||
| + | ||
| +?> | ||
| +===DONE=== | ||
| +--EXPECT-- | ||
| + | ||
| +Access public static function OK | ||
| +Access public static function with different case OK | ||
| +Access public static function with colon scheme OK | ||
| +Access public instance method of object OK | ||
| +Access public instance method of parent object through parent:: OK | ||
| +Function that exists OK | ||
| +Function that exists with different spelling OK | ||
| +Closure is already a closure OK | ||
| +Class with public invokable OK | ||
| +Instance return private method as callable OK | ||
| +Instance return private static method as callable OK | ||
| +Instance return protected static method as callable OK | ||
| +Subclass closure over parent class protected method OK | ||
| +Subclass closure over parent class static protected method OK | ||
| +Access public instance method of parent object through "parent::" OK | ||
| +Access public instance method of self object through "self::" OK | ||
| +Access public instance method of parent object through "self::" to parent method OK | ||
| +Access proteced instance method of parent object through "self::" to parent method OK | ||
| +MagicCall __call instance method __call,nonExistentMethod, OK | ||
| +MagicCall __callStatic static method __callStatic,nonExistentMethod, OK | ||
| +===DONE=== |
215
Zend/tests/closures/closureFunction_error.phpt
| @@ -0,0 +1,215 @@ | ||
| +--TEST-- | ||
| +Testing closure() functionality | ||
| +--FILE-- | ||
| +<?php | ||
| + | ||
| +include('closureFunction.inc'); | ||
| + | ||
| +echo 'Cannot access privateInstance method statically'."\n"; | ||
| +try { | ||
| + $fn = Closure::fromCallable(['Foo', 'privateInstanceFunc']); | ||
| + echo "Test failed to fail and return was : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| + | ||
| +echo 'Cannot access privateInstance method statically with colon scheme'."\n"; | ||
| +try { | ||
| + $fn = Closure::fromCallable('Foo::privateInstanceFunc'); | ||
| + echo "Test failed to fail and return was : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'Cannot access privateInstance method'."\n"; | ||
| +try { | ||
| + $fn = Closure::fromCallable([new Foo, 'privateInstanceFunc']); | ||
| + echo "Test failed to fail and return was : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'SubClass cannot access private instance method'."\n"; | ||
| +try { | ||
| + $fn = Closure::fromCallable([new SubFoo, 'privateInstanceFunc']); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'Cannot access private static function of instance'."\n"; | ||
| +try { | ||
| + $fn = Closure::fromCallable([new Foo, 'privateStaticFunction']); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'Cannot access private static method statically'."\n"; | ||
| +try { | ||
| + $fn = Closure::fromCallable(['Foo', 'privateStaticFunction']); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'Cannot access private static method statically with colon scheme'."\n"; | ||
| +try { | ||
| + $fn = Closure::fromCallable('Foo::privateStaticFunction'); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'Non-existent method should fail'."\n"; | ||
| +try { | ||
| + $fn = Closure::fromCallable('Foo::nonExistentFunction'); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'Non-existent class should fail'."\n"; | ||
| +try { | ||
| + $fn = Closure::fromCallable(['NonExistentClass', 'foo']); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'Non-existent function should fail'."\n"; | ||
| +try { | ||
| + $fn = Closure::fromCallable('thisDoesNotExist'); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| + | ||
| +echo 'Subclass cannot closure over parent private instance method'."\n"; | ||
| +try { | ||
| + $subFoo = new SubFoo; | ||
| + $fn = $subFoo->closePrivateInvalid(); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'Subclass cannot closure over parant private static method'."\n"; | ||
| +try { | ||
| + $subFoo = new SubFoo; | ||
| + $fn = $subFoo->closePrivateStaticInvalid(); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'Function scope cannot closure over protected instance method'."\n"; | ||
| +try { | ||
| + $fn = functionAccessProtected(); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'Function scope cannot closure over private instance method'."\n"; | ||
| +try { | ||
| + $fn = functionAccessPrivate(); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo 'Access private instance method of parent object through "self::" to parent method'."\n"; | ||
| +try { | ||
| + $foo = new SubFoo; | ||
| + $fn = $foo->getSelfColonParentPrivateInstanceMethod(); | ||
| + echo "Test failed to fail, closure is : ".var_export($fn, true)."\n"; | ||
| +} | ||
| +catch (\TypeError $te) { | ||
| + //This is the expected outcome. | ||
| +} | ||
| +catch (\Throwable $t) { | ||
| + echo "Wrong exception type thrown: ".get_class($t)." : ".$t->getMessage()."\n"; | ||
| +} | ||
| + | ||
| +echo "OK\n"; | ||
| + | ||
| +?> | ||
| +===DONE=== | ||
| +--EXPECT-- | ||
| + | ||
| +Cannot access privateInstance method statically | ||
| +Cannot access privateInstance method statically with colon scheme | ||
| +Cannot access privateInstance method | ||
| +SubClass cannot access private instance method | ||
| +Cannot access private static function of instance | ||
| +Cannot access private static method statically | ||
| +Cannot access private static method statically with colon scheme | ||
| +Non-existent method should fail | ||
| +Non-existent class should fail | ||
| +Non-existent function should fail | ||
| +Subclass cannot closure over parent private instance method | ||
| +Subclass cannot closure over parant private static method | ||
| +Function scope cannot closure over protected instance method | ||
| +Function scope cannot closure over private instance method | ||
| +Access private instance method of parent object through "self::" to parent method | ||
| +OK | ||
| +===DONE=== |
104
Zend/zend_closures.c
45
ext/reflection/php_reflection.c
0 comments on commit
63ca65d