Skip to content

Commit

Permalink
added string concat operator
Browse files Browse the repository at this point in the history
  • Loading branch information
ArekX committed May 2, 2019
1 parent e7c9c05 commit ea1ca05
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 23 deletions.
3 changes: 1 addition & 2 deletions FUTURE.md
Original file line number Diff line number Diff line change
@@ -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', <expression>]`
* Length operator `['length', <expression>]`, `mb_strlen` for string, `count` for array.
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ COMPARE | Comparison operator |`['compare', <expressionA>, <expressionB>]`, `['c
REGEX | Regex operator | `['regex', <expression>, '/pattern/']`, `['regex', <expression>, <patternExpression>]`
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', <expression1>, ..., <expressionN>]`

##### AND Operator

Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/DefaultExpressionParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
]);
}
}
79 changes: 79 additions & 0 deletions src/Operators/ConcatOperator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
/**
* @author Aleksandar Panic
* @license http://www.apache.org/licenses/LICENSE-2.0
* @since 1.0.0
**/

namespace ArekX\ArrayExpression\Operators;


use ArekX\ArrayExpression\Exceptions\InvalidEvaluationResultType;
use ArekX\ArrayExpression\Interfaces\ExpressionParser;
use ArekX\ArrayExpression\Interfaces\Operator;
use ArekX\ArrayExpression\Interfaces\ValueParser;

/**
* Class ConcatOperator
* Operator for returning concatenated strings
*
* @package ArekX\ArrayExpression\Operators
*/
class ConcatOperator extends BaseOperator
{
/**
* Expressions of which will be in concat operation.
*
* @var Operator[]
*/
public $concatOperators = [];

/**
* Passes data from operator configuration.
*
* Depending on the operator this data can contain other sub-expressions which need to be parsed using
* ExpressionParser
*
* @param array $config Expressions to be processed
* @see ExpressionParser
*/
public function configure(array $config)
{
$this->setName($config[0] ?? 'unknown');

if (count($config) < 2) {
throw new \InvalidArgumentException("Minimum format must be satisfied: ['{$this->getName()}', <expression>]");
}

$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;
}

}
24 changes: 4 additions & 20 deletions test.php
Original file line number Diff line number Diff line change
@@ -1,26 +1,10 @@
<?php
require __DIR__ . '/vendor/autoload.php';
$isAStark = [
'or',
[
'and',
['compare', ['get', 'first'], 'in', ['value', ['Arya', 'Sansa']]],
['compare', ['get', 'last'], '=', ['value', 'Stark']],
],
['regex', ['get', 'emblem'], '/stark/i']
];

$evaluator = \ArekX\ArrayExpression\Evaluator::create();
$expression = ['concat', ['get', 'first'], ['value', ' '], ['get', 'last']];

$values = [
['first' => '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)
echo $result;
93 changes: 93 additions & 0 deletions tests/Operators/ConcatOperatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php
/**
* @author Aleksandar Panic
* @license http://www.apache.org/licenses/LICENSE-2.0
* @since 1.0.0
**/

namespace tests\Operators;


use ArekX\ArrayExpression\Exceptions\InvalidEvaluationResultType;
use ArekX\ArrayExpression\ExpressionParser;
use ArekX\ArrayExpression\Operators\GetOperator;
use ArekX\ArrayExpression\Operators\ValueOperator;
use ArekX\ArrayExpression\ValueParsers\ArrayValueParser;
use tests\Mocks\MockOperator;
use tests\Spies\ConcatOperatorSpy;
use tests\TestCase;

/**
* Class ConcatOperatorTest
* @package tests\Operators
*
*/
class ConcatOperatorTest extends TestCase
{
public function testParserIsSet()
{
$i = $this->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;
}
}
23 changes: 23 additions & 0 deletions tests/Spies/ConcatOperatorSpy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
/**
* @author Aleksandar Panic
* @license http://www.apache.org/licenses/LICENSE-2.0
* @since 1.0.0
**/

namespace tests\Spies;

use ArekX\ArrayExpression\Operators\ConcatOperator;

class ConcatOperatorSpy extends ConcatOperator
{
public function getParser()
{
return $this->parser;
}

public function getConcatOperators()
{
return $this->concatOperators;
}
}

0 comments on commit ea1ca05

Please sign in to comment.