From 03243e10e259f83aab6eaf7047999d39f1b9d8fb Mon Sep 17 00:00:00 2001 From: Nicolas Oelgart Date: Thu, 28 Nov 2019 01:02:47 +0100 Subject: [PATCH] Add support for custom object calls --- src/TokenStream/AST.php | 53 ++++++++++++------ src/TokenStream/Token/TokenObject.php | 4 +- tests/integration/ObjectTest.php | 78 ++++++++++++++++++++++++++- 3 files changed, 116 insertions(+), 19 deletions(-) diff --git a/src/TokenStream/AST.php b/src/TokenStream/AST.php index 909a1d8..311bf95 100644 --- a/src/TokenStream/AST.php +++ b/src/TokenStream/AST.php @@ -10,6 +10,7 @@ use Closure; use InvalidArgumentException; use nicoSWD\Rule\Grammar\CallableUserFunctionInterface; +use nicoSWD\Rule\Parser\Exception\ParserException; use nicoSWD\Rule\TokenStream\Exception\UndefinedVariableException; use nicoSWD\Rule\TokenStream\Token\BaseToken; use nicoSWD\Rule\TokenStream\Token\TokenFactory; @@ -49,7 +50,7 @@ public function getStream(string $rule): TokenStream public function getMethod(string $methodName, BaseToken $token): CallableUserFunctionInterface { if ($token instanceof TokenObject) { - return $this->getUserObjectCallable($token, $methodName); + return $this->getCallableUserObject($token, $methodName); } if (empty($this->methods)) { @@ -126,33 +127,55 @@ private function registerFunctions() } } - private function getUserObjectCallable(BaseToken $token, string $methodName): CallableUserFunctionInterface + private function getCallableUserObject(BaseToken $token, string $methodName): CallableUserFunctionInterface { - return new class ($token, $this->tokenFactory, $methodName) implements CallableUserFunctionInterface - { - /** @var BaseToken */ - private $token; + return new class ($token, $this->tokenFactory, $methodName) implements CallableUserFunctionInterface { /** @var TokenFactory */ private $tokenFactory; - /** @var string */ - private $methodName; + /** @var Closure */ + private $callable; + /** @var array */ + private $variations = ['get', 'is', '']; public function __construct(BaseToken $token, TokenFactory $tokenFactory, string $methodName) { - $this->token = $token; $this->tokenFactory = $tokenFactory; - $this->methodName = $methodName; + $this->callable = $this->getCallable($token, $methodName); } - public function call(BaseToken $param = null): BaseToken + private function getCallable(BaseToken $token, string $methodName): Closure { - $object = [$this->token->getValue(), $this->methodName]; + $object = $token->getValue(); - if (!is_callable($object)) { - throw new \Exception(); + if (property_exists($object, $methodName)) { + return function () use ($object, $methodName) { + return $object->{$methodName}; + }; } - return $this->tokenFactory->createFromPHPType($object()); + $method[0] = $object; + $index = 0; + + do { + if (!isset($this->variations[$index])) { + throw ParserException::undefinedMethod($methodName, $token); + } + + $method[1] = $this->variations[$index] . $methodName; + } while (!is_callable($method) && isset($this->variations[$index++])); + + return function (BaseToken $param = null) use ($method) { + return $method($param ? $param->getValue() : null); + }; + } + + public function call(BaseToken $param = null): BaseToken + { + $callable = $this->callable; + + return $this->tokenFactory->createFromPHPType( + $callable($param) + ); } }; } diff --git a/src/TokenStream/Token/TokenObject.php b/src/TokenStream/Token/TokenObject.php index e03f832..c6c3e0a 100644 --- a/src/TokenStream/Token/TokenObject.php +++ b/src/TokenStream/Token/TokenObject.php @@ -9,8 +9,8 @@ final class TokenObject extends BaseToken { - public function getType() : int + public function getType(): int { - return TokenType::OBJECT; + return TokenType::VALUE; } } diff --git a/tests/integration/ObjectTest.php b/tests/integration/ObjectTest.php index be9b0b1..7545e6b 100755 --- a/tests/integration/ObjectTest.php +++ b/tests/integration/ObjectTest.php @@ -5,6 +5,8 @@ * @link https://github.com/nicoSWD * @author Nicolas Oelgart */ + +use nicoSWD\Rule\Rule; use nicoSWD\Rule\tests\integration\AbstractTestBase; final class ObjectTest extends AbstractTestBase @@ -15,14 +17,86 @@ public function testObjects() function test() { return 'test one two'; } + + function test2() { + return new class () + { + function miau() { + return 'miau'; + } + }; + } }; $variables = [ 'my_obj' => $myObj, - 'my_string' => 'some test' ]; $this->assertTrue($this->evaluate('my_obj.test() === "test one two"', $variables)); - $this->assertFalse($this->evaluate('my_obj.test() === "oh no"', $variables)); + $this->assertTrue($this->evaluate('my_obj.test2().miau() === "miau"', $variables)); + } + + public function testPublicPropertyShouldBeAccessible() + { + $myObj = new class { + public $test = 'my string'; + }; + + $variables = [ + 'my_obj' => $myObj, + ]; + + $this->assertTrue($this->evaluate('my_obj.test() === "my string"', $variables)); + } + + public function testPublicMethodsShouldBeAccessibleMagicallyViaGet() + { + $myObj = new class { + public function getString() + { + return 'some string'; + } + }; + + $variables = [ + 'my_obj' => $myObj, + ]; + + $this->assertTrue($this->evaluate('my_obj.string() === "some string"', $variables)); + } + + public function testPublicMethodsShouldBeAccessibleMagicallyViaIs() + { + $myObj = new class { + public function isString($string) + { + return $string; + } + + public function yes() + { + return 'yes'; + } + }; + + $variables = [ + 'my_obj' => $myObj, + ]; + + $this->assertTrue($this->evaluate('my_obj.string(my_obj.yes()) === "yes"', $variables)); + } + + public function testUndefinedMethodsShouldThrowAnError() + { + $myObj = new class {}; + + $variables = [ + 'my_obj' => $myObj, + ]; + + $rule = new Rule('my_obj.nope() === false', $variables); + + $this->assertFalse($rule->isValid()); + $this->assertSame('Undefined method "nope" at position 0', $rule->getError()); } }