├── infection.json.dist
├── psalm.xml
├── phpunit.xml
├── composer.json
├── CHANGELOG.md
├── LICENSE.md
├── src
└── Enum.php
└── README.md
/infection.json.dist:
--------------------------------------------------------------------------------
1 | {
2 | "source": {
3 | "directories": [
4 | "src"
5 | ]
6 | },
7 | "logs": {
8 | "text": "php:\/\/stderr",
9 | "badge": {
10 | "branch": "master"
11 | }
12 | },
13 | "mutators": {
14 | "@default": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ./tests
21 |
22 |
23 |
24 |
25 |
26 | ./src
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vjik/php-enum",
3 | "description": "PHP Enum Implementation",
4 | "type": "library",
5 | "keywords": [
6 | "php",
7 | "enum"
8 | ],
9 | "license": "BSD-3-Clause",
10 | "support": {
11 | "issues": "https://github.com/vjik/php-enum/issues",
12 | "source": "https://github.com/vjik/php-enum"
13 | },
14 | "minimum-stability": "stable",
15 | "require": {
16 | "php": "^8.0"
17 | },
18 | "require-dev": {
19 | "infection/infection": "^0.23.0",
20 | "phpunit/phpunit": "^9.4",
21 | "vimeo/psalm": "^4.7"
22 | },
23 | "autoload": {
24 | "psr-4": {
25 | "Vjik\\Enum\\": "src"
26 | }
27 | },
28 | "autoload-dev": {
29 | "psr-4": {
30 | "Vjik\\Enum\\Tests\\": "tests"
31 | }
32 | },
33 | "config": {
34 | "sort-packages": true
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # PHP Enum Implementation Change Log
2 |
3 | ## 4.0.0 May 27, 2021
4 |
5 | Implement ideas from [RFC Enumerations](https://wiki.php.net/rfc/enumerations):
6 |
7 | - New: Add protected method `match()`.
8 | - New: Add factory method `tryFrom()`.
9 | - New: Add method `getName()`.
10 | - Chg: Remove immutability objects.
11 | - Chg: Rename methods `toObjects()` to `cases()` and `toValues()` to `values()`.
12 | - Chg: Use private constants in enum object.
13 | - Chg: On create object via method `from()` with invalid value throws `ValueError` instead `UnexpectedValueException`.
14 |
15 | ## 3.0.0 May 26, 2021
16 |
17 | - Chg: Rewrite the package from scratch.
18 |
19 | ## 2.2.0 January 15, 2020
20 |
21 | - New: Add support static methods of current object in filters.
22 |
23 | ## 2.1.0 February 26, 2018
24 |
25 | - New: Add static method `get()` that returns object by its identifier.
26 |
27 | ## 2.0.0 September 4, 2017
28 |
29 | - Chg: Property `$value` renamed to `$id`.
30 | - Chg: Method `toValues()` renamed to `toIds()`.
31 |
32 | ## 1.2.0 August 29, 2017
33 |
34 | - New: Add support operator `in` in filters.
35 |
36 | ## 1.1.1 August 29, 2017
37 |
38 | - Bug: Fixed problem with values type casting to integer.
39 |
40 | ## 1.1.0 August 21, 2017
41 |
42 | - New: Add method `toObjects()`.
43 |
44 | ## 1.0.0 July 15, 2017
45 |
46 | - Initial release.
47 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright © 2021 by Sergei Predvoditelev (https://predvoditelev.ru)
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in
12 | the documentation and/or other materials provided with the
13 | distribution.
14 | * Neither the name of the copyright holder nor the names of its
15 | contributors may be used to endorse or promote products derived
16 | from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 | COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/src/Enum.php:
--------------------------------------------------------------------------------
1 | >
22 | */
23 | private static array $cache = [];
24 |
25 | /**
26 | * @psalm-var array>
27 | */
28 | private static array $instances = [];
29 |
30 | final protected function __construct(string $name, mixed $value)
31 | {
32 | $this->name = $name;
33 | $this->value = $value;
34 | }
35 |
36 | final public function getName(): string
37 | {
38 | return $this->name;
39 | }
40 |
41 | final public function getValue(): mixed
42 | {
43 | return $this->value;
44 | }
45 |
46 | final public function __toString(): string
47 | {
48 | return (string)$this->value;
49 | }
50 |
51 | /**
52 | * @return static
53 | */
54 | final public static function from(mixed $value): self
55 | {
56 | $object = static::getInstanceByValue($value);
57 | if ($object === null) {
58 | throw new ValueError("Value '$value' is not part of the enum " . static::class . '.');
59 | }
60 |
61 | return $object;
62 | }
63 |
64 | /**
65 | * @return static|null
66 | */
67 | final public static function tryFrom(mixed $value): ?self
68 | {
69 | return static::getInstanceByValue($value);
70 | }
71 |
72 | /**
73 | * @return static
74 | */
75 | final public static function __callStatic(string $name, array $arguments): self
76 | {
77 | $class = static::class;
78 | if (!isset(self::$instances[$class][$name])) {
79 | $enumValues = static::getEnumValues();
80 | if (!array_key_exists($name, $enumValues)) {
81 | $message = "No static method or enum constant '$name' in class " . static::class . '.';
82 | throw new BadMethodCallException($message);
83 | }
84 | self::$instances[$class][$name] = new static($name, $enumValues[$name]);
85 | }
86 | return self::$instances[$class][$name];
87 | }
88 |
89 | final public static function values(): array
90 | {
91 | return array_values(self::getEnumValues());
92 | }
93 |
94 | /**
95 | * @return static[]
96 | */
97 | final public static function cases(): array
98 | {
99 | $class = static::class;
100 |
101 | $objects = [];
102 | /** @var mixed $value */
103 | foreach (self::getEnumValues() as $key => $value) {
104 | if (!isset(self::$instances[$class][$key])) {
105 | self::$instances[$class][$key] = new static($key, $value);
106 | }
107 | $objects[] = self::$instances[$class][$key];
108 | }
109 |
110 | return $objects;
111 | }
112 |
113 | final public static function isValid(mixed $value): bool
114 | {
115 | return in_array($value, static::getEnumValues(), true);
116 | }
117 |
118 | /**
119 | * @psalm-return array>
120 | */
121 | protected static function data(): array
122 | {
123 | return [];
124 | }
125 |
126 | final protected function getPropertyValue(string $key, mixed $default = null): mixed
127 | {
128 | /** @psalm-suppress MixedArrayOffset */
129 | return static::data()[$this->value][$key] ?? $default;
130 | }
131 |
132 | final protected function match(array $data, mixed $default = null): mixed
133 | {
134 | /** @psalm-suppress MixedArrayOffset */
135 | return $data[$this->value] ?? $default;
136 | }
137 |
138 | /**
139 | * @return static|null
140 | */
141 | private static function getInstanceByValue(mixed $value): ?self
142 | {
143 | $class = static::class;
144 |
145 | /** @var mixed $enumValue */
146 | foreach (self::getEnumValues() as $key => $enumValue) {
147 | if ($enumValue === $value) {
148 | if (!isset(self::$instances[$class][$key])) {
149 | self::$instances[$class][$key] = new static($key, $value);
150 | }
151 | return self::$instances[$class][$key];
152 | }
153 | }
154 |
155 | return null;
156 | }
157 |
158 | /**
159 | * @psalm-return array
160 | */
161 | private static function getEnumValues(): array
162 | {
163 | $class = static::class;
164 |
165 | if (!isset(static::$cache[$class])) {
166 | /** @psalm-suppress TooManyArguments Remove this after fix https://github.com/vimeo/psalm/issues/5837 */
167 | static::$cache[$class] = (new ReflectionClass($class))->getConstants(ReflectionClassConstant::IS_PRIVATE);
168 | }
169 |
170 | return static::$cache[$class];
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PHP Enum Implementation
2 |
3 | [](https://packagist.org/packages/vjik/php-enum)
4 | [](https://packagist.org/packages/vjik/php-enum)
5 | [](https://github.com/vjik/php-enum/actions?query=workflow%3Abuild)
6 | [](https://dashboard.stryker-mutator.io/reports/github.com/vjik/php-enum/master)
7 | [](https://github.com/vjik/php-enum/actions?query=workflow%3A%22static+analysis%22)
8 |
9 | The package implement ideas from [RFC Enumerations](https://wiki.php.net/rfc/enumerations) and provide abstract class `Enum` that intended to create
10 | [enumerated objects](https://en.wikipedia.org/wiki/Enumerated_type) with support [extra data](#extradata) and auxiliary static functions [`values()`](#values), [`cases()`](#cases) and [`isValid()`](#isValid).
11 |
12 | ## Requirements
13 |
14 | - PHP 8.0 or higher.
15 |
16 | ## Installation
17 |
18 | The package could be installed with composer:
19 |
20 | ```shell
21 | composer require vjik/php-enum --prefer-dist
22 | ```
23 |
24 | ## General usage
25 |
26 | ### Declaration of class
27 |
28 | ```php
29 | use Vjik\Enum\Enum;
30 |
31 | /**
32 | * @method static self NEW()
33 | * @method static self PROCESS()
34 | * @method static self DONE()
35 | */
36 | final class Status extends Enum
37 | {
38 | private const NEW = 'new';
39 | private const PROCESS = 'process';
40 | private const DONE = 'done';
41 | }
42 | ```
43 |
44 | ### Creating an object
45 |
46 | #### By static method `from()`
47 |
48 | ```php
49 | $process = Status::from('process');
50 | ```
51 |
52 | On create object with invalid value throws `ValueError`.
53 |
54 | #### By static method `tryFrom()`
55 |
56 | ```php
57 | $process = Status::tryFrom('process'); // Status object with value "process"
58 | $process = Status::tryFrom('not-exists'); // null
59 | ```
60 |
61 | On create object with invalid value returns `null`.
62 |
63 | #### By static method with a name identical to the constant name
64 |
65 | Static methods are automatically implemented to provide quick access to an enum value.
66 |
67 | ```php
68 | $process = Status::PROCESS();
69 | ```
70 |
71 | ### Getting value and name
72 |
73 | ```php
74 | Status::DONE()->getName(); // DONE
75 | Status::DONE()->getValue(); // done
76 | ```
77 |
78 | ### Class with extra data
79 |
80 | Set data in the protected static function `data()` and create getters using the protected method `getPropertyValue()`.
81 | Also you can create getter using protected method `match()`.
82 |
83 | ```php
84 | use Vjik\Enum\Enum;
85 |
86 | /**
87 | * @method static self CREATE()
88 | * @method static self UPDATE()
89 | */
90 | final class Action extends Enum
91 | {
92 | private const CREATE = 1;
93 | private const UPDATE = 2;
94 |
95 | protected static function data(): array
96 | {
97 | return [
98 | self::CREATE => [
99 | 'tip' => 'Create document',
100 | ],
101 | self::UPDATE => [
102 | 'tip' => 'Update document',
103 | ],
104 | ];
105 | }
106 |
107 | public function getTip(): string
108 | {
109 | /** @var string */
110 | return $this->getPropertyValue('tip');
111 | }
112 |
113 | public function getColor(): string
114 | {
115 | return $this->match([
116 | self::CREATE => 'red',
117 | self::UPDATE => 'blue',
118 | ]);
119 | }
120 |
121 | public function getCode(): int
122 | {
123 | return $this->match([
124 | self::CREATE => 1,
125 | ], 99);
126 | }
127 | }
128 | ```
129 |
130 | Usage:
131 |
132 | ```php
133 | echo Action::CREATE()->getTip();
134 | echo Action::CREATE()->getColor();
135 | echo Action::CREATE()->getCode();
136 | ```
137 |
138 | ### Auxiliary static functions
139 |
140 | #### List of values `values()`
141 |
142 | Returns list of values.
143 |
144 | ```php
145 | // [1, 2]
146 | Action::values();
147 | ```
148 |
149 | #### List of objects `cases()`
150 |
151 | Returns list of objects:
152 |
153 | ```php
154 | // [$createObject, $updateObject]
155 | Action::cases();
156 | ```
157 |
158 | #### Validate value `isValid()`
159 |
160 | Check if value is valid on the enum set.
161 |
162 | ```php
163 | Action::isValid(1); // true
164 | Action::isValid(99); // false
165 | ```
166 |
167 | ### Casting to string
168 |
169 | `Enum` support casting to string (using magic method `__toString`). The value is returned as a string.
170 |
171 | ```php
172 | echo Status::DONE(); // done
173 | ```
174 |
175 | ## Testing
176 |
177 | ### Unit testing
178 |
179 | The package is tested with [PHPUnit](https://phpunit.de/). To run tests:
180 |
181 | ```shell
182 | ./vendor/bin/phpunit
183 | ```
184 |
185 | ### Mutation testing
186 |
187 | The package tests are checked with [Infection](https://infection.github.io/) mutation framework. To run it:
188 |
189 | ```shell
190 | ./vendor/bin/infection
191 | ```
192 |
193 | ### Static analysis
194 |
195 | The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis:
196 |
197 | ```shell
198 | ./vendor/bin/psalm
199 | ```
200 |
201 | ## License
202 |
203 | The PHP Enum implementation is free software. It is released under the terms of the BSD License. Please see [`LICENSE`](./LICENSE.md) for more information.
204 |
205 | ## Credits
206 |
207 | Version 3 of this package is inspired by [`myclabs/php-enum`](https://github.com/myclabs/php-enum).
208 |
--------------------------------------------------------------------------------