├── UPGRADE-2.1.md ├── tests ├── Fixtures │ ├── FooInterface.php │ ├── DummyEnum.php │ ├── DummyWithSameValuesEnum.php │ ├── FooEnum.php │ └── AllEnum.php ├── Bridge │ ├── Symfony │ │ ├── Bundle │ │ │ └── Greg0ireEnumBundleTest.php │ │ ├── Validator │ │ │ └── Constraint │ │ │ │ ├── EnumTest.php │ │ │ │ └── EnumValidatorTest.php │ │ ├── Form │ │ │ └── Type │ │ │ │ └── EnumTypeTest.php │ │ └── DependencyInjection │ │ │ └── Greg0ireEnumExtensionTest.php │ └── Twig │ │ └── Extension │ │ └── EnumExtensionTest.php └── AbstractEnumTest.php ├── UPGRADE-2.0.md ├── UPGRADE-3.0.md ├── UPGRADE-4.0.md ├── src ├── Exception │ ├── InvalidEnumName.php │ └── InvalidEnumValue.php ├── Bridge │ ├── Symfony │ │ ├── Resources │ │ │ └── config │ │ │ │ ├── services.xml │ │ │ │ └── twig.xml │ │ ├── DependencyInjection │ │ │ ├── Compiler │ │ │ │ └── TranslatorCompilerPass.php │ │ │ └── Greg0ireEnumExtension.php │ │ ├── Bundle │ │ │ └── Greg0ireEnumBundle.php │ │ ├── Validator │ │ │ └── Constraint │ │ │ │ └── Enum.php │ │ ├── Form │ │ │ └── Type │ │ │ │ └── EnumType.php │ │ └── Translator │ │ │ └── GetLabel.php │ └── Twig │ │ └── Extension │ │ └── EnumExtension.php └── AbstractEnum.php ├── UPGRADE-1.1.md ├── phpunit.xml.dist ├── CONTRIBUTING.md ├── composer.json ├── README.md └── LICENSE /UPGRADE-2.1.md: -------------------------------------------------------------------------------- 1 | Upgrade from 2.0 to 2.1 2 | ======================= 3 | 4 | ### Enum class rename 5 | 6 | * `BaseEnum` is now deprecated in favor of `AbstractEnum`. 7 | -------------------------------------------------------------------------------- /tests/Fixtures/FooInterface.php: -------------------------------------------------------------------------------- 1 | FooInterface::class, 13 | DummyEnum::class, 14 | ]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /UPGRADE-3.0.md: -------------------------------------------------------------------------------- 1 | Upgrade from 2.x to 3.0 2 | ======================= 3 | 4 | ## PHP support 5 | 6 | PHP 5.3, 5.4 and 5.5 versions are not supported anymore. 7 | 8 | ## Deprecations 9 | 10 | All the deprecated code introduced on 2.x is removed on 3.0. 11 | 12 | Please read 2.x upgrade guides for more information. 13 | 14 | See also the [diff code](https://github.com/greg0ire/enum/compare/v2.0.0...v3.0.0). 15 | -------------------------------------------------------------------------------- /UPGRADE-4.0.md: -------------------------------------------------------------------------------- 1 | # Upgrade from 3.x to 4.0 2 | 3 | ## Dependencies 4 | 5 | Support for Symfony < 3.2 has been dropped. 6 | Support for Twig < 2.4.2 has been dropped. 7 | Support for PHP < 7.1 has been dropped. 8 | 9 | ## API 10 | 11 | Type hinting has been added to most classes. 12 | 13 | ### `Greg0ire\Enum\AbstractEnum` 14 | 15 | `getEnumTypes` may no longer return a string, it should always return an array 16 | now. 17 | -------------------------------------------------------------------------------- /src/Exception/InvalidEnumName.php: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/config/twig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | tests 18 | 19 | 20 | 21 | 22 | src 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/DependencyInjection/Compiler/TranslatorCompilerPass.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class TranslatorCompilerPass implements CompilerPassInterface 14 | { 15 | /** 16 | * {@inheritdoc} 17 | */ 18 | public function process(ContainerBuilder $container): void 19 | { 20 | if (class_exists(AbstractExtension::class) && $container->hasDefinition('translator.default')) { 21 | $container->getDefinition('greg0ire_enum.symfony.translator.get_label') 22 | ->addArgument(new Reference('translator.default')); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Bundle/Greg0ireEnumBundle.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class Greg0ireEnumBundle extends Bundle 14 | { 15 | /** 16 | * {@inheritdoc} 17 | */ 18 | public function build(ContainerBuilder $container): void 19 | { 20 | parent::build($container); 21 | 22 | $container 23 | ->addCompilerPass(new TranslatorCompilerPass()); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | protected function getContainerExtensionClass(): string 30 | { 31 | return Greg0ireEnumExtension::class; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/DependencyInjection/Greg0ireEnumExtension.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class Greg0ireEnumExtension extends Extension 15 | { 16 | /** 17 | * {@inheritdoc} 18 | */ 19 | public function load(array $configs, ContainerBuilder $container): void 20 | { 21 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 22 | 23 | if (class_exists(AbstractExtension::class)) { 24 | $loader->load('twig.xml'); 25 | } 26 | 27 | $loader->load('services.xml'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Hi there! We're thrilled that you'd like to contribute to this project. 4 | Your help is essential for keeping it great. 5 | 6 | ## Submitting a pull request 7 | 8 | 1. [Fork](https://github.com/greg0ire/enum/fork) and clone the repository 9 | 2. Configure and install the dependencies: `composer install` 10 | 3. Make sure the tests pass on your machine via the section [Running the tests](#running-the-tests) 11 | 4. Create a new branch: `git checkout -b my-branch-name` 12 | 5. Make your change, add tests, and make sure the tests still pass 13 | 6. Push to your fork and [submit a pull request](https://github.com/greg0ire/enum/compare) 14 | 7. Pat yourself on the back and wait for your pull request to be reviewed and merged. 15 | 16 | ## Running the tests 17 | 18 | ### Prerequisites 19 | 20 | Install the development dependencies via [Composer](https://getcomposer.org/): 21 | 22 | ```bash 23 | composer install 24 | ``` 25 | 26 | Then run: 27 | 28 | ```bash 29 | php vendor/bin/simple-phpunit 30 | ``` 31 | -------------------------------------------------------------------------------- /tests/Bridge/Symfony/Bundle/Greg0ireEnumBundleTest.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class Greg0ireEnumBundleTest extends AbstractContainerBuilderTestCase 13 | { 14 | /** 15 | * @var Greg0ireEnumBundle 16 | */ 17 | private $bundle; 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | protected function setUp(): void 23 | { 24 | parent::setUp(); 25 | 26 | $this->bundle = new Greg0ireEnumBundle(); 27 | } 28 | 29 | public function testBuild() 30 | { 31 | $this->assertNull($this->bundle->build($this->container)); 32 | } 33 | 34 | public function testGetContainerExtension() 35 | { 36 | $this->assertInstanceOf(Greg0ireEnumExtension::class, $this->bundle->getContainerExtension()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Bridge/Symfony/Validator/Constraint/EnumTest.php: -------------------------------------------------------------------------------- 1 | assertSame($enumClass, $enumConstraint->class); 23 | } 24 | 25 | public function getValidEnums() 26 | { 27 | return [ 28 | [AllEnum::class], 29 | [DummyEnum::class], 30 | [FooEnum::class], 31 | ]; 32 | } 33 | 34 | /** 35 | * @dataProvider getInvalidEnums 36 | */ 37 | public function testInvalidEnumClasses($enumClass) 38 | { 39 | $this->expectException(ConstraintDefinitionException::class); 40 | 41 | new Enum($enumClass); 42 | } 43 | 44 | public function getInvalidEnums() 45 | { 46 | return [ 47 | [FooInterface::class], 48 | [\stdClass::class], 49 | ['This\Does\Not\Exist\At\All'], 50 | ]; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Validator/Constraint/Enum.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class Enum extends Choice 18 | { 19 | /** 20 | * @var string 21 | */ 22 | public $class; 23 | 24 | /** 25 | * @var bool 26 | */ 27 | public $showKeys = false; 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function __construct($options = null) 33 | { 34 | parent::__construct($options); 35 | 36 | if (!is_a($this->class, AbstractEnum::class, true)) { 37 | throw new ConstraintDefinitionException( 38 | 'The option "class" must be a class that inherits from '.AbstractEnum::class 39 | ); 40 | } 41 | $this->choices = call_user_func([$this->class, 'getConstants']); 42 | 43 | if ($this->showKeys) { 44 | $keysMessage = 'Valid '.$this->class.' constant keys are: {{ choices }}'; 45 | $this->message .= ' '.$keysMessage; 46 | $this->multipleMessage .= ' '.$keysMessage; 47 | } 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function validatedBy(): string 54 | { 55 | return ChoiceValidator::class; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function getDefaultOption(): string 62 | { 63 | return 'class'; 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function getRequiredOptions(): array 70 | { 71 | return ['class']; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Form/Type/EnumType.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class EnumType extends AbstractType 16 | { 17 | /** 18 | * {@inheritdoc} 19 | */ 20 | public function configureOptions(OptionsResolver $resolver): void 21 | { 22 | $resolver->setRequired('class'); 23 | $resolver->setAllowedTypes('class', 'string'); 24 | $resolver->setNormalizer('class', function (Options $options, string $class): string { 25 | if (!is_a($class, AbstractEnum::class, true)) { 26 | throw new LogicException('The option "class" must be a class that inherits from '.AbstractEnum::class); 27 | } 28 | 29 | return $class; 30 | }); 31 | 32 | $resolver->setDefault('prefix_label_with_class', false); 33 | $resolver->setAllowedTypes('prefix_label_with_class', 'bool'); 34 | 35 | $resolver->setDefault('choices', function (Options $options): array { 36 | $class = $options['class']; 37 | 38 | $keys = $options['prefix_label_with_class'] 39 | ? call_user_func([$class, 'getClassPrefixedKeys'], 'strtolower') 40 | : call_user_func([$class, 'getKeys'], 'strtolower'); 41 | 42 | return array_combine($keys, call_user_func([$class, 'getConstants'])); 43 | }); 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function getBlockPrefix(): string 50 | { 51 | return 'enum'; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function getParent(): string 58 | { 59 | return ChoiceType::class; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "greg0ire/enum", 3 | "type": "library", 4 | "description": "work around the missing enum type in php", 5 | "keywords": [ 6 | "enum", 7 | "enumeration", 8 | "Symfony", 9 | "validator", 10 | "form" 11 | ], 12 | "license": "CC-BY-SA-3.0", 13 | "authors": [ 14 | { 15 | "name": "Grégoire Paris", 16 | "email": "postmaster@greg0ire.fr" 17 | }, 18 | { 19 | "name": "Sullivan Sénéchal", 20 | "email": "soullivaneuh@gmail.com" 21 | } 22 | ], 23 | "require": { 24 | "php": "^7.1 || ^8.0", 25 | "doctrine/inflector": "^1.4 || ^2.0" 26 | }, 27 | "conflict": { 28 | "symfony/form": "<3.2 || >=7.0 <999", 29 | "symfony/validator": "<3.2 || >=7.0 <999" 30 | }, 31 | "require-dev": { 32 | "matthiasnoback/symfony-dependency-injection-test": "^4.0", 33 | "symfony/config": "^4.2 || ^5.0 || ^6.0", 34 | "symfony/dependency-injection": "^4.2 || ^5.0 || ^6.0", 35 | "symfony/form": "^4.2.5 || ^5.0 || ^6.0", 36 | "symfony/framework-bundle": "^4.2 || ^5.0 || ^6.0", 37 | "symfony/http-kernel": "^4.2 || ^5.1.5 || ^6.0", 38 | "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0", 39 | "symfony/phpunit-bridge": "^5.0 || ^6.0", 40 | "symfony/translation": "^4.2 || ^5.0 || ^6.0", 41 | "symfony/translation-contracts": "^2.0", 42 | "symfony/twig-bundle": "^4.2 || ^5.0 || ^6.0", 43 | "symfony/validator": "^4.3.6 || ^5.0 || ^6.0", 44 | "twig/twig": "^2.4.2 || ^3.0" 45 | }, 46 | "suggest": { 47 | "symfony/form": "To use enum form type", 48 | "symfony/validator": "To use enum validator" 49 | }, 50 | "config": { 51 | "sort-packages": true 52 | }, 53 | "extra": { 54 | "branch-alias": { 55 | "dev-legacy-stable": "3.x-dev", 56 | "dev-stable": "4.x-dev", 57 | "dev-unstable": "5.x-dev" 58 | } 59 | }, 60 | "autoload": { 61 | "psr-4": { 62 | "Greg0ire\\Enum\\": "src" 63 | } 64 | }, 65 | "autoload-dev": { 66 | "psr-4": { 67 | "Greg0ire\\Enum\\Tests\\": "tests" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Bridge/Symfony/Form/Type/EnumTypeTest.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class EnumTypeTest extends TypeTestCase 15 | { 16 | public function testEnumChoices() 17 | { 18 | $view = $this->factory->create(EnumType::class, null, [ 19 | 'class' => DummyEnum::class, 20 | ])->createView(); 21 | 22 | $this->assertSame('42', $view->vars['choices'][0]->value); 23 | $this->assertSame('some_value', $view->vars['choices'][1]->value); 24 | $this->assertFalse($view->vars['is_selected']($view->vars['choices'][0], $view->vars['value'])); 25 | $this->assertFalse($view->vars['is_selected']($view->vars['choices'][1], $view->vars['value'])); 26 | $this->assertSame('first', $view->vars['choices'][0]->label); 27 | $this->assertSame('second', $view->vars['choices'][1]->label); 28 | } 29 | 30 | public function testEnumChoicesClassPrefix() 31 | { 32 | $view = $this->factory->create(EnumType::class, null, [ 33 | 'class' => DummyEnum::class, 34 | 'prefix_label_with_class' => true, 35 | ])->createView(); 36 | 37 | $this->assertSame('greg0ire_enum_tests_fixtures_dummy_enum_first', $view->vars['choices'][0]->label); 38 | $this->assertSame('greg0ire_enum_tests_fixtures_dummy_enum_second', $view->vars['choices'][1]->label); 39 | } 40 | 41 | /** 42 | * @dataProvider getInvalidEnums 43 | * 44 | * @param string $class 45 | */ 46 | public function testInvalidEnums($class) 47 | { 48 | $this->expectException( 49 | LogicException::class, 50 | 'The option "class" must be a class that inherits from Greg0ire\Enum\AbstractEnum' 51 | ); 52 | 53 | $this->factory->create(EnumType::class, null, [ 54 | 'class' => $class, 55 | ]); 56 | 57 | $this->builder->getForm(); 58 | } 59 | 60 | public function getInvalidEnums() 61 | { 62 | return [ 63 | [FooInterface::class], 64 | [\stdClass::class], 65 | ['This\Does\Not\Exist\At\All'], 66 | ]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Translator/GetLabel.php: -------------------------------------------------------------------------------- 1 | translator = $translator; 17 | } 18 | 19 | /** 20 | * Displays the label corresponding to a specific value of an enumeration. 21 | * 22 | * @param mixed $value Must exists in the enumeration class specified with $class 23 | * @param string $class The enum class name 24 | * @param string|bool $translationDomain the translation domain to use if the translator if available. 25 | * string: Use the specified one 26 | * null: Use the default one 27 | * false: Do not use the translator 28 | * @param bool $classPrefixed Prefix the label with the enum class. Defaults to true if the translator 29 | * is available and enabled, false otherwise. 30 | * @param string $namespaceSeparator namespace separator to use with the class prefix. 31 | * This takes effect only if $classPrefixed is true 32 | */ 33 | public function __invoke( 34 | $value, 35 | string $class, 36 | $translationDomain = null, 37 | ?bool $classPrefixed = null, 38 | ?string $namespaceSeparator = null 39 | ): string { 40 | // Determine if the translator can be used or not. 41 | $useTranslation = $this->translator instanceof TranslatorInterface 42 | && (is_null($translationDomain) || is_string($translationDomain)); 43 | 44 | // If not defined, guess the default behavior. 45 | if (is_null($classPrefixed)) { 46 | $classPrefixed = $useTranslation; 47 | } 48 | 49 | $label = array_search( 50 | $value, 51 | call_user_func([$class, 'getConstants'], 'strtolower', $classPrefixed, $namespaceSeparator) 52 | ); 53 | 54 | if ($useTranslation) { 55 | $translatedLabel = $this->translator->trans($label, [], $translationDomain); 56 | 57 | return $translatedLabel ?: $label; 58 | } 59 | 60 | return $label; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Bridge/Symfony/DependencyInjection/Greg0ireEnumExtensionTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class Greg0ireEnumExtensionTest extends AbstractExtensionTestCase 16 | { 17 | protected $frameworkExtension; 18 | 19 | protected function setUp(): void 20 | { 21 | parent::setUp(); 22 | $this->setParameter('kernel.debug', true); 23 | $this->setParameter('kernel.root_dir', sys_get_temp_dir()); 24 | $this->setParameter('kernel.project_dir', sys_get_temp_dir()); 25 | $this->setParameter('kernel.build_dir', sys_get_temp_dir()); 26 | $this->setParameter('kernel.bundles_metadata', []); 27 | $this->setParameter('kernel.container_class', Container::class); 28 | 29 | // needed for legacy versions of symfony 30 | $this->setParameter('kernel.bundles', []); 31 | $this->setParameter('kernel.cache_dir', sys_get_temp_dir()); 32 | 33 | $this->container->registerExtension($this->frameworkExtension = new FrameworkExtension()); 34 | } 35 | 36 | public function testLoad() 37 | { 38 | $this->frameworkExtension->load( 39 | ['framework' => ['translator' => ['fallbacks' => ['en']]]], 40 | $this->container 41 | ); 42 | $this->load(); 43 | 44 | $this->assertContainerBuilderHasService( 45 | 'greg0ire_enum.twig.extension.enum', 46 | EnumExtension::class 47 | ); 48 | } 49 | 50 | public function testLoadWithoutATranslator() 51 | { 52 | $this->frameworkExtension->load( 53 | ['framework' => ['translator' => ['enabled' => false]]], 54 | $this->container 55 | ); 56 | $this->load(); 57 | 58 | $this->assertContainerBuilderHasService( 59 | 'greg0ire_enum.twig.extension.enum', 60 | EnumExtension::class 61 | ); 62 | } 63 | 64 | public function testLabelServiceHasTheTranslator() 65 | { 66 | $this->registerService('translator.logging.inner', \stdClass::class); 67 | $this->registerService('logger', \stdClass::class); 68 | $this->frameworkExtension->load( 69 | ['framework' => ['translator' => ['fallbacks' => ['en']]]], 70 | $this->container 71 | ); 72 | $this->load(); 73 | $this->compile(); 74 | $compilerPass = new TranslatorCompilerPass(); 75 | $compilerPass->process($this->container); 76 | 77 | $this->assertContainerBuilderHasServiceDefinitionWithArgument( 78 | 'greg0ire_enum.symfony.translator.get_label', 79 | 0, 80 | new Reference('translator.default') 81 | ); 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | protected function getContainerExtensions(): array 88 | { 89 | return [new Greg0ireEnumExtension()]; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/Bridge/Symfony/Validator/Constraint/EnumValidatorTest.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class EnumValidatorTest extends ConstraintValidatorTestCase 17 | { 18 | public function testNullIsValid() 19 | { 20 | $this->validator->validate(null, new Enum([ 21 | 'strict' => true, 22 | 'class' => DummyEnum::class, 23 | ])); 24 | 25 | $this->assertNoViolation(); 26 | } 27 | 28 | public function testBlankButStringIsInvalid() 29 | { 30 | $this->validator->validate(' ', new Enum([ 31 | 'strict' => true, 32 | 'class' => DummyEnum::class, 33 | ])); 34 | 35 | $this->buildViolation('The value you selected is not a valid choice.') 36 | ->setParameter('{{ value }}', '" "') 37 | ->setParameter('{{ choices }}', '42, "some_value"') 38 | ->setCode(Choice::NO_SUCH_CHOICE_ERROR) 39 | ->assertRaised(); 40 | } 41 | 42 | public function testValidValues() 43 | { 44 | $this->validator->validate(DummyEnum::FIRST, new Enum([ 45 | 'strict' => true, 46 | 'class' => DummyEnum::class, 47 | ])); 48 | $this->validator->validate(DummyEnum::SECOND, new Enum([ 49 | 'strict' => true, 50 | 'class' => DummyEnum::class, 51 | ])); 52 | 53 | foreach (FooEnum::getConstants() as $value) { 54 | $this->validator->validate($value, new Enum([ 55 | 'strict' => true, 56 | 'class' => FooEnum::class, 57 | ])); 58 | } 59 | 60 | foreach (AllEnum::getConstants() as $value) { 61 | $this->validator->validate($value, new Enum([ 62 | 'strict' => true, 63 | 'class' => AllEnum::class, 64 | ])); 65 | } 66 | 67 | $this->assertNoViolation(); 68 | } 69 | 70 | public function testInvalidValue() 71 | { 72 | $this->validator->validate(1337, new Enum([ 73 | 'strict' => true, 74 | 'class' => DummyEnum::class, 75 | ])); 76 | 77 | $this->buildViolation('The value you selected is not a valid choice.') 78 | ->setParameter('{{ value }}', '1337') 79 | ->setParameter('{{ choices }}', '42, "some_value"') 80 | ->setCode(Choice::NO_SUCH_CHOICE_ERROR) 81 | ->assertRaised(); 82 | } 83 | 84 | public function testInvalidValueWithShowKeys() 85 | { 86 | $this->validator->validate(1337, new Enum([ 87 | 'strict' => true, 88 | 'class' => DummyEnum::class, 89 | 'showKeys' => true, 90 | ])); 91 | 92 | $this->buildViolation('The value you selected is not a valid choice. ' 93 | .'Valid '.DummyEnum::class.' constant keys are: {{ choices }}') 94 | ->setParameter('{{ value }}', '1337') 95 | ->setParameter('{{ choices }}', '42, "some_value"') 96 | ->setCode(Choice::NO_SUCH_CHOICE_ERROR) 97 | ->assertRaised(); 98 | } 99 | 100 | /** 101 | * {@inheritdoc} 102 | */ 103 | protected function createValidator() 104 | { 105 | return new ChoiceValidator(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/Bridge/Twig/Extension/EnumExtensionTest.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final class EnumExtensionTest extends TestCase 19 | { 20 | /** 21 | * @var TranslatorInterface|\PHPUnit_Framework_MockObject_MockObject 22 | */ 23 | private $translator; 24 | 25 | /** 26 | * @var EnumExtension 27 | */ 28 | private $extension; 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | protected function setUp(): void 34 | { 35 | $this->translator = $this->createMock(TranslatorInterface::class); 36 | $this->extension = new EnumExtension(new GetLabel($this->translator)); 37 | } 38 | 39 | public function testEnvironment() 40 | { 41 | $twig = new Environment($this->createMock(LoaderInterface::class)); 42 | $twig->addExtension($this->extension); 43 | 44 | $this->assertInstanceOf(TwigFilter::class, $twig->getFilter('enum_label')); 45 | 46 | $this->assertTrue($twig->hasExtension(EnumExtension::class)); 47 | } 48 | 49 | /** 50 | * @dataProvider getLabels 51 | */ 52 | public function testLabel($value, $class, $classPrefix, $separator, $expectedResult) 53 | { 54 | $this->assertSame( 55 | $expectedResult, 56 | $this->extension->label($value, $class, false, $classPrefix, $separator) 57 | ); 58 | } 59 | 60 | public function getLabels() 61 | { 62 | return [ 63 | [FooInterface::CHUCK, FooEnum::class, false, null, 'chuck'], 64 | [FooInterface::CHUCK, FooEnum::class, true, null, 'greg0ire_enum_tests_fixtures_foo_enum_chuck'], 65 | [FooInterface::CHUCK, FooEnum::class, true, '.', 'greg0ire.enum.tests.fixtures.foo_enum.chuck'], 66 | ]; 67 | } 68 | 69 | public function testLabelWithTranslator() 70 | { 71 | $this->translator->expects($this->once()) 72 | ->method('trans')->with('greg0ire_enum_tests_fixtures_foo_enum_chuck', [], 'test'); 73 | 74 | $this->assertSame( 75 | 'greg0ire_enum_tests_fixtures_foo_enum_chuck', 76 | $this->extension->label(FooInterface::CHUCK, FooEnum::class, 'test'), 77 | 'Without any available translation, the filter should just return the key.' 78 | ); 79 | } 80 | 81 | public function testGetConstants() 82 | { 83 | $this->assertSame( 84 | [ 85 | 'GOD' => 'Dieu', 86 | 'CHUCK' => 'Chuck Norris', 87 | 'GUITRY' => 'Sacha Guitry', 88 | ], 89 | $this->extension->getConstants(FooEnum::class) 90 | ); 91 | } 92 | 93 | public function testGetKeys() 94 | { 95 | $this->assertSame( 96 | [ 97 | 'GOD', 98 | 'CHUCK', 99 | 'GUITRY', 100 | ], 101 | $this->extension->getKeys(FooEnum::class) 102 | ); 103 | } 104 | 105 | public function testGetClassPrefixedKeys() 106 | { 107 | $this->assertSame( 108 | [ 109 | 'greg0ire_enum_tests_fixtures_foo_enum_GOD', 110 | 'greg0ire_enum_tests_fixtures_foo_enum_CHUCK', 111 | 'greg0ire_enum_tests_fixtures_foo_enum_GUITRY', 112 | ], 113 | $this->extension->getClassPrefixedKeys(FooEnum::class) 114 | ); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Bridge/Twig/Extension/EnumExtension.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class EnumExtension extends AbstractExtension 16 | { 17 | /** 18 | * @var GetLabel 19 | */ 20 | private $label; 21 | 22 | public function __construct($label) 23 | { 24 | if ($label instanceof TranslatorInterface) { 25 | @trigger_error(sprintf( 26 | 'Providing a %s instance to %s is deprecated and will not be supported in 5.0. Please provide a %s instance instead.', 27 | TranslatorInterface::class, 28 | __METHOD__, 29 | GetLabel::class 30 | ), E_USER_DEPRECATED); 31 | $this->label = new GetLabel($label); 32 | 33 | return; 34 | } 35 | $this->label = $label; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function getFilters(): array 42 | { 43 | return [new TwigFilter('enum_label', [$this, 'label'])]; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function getFunctions(): array 50 | { 51 | return [ 52 | new TwigFunction('enum_get_constants', [$this, 'getConstants']), 53 | new TwigFunction('enum_get_keys', [$this, 'getKeys']), 54 | new TwigFunction('enum_get_class_prefixed_keys', [$this, 'getClassPrefixedKeys']), 55 | ]; 56 | } 57 | 58 | /** 59 | * Displays the label corresponding to a specific value of an enumeration. 60 | * 61 | * @param mixed $value Must exists in the enumeration class specified with $class 62 | * @param string $class The enum class name 63 | * @param string|bool $translationDomain the translation domain to use if the translator if available. 64 | * string: Use the specified one 65 | * null: Use the default one 66 | * false: Do not use the translator 67 | * @param bool $classPrefixed Prefix the label with the enum class. Defaults to true if the translator 68 | * is available and enabled, false otherwise. 69 | * @param string $namespaceSeparator namespace separator to use with the class prefix. 70 | * This takes effect only if $classPrefixed is true 71 | */ 72 | public function label( 73 | $value, 74 | string $class, 75 | $translationDomain = null, 76 | ?bool $classPrefixed = null, 77 | ?string $namespaceSeparator = null 78 | ): string { 79 | return ($this->label)($value, $class, $translationDomain, $classPrefixed, $namespaceSeparator); 80 | } 81 | 82 | /** 83 | * @see AbstractEnum::getConstants() 84 | */ 85 | public function getConstants( 86 | string $class, 87 | ?callable $keysCallback = null, 88 | ?bool $classPrefixed = false, 89 | ?string $namespaceSeparator = null 90 | ): array { 91 | return call_user_func([$class, 'getConstants'], $keysCallback, $classPrefixed, $namespaceSeparator); 92 | } 93 | 94 | /** 95 | * @see AbstractEnum::getKeys() 96 | */ 97 | public function getKeys(string $class, ?callable $callback = null) 98 | { 99 | return call_user_func([$class, 'getKeys'], $callback); 100 | } 101 | 102 | /** 103 | * @see AbstractEnum::getClassPrefixedKeys() 104 | */ 105 | public function getClassPrefixedKeys( 106 | string $class, 107 | ?callable $callback = null, 108 | ?string $namespaceSeparator = null 109 | ): array { 110 | return call_user_func([$class, 'getClassPrefixedKeys'], $callback, $namespaceSeparator); 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | public function getName(): string 117 | { 118 | return 'greg0ire_enum'; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tests/AbstractEnumTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 18 | [ 19 | 'FIRST' => 42, 20 | 'SECOND' => 'some_value', 21 | ], 22 | DummyEnum::getConstants() 23 | ); 24 | } 25 | 26 | public function testFooGetConstants() 27 | { 28 | $this->assertSame( 29 | [ 30 | 'GOD' => 'Dieu', 31 | 'CHUCK' => 'Chuck Norris', 32 | 'GUITRY' => 'Sacha Guitry', 33 | ], 34 | FooEnum::getConstants() 35 | ); 36 | 37 | $this->assertSame( 38 | [ 39 | 'god' => 'Dieu', 40 | 'chuck' => 'Chuck Norris', 41 | 'guitry' => 'Sacha Guitry', 42 | ], 43 | FooEnum::getConstants('strtolower') 44 | ); 45 | 46 | $this->assertSame( 47 | [ 48 | 'greg0ire.enum.tests.fixtures.foo_enum.god' => 'Dieu', 49 | 'greg0ire.enum.tests.fixtures.foo_enum.chuck' => 'Chuck Norris', 50 | 'greg0ire.enum.tests.fixtures.foo_enum.guitry' => 'Sacha Guitry', 51 | ], 52 | FooEnum::getConstants('strtolower', true, '.') 53 | ); 54 | } 55 | 56 | public function testAllGetConstants() 57 | { 58 | $this->assertSame( 59 | [ 60 | 'originally.GOD' => 'Dieu', 61 | 'originally.CHUCK' => 'Chuck Norris', 62 | 'originally.GUITRY' => 'Sacha Guitry', 63 | 'Greg0ire\Enum\Tests\Fixtures\DummyEnum::FIRST' => 42, 64 | 'Greg0ire\Enum\Tests\Fixtures\DummyEnum::SECOND' => 'some_value', 65 | ], 66 | AllEnum::getConstants() 67 | ); 68 | 69 | $this->assertSame( 70 | [ 71 | 'originally.god' => 'Dieu', 72 | 'originally.chuck' => 'Chuck Norris', 73 | 'originally.guitry' => 'Sacha Guitry', 74 | 'greg0ire\enum\tests\fixtures\dummyenum::first' => 42, 75 | 'greg0ire\enum\tests\fixtures\dummyenum::second' => 'some_value', 76 | ], 77 | AllEnum::getConstants('strtolower') 78 | ); 79 | } 80 | 81 | public function testFooGetKeys() 82 | { 83 | $this->assertSame( 84 | [ 85 | 'GOD', 86 | 'CHUCK', 87 | 'GUITRY', 88 | ], 89 | FooEnum::getKeys() 90 | ); 91 | 92 | $this->assertSame( 93 | [ 94 | 'god', 95 | 'chuck', 96 | 'guitry', 97 | ], 98 | FooEnum::getKeys('strtolower') 99 | ); 100 | } 101 | 102 | public function testFooGetClassPrefixedKeys() 103 | { 104 | $this->assertSame( 105 | [ 106 | 'greg0ire_enum_tests_fixtures_foo_enum_GOD', 107 | 'greg0ire_enum_tests_fixtures_foo_enum_CHUCK', 108 | 'greg0ire_enum_tests_fixtures_foo_enum_GUITRY', 109 | ], 110 | FooEnum::getClassPrefixedKeys() 111 | ); 112 | 113 | $this->assertSame( 114 | [ 115 | 'greg0ire_enum_tests_fixtures_foo_enum_god', 116 | 'greg0ire_enum_tests_fixtures_foo_enum_chuck', 117 | 'greg0ire_enum_tests_fixtures_foo_enum_guitry', 118 | ], 119 | FooEnum::getClassPrefixedKeys('strtolower') 120 | ); 121 | 122 | $this->assertSame( 123 | [ 124 | 'greg0ire.enum.tests.fixtures.foo_enum.god', 125 | 'greg0ire.enum.tests.fixtures.foo_enum.chuck', 126 | 'greg0ire.enum.tests.fixtures.foo_enum.guitry', 127 | ], 128 | FooEnum::getClassPrefixedKeys('strtolower', '.') 129 | ); 130 | } 131 | 132 | public function testsIsValidName() 133 | { 134 | $this->assertFalse(DummyEnum::isValidName('fiRsT')); 135 | $this->assertFalse(DummyEnum::isValidName('invalid')); 136 | } 137 | 138 | public function testAssertValidName() 139 | { 140 | $this->expectException(InvalidEnumName::class); 141 | $this->expectExceptionMessage( 142 | '"fiRsT" is not a valid name, valid names are: ("FIRST", "SECOND")' 143 | ); 144 | DummyEnum::assertValidName('fiRsT'); 145 | } 146 | 147 | public function testIsValidValue() 148 | { 149 | $this->assertTrue(DummyEnum::isValidValue(42)); 150 | $this->assertFalse(DummyEnum::isValidValue('42')); 151 | } 152 | 153 | public function testAssertValidValue() 154 | { 155 | $this->expectException(InvalidEnumValue::class); 156 | $this->expectExceptionMessage( 157 | '"test" is not a valid value, valid values are: ("42", "some_value")' 158 | ); 159 | DummyEnum::assertValidValue('test'); 160 | } 161 | 162 | public function testSimpleKeyFromValue() 163 | { 164 | $this->assertSame('FIRST', DummyEnum::getKeysFromValue(42)); 165 | } 166 | 167 | public function testMultiKeysFromValue() 168 | { 169 | $this->assertSame(['FIRST', 'SECOND'], DummyWithSameValuesEnum::getKeysFromValue(42)); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/AbstractEnum.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Sullivan Senechal 13 | */ 14 | abstract class AbstractEnum 15 | { 16 | /** 17 | * @var string 18 | */ 19 | public static $defaultNamespaceSeparator = '_'; 20 | 21 | private static $constCache = []; 22 | 23 | /** 24 | * @var Inflector 25 | */ 26 | private static $inflector; 27 | 28 | /** 29 | * Uses reflection to find the constants defined in the class and cache 30 | * them in a local property for performance, before returning them. 31 | * 32 | * @param bool $classPrefixed true if you want the enum class prefix on each keys, false otherwise 33 | * @param string $namespaceSeparator only relevant if $classPrefixed is set to true 34 | * 35 | * @return array a hash with your constants and their value. Useful for 36 | * building a choice widget 37 | */ 38 | final public static function getConstants( 39 | ?callable $keysCallback = null, 40 | bool $classPrefixed = false, 41 | string $namespaceSeparator = null 42 | ): array { 43 | $namespaceSeparator = $namespaceSeparator ?: static::$defaultNamespaceSeparator; 44 | $enumTypes = static::getEnumTypes(); 45 | $enums = []; 46 | 47 | foreach ($enumTypes as $key => $enumType) { 48 | $cacheKey = is_int($key) ? $enumType : $key; 49 | 50 | if (!isset(self::$constCache[$cacheKey])) { 51 | $reflect = new \ReflectionClass($enumType); 52 | self::$constCache[$cacheKey] = $reflect->getConstants(); 53 | } 54 | if (count($enumTypes) > 1) { 55 | foreach (self::$constCache[$cacheKey] as $subKey => $value) { 56 | $subKey = $cacheKey.(is_int($key) ? '::' : '.').$subKey; 57 | $enums[$subKey] = $value; 58 | } 59 | } else { 60 | $enums = self::$constCache[$cacheKey]; 61 | } 62 | } 63 | 64 | if (is_callable($keysCallback) || $classPrefixed) { 65 | return array_combine( 66 | $classPrefixed 67 | ? static::getClassPrefixedKeys($keysCallback, $namespaceSeparator) 68 | : static::getKeys($keysCallback), 69 | $enums 70 | ); 71 | } 72 | 73 | return $enums; 74 | } 75 | 76 | /** 77 | * Returns constants keys. 78 | * 79 | * @param callable|null $callback A callable function compatible with array_map 80 | * 81 | * @return string[] 82 | */ 83 | final public static function getKeys($callback = null): array 84 | { 85 | $keys = array_keys(static::getConstants()); 86 | 87 | if ($callback !== null) { 88 | return array_map($callback, $keys); 89 | } 90 | 91 | return $keys; 92 | } 93 | 94 | /** 95 | * @param callable|null $callback A callable function compatible with array_map 96 | * @param string|null $namespaceSeparator Choose which character should replace namespaces separation. 97 | * Example: With Foo\BarMagic enum class with '.' separator, 98 | * it will be converted to foo.bar_magic.YOUR_KEY 99 | * 100 | * @return string[] 101 | */ 102 | final public static function getClassPrefixedKeys( 103 | ?callable $callback = null, 104 | ?string $namespaceSeparator = null 105 | ): array { 106 | $namespaceSeparator = $namespaceSeparator ?: static::$defaultNamespaceSeparator; 107 | $classKey = str_replace( 108 | '\\', 109 | $namespaceSeparator, 110 | self::inflector()->tableize(static::class) 111 | ); 112 | 113 | $keys = static::getKeys(function ($key) use ($namespaceSeparator, $classKey) { 114 | return $classKey.$namespaceSeparator.$key; 115 | }); 116 | 117 | if (is_callable($callback)) { 118 | return array_map($callback, $keys); 119 | } 120 | 121 | return $keys; 122 | } 123 | 124 | private static function inflector(): Inflector 125 | { 126 | if (!isset(self::$inflector)) { 127 | self::$inflector = InflectorFactory::create()->build(); 128 | } 129 | 130 | return self::$inflector; 131 | } 132 | 133 | /** 134 | * Checks whether a constant with this name is defined. 135 | */ 136 | final public static function isValidName(string $name): bool 137 | { 138 | return array_key_exists($name, self::getConstants()); 139 | } 140 | 141 | /** 142 | * Asserts a constant with this name is defined. 143 | * 144 | * @throws InvalidEnumName 145 | */ 146 | final public static function assertValidName(string $name): void 147 | { 148 | if (!self::isValidName($name)) { 149 | throw InvalidEnumName::fromName($name, self::getKeys()); 150 | } 151 | } 152 | 153 | /** 154 | * Checks whether a constant with this value is defined. 155 | * 156 | * @param int|string $value the value to test 157 | * @param bool $strict check the types of the value in the values 158 | * 159 | * @return bool the result of the test 160 | */ 161 | final public static function isValidValue($value, bool $strict = true): bool 162 | { 163 | $values = array_values(self::getConstants()); 164 | 165 | return in_array($value, $values, $strict); 166 | } 167 | 168 | /** 169 | * Asserts a constant with this value is defined. 170 | * 171 | * @param int|string $value the value to test 172 | * @param bool $strict check the types of the value in the values 173 | * 174 | * @throws InvalidEnumValue 175 | */ 176 | final public static function assertValidValue($value, bool $strict = true): void 177 | { 178 | if (!self::isValidValue($value, $strict)) { 179 | throw InvalidEnumValue::fromValue($value, self::getConstants()); 180 | } 181 | } 182 | 183 | /** 184 | * Can be useful if you need to get constants from several classes/interfaces. 185 | * 186 | * @return string[] 187 | */ 188 | protected static function getEnumTypes(): array 189 | { 190 | return [get_called_class()]; 191 | } 192 | 193 | /** 194 | * Returns keys from a value. 195 | * 196 | * @param mixed $value 197 | * 198 | * @return string|string[] 199 | */ 200 | public static function getKeysFromValue($value) 201 | { 202 | $keys = []; 203 | 204 | foreach (static::getConstants() as $key => $item) { 205 | if ($value === $item) { 206 | $keys[] = $key; 207 | } 208 | } 209 | 210 | return count($keys) === 1 ? current($keys) : $keys; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enums 2 | 3 | This package holds a simple class that may be used as an ancestor for your 4 | enum classes. 5 | 6 | [![Build Status][3]](https://travis-ci.org/greg0ire/enum) 7 | 8 | ## Installation 9 | 10 | composer require greg0ire/enum 11 | 12 | ## Usage 13 | 14 | ### Basic usage 15 | 16 | Extend the `Greg0ire\Enum\AbstractEnum`, define your enum key values as constants, 17 | and Bob's your uncle. You can make the class abstract or final, as you see fit. 18 | 19 | ```php 20 | use Greg0ire\Enum\AbstractEnum; 21 | 22 | final class DaysOfWeek extends AbstractEnum { 23 | const Sunday = 0; 24 | const Monday = 1; 25 | const Tuesday = 2; 26 | const Wednesday = 3; 27 | const Thursday = 4; 28 | const Friday = 5; 29 | const Saturday = 6; 30 | } 31 | ``` 32 | 33 | Then, you may use the DaysOfWeek class for input validation: 34 | 35 | ```php 36 | DaysOfWeek::isValidName('Humpday'); // false 37 | DaysOfWeek::isValidName('Monday'); // true 38 | DaysOfWeek::isValidName('monday'); // false 39 | DaysOfWeek::isValidName(0); // false 40 | 41 | DaysOfWeek::isValidValue(0); // true 42 | DaysOfWeek::isValidValue(5); // true 43 | DaysOfWeek::isValidValue(7); // false 44 | DaysOfWeek::isValidValue('Friday'); // false 45 | ``` 46 | 47 | Both methods have an `assert*` counterpart that will throw a 48 | `Greg0ire\Enum\Exception\InvalidEnumValue` exception: 49 | 50 | ``` 51 | DaysOfWeek::assertValidName(0); // InvalidEnumName 52 | DaysOfWeek::assertValidValue('Friday'); // InvalidEnumValue 53 | ``` 54 | 55 | Additionally, you may get all the constants in your class as a hash: 56 | 57 | ```php 58 | DaysOfWeek::getConstants(); 59 | DaysOfWeek::getConstants('strtolower'); // Will combine your values with `DaysOfWeek::getKeys($callback)`. 60 | DaysOfWeek::getConstants('strtolower', true); // Values combine with `DaysOfWeek::getClassPrefixedKeys($callback)`. 61 | DaysOfWeek::getConstants('strtolower', true, '.'); // Same with `DaysOfWeek::getClassPrefixedKeys($callback, $separator)`. 62 | ``` 63 | 64 | You may also get all the keys in your class as an array: 65 | 66 | ```php 67 | DaysOfWeek::getKeys(); 68 | DaysOfWeek::getKeys('strtolower'); // Will call `array_map` with the given callback. 69 | ``` 70 | 71 | Or the key with the enum class prefix: 72 | 73 | ```php 74 | DaysOfWeek::getClassPrefixedKeys(); 75 | DaysOfWeek::getClassPrefixedKeys('strtolower'); // Will call `array_map` with the given callback. 76 | DaysOfWeek::getClassPrefixedKeys('strtolower', '.'); // Replace the namespace separator ('_' by default). 77 | ``` 78 | 79 | If you would like to get the keys from a value: 80 | 81 | ```php 82 | $key = DaysOfWeek::getKeysFromValue(1); // Monday will be assigned to $key 83 | ``` 84 | 85 | If you have many keys with the same value you will get an array, and a value otherwise. 86 | 87 | ### Advanced usage 88 | 89 | If you need to get the constants from a class you cannot modify, or from an 90 | interface, or even from several classes / interfaces, you may override 91 | `AbstractEnum::getEnumTypes()`. 92 | 93 | For example, if you have the following class and interface : 94 | 95 | 96 | ```php 97 | namespace Vendor\Namespace; 98 | 99 | class ClassFromAVendor 100 | { 101 | const SOMETHING = 'something'; 102 | const SOMETHING_ELSE = 'something_else'; 103 | } 104 | ``` 105 | 106 | ```php 107 | namespace My\Namespace; 108 | 109 | interface SomeInterface 110 | { 111 | const ANOTHER_CONST = 'another_const'; 112 | } 113 | ``` 114 | 115 | You can get all three constants by creating this Enum : 116 | 117 | ```php 118 | use Greg0ire\Enum\AbstractEnum; 119 | use My\Namespace\SomeInterface; 120 | use Vendor\Namespace\ClassFromAVendor; 121 | 122 | final class MyEnum extends AbstractEnum 123 | { 124 | protected static function getEnumTypes() 125 | { 126 | return [ClassFromAVendor::class, SomeInterface::class]; 127 | } 128 | } 129 | ``` 130 | 131 | Alternatively, you can specify a prefix for each type to avoid getting FQCNs in 132 | the hash keys. 133 | 134 | ```php 135 | use Greg0ire\Enum\AbstractEnum; 136 | use My\Namespace\SomeInterface; 137 | use Vendor\Namespace\ClassFromAVendor; 138 | 139 | final class MyEnum extends AbstractEnum 140 | { 141 | protected static function getEnumTypes() 142 | { 143 | return [ 144 | 'prefix1' => ClassFromAVendor::class, 145 | 'prefix2' => SomeInterface::class, 146 | ]; 147 | } 148 | } 149 | ``` 150 | 151 | ### Get label from a service 152 | 153 | If you want to get the constant label behind an enum value, you can instantiate 154 | the `GetLabel` class and invoke it. 155 | 156 | ```php 157 | use Greg0ire\Enum\Bridge\Symfony\Translator\GetLabel; 158 | 159 | $label = new GetLabel(); 160 | $label(Your\Enum\Class::VALUE, Your\Enum\Class::class); 161 | ``` 162 | 163 | To enable translation, require the `symfony/translation` component 164 | and pass a `Symfony\Contracts\Translation\TranslationInterface` instance on the 165 | `GetLabel` constructor 166 | 167 | ```php 168 | use Greg0ire\Enum\Bridge\Symfony\Translator\GetLabel; 169 | use Symfony\Contracts\Translation\TranslationInterface; 170 | 171 | $label = new GetLabel($translator); 172 | $label(Your\Enum\Class::VALUE, Your\Enum\Class::class); 173 | ``` 174 | 175 | If you're using Symfony, alias the service and simply inject it. 176 | If translations are enabled, the `TranslatorInterface` will be automatically injected. 177 | 178 | ```yaml 179 | services: 180 | # ... 181 | Greg0ire\Enum\Bridge\Symfony\Translator\GetLabel: "@greg0ire_enum.symfony.translator.get_label" 182 | ``` 183 | 184 | ```php 185 | public function index(GetLabel $label) 186 | { 187 | $label(Your\Enum\Class::VALUE, Your\Enum\Class::class); 188 | $label(Your\Enum\Class::VALUE, Your\Enum\Class::class, 'another_domain'); // Change the translation domain 189 | $label(Your\Enum\Class::VALUE, Your\Enum\Class::class, false); // Disable translation. In this case the class prefix wont be added 190 | $label(Your\Enum\Class::VALUE, Your\Enum\Class::class, false, true); // Disable translation but keep class prefix 191 | $label(Your\Enum\Class::VALUE, Your\Enum\Class::class, false, true, '.'); // Disable translation but keep class prefix with a custom separator 192 | } 193 | ``` 194 | 195 | ### Integration with other libraries 196 | 197 | `greg0ire/enum` integrates with other libraries. The list is available in the 198 | `suggest` section of the Composer dependency manifest. 199 | 200 | #### Symfony validator 201 | 202 | This package provides a "ready to use" symfony validator. 203 | You have to require the `symfony/validator` package to get it working. 204 | 205 | ```php 206 | use Greg0ire\Enum\Bridge\Symfony\Validator\Constraint\Enum; 207 | use Symfony\Component\Validator\Validation; 208 | use Your\Namespace\EnumClass; 209 | 210 | $validator = Validation::createValidator(); 211 | 212 | $violations = $validator->validateValue(42, new Enum(EnumClass::class)); 213 | // You can also show the constants keys on the error message: 214 | $violations = $validator->validateValue(42, new Enum(['class' => EnumClass::class, 'showKeys' => true])); 215 | // Enum constraint inherits from Choice constraint. You can use inherited options too: 216 | $violations = $validator->validateValue(42, new Enum(['class' => EnumClass::class, 'strict' => true])); 217 | ``` 218 | 219 | Another example with annotations: 220 | 221 | ```php 222 | use Doctrine\Common\Annotations\AnnotationRegistry; 223 | use Greg0ire\Enum\Bridge\Symfony\Validator\Constraint\Enum as EnumAssert; 224 | use Symfony\Component\Validator\Validation; 225 | 226 | class MyClass 227 | { 228 | /** 229 | * @EnumAssert("Your\Namespace\EnumClass") 230 | */ 231 | private $dummy; 232 | 233 | public function __construct($dummy) 234 | { 235 | $this->dummy = $dummy 236 | } 237 | } 238 | 239 | AnnotationRegistry::registerLoader('class_exists'); 240 | $validator = Validation::createValidatorBuilder() 241 | ->enableAnnotationMapping() 242 | ->getValidator(); 243 | 244 | $object = new MyClass(42); 245 | 246 | $violations = $validator->validate($object); 247 | ``` 248 | 249 | Note: You will have to get `doctrine/annotations` and `doctrine/cache` packages to get it working. 250 | 251 | #### Symfony form 252 | 253 | This package provides a "ready to use" symfony form type. 254 | You have to require the `symfony/form` package to get it working. 255 | 256 | ```php 257 | use Greg0ire\Enum\Bridge\Symfony\Form\Type\EnumType; 258 | use Symfony\Component\Form\Forms; 259 | use Your\Namespace\EnumClass; 260 | 261 | $formFactory = Forms::createFormFactory(); 262 | 263 | $view = $this->factory->create(EnumType::class, null, array( 264 | 'class' => EnumClass::class, 265 | ))->createView(); 266 | ``` 267 | 268 | #### Twig extension 269 | 270 | This package comes with an `EnumExtension` Twig class. It contains a filter and some functions. 271 | You have to require the `twig/twig` package to get it working. 272 | 273 | ##### Filter 274 | 275 | The `enum_label` filter will try to return the constant label corresponding to the given value. 276 | 277 | This filter relies on the `Greg0ire\Enum\Bridge\Symfony\Translator\GetLabel` service. 278 | 279 | It will try to translate it if possible. To enable translation, require the `symfony/translation` component 280 | and pass a `Symfony\Contracts\Translation\TranslationInterface` instance on the `GetLabel` constructor. 281 | `GetLabel` instance will be injected on the `EnumExtension` constructor. 282 | 283 | If translation is not available, you will have the default label with class prefixing. 284 | 285 | Usage: 286 | 287 | ```twig 288 | {{ value|enum_label('Your\\Enum\\Class') }} 289 | {{ value|enum_label('Your\\Enum\\Class', 'another_domain') }} {# Change the translation domain #} 290 | {{ value|enum_label('Your\\Enum\\Class', false) }} {# Disable translation. In this case the class prefix wont be added #} 291 | {{ value|enum_label('Your\\Enum\\Class', false, true) }} {# Disable translation but keep class prefix #} 292 | {{ value|enum_label('Your\\Enum\\Class', false, true, '.') }} {# Disable translation but keep class prefix with a custom separator #} 293 | ``` 294 | 295 | ##### Functions 296 | 297 | The 3 available twig functions are ports of some `AbstractEnum` methods that can be useful in a twig template: 298 | 299 | * `enum_get_constants` => `AbstractEnum::getConstants` 300 | * `enum_get_keys` => `AbstractEnum::getKeys` 301 | * `enum_get_class_prefixed_keys` => `AbstractEnum::getClassPrefixedKeys` 302 | 303 | The arguments are exactly the same except you have to specify the targeted class first (as `enum_label` filter). 304 | 305 | Here is a concrete example with `enum_get_constants` function: 306 | 307 | ```twig 308 | {% for enum_key, enum_value in enum_get_constants('Your\\Enum\\Class') %} 309 | {{ enum_key }} -> {{ enum_value }} 310 | {% endfor %} 311 | ``` 312 | 313 | ##### Twig extension as a service 314 | 315 | On Symfony projects, the extension can be autoloaded. 316 | First, you have to require the `symfony/framework-bundle` and `symfony/twig-bundle` packages, or use Symfony fullstack. 317 | 318 | Then, register the bundle in the kernel of your application: 319 | 320 | ``` php 321 | // app/AppKernel.php 322 | 323 | public function registerBundles() 324 | { 325 | $bundles = [ 326 | // ... 327 | new Greg0ire\Enum\Bridge\Symfony\Bundle\Greg0ireEnumBundle(), 328 | ]; 329 | 330 | // ... 331 | 332 | return $bundles 333 | } 334 | ``` 335 | 336 | That's all. You can now directly use the filter. 337 | 338 | ## Contributing 339 | 340 | see [CONTRIBUTING.md][1] 341 | 342 | ## Credits 343 | 344 | This is a shameless rip-off of [this Stack Overflow answer][0], with one 345 | modification: the `getConstants` method has been made public so that it is 346 | available for building choice widgets, for instance. If you want to give credit 347 | to someone for this, give it to [Brian Cline][2] 348 | 349 | [0]: http://stackoverflow.com/a/254543/353612 350 | [1]: ./CONTRIBUTING.md 351 | [2]: http://stackoverflow.com/users/32536/brian-cline 352 | [3]: https://travis-ci.org/greg0ire/enum.svg?branch=master 353 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | Attribution-ShareAlike 3.0 Unported 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR 10 | DAMAGES RESULTING FROM ITS USE. 11 | 12 | License 13 | 14 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE 15 | COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY 16 | COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS 17 | AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 18 | 19 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE 20 | TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY 21 | BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS 22 | CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND 23 | CONDITIONS. 24 | 25 | 1. Definitions 26 | 27 | a. "Adaptation" means a work based upon the Work, or upon the Work and 28 | other pre-existing works, such as a translation, adaptation, 29 | derivative work, arrangement of music or other alterations of a 30 | literary or artistic work, or phonogram or performance and includes 31 | cinematographic adaptations or any other form in which the Work may be 32 | recast, transformed, or adapted including in any form recognizably 33 | derived from the original, except that a work that constitutes a 34 | Collection will not be considered an Adaptation for the purpose of 35 | this License. For the avoidance of doubt, where the Work is a musical 36 | work, performance or phonogram, the synchronization of the Work in 37 | timed-relation with a moving image ("synching") will be considered an 38 | Adaptation for the purpose of this License. 39 | b. "Collection" means a collection of literary or artistic works, such as 40 | encyclopedias and anthologies, or performances, phonograms or 41 | broadcasts, or other works or subject matter other than works listed 42 | in Section 1(f) below, which, by reason of the selection and 43 | arrangement of their contents, constitute intellectual creations, in 44 | which the Work is included in its entirety in unmodified form along 45 | with one or more other contributions, each constituting separate and 46 | independent works in themselves, which together are assembled into a 47 | collective whole. A work that constitutes a Collection will not be 48 | considered an Adaptation (as defined below) for the purposes of this 49 | License. 50 | c. "Creative Commons Compatible License" means a license that is listed 51 | at http://creativecommons.org/compatiblelicenses that has been 52 | approved by Creative Commons as being essentially equivalent to this 53 | License, including, at a minimum, because that license: (i) contains 54 | terms that have the same purpose, meaning and effect as the License 55 | Elements of this License; and, (ii) explicitly permits the relicensing 56 | of adaptations of works made available under that license under this 57 | License or a Creative Commons jurisdiction license with the same 58 | License Elements as this License. 59 | d. "Distribute" means to make available to the public the original and 60 | copies of the Work or Adaptation, as appropriate, through sale or 61 | other transfer of ownership. 62 | e. "License Elements" means the following high-level license attributes 63 | as selected by Licensor and indicated in the title of this License: 64 | Attribution, ShareAlike. 65 | f. "Licensor" means the individual, individuals, entity or entities that 66 | offer(s) the Work under the terms of this License. 67 | g. "Original Author" means, in the case of a literary or artistic work, 68 | the individual, individuals, entity or entities who created the Work 69 | or if no individual or entity can be identified, the publisher; and in 70 | addition (i) in the case of a performance the actors, singers, 71 | musicians, dancers, and other persons who act, sing, deliver, declaim, 72 | play in, interpret or otherwise perform literary or artistic works or 73 | expressions of folklore; (ii) in the case of a phonogram the producer 74 | being the person or legal entity who first fixes the sounds of a 75 | performance or other sounds; and, (iii) in the case of broadcasts, the 76 | organization that transmits the broadcast. 77 | h. "Work" means the literary and/or artistic work offered under the terms 78 | of this License including without limitation any production in the 79 | literary, scientific and artistic domain, whatever may be the mode or 80 | form of its expression including digital form, such as a book, 81 | pamphlet and other writing; a lecture, address, sermon or other work 82 | of the same nature; a dramatic or dramatico-musical work; a 83 | choreographic work or entertainment in dumb show; a musical 84 | composition with or without words; a cinematographic work to which are 85 | assimilated works expressed by a process analogous to cinematography; 86 | a work of drawing, painting, architecture, sculpture, engraving or 87 | lithography; a photographic work to which are assimilated works 88 | expressed by a process analogous to photography; a work of applied 89 | art; an illustration, map, plan, sketch or three-dimensional work 90 | relative to geography, topography, architecture or science; a 91 | performance; a broadcast; a phonogram; a compilation of data to the 92 | extent it is protected as a copyrightable work; or a work performed by 93 | a variety or circus performer to the extent it is not otherwise 94 | considered a literary or artistic work. 95 | i. "You" means an individual or entity exercising rights under this 96 | License who has not previously violated the terms of this License with 97 | respect to the Work, or who has received express permission from the 98 | Licensor to exercise rights under this License despite a previous 99 | violation. 100 | j. "Publicly Perform" means to perform public recitations of the Work and 101 | to communicate to the public those public recitations, by any means or 102 | process, including by wire or wireless means or public digital 103 | performances; to make available to the public Works in such a way that 104 | members of the public may access these Works from a place and at a 105 | place individually chosen by them; to perform the Work to the public 106 | by any means or process and the communication to the public of the 107 | performances of the Work, including by public digital performance; to 108 | broadcast and rebroadcast the Work by any means including signs, 109 | sounds or images. 110 | k. "Reproduce" means to make copies of the Work by any means including 111 | without limitation by sound or visual recordings and the right of 112 | fixation and reproducing fixations of the Work, including storage of a 113 | protected performance or phonogram in digital form or other electronic 114 | medium. 115 | 116 | 2. Fair Dealing Rights. Nothing in this License is intended to reduce, 117 | limit, or restrict any uses free from copyright or rights arising from 118 | limitations or exceptions that are provided for in connection with the 119 | copyright protection under copyright law or other applicable laws. 120 | 121 | 3. License Grant. Subject to the terms and conditions of this License, 122 | Licensor hereby grants You a worldwide, royalty-free, non-exclusive, 123 | perpetual (for the duration of the applicable copyright) license to 124 | exercise the rights in the Work as stated below: 125 | 126 | a. to Reproduce the Work, to incorporate the Work into one or more 127 | Collections, and to Reproduce the Work as incorporated in the 128 | Collections; 129 | b. to create and Reproduce Adaptations provided that any such Adaptation, 130 | including any translation in any medium, takes reasonable steps to 131 | clearly label, demarcate or otherwise identify that changes were made 132 | to the original Work. For example, a translation could be marked "The 133 | original work was translated from English to Spanish," or a 134 | modification could indicate "The original work has been modified."; 135 | c. to Distribute and Publicly Perform the Work including as incorporated 136 | in Collections; and, 137 | d. to Distribute and Publicly Perform Adaptations. 138 | e. For the avoidance of doubt: 139 | 140 | i. Non-waivable Compulsory License Schemes. In those jurisdictions in 141 | which the right to collect royalties through any statutory or 142 | compulsory licensing scheme cannot be waived, the Licensor 143 | reserves the exclusive right to collect such royalties for any 144 | exercise by You of the rights granted under this License; 145 | ii. Waivable Compulsory License Schemes. In those jurisdictions in 146 | which the right to collect royalties through any statutory or 147 | compulsory licensing scheme can be waived, the Licensor waives the 148 | exclusive right to collect such royalties for any exercise by You 149 | of the rights granted under this License; and, 150 | iii. Voluntary License Schemes. The Licensor waives the right to 151 | collect royalties, whether individually or, in the event that the 152 | Licensor is a member of a collecting society that administers 153 | voluntary licensing schemes, via that society, from any exercise 154 | by You of the rights granted under this License. 155 | 156 | The above rights may be exercised in all media and formats whether now 157 | known or hereafter devised. The above rights include the right to make 158 | such modifications as are technically necessary to exercise the rights in 159 | other media and formats. Subject to Section 8(f), all rights not expressly 160 | granted by Licensor are hereby reserved. 161 | 162 | 4. Restrictions. The license granted in Section 3 above is expressly made 163 | subject to and limited by the following restrictions: 164 | 165 | a. You may Distribute or Publicly Perform the Work only under the terms 166 | of this License. You must include a copy of, or the Uniform Resource 167 | Identifier (URI) for, this License with every copy of the Work You 168 | Distribute or Publicly Perform. You may not offer or impose any terms 169 | on the Work that restrict the terms of this License or the ability of 170 | the recipient of the Work to exercise the rights granted to that 171 | recipient under the terms of the License. You may not sublicense the 172 | Work. You must keep intact all notices that refer to this License and 173 | to the disclaimer of warranties with every copy of the Work You 174 | Distribute or Publicly Perform. When You Distribute or Publicly 175 | Perform the Work, You may not impose any effective technological 176 | measures on the Work that restrict the ability of a recipient of the 177 | Work from You to exercise the rights granted to that recipient under 178 | the terms of the License. This Section 4(a) applies to the Work as 179 | incorporated in a Collection, but this does not require the Collection 180 | apart from the Work itself to be made subject to the terms of this 181 | License. If You create a Collection, upon notice from any Licensor You 182 | must, to the extent practicable, remove from the Collection any credit 183 | as required by Section 4(c), as requested. If You create an 184 | Adaptation, upon notice from any Licensor You must, to the extent 185 | practicable, remove from the Adaptation any credit as required by 186 | Section 4(c), as requested. 187 | b. You may Distribute or Publicly Perform an Adaptation only under the 188 | terms of: (i) this License; (ii) a later version of this License with 189 | the same License Elements as this License; (iii) a Creative Commons 190 | jurisdiction license (either this or a later license version) that 191 | contains the same License Elements as this License (e.g., 192 | Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible 193 | License. If you license the Adaptation under one of the licenses 194 | mentioned in (iv), you must comply with the terms of that license. If 195 | you license the Adaptation under the terms of any of the licenses 196 | mentioned in (i), (ii) or (iii) (the "Applicable License"), you must 197 | comply with the terms of the Applicable License generally and the 198 | following provisions: (I) You must include a copy of, or the URI for, 199 | the Applicable License with every copy of each Adaptation You 200 | Distribute or Publicly Perform; (II) You may not offer or impose any 201 | terms on the Adaptation that restrict the terms of the Applicable 202 | License or the ability of the recipient of the Adaptation to exercise 203 | the rights granted to that recipient under the terms of the Applicable 204 | License; (III) You must keep intact all notices that refer to the 205 | Applicable License and to the disclaimer of warranties with every copy 206 | of the Work as included in the Adaptation You Distribute or Publicly 207 | Perform; (IV) when You Distribute or Publicly Perform the Adaptation, 208 | You may not impose any effective technological measures on the 209 | Adaptation that restrict the ability of a recipient of the Adaptation 210 | from You to exercise the rights granted to that recipient under the 211 | terms of the Applicable License. This Section 4(b) applies to the 212 | Adaptation as incorporated in a Collection, but this does not require 213 | the Collection apart from the Adaptation itself to be made subject to 214 | the terms of the Applicable License. 215 | c. If You Distribute, or Publicly Perform the Work or any Adaptations or 216 | Collections, You must, unless a request has been made pursuant to 217 | Section 4(a), keep intact all copyright notices for the Work and 218 | provide, reasonable to the medium or means You are utilizing: (i) the 219 | name of the Original Author (or pseudonym, if applicable) if supplied, 220 | and/or if the Original Author and/or Licensor designate another party 221 | or parties (e.g., a sponsor institute, publishing entity, journal) for 222 | attribution ("Attribution Parties") in Licensor's copyright notice, 223 | terms of service or by other reasonable means, the name of such party 224 | or parties; (ii) the title of the Work if supplied; (iii) to the 225 | extent reasonably practicable, the URI, if any, that Licensor 226 | specifies to be associated with the Work, unless such URI does not 227 | refer to the copyright notice or licensing information for the Work; 228 | and (iv) , consistent with Ssection 3(b), in the case of an 229 | Adaptation, a credit identifying the use of the Work in the Adaptation 230 | (e.g., "French translation of the Work by Original Author," or 231 | "Screenplay based on original Work by Original Author"). The credit 232 | required by this Section 4(c) may be implemented in any reasonable 233 | manner; provided, however, that in the case of a Adaptation or 234 | Collection, at a minimum such credit will appear, if a credit for all 235 | contributing authors of the Adaptation or Collection appears, then as 236 | part of these credits and in a manner at least as prominent as the 237 | credits for the other contributing authors. For the avoidance of 238 | doubt, You may only use the credit required by this Section for the 239 | purpose of attribution in the manner set out above and, by exercising 240 | Your rights under this License, You may not implicitly or explicitly 241 | assert or imply any connection with, sponsorship or endorsement by the 242 | Original Author, Licensor and/or Attribution Parties, as appropriate, 243 | of You or Your use of the Work, without the separate, express prior 244 | written permission of the Original Author, Licensor and/or Attribution 245 | Parties. 246 | d. Except as otherwise agreed in writing by the Licensor or as may be 247 | otherwise permitted by applicable law, if You Reproduce, Distribute or 248 | Publicly Perform the Work either by itself or as part of any 249 | Adaptations or Collections, You must not distort, mutilate, modify or 250 | take other derogatory action in relation to the Work which would be 251 | prejudicial to the Original Author's honor or reputation. Licensor 252 | agrees that in those jurisdictions (e.g. Japan), in which any exercise 253 | of the right granted in Section 3(b) of this License (the right to 254 | make Adaptations) would be deemed to be a distortion, mutilation, 255 | modification or other derogatory action prejudicial to the Original 256 | Author's honor and reputation, the Licensor will waive or not assert, 257 | as appropriate, this Section, to the fullest extent permitted by the 258 | applicable national law, to enable You to reasonably exercise Your 259 | right under Section 3(b) of this License (right to make Adaptations) 260 | but not otherwise. 261 | 262 | 5. Representations, Warranties and Disclaimer 263 | 264 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR 265 | OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY 266 | KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, 267 | INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, 268 | FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF 269 | LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, 270 | WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION 271 | OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 272 | 273 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE 274 | LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR 275 | ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES 276 | ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS 277 | BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 278 | 279 | 7. Termination 280 | 281 | a. This License and the rights granted hereunder will terminate 282 | automatically upon any breach by You of the terms of this License. 283 | Individuals or entities who have received Adaptations or Collections 284 | from You under this License, however, will not have their licenses 285 | terminated provided such individuals or entities remain in full 286 | compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will 287 | survive any termination of this License. 288 | b. Subject to the above terms and conditions, the license granted here is 289 | perpetual (for the duration of the applicable copyright in the Work). 290 | Notwithstanding the above, Licensor reserves the right to release the 291 | Work under different license terms or to stop distributing the Work at 292 | any time; provided, however that any such election will not serve to 293 | withdraw this License (or any other license that has been, or is 294 | required to be, granted under the terms of this License), and this 295 | License will continue in full force and effect unless terminated as 296 | stated above. 297 | 298 | 8. Miscellaneous 299 | 300 | a. Each time You Distribute or Publicly Perform the Work or a Collection, 301 | the Licensor offers to the recipient a license to the Work on the same 302 | terms and conditions as the license granted to You under this License. 303 | b. Each time You Distribute or Publicly Perform an Adaptation, Licensor 304 | offers to the recipient a license to the original Work on the same 305 | terms and conditions as the license granted to You under this License. 306 | c. If any provision of this License is invalid or unenforceable under 307 | applicable law, it shall not affect the validity or enforceability of 308 | the remainder of the terms of this License, and without further action 309 | by the parties to this agreement, such provision shall be reformed to 310 | the minimum extent necessary to make such provision valid and 311 | enforceable. 312 | d. No term or provision of this License shall be deemed waived and no 313 | breach consented to unless such waiver or consent shall be in writing 314 | and signed by the party to be charged with such waiver or consent. 315 | e. This License constitutes the entire agreement between the parties with 316 | respect to the Work licensed here. There are no understandings, 317 | agreements or representations with respect to the Work not specified 318 | here. Licensor shall not be bound by any additional provisions that 319 | may appear in any communication from You. This License may not be 320 | modified without the mutual written agreement of the Licensor and You. 321 | f. The rights granted under, and the subject matter referenced, in this 322 | License were drafted utilizing the terminology of the Berne Convention 323 | for the Protection of Literary and Artistic Works (as amended on 324 | September 28, 1979), the Rome Convention of 1961, the WIPO Copyright 325 | Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 326 | and the Universal Copyright Convention (as revised on July 24, 1971). 327 | These rights and subject matter take effect in the relevant 328 | jurisdiction in which the License terms are sought to be enforced 329 | according to the corresponding provisions of the implementation of 330 | those treaty provisions in the applicable national law. If the 331 | standard suite of rights granted under applicable copyright law 332 | includes additional rights not granted under this License, such 333 | additional rights are deemed to be included in the License; this 334 | License is not intended to restrict the license of any rights under 335 | applicable law. 336 | 337 | 338 | Creative Commons Notice 339 | 340 | Creative Commons is not a party to this License, and makes no warranty 341 | whatsoever in connection with the Work. Creative Commons will not be 342 | liable to You or any party on any legal theory for any damages 343 | whatsoever, including without limitation any general, special, 344 | incidental or consequential damages arising in connection to this 345 | license. Notwithstanding the foregoing two (2) sentences, if Creative 346 | Commons has expressly identified itself as the Licensor hereunder, it 347 | shall have all rights and obligations of Licensor. 348 | 349 | Except for the limited purpose of indicating to the public that the 350 | Work is licensed under the CCPL, Creative Commons does not authorize 351 | the use by either party of the trademark "Creative Commons" or any 352 | related trademark or logo of Creative Commons without the prior 353 | written consent of Creative Commons. Any permitted use will be in 354 | compliance with Creative Commons' then-current trademark usage 355 | guidelines, as may be published on its website or otherwise made 356 | available upon request from time to time. For the avoidance of doubt, 357 | this trademark restriction does not form part of the License. 358 | 359 | Creative Commons may be contacted at http://creativecommons.org/. 360 | --------------------------------------------------------------------------------