Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a3a0b78

Browse files
Initial commit
0 parents commit a3a0b78

File tree

13 files changed

+806
-0
lines changed

13 files changed

+806
-0
lines changed

‎.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml

‎README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# PHP Enum Symfony
2+
3+
Symfony support for [zlikavac32/php-enum](https://github.com/zlikavac32/php-enum).
4+
5+
## Table of contents
6+
7+
1. [Installation](#installation)
8+
1. [Usage](#usage)
9+
1. [Form](#form)
10+
1. [Validator](#validator)
11+
1. [Limitations](#usage)
12+
13+
## Installation
14+
15+
Recommended installation is through Composer.
16+
17+
```
18+
composer require zlikavac32/php-enum-symfony
19+
```
20+
21+
## Usage
22+
23+
Assumption is that there exists a valid enum `\YesNoEnum`.
24+
25+
### Form
26+
27+
Form type for enum is provided as `\Zlikavac32\SymfonyEnum\Form\Type\EnumType`. There is one required options `enum_class` which must contain enum class FQN.
28+
29+
Internally this extends [\Symfony\Component\Form\Extension\Core\Type\ChoiceType](https://symfony.com/doc/current/reference/forms/types/choice.html) and populates choices from the defining `enum_class`.
30+
31+
If any of the `choices` and/or `choice_loader` options is/are passed, an `\LogicException` will be thrown. Since these fields are overridden internally, passing them from the outside could cloud code's original purpose. Any other option provided by the `\Symfony\Component\Form\Extension\Core\Type\ChoiceType` can be used.
32+
33+
```php
34+
use \Zlikavac32\SymfonyEnum\Form\Type\EnumType;
35+
36+
class FormModel
37+
{
38+
public $answer;
39+
40+
// ...
41+
}
42+
43+
$formModel = new FormModel();
44+
45+
$form = $this->createFormBuilder($formModel)
46+
->add('answer', EnumType::class, [
47+
'enum_class' => \YesNoEnum::class
48+
])
49+
// ...
50+
->getForm();
51+
```
52+
53+
### Validator
54+
55+
Two constraints are provided, `\Zlikavac32\SymfonyEnum\Validator\Constraints\ValidEnumElement` and `\Zlikavac32\SymfonyEnum\Validator\Constraints\ValidEnumElementName`. Internally, they use `\Symfony\Component\Validator\Constraints\Choice`.
56+
57+
Required constraint argument is `enumClass` which must contain enum class FQN.
58+
59+
If any of the `choices`, `callback` and/or `strict` options is/are passed, an `\LogicException` will be thrown. Since these fields are overridden internally, passing them from the outside could cloud code's original purpose. Any other option provided by the `\Symfony\Component\Validator\Constraints\Choice` can be used.
60+
61+
- `ValidEnumElement` - accepted values are `null` and any valid enum element from the defined enum class FQN
62+
- `ValidEnumElementName` - accepted values are `null` and any valid enum element name from the defined enum class FQN
63+
64+
Example for annotation use:
65+
66+
```php
67+
/**
68+
* @ValidEnumElement(enumClass="\YesNoEnum")
69+
*/
70+
```
71+
72+
## Limitations
73+
74+
Due to [doctrine/common issue #794](https://github.com/doctrine/common/issues/794) with checks for aliased namespaces, validation of form enum element within an array will throw exception in following cases:
75+
76+
- on `Windows` validation does not work at all (due to the anonymous classes)
77+
- on `Linux` - short enum definition (one that uses `eval()`) does not work so the workaround is to manually instantiate elements
78+
- on `OSX` - have to check but I'd assume same as `Linux`
79+
80+
For more details on what's wrong and why, feel free to check related issue.

‎composer.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "zlikavac32/php-enum-symfony",
3+
"description": "Symfony PHP enum support",
4+
"keywords": ["enum", "php-enum", "enumeration", "symfony-enum", "symfony"],
5+
"type": "library",
6+
"license": "MIT",
7+
"authors": [
8+
{
9+
"name": "Marijan Šuflaj",
10+
"email": "msufflaj32@gmail.com"
11+
}
12+
],
13+
"minimum-stability": "stable",
14+
"require": {
15+
"php": ">=7.1",
16+
"zlikavac32/php-enum": "^1.0",
17+
"symfony/form": "^3.3",
18+
"symfony/options-resolver": "^3.3",
19+
"symfony/validator": "^3.3"
20+
},
21+
"require-dev": {
22+
"phpunit/phpunit": "^6.4"
23+
},
24+
"autoload": {
25+
"psr-4": {
26+
"Zlikavac32\\SymfonyEnum\\": "src/"
27+
}
28+
},
29+
"autoload-dev": {
30+
"psr-4": {
31+
"Zlikavac32\\SymfonyEnum\\Tests\\": "tests/"
32+
}
33+
}
34+
}

‎phpunit.xml.dist

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd">
3+
4+
<testsuites>
5+
<testsuite name="all">
6+
<directory>./tests</directory>
7+
</testsuite>
8+
</testsuites>
9+
</phpunit>

‎src/Form/Type/EnumType.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Zlikavac32\SymfonyEnum\Form\Type;
6+
7+
use LogicException;
8+
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
9+
use Symfony\Component\Form\FormBuilderInterface;
10+
use Symfony\Component\OptionsResolver\OptionsResolver;
11+
use Zlikavac32\Enum\Enum;
12+
13+
class EnumType extends ChoiceType
14+
{
15+
public function buildForm(FormBuilderInterface $builder, array $options)
16+
{
17+
$this->assertThatOverriddenOptionsAreNotSet($options);
18+
19+
/* @var Enum|string $enumClass */
20+
$enumClass = $options['enum_class'];
21+
22+
$this->assertThatEnumClassIsValid($enumClass);
23+
24+
$options = $this->populateOptionsWithDefaults($options);
25+
26+
parent::buildForm(
27+
$builder,
28+
[
29+
'choices' => $this->buildChoicesForEnumClass($enumClass),
30+
'choice_loader' => null,
31+
] + $options
32+
);
33+
}
34+
35+
private function populateOptionsWithDefaults(array $options): array
36+
{
37+
$elementNameClosure = function (?Enum $enum): string {
38+
if (null === $enum) {
39+
return '';
40+
}
41+
42+
return $enum->name();
43+
};
44+
45+
foreach (['choice_label', 'choice_value'] as $optionKey) {
46+
if (isset($options[$optionKey])) {
47+
continue;
48+
}
49+
50+
$options[$optionKey] = $elementNameClosure;
51+
}
52+
53+
return $options;
54+
}
55+
56+
private function buildChoicesForEnumClass(string $enumClass): array
57+
{
58+
$choices = [];
59+
60+
/* @var Enum $enumClass Just for IDE auto-complete support */
61+
foreach ($enumClass::values() as $element) {
62+
$choices[$element->name()] = $element;
63+
}
64+
65+
return $choices;
66+
}
67+
68+
private function assertThatEnumClassIsValid(string $enumClass): void
69+
{
70+
if (false === is_subclass_of($enumClass, Enum::class)) {
71+
throw new LogicException(sprintf('%s does not have %s as it\'s parent', $enumClass, Enum::class));
72+
}
73+
}
74+
75+
private function assertThatOverriddenOptionsAreNotSet(array $options): void
76+
{
77+
$optionsToCheckFor = [
78+
'choices',
79+
'choice_loader',
80+
];
81+
82+
foreach ($optionsToCheckFor as $optionName) {
83+
if (empty($options[$optionName])) {
84+
continue;
85+
}
86+
87+
throw new LogicException(
88+
sprintf('Option %s is overridden by the type so don\'t pass your own value', $optionName)
89+
);
90+
}
91+
}
92+
93+
public function configureOptions(OptionsResolver $resolver): void
94+
{
95+
parent::configureOptions($resolver);
96+
97+
$resolver->setDefaults(['enum_class' => null])
98+
->setAllowedTypes('enum_class', ['string']);
99+
}
100+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Zlikavac32\SymfonyEnum\Validator\Constraints;
6+
7+
use Closure;
8+
use LogicException;
9+
use Symfony\Component\Validator\Constraints\Choice;
10+
use Symfony\Component\Validator\Constraints\ChoiceValidator;
11+
use Zlikavac32\Enum\Enum;
12+
13+
abstract class AbstractEnumConstraint extends Choice
14+
{
15+
/**
16+
* @var string|Enum
17+
*/
18+
public $enumClass;
19+
20+
public function __construct(Closure $callback, $options = null)
21+
{
22+
if (false === is_array($options)) {
23+
$options = ['enumClass' => $options];
24+
}
25+
26+
$this->assertThatOverriddenKeysAreNotSet($options);
27+
28+
parent::__construct(['strict' => true, 'callback' => $callback] + $options);
29+
30+
$this->assertEnumClassIsValid($this->enumClass);
31+
}
32+
33+
public function validatedBy()
34+
{
35+
return ChoiceValidator::class;
36+
}
37+
38+
private function assertThatOverriddenKeysAreNotSet(array $options): void
39+
{
40+
foreach (['choices', 'callback', 'strict'] as $key) {
41+
if (array_key_exists($key, $options)) {
42+
throw new LogicException(
43+
sprintf('Key %s is overridden internally so it should not be set from the outside', $key)
44+
);
45+
}
46+
}
47+
}
48+
49+
private function assertEnumClassIsValid(?string $enumClass): void
50+
{
51+
if (null === $enumClass) {
52+
throw new LogicException('Enum class can not be null');
53+
}
54+
55+
if (in_array(Enum::class, class_parents($enumClass))) {
56+
return;
57+
}
58+
59+
throw new LogicException(
60+
sprintf(
61+
'Provided enum class %s is not valid',
62+
$enumClass
63+
)
64+
);
65+
}
66+
67+
/**
68+
* {@inheritdoc}
69+
*/
70+
public function getDefaultOption()
71+
{
72+
return 'enumClass';
73+
}
74+
75+
/**
76+
* {@inheritdoc}
77+
*/
78+
public function getRequiredOptions()
79+
{
80+
return ['enumClass'];
81+
}
82+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Zlikavac32\SymfonyEnum\Validator\Constraints;
6+
7+
/**
8+
* @Annotation
9+
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
10+
*/
11+
class ValidEnumElement extends AbstractEnumConstraint
12+
{
13+
public function __construct($options = null)
14+
{
15+
parent::__construct(
16+
function (): array {
17+
return $this->enumClass::values();
18+
},
19+
$options
20+
);
21+
}
22+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Zlikavac32\SymfonyEnum\Validator\Constraints;
6+
7+
/**
8+
* @Annotation
9+
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
10+
*/
11+
class ValidEnumElementName extends AbstractEnumConstraint
12+
{
13+
public function __construct($options = null)
14+
{
15+
parent::__construct(
16+
function (): array {
17+
$choices = [];
18+
19+
foreach ($this->enumClass::values() as $element) {
20+
$name = $element->name();
21+
$choices[] = $name;
22+
}
23+
24+
return $choices;
25+
},
26+
$options
27+
);
28+
}
29+
}

‎tests/Fixtures/YesNoEnum.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Zlikavac32\SymfonyEnum\Tests\Fixtures;
6+
7+
use Zlikavac32\Enum\Enum;
8+
9+
/**
10+
* @method static YesNoEnum YES
11+
* @method static YesNoEnum NO
12+
*/
13+
abstract class YesNoEnum extends Enum
14+
{
15+
protected static function enumerate(): array
16+
{
17+
return [
18+
'NO' => new class extends YesNoEnum
19+
{
20+
},
21+
'YES' => new class extends YesNoEnum
22+
{
23+
},
24+
];
25+
}
26+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /