Skip to content

Commit

Permalink
Introduce EnumInterface::getLabel (#39)
Browse files Browse the repository at this point in the history
* Introduce EnumInterface::getLabel to not rely on getChoices from the outside

* Store translated enum choices in a private property to avoid calling translation more than once per enum

* Normalize assert method usage to static calls

* Fixed doc use statements

* #37 $choices assignment refacto + comment getLabel in EnumInterface for next major (#40)

Co-authored-by: Mathieu Ducrot <[email protected]>
  • Loading branch information
yann-eugone and mathieu-ducrot authored May 24, 2021
1 parent 89143e5 commit b0c3944
Show file tree
Hide file tree
Showing 22 changed files with 234 additions and 59 deletions.
12 changes: 12 additions & 0 deletions doc/declaring-enum.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Create a new class, implement both `getName` & `getChoices` methods.
namespace App\Enum;
use Yokai\EnumBundle\EnumInterface;
use Yokai\EnumBundle\Exception\InvalidEnumValueException;
class GenderEnum implements EnumInterface
{
Expand All @@ -42,6 +43,15 @@ class GenderEnum implements EnumInterface
{
return ['m' => 'Male', 'f' => 'Female'];
}
public function getLabel(string $value): string
{
if (!isset($this->getChoices()[$value])) {
throw InvalidEnumValueException::create($this, $value);
}
return $this->getChoices()[$value];
}
}
```

Expand All @@ -64,11 +74,13 @@ Create a new class, use `EnumWithClassAsNameTrait` trait and implement `getChoic
namespace App\Enum;
use Yokai\EnumBundle\EnumInterface;
use Yokai\EnumBundle\EnumLabelTrait;
use Yokai\EnumBundle\EnumWithClassAsNameTrait;
class GenderEnum implements EnumInterface
{
use EnumWithClassAsNameTrait;
use EnumLabelTrait;
public function getChoices(): array
{
Expand Down
37 changes: 24 additions & 13 deletions src/AbstractTranslatedEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
abstract class AbstractTranslatedEnum implements EnumInterface
{
use EnumLabelTrait;

/**
* @var TranslatorInterface
*/
Expand All @@ -27,6 +29,11 @@ abstract class AbstractTranslatedEnum implements EnumInterface
*/
private $transDomain = 'messages';

/**
* @var array|null
*/
private $choices = null;

/**
* @param TranslatorInterface $translator
* @param string $transPattern
Expand All @@ -46,19 +53,23 @@ public function __construct(TranslatorInterface $translator, string $transPatter
*/
public function getChoices(): array
{
return array_combine(
$this->getValues(),
array_map(
function (string $value): string {
return $this->translator->trans(
sprintf($this->transPattern, $value),
[],
$this->transDomain
);
},
$this->getValues()
)
);
if ($this->choices === null) {
$this->choices = array_combine(
$this->getValues(),
array_map(
function (string $value): string {
return $this->translator->trans(
sprintf($this->transPattern, $value),
[],
$this->transDomain
);
},
$this->getValues()
)
);
}

return $this->choices;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/ConfigurableEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
*/
class ConfigurableEnum implements EnumInterface
{
use EnumLabelTrait;

/**
* @var string
*/
Expand Down
15 changes: 15 additions & 0 deletions src/EnumInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,31 @@

/**
* @author Yann Eugoné <[email protected]>
*
* NEXT_MAJOR: Add all these methods to the interface by uncommenting them.
*
* @method string getLabel(string $value)
*/
interface EnumInterface
{
/**
* Returns enum choices (value as keys, labels as values)
*
* @return array
*/
public function getChoices(): array;

/**
* Returns enum identifier (must be unique across your app).
*
* @return string
*/
public function getName(): string;

/**
* NEXT_MAJOR: uncomment this method
*
* @return string
*/
// public function getLabel(string $value): string;
}
21 changes: 21 additions & 0 deletions src/EnumLabelTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Yokai\EnumBundle;

use Yokai\EnumBundle\Exception\InvalidEnumValueException;

trait EnumLabelTrait
{
/**
* @inheritdoc
*/
public function getLabel(string $value): string
{
$choices = $this->getChoices();
if (!isset($choices[$value])) {
throw InvalidEnumValueException::create($this, $value);
}

return $choices[$value];
}
}
2 changes: 1 addition & 1 deletion src/Exception/CannotExtractConstantsException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/**
* @author Yann Eugoné <[email protected]>
*/
class CannotExtractConstantsException extends InvalidArgumentException
class CannotExtractConstantsException extends InvalidArgumentException implements ExceptionInterface
{
public static function invalidPattern(string $pattern): self
{
Expand Down
2 changes: 1 addition & 1 deletion src/Exception/DuplicatedEnumException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/**
* @author Yann Eugoné <[email protected]>
*/
class DuplicatedEnumException extends BadMethodCallException
class DuplicatedEnumException extends BadMethodCallException implements ExceptionInterface
{
/**
* @param string $name
Expand Down
9 changes: 9 additions & 0 deletions src/Exception/ExceptionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Yokai\EnumBundle\Exception;

use Throwable;

interface ExceptionInterface extends Throwable
{
}
2 changes: 1 addition & 1 deletion src/Exception/InvalidEnumException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/**
* @author Yann Eugoné <[email protected]>
*/
class InvalidEnumException extends DomainException
class InvalidEnumException extends DomainException implements ExceptionInterface
{
/**
* @param string $name
Expand Down
16 changes: 16 additions & 0 deletions src/Exception/InvalidEnumValueException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Yokai\EnumBundle\Exception;

use InvalidArgumentException;
use Yokai\EnumBundle\EnumInterface;

final class InvalidEnumValueException extends InvalidArgumentException implements ExceptionInterface
{
public static function create(EnumInterface $enum, string $value): self
{
return new self(
\sprintf('Enum "%s" does not have "%s" value.', $enum->getName(), $value)
);
}
}
2 changes: 1 addition & 1 deletion src/Exception/InvalidTranslatePatternException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/**
* @author Yann Eugoné <[email protected]>
*/
class InvalidTranslatePatternException extends InvalidArgumentException
class InvalidTranslatePatternException extends InvalidArgumentException implements ExceptionInterface
{
/**
* @param string $transPattern
Expand Down
12 changes: 11 additions & 1 deletion src/Twig/Extension/EnumExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Twig\TwigFilter;
use Twig\TwigFunction;
use Yokai\EnumBundle\EnumRegistry;
use Yokai\EnumBundle\Exception\InvalidEnumValueException;

/**
* @author Yann Eugoné <[email protected]>
Expand Down Expand Up @@ -56,7 +57,16 @@ public function getFilters(): array
*/
public function getLabel(string $value, string $enum): string
{
return $this->getChoices($enum)[$value] ?? $value;
$enum = $this->registry->get($enum);
if (\method_exists($enum, 'getLabel')) {
try {
return $enum->getLabel($value);
} catch (InvalidEnumValueException $exception) {
return $value;
}
}

return $enum->getChoices()[$value] ?? $value;
}

/**
Expand Down
14 changes: 12 additions & 2 deletions tests/ConfigurableEnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Yokai\EnumBundle\Tests;

use Yokai\EnumBundle\ConfigurableEnum;
use Yokai\EnumBundle\Exception\InvalidEnumValueException;

/**
* @author Yann Eugoné <[email protected]>
Expand All @@ -12,7 +13,16 @@ class ConfigurableEnumTest extends TestCase
public function testConfigurability(): void
{
$fooEnum = new ConfigurableEnum('foo', ['foo' => 'FOO', 'bar' => 'BAR']);
$this->assertSame('foo', $fooEnum->getName());
$this->assertSame(['foo' => 'FOO', 'bar' => 'BAR'], $fooEnum->getChoices());
self::assertSame('foo', $fooEnum->getName());
self::assertSame(['foo' => 'FOO', 'bar' => 'BAR'], $fooEnum->getChoices());
self::assertSame('FOO', $fooEnum->getLabel('foo'));
self::assertSame('BAR', $fooEnum->getLabel('bar'));
}

public function testLabelNotFound(): void
{
$this->expectException(InvalidEnumValueException::class);
$fooEnum = new ConfigurableEnum('foo', ['foo' => 'FOO', 'bar' => 'BAR']);
$fooEnum->getLabel('unknown enum value');
}
}
28 changes: 23 additions & 5 deletions tests/ConfigurableTranslatedEnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Prophecy\Prophecy\ObjectProphecy;
use Symfony\Contracts\Translation\TranslatorInterface;
use Yokai\EnumBundle\ConfigurableTranslatedEnum;
use Yokai\EnumBundle\Exception\InvalidEnumValueException;
use Yokai\EnumBundle\Exception\InvalidTranslatePatternException;

/**
Expand All @@ -25,13 +26,15 @@ public function testTranslatedChoices(): void
$translator = $this->prophesize(TranslatorInterface::class);
$translator->trans('choice.something.foo', [], 'messages', null)->shouldBeCalled()->willReturn('FOO translated');
$translator->trans('choice.something.bar', [], 'messages', null)->shouldBeCalled()->willReturn('BAR translated');
$type = new ConfigurableTranslatedEnum($translator->reveal(), 'choice.something.%s', 'something', ['foo', 'bar']);
$enum = new ConfigurableTranslatedEnum($translator->reveal(), 'choice.something.%s', 'something', ['foo', 'bar']);

$expectedChoices = [
'foo' => 'FOO translated',
'bar' => 'BAR translated',
];
$this->assertEquals($expectedChoices, $type->getChoices());
self::assertEquals($expectedChoices, $enum->getChoices());
self::assertSame('FOO translated', $enum->getLabel('foo'));
self::assertSame('BAR translated', $enum->getLabel('bar'));
}

public function testTranslatedWithDomainChoices(): void
Expand All @@ -42,13 +45,28 @@ public function testTranslatedWithDomainChoices(): void
$translator->trans('choice.something.bar', [], 'messages', null)->shouldNotBeCalled();
$translator->trans('something.foo', [], 'choices', null)->shouldBeCalled()->willReturn('FOO translated');
$translator->trans('something.bar', [], 'choices', null)->shouldBeCalled()->willReturn('BAR translated');
$type = new ConfigurableTranslatedEnum($translator->reveal(), 'something.%s', 'something', ['foo', 'bar']);
$type->setTransDomain('choices');
$enum = new ConfigurableTranslatedEnum($translator->reveal(), 'something.%s', 'something', ['foo', 'bar']);
$enum->setTransDomain('choices');

$expectedChoices = [
'foo' => 'FOO translated',
'bar' => 'BAR translated',
];
$this->assertEquals($expectedChoices, $type->getChoices());
self::assertEquals($expectedChoices, $enum->getChoices());
self::assertSame('FOO translated', $enum->getLabel('foo'));
self::assertSame('BAR translated', $enum->getLabel('bar'));
}

public function testLabelNotFound(): void
{
$this->expectException(InvalidEnumValueException::class);

/** @var ObjectProphecy|TranslatorInterface $translator */
$translator = $this->prophesize(TranslatorInterface::class);
$translator->trans('choice.something.foo', [], 'messages', null)->shouldBeCalled()->willReturn('FOO translated');
$translator->trans('choice.something.bar', [], 'messages', null)->shouldBeCalled()->willReturn('BAR translated');
$enum = new ConfigurableTranslatedEnum($translator->reveal(), 'choice.something.%s', 'something', ['foo', 'bar']);

$enum->getLabel('unknown');
}
}
16 changes: 16 additions & 0 deletions tests/ConstantListEnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Yokai\EnumBundle\Tests;

use Yokai\EnumBundle\ConstantListEnum;
use Yokai\EnumBundle\Exception\InvalidEnumValueException;
use Yokai\EnumBundle\Tests\Fixtures\Vehicle;

/**
Expand All @@ -23,19 +24,34 @@ public function testVehicleEnums(): void
['bike' => 'bike', 'car' => 'car', 'bus' => 'bus'],
$type->getChoices()
);
self::assertSame('bike', $type->getLabel('bike'));
self::assertSame('bus', $type->getLabel('bus'));

$engine = $this->getEnum(Vehicle::class.'::ENGINE_*', 'vehicle.engine');
self::assertSame('vehicle.engine', $engine->getName());
self::assertSame(
['electic' => 'electic', 'combustion' => 'combustion'],
$engine->getChoices()
);
self::assertSame('electic', $engine->getLabel('electic'));
self::assertSame('combustion', $engine->getLabel('combustion'));

$brand = $this->getEnum(Vehicle::class.'::BRAND_*', 'vehicle.brand');
self::assertSame('vehicle.brand', $brand->getName());
self::assertSame(
['renault' => 'renault', 'volkswagen' => 'volkswagen', 'toyota' => 'toyota'],
$brand->getChoices()
);
self::assertSame('renault', $brand->getLabel('renault'));
self::assertSame('toyota', $brand->getLabel('toyota'));
}

public function testLabelNotFound(): void
{
$this->expectException(InvalidEnumValueException::class);

$enum = $this->getEnum(Vehicle::class.'::TYPE_*', 'vehicle.type');

$enum->getLabel('unknown');
}
}
Loading

0 comments on commit b0c3944

Please sign in to comment.