Skip to content

Commit

Permalink
Add an EnumAdapter
Browse files Browse the repository at this point in the history
  • Loading branch information
J-Ben87 committed Feb 24, 2024
1 parent f3667af commit 75aee40
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/.task/
/docker/
/var/
/vendor/
/.phpcs-cache
/.phpunit.result.cache
/composer.lock
/Makefile
/Taskfile.yaml
43 changes: 43 additions & 0 deletions docs/adapters/enum_adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# ConstantAdapter

The [EnumAdapter][1] transforms the input value from the string representation of a backed enum case into it's value.

Ex. The value `<enum("App\Enum\StatusEnum::Doing")>` will be transformed into the enum's case value.

## Usage

Given there is an enum like:

```php
<?php

namespace App\Enum;

enum StatusEnum: string
{
case Todo = 'todo';
case Doing = 'doing';
case Done = 'done';
}
```

Then the following should happen:

```php
<?php

use Presta\BehatEvaluator\Adapter\EnumAdapter;

$evaluate = new EnumAdapter(...);
$value = $evaluate('<enum("App\Enum\StatusEnum::Doing")>');

// $value is equal to 'doing'
```

---

You may return to the [README][2] or read other [adapters][3] guides.

[1]: ../../src/Adapter/EnumAdapter.php
[2]: ../../README.md
[3]: ../adapters/
15 changes: 15 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@ parameters:
count: 1
path: tests/Unit/Adapter/DateTimeAdapterTest.php

-
message: "#^Generator expects value type array\\{bool\\|float\\|int\\|string, bool\\|float\\|int\\|string\\}, array\\{array\\|object\\|null, array\\|object\\|null\\} given\\.$#"
count: 1
path: tests/Unit/Adapter/EnumAdapterTest.php

-
message: "#^Parameter \\#1 \\$excludedTypes of static method Presta\\\\BehatEvaluator\\\\Tests\\\\Unit\\\\Adapter\\\\EnumAdapterTest\\:\\:unsupportedNonScalarValues\\(\\) expects array\\<int, 'array'\\|'null'\\|'object'\\>, array\\<int, 'array'\\|'bool'\\|'float'\\|'int'\\|'null'\\|'object'\\|'string'\\> given\\.$#"
count: 1
path: tests/Unit/Adapter/EnumAdapterTest.php

-
message: "#^Parameter \\#1 \\$excludedTypes of static method Presta\\\\BehatEvaluator\\\\Tests\\\\Unit\\\\Adapter\\\\EnumAdapterTest\\:\\:unsupportedScalarValues\\(\\) expects array\\<int, 'bool'\\|'float'\\|'int'\\|'string'\\>, array\\<int, 'array'\\|'bool'\\|'float'\\|'int'\\|'null'\\|'object'\\|'string'\\> given\\.$#"
count: 1
path: tests/Unit/Adapter/EnumAdapterTest.php

-
message: "#^Generator expects value type array\\{bool\\|float\\|int\\|string, bool\\|float\\|int\\|string\\}, array\\{array\\|object\\|null, array\\|object\\|null\\} given\\.$#"
count: 1
Expand Down
48 changes: 48 additions & 0 deletions src/Adapter/EnumAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Presta\BehatEvaluator\Adapter;

use Presta\BehatEvaluator\ExpressionLanguage\ExpressionLanguage;
use Presta\BehatEvaluator\ExpressionLanguage\ExpressionMatcher\FunctionExpressionMatcher;

final class EnumAdapter implements AdapterInterface
{
public function __construct(private readonly ExpressionLanguage $expressionLanguage)
{
}

public function __invoke(mixed $value): mixed
{
if (!\is_string($value)) {
return $value;
}

$match = new FunctionExpressionMatcher();

foreach ($match('enum', $value) as $expression) {
$evaluated = $this->expressionLanguage->evaluate($expression);

// the evaluation did not end up with a transformation
preg_match('/enum\([\'"](?<value>[^)]+)[\'"]\)/', $expression, $expressionMatches);
if (\is_string($evaluated) && $expressionMatches['value'] === addslashes($evaluated)) {
continue;
}

if (!$evaluated instanceof \BackedEnum) {
$type = get_debug_type($evaluated);

throw new \RuntimeException("The evaluated enum of type \"$type\" is not a backed enum.");
}

// the expression is included in a larger string
$value = str_replace("<$expression>", (string)$evaluated->value, $value);
}

return match (\is_numeric($value)) {
true => (int)$value,
false => $value,
};
}
}
2 changes: 2 additions & 0 deletions src/ExpressionLanguage/BehatExpressionLanguageProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Presta\BehatEvaluator\ExpressionLanguage\ExpressionFunction\ConstantExpressionFunction;
use Presta\BehatEvaluator\ExpressionLanguage\ExpressionFunction\DateTimeExpressionFunction;
use Presta\BehatEvaluator\ExpressionLanguage\ExpressionFunction\DateTimeImmutableExpressionFunction;
use Presta\BehatEvaluator\ExpressionLanguage\ExpressionFunction\EnumExpressionFunction;
use Presta\BehatEvaluator\ExpressionLanguage\ExpressionFunction\FactoryExpressionFunction;
use Presta\BehatEvaluator\Foundry\FactoryClassFactory;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
Expand All @@ -25,6 +26,7 @@ public function getFunctions(): array
new ConstantExpressionFunction(),
new DateTimeExpressionFunction($this->culture),
new DateTimeImmutableExpressionFunction($this->culture),
new EnumExpressionFunction(),
new FactoryExpressionFunction($this->factoryClassFactory),
];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Presta\BehatEvaluator\ExpressionLanguage\ExpressionFunction;

use Presta\BehatEvaluator\ExpressionLanguage\Compiler\ConstantCompiler;
use Presta\BehatEvaluator\ExpressionLanguage\Evaluator\ConstantEvaluator;
use Symfony\Component\ExpressionLanguage\ExpressionFunction;

final class EnumExpressionFunction extends ExpressionFunction
{
public function __construct()
{
parent::__construct('enum', new ConstantCompiler(), new ConstantEvaluator());
}
}
11 changes: 11 additions & 0 deletions tests/Resources/FooBarEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Presta\BehatEvaluator\Tests\Resources;

enum FooBarEnum
{
case Foo;
case Bar;
}
12 changes: 12 additions & 0 deletions tests/Resources/PriorityEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Presta\BehatEvaluator\Tests\Resources;

enum PriorityEnum: int
{
case High = -10;
case Default = 0;
case Low = 10;
}
12 changes: 12 additions & 0 deletions tests/Resources/StatusEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Presta\BehatEvaluator\Tests\Resources;

enum StatusEnum: string
{
case Todo = 'todo';
case Doing = 'doing';
case Done = 'done';
}
102 changes: 102 additions & 0 deletions tests/Unit/Adapter/EnumAdapterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace Presta\BehatEvaluator\Tests\Unit\Adapter;

use PHPUnit\Framework\TestCase;
use Presta\BehatEvaluator\Adapter\EnumAdapter;
use Presta\BehatEvaluator\Tests\Resources\ExpressionLanguageFactory;
use Presta\BehatEvaluator\Tests\Resources\PriorityEnum;
use Presta\BehatEvaluator\Tests\Resources\StatusEnum;
use Presta\BehatEvaluator\Tests\Resources\UnsupportedValuesProvider;
use Symfony\Component\ExpressionLanguage\SyntaxError;

final class EnumAdapterTest extends TestCase
{
use UnsupportedValuesProvider;

/**
* @dataProvider values
*/
public function testInvokingTheAdapter(mixed $expected, mixed $value): void
{
if ($expected instanceof \Throwable) {
$this->expectException(get_class($expected));

if ('' !== $expected->getMessage()) {
$this->expectExceptionMessage($expected->getMessage());
}
}

$evaluate = new EnumAdapter(ExpressionLanguageFactory::create());
$value = $evaluate($value);

if ($expected instanceof \Throwable) {
return;
}

self::assertSame($expected, $value);
}

/**
* @return iterable<string, array{mixed, mixed}>
*/
public function values(): iterable
{
yield 'a string containing only an int backed enum case should return the case\'s value' => [
PriorityEnum::High->value,
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\PriorityEnum::High")>',
];
yield 'a string containing only a string backed enum case should return the case\'s value' => [
StatusEnum::Todo->value,
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\StatusEnum::Todo")>',
];
yield 'a string containing only a non existing enum should return the original expression unchanged' => [
'<enum("Undefined")>',
'<enum("Undefined")>',
];
yield 'a string containing only a non existing enum class should return the original expression unchanged' => [
'<enum("Invalid\\\\Path::Undefined")>',
'<enum("Invalid\\\\Path::Undefined")>',
];
yield 'a string containing only a non existing enum case should return the original expression unchanged' => [
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum::Undefined")>',
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum::Undefined")>',
];
yield 'a string containing only a valid enum case but without the enum function'
. ' should return the original expression unchanged' => [
'Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum::Foo',
'Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum::Foo',
];
yield 'a string containing an int backed enum expression'
. ' should return the string after evaluating the enum expression' => [
'the value ' . PriorityEnum::Default->value . ' comes from a constant',
'the value <enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\PriorityEnum::Default")>'
. ' comes from a constant',
];
yield 'a string containing a string backed enum expression'
. ' should return the string after evaluating the enum expression' => [
'the value ' . StatusEnum::Doing->value . ' comes from a constant',
'the value <enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\StatusEnum::Doing")>'
. ' comes from a constant',
];
yield 'a string containing many constant expressions'
. ' should return the string after evaluating the constant expressions' => [
'the values ' . PriorityEnum::Low->value . ' and ' . StatusEnum::Done->value . ' come from enums',
'the values <enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\PriorityEnum::Low")>'
. ' and <enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\StatusEnum::Done")>'
. ' come from enums',
];
yield from self::unsupportedValues(['string']);
yield 'a string containing a malformed argument should throw a syntax error' => [
new SyntaxError('Variable "this" is not valid', 6, 'enum(this is a malformed argument)'),
'<enum(this is a malformed argument)>',
];
yield 'a string containing an expression returning a non backed enum, should throw a runtime exception' => [
new \RuntimeException(),
'the value <enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum::Foo")>'
. ' is not a backed enum',
];
}
}

0 comments on commit 75aee40

Please sign in to comment.