Skip to content

Commit

Permalink
Allow retrieving the enum itself and add a second argument to the "en…
Browse files Browse the repository at this point in the history
…um()" function to retrieve it's name or value
  • Loading branch information
J-Ben87 committed Feb 25, 2024
1 parent 75aee40 commit fef9a45
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 32 deletions.
45 changes: 43 additions & 2 deletions docs/adapters/enum_adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

## Usage

Expand All @@ -23,6 +23,8 @@ enum StatusEnum: string

Then the following should happen:

### Requesting the enum

```php
<?php

Expand All @@ -31,7 +33,46 @@ use Presta\BehatEvaluator\Adapter\EnumAdapter;
$evaluate = new EnumAdapter(...);
$value = $evaluate('<enum("App\Enum\StatusEnum::Doing")>');

// $value is equal to 'doing'
// $value is the StatusEnum::Doing enum's case
```

### Requesting the enum's name

```php
<?php

use Presta\BehatEvaluator\Adapter\EnumAdapter;

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

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

### Requesting the enum's value

```php
<?php

use Presta\BehatEvaluator\Adapter\EnumAdapter;

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

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

_Note that when evaluating a larger string, the second argument is optional._

```php
<?php

use Presta\BehatEvaluator\Adapter\EnumAdapter;

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

// $value is equal to 'The status is done.'
```

---
Expand Down
35 changes: 26 additions & 9 deletions src/Adapter/EnumAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,39 @@ public function __invoke(mixed $value): mixed
$match = new FunctionExpressionMatcher();

foreach ($match('enum', $value) as $expression) {
$evaluated = $this->expressionLanguage->evaluate($expression);
try {
$evaluated = $this->expressionLanguage->evaluate($expression);
} catch (\Throwable $exception) {
if (\str_ends_with($exception->getMessage(), '" is not a valid enum.')) {
$value = "<$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;
continue;
}

throw $exception;
}

if ($evaluated instanceof \BackedEnum) {
if ($value === "<$expression>") {
return $evaluated;
}

$evaluated = $evaluated->value;
}

if (!$evaluated instanceof \BackedEnum) {
$type = get_debug_type($evaluated);
if ($evaluated instanceof \UnitEnum) {
if ($value === "<$expression>") {
return $evaluated;
}

throw new \RuntimeException("The evaluated enum of type \"$type\" is not a backed enum.");
throw new \RuntimeException("You can not get the \"value\" of a UnitEnum.");
}

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

\assert(\is_int($evaluated) || \is_string($evaluated));

$value = str_replace("<$expression>", (string)$evaluated, $value);
}

return match (\is_numeric($value)) {
Expand Down
2 changes: 2 additions & 0 deletions src/EvaluatorBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Presta\BehatEvaluator\Adapter\AdapterInterface;
use Presta\BehatEvaluator\Adapter\ConstantAdapter;
use Presta\BehatEvaluator\Adapter\DateTimeAdapter;
use Presta\BehatEvaluator\Adapter\EnumAdapter;
use Presta\BehatEvaluator\Adapter\FactoryAdapter;
use Presta\BehatEvaluator\Adapter\JsonAdapter;
use Presta\BehatEvaluator\Adapter\NthAdapter;
Expand Down Expand Up @@ -74,6 +75,7 @@ public function build(): Evaluator
[
new ConstantAdapter($expressionLanguage),
new DateTimeAdapter($expressionLanguage),
new EnumAdapter($expressionLanguage),
new FactoryAdapter($expressionLanguage),
new JsonAdapter(),
new NthAdapter(),
Expand Down
18 changes: 18 additions & 0 deletions src/ExpressionLanguage/Compiler/EnumCompiler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Presta\BehatEvaluator\ExpressionLanguage\Compiler;

use Presta\BehatEvaluator\ExpressionLanguage\ArgumentGuesser\DateTime\ArgumentGuesserInterface;

/**
* @phpstan-import-type IntlFormats from ArgumentGuesserInterface
*/
final class EnumCompiler
{
public function __invoke(string $enum, string $property = null): string
{
return '';
}
}
43 changes: 43 additions & 0 deletions src/ExpressionLanguage/Evaluator/EnumEvaluator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Presta\BehatEvaluator\ExpressionLanguage\Evaluator;

final class EnumEvaluator
{
/**
* @param array<string, mixed> $arguments
*/
public function __invoke(array $arguments, string $enumClass, string $property = null): mixed
{
try {
$enum = constant($enumClass);
} catch (\Throwable) {
throw new \RuntimeException("\"$enumClass\" is not a valid enum.");
}

if (!$enum instanceof \UnitEnum) {
$debugType = get_debug_type($enum);

throw new \RuntimeException("\"$debugType\" is not a valid enum.");
}

if (null === $property) {
return $enum;
}

if (!\in_array($property, ['name', 'value'], true)) {
throw new \RuntimeException("You can not get the \"$property\" property of an enum.");
}

if (!$enum instanceof \BackedEnum) {
throw new \RuntimeException("You can not get the \"$property\" of a UnitEnum.");
}

return match ($property) {
'value' => $enum->value,
'name' => $enum->name,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

namespace Presta\BehatEvaluator\ExpressionLanguage\ExpressionFunction;

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

final class EnumExpressionFunction extends ExpressionFunction
{
public function __construct()
{
parent::__construct('enum', new ConstantCompiler(), new ConstantEvaluator());
parent::__construct('enum', new EnumCompiler(), new EnumEvaluator());
}
}
101 changes: 83 additions & 18 deletions tests/Unit/Adapter/EnumAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPUnit\Framework\TestCase;
use Presta\BehatEvaluator\Adapter\EnumAdapter;
use Presta\BehatEvaluator\Tests\Resources\ExpressionLanguageFactory;
use Presta\BehatEvaluator\Tests\Resources\FooBarEnum;
use Presta\BehatEvaluator\Tests\Resources\PriorityEnum;
use Presta\BehatEvaluator\Tests\Resources\StatusEnum;
use Presta\BehatEvaluator\Tests\Resources\UnsupportedValuesProvider;
Expand Down Expand Up @@ -44,59 +45,123 @@ public function testInvokingTheAdapter(mixed $expected, mixed $value): void
*/
public function values(): iterable
{
yield 'a string containing only an int backed enum case should return the case\'s value' => [
PriorityEnum::High->value,
yield 'a string containing only a unit enum should return the enum' => [
FooBarEnum::Foo,
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum::Foo")>',
];
yield 'a string containing only an int backed enum should return the enum' => [
PriorityEnum::High,
'<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,
yield 'a string containing only a string backed enum should return the enum' => [
StatusEnum::Todo,
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\StatusEnum::Todo")>',
];
yield 'a string containing only a non existing enum should return the original expression unchanged' => [
yield 'a string containing only a unit enum and requesting it\'s value should throw a runtime exception' => [
new \RuntimeException('You can not get the "value" of a UnitEnum.'),
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum::Foo", "value")>',
];
yield 'a string containing only an int backed enum and requesting it\'s value'
. ' should return the enum\'s value' => [
PriorityEnum::High->value,
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\PriorityEnum::High", "value")>',
];
yield 'a string containing only a string backed enum and requesting it\'s value'
. ' should return the enum\'s value' => [
StatusEnum::Todo->value,
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\StatusEnum::Todo", "value")>',
];
yield 'a string containing only a unit enum and requesting it\'s name'
. ' should throw a runtime exception' => [
new \RuntimeException('You can not get the "name" of a UnitEnum.'),
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum::Foo", "name")>',
];
yield 'a string containing only an int backed enum and requesting it\'s name'
. ' should return the enum\'s name' => [
PriorityEnum::High->name,
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\PriorityEnum::High", "name")>',
];
yield 'a string containing only a string backed enum and requesting it\'s name'
. ' should return the enum\'s name' => [
StatusEnum::Todo->name,
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\StatusEnum::Todo", "name")>',
];
yield 'a string containing only an enum class should return the original expression unchanged' => [
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum")>',
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum")>',
];
yield 'a string containing only a constant should return the original expression unchanged' => [
'<enum("ARRAY_FILTER_USE_BOTH")>',
'<enum("ARRAY_FILTER_USE_BOTH")>',
];
yield 'a string containing only a non enum string 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' => [
yield 'a string containing only a non existing enum 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'
yield 'a string containing only a valid enum 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 a unit enum expression should throw a runtime exception' => [
new \RuntimeException('You can not get the "value" of a UnitEnum.'),
'the value <enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum::Foo")>'
. ' comes from an enum',
];
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 ' . PriorityEnum::Default->value . ' comes from an enum',
'the value <enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\PriorityEnum::Default")>'
. ' comes from a constant',
. ' comes from an enum',
];
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 ' . StatusEnum::Doing->value . ' comes from an enum',
'the value <enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\StatusEnum::Doing")>'
. ' comes from a constant',
. ' comes from an enum',
];
yield 'a string containing a unit enum expression and requesting it\'s value'
. ' should throw a runtime exception' => [
new \RuntimeException('You can not get the "value" of a UnitEnum.'),
'the value <enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\FooBarEnum::Foo", "value")>'
. ' comes from an enum',
];
yield 'a string containing an int backed enum expression and requesting it\'s value'
. ' should return the string after evaluating the enum expression' => [
'the value ' . PriorityEnum::Default->value . ' comes from an enum',
'the value <enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\PriorityEnum::Default", "value")>'
. ' comes from an enum',
];
yield 'a string containing a string backed enum expression and requesting it\'s value'
. ' should return the string after evaluating the enum expression' => [
'the value ' . StatusEnum::Doing->value . ' comes from an enum',
'the value <enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\StatusEnum::Doing", "value")>'
. ' comes from an enum',
];
yield 'a string containing many constant expressions'
. ' should return the string after evaluating the constant expressions' => [
yield 'a string containing many enum expressions'
. ' should return the string after evaluating the enum 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 only a string backed enum and requesting a non existing property'
. ' should throw a runtime exception' => [
new \RuntimeException('You can not get the "invalid" property of an enum.'),
'<enum("Presta\\\\BehatEvaluator\\\\Tests\\\\Resources\\\\StatusEnum::Todo", "invalid")>',
];
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 fef9a45

Please sign in to comment.