diff --git a/FUTURE.md b/FUTURE.md index f02c2a0..153540b 100644 --- a/FUTURE.md +++ b/FUTURE.md @@ -1,8 +1,7 @@ # Future Roadmap -* Add `Data`, unified key value database where operators can read general data from. As an operator. `['data', 'key1.subkey']` * Allow passing instances of operator into expressions. -* Add info aboout object/array traversal +* Add info about object/array traversal * Exists operator `['exists', 'keyName']` * Empty operator `['empty', ]` * Length operator `['length', ]`, `mb_strlen` for string, `count` for array. diff --git a/README.md b/README.md index d0b891b..8b7dbc5 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ COMPARE | Comparison operator |`['compare', , ]`, `['c REGEX | Regex operator | `['regex', , '/pattern/']`, `['regex', , ]` VALUE | Value operator, returns static values | `['value', 'this is a static value']` GET | GET operator, returns values by name from passed value | `['get', 'keyFromValue']` +CONCAT | CONCAT operator, concatenates strings | `['concat', , ..., ]` ##### AND Operator @@ -228,6 +229,20 @@ $evaluator = \ArekX\ArrayExpression\Evaluator::create(); $evaluator->run($expression, ['name' => 'John']); // returns 'John' ``` +##### Concat Operator + +Concat operator is defined in `ArekX\ArrayExpression\Operators\ConcatOperator` class and is used to concatenate two or more strings. +It requires evaluation results to be strings. + +Example: + ```php +$expression = ['concat', ['get', 'first'], ['value', ' '], ['get', 'last']]; + +$evaluator = \ArekX\ArrayExpression\Evaluator::create(); + +$evaluator->run($expression, ['first' => 'John', 'last' => 'Snow']); // returns 'John Snow' +``` + #### Custom operators You can create your own custom operator manually by implementing `Operator` interface and adding that operator diff --git a/src/DefaultExpressionParser.php b/src/DefaultExpressionParser.php index 83a32d4..26b6ac7 100644 --- a/src/DefaultExpressionParser.php +++ b/src/DefaultExpressionParser.php @@ -13,6 +13,7 @@ use ArekX\ArrayExpression\Operators\AndOperator; use ArekX\ArrayExpression\Operators\BetweenOperator; use ArekX\ArrayExpression\Operators\CompareOperator; +use ArekX\ArrayExpression\Operators\ConcatOperator; use ArekX\ArrayExpression\Operators\OrOperator; use ArekX\ArrayExpression\Operators\RegexOperator; use ArekX\ArrayExpression\Operators\GetOperator; @@ -37,7 +38,8 @@ public function __construct() 'xor' => XOrOperator::class, 'value' => ValueOperator::class, 'get' => GetOperator::class, - 'between' => BetweenOperator::class + 'between' => BetweenOperator::class, + 'concat' => ConcatOperator::class ]); } } \ No newline at end of file diff --git a/src/Operators/ConcatOperator.php b/src/Operators/ConcatOperator.php new file mode 100644 index 0000000..c69ec2e --- /dev/null +++ b/src/Operators/ConcatOperator.php @@ -0,0 +1,79 @@ +setName($config[0] ?? 'unknown'); + + if (count($config) < 2) { + throw new \InvalidArgumentException("Minimum format must be satisfied: ['{$this->getName()}', ]"); + } + + $this->concatOperators = []; + + for ($i = 1; $i < count($config); $i++) { + $this->assertIsExpression($config[$i]); + $this->concatOperators[] = $this->parser->parse($config[$i]); + } + } + + /** + * Evaluates one value. + * + * @param ValueParser $value Value to be evaluated + * @return string Evaluation result + * @throws InvalidEvaluationResultType + */ + public function evaluate(ValueParser $value) + { + $result = ""; + foreach ($this->concatOperators as $operator) { + $concatResult = $operator->evaluate($value); + + if (!is_string($concatResult)) { + throw new InvalidEvaluationResultType($concatResult, 'string'); + } + + $result .= $concatResult; + } + + return $result; + } + +} \ No newline at end of file diff --git a/test.php b/test.php index b628504..7b7ea20 100644 --- a/test.php +++ b/test.php @@ -1,26 +1,10 @@ 'John', 'last' => 'Snow', 'emblem' => 'stark'], - ['first' => 'Arya', 'last' => 'Stark', 'emblem' => 'stark'], - ['first' => 'Sansa', 'last' => 'Stark', 'emblem' => 'stark'], - ['first' => 'Joffrey', 'last' => 'Lannister', 'emblem' => 'lannister'] -]; +$evaluator = \ArekX\ArrayExpression\Evaluator::create(); -foreach ($values as $value) { - var_dump($evaluator->run($isAStark, $value)); -} +$result = $evaluator->run($expression, ['first' => 'John', 'last' => 'Snow']); // returns 'John Snow' -// Output: bool(true), bool(true), bool(true), bool(false) \ No newline at end of file +echo $result; \ No newline at end of file diff --git a/tests/Operators/ConcatOperatorTest.php b/tests/Operators/ConcatOperatorTest.php new file mode 100644 index 0000000..657097c --- /dev/null +++ b/tests/Operators/ConcatOperatorTest.php @@ -0,0 +1,93 @@ +createInstance(); + $parser = new ExpressionParser(); + $i->setParser($parser); + $this->assertSame($parser, $i->getParser()); + } + + public function testConfigureCreatesConcatOperators() + { + $i = $this->createInstance(); + $i->configure(['concat', ['get', 'first'], ['value', false], ['mock']]); + $this->assertInstanceOf(GetOperator::class, $i->concatOperators[0]); + $this->assertInstanceOf(ValueOperator::class, $i->concatOperators[1]); + $this->assertInstanceOf(MockOperator::class, $i->concatOperators[2]); + } + + public function testNameIsSet() + { + $i = $this->createInstance(); + $i->configure(['concat', ['mock']]); + $this->assertSame('concat', $i->getName()); + $i->configure([1 => ['mock'], 2 => ['mock']]); + $this->assertSame('unknown', $i->getName()); + } + + public function testConcatenation() + { + $i = $this->createInstance(); + $i->configure(['concat', ['get', 'first'], ['value', ' '], ['get', 'last']]); + $this->assertSame('John Snow', $i->evaluate(ArrayValueParser::from(['first' => 'John', 'last' => 'Snow']))); + } + + public function testInvalidResult() + { + $i = $this->createInstance(); + $i->configure(['concat', ['get', 'first'], ['value', false], ['get', 'last']]); + $this->expectException(InvalidEvaluationResultType::class); + $this->assertSame('John Snow', $i->evaluate(ArrayValueParser::from(['first' => 'John', 'last' => 'Snow']))); + } + + public function testThrowsErrorIfNotValidSyntax() + { + $i = $this->createInstance(); + $this->expectException(\InvalidArgumentException::class); + $i->configure([]); + } + + public function testThrowsErrorWhenJustOneName() + { + $i = $this->createInstance(); + $this->expectException(\InvalidArgumentException::class); + $i->configure(['concat']); + } + + protected function createInstance(): ConcatOperatorSpy + { + $parser = new ExpressionParser(); + $parser->setType('mock', MockOperator::class); + $parser->setType('get', GetOperator::class); + $parser->setType('value', ValueOperator::class); + $operator = new ConcatOperatorSpy(); + $operator->setParser($parser); + + return $operator; + } +} \ No newline at end of file diff --git a/tests/Spies/ConcatOperatorSpy.php b/tests/Spies/ConcatOperatorSpy.php new file mode 100644 index 0000000..8f13573 --- /dev/null +++ b/tests/Spies/ConcatOperatorSpy.php @@ -0,0 +1,23 @@ +parser; + } + + public function getConcatOperators() + { + return $this->concatOperators; + } +} \ No newline at end of file