diff --git a/README.md b/README.md index dca04e2..8fd1e8a 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,53 @@ $view = $this->factory->create(EnumType::class, null, array( ))->createView(); ``` +#### Twig extension + +This package comes with an `enum_label` filter, available thanks to the `EnumExtension` Twig class. +You have to require the `twig/twig` package to get it working. + +The filter will try to return the constant label corresponding to the given value. + +It will try to translate it if possible. To enable translation, require the `symfony/translation` component +and pass a `Symfony\Component\Translation\TranslationInterface` instance on the `EnumExtension` constructor. + +If translation is not available, you will have the default label with class prefixing. + +Usage: + +```twig +{{ value|enum_label('Your\\Enum\\Class') }} +{{ value|enum_label('Your\\Enum\\Class', 'another_domain') }} {# Change the translation domain #} +{{ value|enum_label('Your\\Enum\\Class', false) }} {# Disable translation. In this case the class prefix wont be added #} +{{ value|enum_label('Your\\Enum\\Class', false, true) }} {# Disable translation but keep class prefix #} +{{ value|enum_label('Your\\Enum\\Class', false, true, '.') }} {# Disable translation but keep class prefix with a custom separator #} +``` + +##### Twig extension as a service + +On Symfony projects, the extension can be autoloaded. +First, you have to require the `symfony/framework-bundle` and `symfony/twig-bundle` packages, or use Symfony fullstack. + +Then, register the bundle in the kernel of your application: + +``` php +// app/AppKernel.php + +public function registerBundles() +{ + $bundles = array( + // ... + new Greg0ire\Enum\Bridge\Symfony\Bundle\Greg0ireEnumBundle(), + ); + + // ... + + return $bundles +} +``` + +That's all. You can now directly use the filter. + ## Contributing see [CONTRIBUTING.md][1] diff --git a/composer.json b/composer.json index 1a3d327..8e7e09f 100644 --- a/composer.json +++ b/composer.json @@ -15,10 +15,14 @@ "doctrine/inflector": "^1.0" }, "require-dev": { + "matthiasnoback/symfony-dependency-injection-test": "^0.7.6", "phpunit/phpunit": "^4.1", "sllh/php-cs-fixer-styleci-bridge": "^2.0", "symfony/form": "^2.7 || ^3.0", - "symfony/validator": "^2.7 || ^3.0" + "symfony/framework-bundle": "^2.7 || ^3.0", + "symfony/twig-bundle": "^2.7 || ^3.0", + "symfony/validator": "^2.7 || ^3.0", + "twig/twig": "^1.24" }, "suggest": { "symfony/form": "To use enum form type", diff --git a/src/Bridge/Symfony/Bundle/Greg0ireEnumBundle.php b/src/Bridge/Symfony/Bundle/Greg0ireEnumBundle.php new file mode 100644 index 0000000..72ef788 --- /dev/null +++ b/src/Bridge/Symfony/Bundle/Greg0ireEnumBundle.php @@ -0,0 +1,20 @@ + + */ +final class Greg0ireEnumBundle extends Bundle +{ + /** + * {@inheritdoc} + */ + protected function getContainerExtensionClass() + { + return Greg0ireEnumExtension::class; + } +} diff --git a/src/Bridge/Symfony/DependencyInjection/Greg0ireEnumExtension.php b/src/Bridge/Symfony/DependencyInjection/Greg0ireEnumExtension.php new file mode 100644 index 0000000..74e0cfa --- /dev/null +++ b/src/Bridge/Symfony/DependencyInjection/Greg0ireEnumExtension.php @@ -0,0 +1,33 @@ + + */ +final class Greg0ireEnumExtension extends Extension +{ + /** + * {@inheritdoc} + */ + public function load(array $configs, ContainerBuilder $container) + { + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + + if (class_exists(\Twig_Extension::class)) { + $loader->load('twig.xml'); + + if (class_exists(Translator::class)) { + $container->getDefinition('greg0ire_enum.twig.extension.enum') + ->addArgument(new Reference('translator.default')); + } + } + } +} diff --git a/src/Bridge/Symfony/Resources/config/twig.xml b/src/Bridge/Symfony/Resources/config/twig.xml new file mode 100644 index 0000000..ba99985 --- /dev/null +++ b/src/Bridge/Symfony/Resources/config/twig.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/src/Bridge/Twig/Extension/EnumExtension.php b/src/Bridge/Twig/Extension/EnumExtension.php new file mode 100644 index 0000000..0f84c9f --- /dev/null +++ b/src/Bridge/Twig/Extension/EnumExtension.php @@ -0,0 +1,83 @@ + + */ +final class EnumExtension extends \Twig_Extension +{ + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @param TranslatorInterface $translator + */ + public function __construct(TranslatorInterface $translator = null) + { + $this->translator = $translator; + } + + /** + * {@inheritdoc} + */ + public function getFilters() + { + return [ + new \Twig_SimpleFilter('enum_label', [$this, 'label']), + ]; + } + + /** + * Displays the label corresponding to a specific value of an enumeration. + * + * @param mixed $value Must exists in the enumeration class specified with $class + * @param string $class The enum class name + * @param string|bool $translationDomain The translation domain to use if the translator if available. + * string: Use the specified one + * null: Use the default one + * false: Do not use the translator + * @param bool $classPrefixed Prefix the label with the enum class. Defaults to true if the translator + * is available and enabled, false otherwise. + * @param string $namespaceSeparator Namespace separator to use with the class prefix. + * This takes effect only if $classPrefixed is true. + * + * @return string + */ + public function label($value, $class, $translationDomain = null, $classPrefixed = null, $namespaceSeparator = null) + { + // Determine if the translator can be used or not. + $useTranslation = $this->translator instanceof TranslatorInterface + && (is_null($translationDomain) || is_string($translationDomain)); + + // If not defined, guess the default behavior. + if (is_null($classPrefixed)) { + $classPrefixed = $useTranslation; + } + + $label = array_search( + $value, + call_user_func([$class, 'getConstants'], 'strtolower', $classPrefixed, $namespaceSeparator) + ); + + if ($useTranslation) { + $translatedLabel = $this->translator->trans($label, [], $translationDomain); + + return $translatedLabel ?: $label; + } + + return $label; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'greg0ire_enum'; + } +} diff --git a/test/Bridge/Symfony/Bundle/Greg0ireEnumBundleTest.php b/test/Bridge/Symfony/Bundle/Greg0ireEnumBundleTest.php new file mode 100644 index 0000000..054948d --- /dev/null +++ b/test/Bridge/Symfony/Bundle/Greg0ireEnumBundleTest.php @@ -0,0 +1,38 @@ + + */ +final class Greg0ireEnumBundleTest extends AbstractContainerBuilderTestCase +{ + /** + * @var Greg0ireEnumBundle + */ + private $bundle; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + parent::setUp(); + + $this->bundle = new Greg0ireEnumBundle(); + } + + public function testBuild() + { + $this->bundle->build($this->container); + } + + public function testGetContainerExtension() + { + $this->assertInstanceOf(Greg0ireEnumExtension::class, $this->bundle->getContainerExtension()); + } +} diff --git a/test/Bridge/Symfony/DependencyInjection/Greg0ireEnumExtensionTest.php b/test/Bridge/Symfony/DependencyInjection/Greg0ireEnumExtensionTest.php new file mode 100644 index 0000000..1f1ad2b --- /dev/null +++ b/test/Bridge/Symfony/DependencyInjection/Greg0ireEnumExtensionTest.php @@ -0,0 +1,36 @@ + + */ +final class Greg0ireEnumExtensionTest extends AbstractExtensionTestCase +{ + public function testLoad() + { + $this->load(); + + $this->assertContainerBuilderHasService('greg0ire_enum.twig.extension.enum', EnumExtension::class); + $this->assertContainerBuilderHasServiceDefinitionWithArgument( + 'greg0ire_enum.twig.extension.enum', + 0, + new Reference('translator.default') + ); + } + + /** + * {@inheritdoc} + */ + protected function getContainerExtensions() + { + return [ + new Greg0ireEnumExtension(), + ]; + } +} diff --git a/test/Bridge/Twig/Extension/EnumExtensionTest.php b/test/Bridge/Twig/Extension/EnumExtensionTest.php new file mode 100644 index 0000000..3506583 --- /dev/null +++ b/test/Bridge/Twig/Extension/EnumExtensionTest.php @@ -0,0 +1,74 @@ + + */ +final class EnumExtensionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var TranslatorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $translator; + + /** + * @var EnumExtension + */ + private $extension; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + $this->translator = $this->getMock(TranslatorInterface::class); + $this->extension = new EnumExtension($this->translator); + } + + public function testEnvironment() + { + $twig = new \Twig_Environment(); + $twig->addExtension($this->extension); + + $this->assertTrue($twig->hasExtension('greg0ire_enum')); + $this->assertInstanceOf(\Twig_SimpleFilter::class, $twig->getFilter('enum_label')); + } + + /** + * @dataProvider getLabels + */ + public function testLabel($value, $class, $classPrefix, $separator, $expectedResult) + { + $this->assertSame( + $expectedResult, + $this->extension->label($value, $class, false, $classPrefix, $separator) + ); + } + + public function getLabels() + { + return [ + [FooInterface::CHUCK, FooEnum::class, false, null, 'chuck'], + [FooInterface::CHUCK, FooEnum::class, true, null, 'greg0ire_enum_tests_fixtures_foo_enum_chuck'], + [FooInterface::CHUCK, FooEnum::class, true, '.', 'greg0ire.enum.tests.fixtures.foo_enum.chuck'], + ]; + } + + public function testLabelWithTranslator() + { + $this->translator->expects($this->once()) + ->method('trans')->with('greg0ire_enum_tests_fixtures_foo_enum_chuck', [], 'test'); + + $this->assertSame( + 'greg0ire_enum_tests_fixtures_foo_enum_chuck', + $this->extension->label(FooInterface::CHUCK, FooEnum::class, 'test'), + 'Without any available translation, the filter should just return the key.' + ); + } +}