diff --git a/CHANGELOG.md b/CHANGELOG.md index 9031879..7dc453d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## 1.2.0 - 2022-12-27 + +* Allow method calls + + ## 1.1.0 - 2021-05-14 * Use Smfony 5 diff --git a/composer.json b/composer.json index 3c8eef4..c39629d 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "type": "library", "description" : "A JUEL like extension for Symfony Expression Language", "homepage": "https://github.com/zef-dev/zef-expression-language", - "version": "1.1.0", + "version": "1.2.0", "keywords" : [ "symfony", "expression-language" ], diff --git a/src/Zef/Zel/Symfony/GetAttrNode.php b/src/Zef/Zel/Symfony/GetAttrNode.php index 2fb40b7..22be2dc 100644 --- a/src/Zef/Zel/Symfony/GetAttrNode.php +++ b/src/Zef/Zel/Symfony/GetAttrNode.php @@ -24,12 +24,17 @@ public function evaluate( $functions, $values) } $property = $this->nodes['attribute']->attributes['value']; - + + if (!$this->_shouldCallMethod($this->attributes['type'], $obj, $property)) { + return null; + } + return $obj->$property; case self::METHOD_CALL: $obj = $this->nodes['node']->evaluate($functions, $values); - + $method = $this->nodes['attribute']->attributes['value']; + if ( is_null( $obj)) { return null; } @@ -41,6 +46,10 @@ public function evaluate( $functions, $values) throw new \RuntimeException(sprintf('Unable to call method "%s" of object "%s".', $this->nodes['attribute']->attributes['value'], \get_class($obj))); } + if (!$this->_shouldCallMethod($this->attributes['type'], $obj, $method)) { + return null; + } + return $toCall(...array_values($this->nodes['arguments']->evaluate($functions, $values))); case self::ARRAY_CALL: @@ -57,5 +66,62 @@ public function evaluate( $functions, $values) return $array[$this->nodes['attribute']->evaluate($functions, $values)] ?? null; } } - -} \ No newline at end of file + + private function _shouldCallMethod($callType, $object, $value) { + $isAccessible = 0; + if (is_array($object)) { + return true; + } + if (is_a($object, 'Zef\Zel\IValueAdapter') && is_array($object->get())) { + return true; + } + switch ($callType) { + case self::PROPERTY_CALL: + if (in_array($value, array_keys(get_object_vars($object)))) { + $isAccessible++; + } + + if (is_a($object, 'Zef\Zel\IValueAdapter') && in_array($value, array_keys(get_object_vars($object->get())))) { + $isAccessible++; + } + + $wrappedClassMethods = get_class_methods($object); + + foreach ($wrappedClassMethods as $wrappedClassMethod) { + if (str_contains(strtolower($wrappedClassMethod), strtolower($value))) { + $isAccessible++; + } + } + + if (is_a( $object, 'Zef\Zel\IValueAdapter')) { + $originalClassMethods = get_class_methods($object->get()); + foreach ($originalClassMethods as $originalClassMethod) { + if (str_contains(strtolower($originalClassMethod), strtolower($value))) { + $isAccessible++; + } + } + } + + break; + case self::METHOD_CALL: + if (in_array($value, get_class_methods($object))) { + $isAccessible++; + } + + if (is_a( $object, 'Zef\Zel\IValueAdapter') && in_array($value, get_class_methods($object->get()))) { + $isAccessible++; + } + + break; + default: + break; + } + + if (empty($isAccessible)) { + return false; + } + + return true; + } + +} diff --git a/tests/CorrectEvaluationTest.php b/tests/CorrectEvaluationTest.php index d65e3ee..584624f 100644 --- a/tests/CorrectEvaluationTest.php +++ b/tests/CorrectEvaluationTest.php @@ -39,6 +39,57 @@ public function getFunctions() $this->assertEquals( $expected, $expressionLanguage->evaluate( $expression, $values)); } + /** + * @dataProvider provideAlsoNonPublicObjectMethods + */ + public function testResolveWrappedNonPublicProperties( $expression, array $values, $expected) + { + $provider = new class() implements ExpressionFunctionProviderInterface { + public function getFunctions() + { + $functions = []; + $functions[] = ExpressionFunction::fromPhp( 'stripos'); + return $functions; + } + }; + + $expressionLanguage = new ExpressionLanguage( null, [$provider]); + $resolver = new ObjectResolver( $values); + $values = $resolver->get(); + + $this->assertEquals( $expected, $expressionLanguage->evaluate( $expression, $values)); + } + + public function testResolveWrappedPrivateMethodCall() + { + $child = new class() { + public function greet($name) { return "Hello $name"; } + }; + $user = new class($child) + { + private $test = 'Another Test'; + + private $_name = 'Test'; + + private $_child; + + public function __construct($child) + { + $this->_child = $child; + } + + private function sayHi() { return 'Hi'; } + + public function getName() { return $this->_name; } + + public function getChild() { return $this->_child; } + }; + + $this->expectException('RuntimeException'); + $el = new ExpressionLanguage(); + $el->evaluate('foo.sayHi()', ['foo' => $user]); + } + /** * @dataProvider provideSimpleValues * @dataProvider provideArrayValues @@ -160,8 +211,36 @@ public function getChild() { return $this->_child; } return [ - ['user.getName()', ['user' => $user], 'Test'], - ['user.getChild().greet(\'Goofus\')', ['user' => $user], 'Hello Goofus'] + ['user.get().getName()', ['user' => $user], 'Test'], + ['user.get().getChild().greet(\'Goofus\')', ['user' => $user], 'Hello Goofus'] + ]; + } + + public function provideAlsoNonPublicObjectMethods() + { + $child = new class() { + public function greet($name) { return "Hello $name"; } + }; + $user = new class($child) + { + private $test = 'Another Test'; + + private $_name = 'Test'; + + private $_child; + + public function __construct($child) + { + $this->_child = $child; + } + + public function getName() { return $this->_name; } + + public function getChild() { return $this->_child; } + }; + + return [ + ['user.test', ['user' => $user], null] ]; } -} \ No newline at end of file +}