Skip to content

Commit adca3da

Browse files
committedMay 26, 2021
Rewrite the package from scratch
1 parent 98f3b8e commit adca3da

23 files changed

+723
-948
lines changed
 

‎.editorconfig

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ indent_size = 4
1111
trim_trailing_whitespace = true
1212

1313
[*.md]
14-
trim_trailing_whitespace = false
14+
trim_trailing_whitespace = false
15+
16+
[*.yml]
17+
indent_size = 2

‎.gitattributes

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Autodetect text files
2+
* text=auto
3+
4+
# ...Unless the name matches the following overriding patterns
5+
6+
# Definitively text files
7+
*.php text
8+
*.css text
9+
*.js text
10+
*.txt text
11+
*.md text
12+
*.xml text
13+
*.json text
14+
*.bat text
15+
*.sql text
16+
*.yml text
17+
18+
# Ensure those won't be messed up with
19+
*.png binary
20+
*.jpg binary
21+
*.gif binary
22+
*.ttf binary
23+
24+
# Ignore some meta files when creating an archive of this repository
25+
/.github export-ignore
26+
/.editorconfig export-ignore
27+
/.gitattributes export-ignore
28+
/.gitignore export-ignore
29+
/phpunit.xml.dist export-ignore
30+
/tests export-ignore
31+
32+
# Avoid merge conflicts in CHANGELOG
33+
# https://about.gitlab.com/2015/02/10/gitlab-reduced-merge-conflicts-by-90-percent-with-changelog-placeholders/
34+
/CHANGELOG.md merge=union

‎.github/workflows/build.yml

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
on:
2+
- pull_request
3+
- push
4+
5+
name: build
6+
7+
jobs:
8+
tests:
9+
name: PHP ${{ matrix.php }}-${{ matrix.os }}
10+
11+
env:
12+
key: cache-v1
13+
14+
runs-on: ${{ matrix.os }}
15+
16+
strategy:
17+
matrix:
18+
os:
19+
- ubuntu-latest
20+
- windows-latest
21+
22+
php:
23+
- "8.0"
24+
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v2.3.4
28+
29+
- name: Install PHP
30+
uses: shivammathur/setup-php@v2
31+
with:
32+
php-version: ${{ matrix.php }}
33+
ini-values: date.timezone='UTC'
34+
coverage: pcov
35+
tools: composer:v2
36+
37+
- name: Determine composer cache directory on Linux
38+
if: matrix.os == 'ubuntu-latest'
39+
run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
40+
41+
- name: Determine composer cache directory on Windows
42+
if: matrix.os == 'windows-latest'
43+
run: echo "COMPOSER_CACHE_DIR=~\AppData\Local\Composer" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
44+
45+
- name: Cache dependencies installed with composer
46+
uses: actions/cache@v2
47+
with:
48+
path: ${{ env.COMPOSER_CACHE_DIR }}
49+
key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
50+
restore-keys: |
51+
php${{ matrix.php }}-composer-
52+
- name: Update composer
53+
run: composer self-update
54+
55+
- name: Install dependencies with composer php 8.0
56+
if: matrix.php == '8.0'
57+
run: composer update --ignore-platform-reqs --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
58+
59+
- name: Run tests with phpunit
60+
run: vendor/bin/phpunit --colors=always

‎.github/workflows/mutation.yml

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
on:
2+
pull_request:
3+
push:
4+
branches:
5+
- "master"
6+
7+
name: mutation test
8+
9+
jobs:
10+
mutation:
11+
name: PHP ${{ matrix.php }}-${{ matrix.os }}
12+
13+
runs-on: ${{ matrix.os }}
14+
15+
strategy:
16+
matrix:
17+
os:
18+
- ubuntu-latest
19+
20+
php:
21+
- "8.0"
22+
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v2.3.4
26+
27+
- name: Install PHP
28+
uses: shivammathur/setup-php@v2
29+
with:
30+
php-version: "${{ matrix.php }}"
31+
ini-values: memory_limit=-1
32+
coverage: "pcov"
33+
tools: composer:v2
34+
35+
- name: Determine composer cache directory
36+
run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
37+
38+
- name: Cache dependencies installed with composer
39+
uses: actions/cache@v2
40+
with:
41+
path: ${{ env.COMPOSER_CACHE_DIR }}
42+
key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
43+
restore-keys: |
44+
php${{ matrix.php }}-composer-
45+
- name: Update composer
46+
run: composer self-update
47+
48+
- name: Install dependencies with composer
49+
run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
50+
51+
- name: Run infection
52+
run: |
53+
git fetch --depth=1 origin $GITHUB_BASE_REF
54+
vendor/bin/roave-infection-static-analysis-plugin -j2 --git-diff-filter=A --git-diff-base=origin/$GITHUB_BASE_REF --logger-github --ignore-msi-with-no-mutations --only-covered
55+
env:
56+
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}

‎.github/workflows/static.yml

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
on:
2+
- pull_request
3+
- push
4+
5+
name: static analysis
6+
7+
jobs:
8+
mutation:
9+
name: PHP ${{ matrix.php }}-${{ matrix.os }}
10+
11+
runs-on: ${{ matrix.os }}
12+
13+
strategy:
14+
matrix:
15+
os:
16+
- ubuntu-latest
17+
18+
php:
19+
- "8.0"
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v2.3.4
24+
25+
- name: Install PHP
26+
uses: shivammathur/setup-php@v2
27+
with:
28+
php-version: "${{ matrix.php }}"
29+
tools: composer:v2, cs2pr
30+
coverage: none
31+
32+
- name: Determine composer cache directory
33+
run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
34+
35+
- name: Cache dependencies installed with composer
36+
uses: actions/cache@v2
37+
with:
38+
path: ${{ env.COMPOSER_CACHE_DIR }}
39+
key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
40+
restore-keys: |
41+
php${{ matrix.php }}-composer-
42+
- name: Update composer
43+
run: composer self-update
44+
45+
- name: Install dependencies with composer
46+
run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
47+
48+
- name: Static analysis
49+
run: vendor/bin/psalm --shepherd --stats --output-format=checkstyle | cs2pr --graceful-warnings --colorize

‎CHANGELOG.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# PHP Enum Implementation Change Log
2+
3+
## 3.0.0 May 26, 2021
4+
5+
- Chg: Rewrite the package from scratch.
6+
7+
## 2.2.0 January 15, 2020
8+
9+
- New: Add support static methods of current object in filters.
10+
11+
## 2.1.0 February 26, 2018
12+
13+
- New: Add static method `get()` that returns object by its identifier.
14+
15+
## 2.0.0 September 4, 2017
16+
17+
- Chg: Property `$value` renamed to `$id`.
18+
- Chg: Method `toValues()` renamed to `toIds()`.
19+
20+
## 1.2.0 August 29, 2017
21+
22+
- New: Add support operator `in` in filters.
23+
24+
## 1.1.1 August 29, 2017
25+
26+
- Bug: Fixed problem with values type casting to integer.
27+
28+
## 1.1.0 August 21, 2017
29+
30+
- New: Add method `toObjects()`.
31+
32+
## 1.0.0 July 15, 2017
33+
34+
- Initial release.

‎Enum.php

-261
This file was deleted.

‎LICENSE.md

+26-12
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
1-
The MIT License (MIT)
2-
Copyright (c) 2017 Sergey Predvoditelev
1+
Copyright © 2021 by Sergei Predvoditelev (https://predvoditelev.ru)
2+
All rights reserved.
33

4-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5-
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6-
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
7-
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions
6+
are met:
87

9-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of
10-
the Software.
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.
1117

12-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13-
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
14-
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
15-
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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.

‎README.md

+98-171
Original file line numberDiff line numberDiff line change
@@ -1,245 +1,172 @@
1-
# Реализация перечисляемого типа (enum) на PHP
1+
# PHP Enum Implementation
22

3-
Абстрактный класс `Enum` позволяет создавать enum-объекты (см. [перечисляемый тип](https://ru.wikipedia.org/wiki/Перечисляемый_тип)).
3+
[![Latest Stable Version](https://poser.pugx.org/vjik/php-enum/v/stable.png)](https://packagist.org/packages/vjik/php-enum)
4+
[![Total Downloads](https://poser.pugx.org/vjik/php-enum/downloads.png)](https://packagist.org/packages/vjik/php-enum)
5+
[![Build status](https://github.com/vjik/php-enum/workflows/build/badge.svg)](https://github.com/vjik/php-enum/actions?query=workflow%3Abuild)
6+
[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fvjik%2Fphp-enum%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/vjik/php-enum/master)
7+
[![static analysis](https://github.com/vjik/php-enum/workflows/static%20analysis/badge.svg)](https://github.com/vjik/php-enum/actions?query=workflow%3A%22static+analysis%22)
48

5-
- Поддержка [дополнительных данных](#extradata) для значений.
6-
- Поддержка [геттеров](#getters).
7-
- Поддержка [фильтрации](#filtering).
8-
- Вспомогательные функции ([`toIds`](#toIds), [`toList`](#toList), [`toArray`](#toArray), [`toObjects`](#toObjects), [`isValid`](#isValid)).
9+
The package provide abstract class `Enum` that intended to create
10+
[enumerated objects](https://en.wikipedia.org/wiki/Enumerated_type) with support [extra data](#extradata) and
11+
auxiliary static functions [`toValues()`](#toList), [`toObjects()`](#toObjects) and [`isValid()`](#isValid).
912

10-
## Установка
13+
## Requirements
1114

12-
Рекомендуется установка через [composer](http://getcomposer.org/download/):
15+
- PHP 8.0 or higher.
1316

14-
```
15-
composer require vjik/php-enum
17+
## Installation
18+
19+
The package could be installed with composer:
20+
21+
```shell
22+
composer require vjik/php-enum --prefer-dist
1623
```
1724

18-
## Определение класса
25+
## General usage
1926

20-
### Вариант 1. Базовый
27+
### Declaration of class
2128

2229
```php
23-
use vjik\enum\Enum;
24-
25-
class Status extends Enum
30+
use Vjik\Enum\Enum;
31+
32+
/**
33+
* @method static self NEW()
34+
* @method static self PROCESS()
35+
* @method static self DONE()
36+
*/
37+
final class Status extends Enum
2638
{
27-
const DRAFT = 'draft';
28-
const PUBLISH = 'publish';
39+
public const NEW = 'new';
40+
public const PROCESS = 'process';
41+
public const DONE = 'done';
2942
}
3043
```
3144

32-
### Вариант 2. С именами
45+
### Creating an object
3346

34-
Массив с данными задаётся в статической функции `items()`.
47+
#### By static method `from()`
3548

3649
```php
37-
use vjik\enum\Enum;
50+
$process = Status::from('process');
51+
```
3852

39-
class Status extends Enum
40-
{
41-
const DRAFT = 'draft';
42-
const PUBLISH = 'publish';
43-
44-
public static function items()
45-
{
46-
return [
47-
self::DRAFT => 'Черновик',
48-
self::PUBLISH => 'Опубликован',
49-
];
50-
}
51-
}
53+
#### By static method with a name identical to the constant name
54+
55+
Static methods are automatically implemented to provide quick access to an enum value.
56+
57+
```php
58+
$process = Status::PROCESS();
5259
```
5360

54-
### Вариант 3. <a name="extradata"></a>С дополнительными данными
61+
### <a name="extradata"></a>Class with extra data
5562

56-
Для всех дополнительных данных необходимо прописать свойства `proteсted`.
63+
Set data in protected static function `data()` and create getters using protected method `getPropertyValue()`:
5764

5865
```php
59-
use vjik\enum\Enum;
66+
use Vjik\Enum\Enum;
6067

61-
class Status extends Enum
68+
/**
69+
* @method static self CREATE()
70+
* @method static self UPDATE()
71+
*/
72+
final class Action extends Enum
6273
{
63-
const DRAFT = 'draft';
64-
const PUBLISH = 'publish';
65-
66-
protected $priority;
67-
68-
public static function items()
74+
public const CREATE = 1;
75+
public const UPDATE = 2;
76+
77+
protected static function data(): array
6978
{
7079
return [
71-
self::DRAFT => [
72-
'name' => 'Черновик',
73-
'priority' => -10,
80+
self::CREATE => [
81+
'tip' => 'Create document',
82+
],
83+
self::UPDATE => [
84+
'tip' => 'Update document',
7485
],
75-
self::PUBLISH => [
76-
'name' => 'Опубликован',
77-
'priority' => 20,
78-
]
7986
];
8087
}
88+
89+
public function getTip(): string
90+
{
91+
/** @var string */
92+
return $this->getPropertyValue('tip');
93+
}
8194
}
8295
```
8396

84-
## Создание объекта
85-
86-
Создать объект можно через создание класса или статическую функцию `get`:
97+
Usage:
8798

8899
```php
89-
$status = new Status(Status::DRAFT);
90-
$status = Status::get(Status::DRAFT);
100+
echo Action::CREATE()->getTip();
91101
```
92102

93-
Второй вариант препочтительнее, так как он кэширует объекты и, если объект уже был инициализирован,
94-
то он будет взят из кэша, а не будет создан заново.
95-
96-
## <a name="toIds"></a>Список значений `toIds`
97-
98-
Возвращает массив значений объекта. Поддерживает [фильтрацию](#filtering).
99-
100-
```php
101-
Status::toIds(); // ['draft', 'publish']
102-
Status::toIds(['priority' => 20]); // ['publish']
103-
```
103+
### Auxiliary static functions
104104

105-
## <a name="toList"></a>Список с названиями `toList`
105+
#### <a name="toValues"></a> List of values `toValues()`
106106

107-
Возвращает массив вида `$id => $name`. Поддерживает [фильтрацию](#filtering).
107+
Returns array of pairs constant names and values.
108108

109109
```php
110-
Status::toList(); // ['draft' => 'Черновик', 'publish' => 'Опубликован']
111-
Status::toList(['priority' => 20]); // ['publish' => 'Опубликован']
110+
// ['CREATE' => 1, 'UPDATE' => 2]
111+
Action::toValues();
112112
```
113113

114-
## <a name="toArray"></a>Массив с данными `toArray`
115-
116-
Возвращает массив вида:
114+
#### <a name="toObjects"></a> List of objects `toObjects`
117115

118-
```php
119-
[
120-
$id => [
121-
'id' => $id,
122-
'name' => $name,
123-
'param1' => $param1,
124-
'param2' => $param2,
125-
126-
],
127-
128-
]
129-
```
130-
131-
Поддерживает [фильтрацию](#filtering).
116+
Returns array of pairs constant names and objects:
132117

133118
```php
134-
Status::toArray();
135-
Status::toArray(['priority' => 20]); // ['publish' => 'Опубликован']
119+
// ['CREATE' => $createObject, 'UPDATE' => $updateObject]
120+
Action::toObjects();
136121
```
137122

138-
## <a name="toObjects"></a>Массив объектов `toObjects`
123+
#### <a name="isValid"></a> Validate value `isValid`
139124

140-
Возвращает массив вида:
125+
Check if value is valid on the enum set.
141126

142127
```php
143-
[
144-
$id => Enum,
145-
146-
]
128+
Action::isValid(1); // true
129+
Action::isValid(99); // false
147130
```
148131

149-
## <a name="isValid"></a>Проверка значения `isValid`
132+
### Casting to string
150133

151-
Проверяет, существует ли значение в перечисляемом типе. Поддерживает [фильтрацию](#filtering).
134+
`Enum` support casting to string (using magic method `__toString`). The value is returned as a string.
152135

153136
```php
154-
Status::isValid('new'); // false
155-
Status::isValid('publish'); // true
156-
Status::isValid('publish', [['<', 'priority', 5]]); // false
137+
echo Status::DONE(); // done
157138
```
158139

159-
## <a name="filtering"></a>Фильтрация
140+
## Testing
160141

161-
Методы [`toIds`](#toIds), [`toList`](#toList), [`toArray`](#toArray), [`toObjects`](#toObjects), [`isValid`](#isValid) поддерживают фильтрацию.
142+
### Unit testing
162143

163-
Фильтр передаётся в виде массива:
144+
The package is tested with [PHPUnit](https://phpunit.de/). To run tests:
164145

165-
```php
166-
[
167-
$key => $value,
168-
[$operator, $key, $value],
169-
170-
]
146+
```shell
147+
./vendor/bin/phpunit
171148
```
172149

173-
Поддерживаемые операторы: `=`, `!=`, `>`, `<`, `>=`, `<=`, `in`.
150+
### Mutation testing
174151

175-
В качестве оператора можно использовать статическую функцию объекта. В функцию будут переданые все элементы массива за исключением оператора. Например:
152+
The package tests are checked with [Infection](https://infection.github.io/) mutation framework. To run it:
176153

177-
```php
178-
// Фильтр
179-
[['numberMore', 102]]
180-
181-
// Функция в объекте
182-
public static function numberMore($item, $v)
183-
{
184-
return $item['number'] > $v;
185-
}
154+
```shell
155+
./vendor/bin/infection
186156
```
187157

188-
### Оператор `in`
158+
### Static analysis
189159

190-
Проверяет, что значение соответствует одному из значений, указанных в массиве `$value`. Например:
160+
The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis:
191161

192-
```php
193-
[
194-
Status::isValid('publish', [['in', 'priority', [5, 10]]]);
195-
Status::isValid('closed', [['in', 'id', ['publish', 'closed', 'draft']]]);
196-
]
162+
```shell
163+
./vendor/bin/psalm
197164
```
198165

199-
## <a name="getters"></a>Геттеры
166+
## License
200167

201-
Геттер — это метод, чьё название начинается со слова `get`. Часть названия после `get` определяет имя свойства.
202-
Например, геттер `getAbsPriority` определяет свойство `absPriority`, как показано в коде ниже:
168+
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.
203169

204-
```php
205-
use vjik\enum\Enum;
170+
## Credits
206171

207-
class Status extends Enum
208-
{
209-
const DRAFT = 'draft';
210-
const PUBLISH = 'publish';
211-
212-
protected $priority;
213-
214-
protected function getAbsPriority()
215-
{
216-
return abs($this->priority);
217-
}
218-
219-
public static function items()
220-
{
221-
return [
222-
self::DRAFT => [
223-
'name' => Черновик',
224-
'priority' => -10,
225-
],
226-
self::PUBLISH => [
227-
'name' => 'Опубликован',
228-
'priority' => 20,
229-
]
230-
];
231-
}
232-
}
233-
234-
$status = new Status(Status::DRAFT);
235-
echo $status->absPriority; // 10
236-
```
237-
238-
## Преобразование в строку
239-
240-
Объект поддерживает преобразование в строку (магический метод `__toString`). Возвращается значение в виде строки.
241-
242-
```php
243-
$status = new Status(Status::DRAFT);
244-
echo $status; // draft
245-
```
172+
Version 3 of this package is inspired by [`myclabs/php-enum`](https://github.com/myclabs/php-enum).

‎composer.json

+16-15
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
11
{
22
"name": "vjik/php-enum",
3-
"description": "PHP Enum implementation",
4-
"version": "2.2.0",
3+
"description": "PHP Enum Implementation",
54
"type": "library",
65
"keywords": [
76
"php",
87
"enum"
98
],
10-
"license": "MIT",
9+
"license": "BSD-3-Clause",
1110
"support": {
1211
"issues": "https://github.com/vjik/php-enum/issues",
1312
"source": "https://github.com/vjik/php-enum"
1413
},
15-
"authors": [
16-
{
17-
"name": "Sergey Predvoditelev",
18-
"email": "sergey.predvoditelev@gmail.com"
19-
}
20-
],
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+
},
2123
"autoload": {
2224
"psr-4": {
23-
"vjik\\enum\\": ""
25+
"Vjik\\Enum\\": "src"
2426
}
2527
},
26-
"require": {
27-
"php": "^7.4"
28-
},
29-
"require-dev": {
30-
"phpunit/phpunit": "^9.4"
28+
"autoload-dev": {
29+
"psr-4": {
30+
"Vjik\\Enum\\Tests\\": "tests"
31+
}
3132
},
3233
"config": {
3334
"sort-packages": true

‎infection.json.dist

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
}

‎phpunit.xml

+21-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
1-
<?xml version="1.0" encoding="utf-8"?>
2-
<phpunit bootstrap="./vendor/autoload.php"
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit bootstrap="vendor/autoload.php"
34
colors="true"
5+
verbose="true"
6+
failOnRisky="true"
7+
failOnWarning="true"
48
convertErrorsToExceptions="true"
59
convertNoticesToExceptions="true"
610
convertWarningsToExceptions="true"
7-
stopOnFailure="false">
11+
stopOnFailure="false"
12+
executionOrder="random"
13+
resolveDependencies="true">
14+
<php>
15+
<ini name="error_reporting" value="-1"/>
16+
</php>
17+
818
<testsuites>
9-
<testsuite name="Test Suite">
19+
<testsuite name="Vjik PHP Enum tests">
1020
<directory>./tests</directory>
1121
</testsuite>
1222
</testsuites>
13-
</phpunit>
23+
24+
<coverage>
25+
<include>
26+
<directory>./src</directory>
27+
</include>
28+
</coverage>
29+
</phpunit>

‎psalm.xml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0"?>
2+
<psalm
3+
errorLevel="1"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns="https://getpsalm.org/schema/config"
6+
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
7+
>
8+
<projectFiles>
9+
<directory name="src"/>
10+
</projectFiles>
11+
</psalm>

‎src/Enum.php

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Vjik\Enum;
6+
7+
use BadMethodCallException;
8+
use ReflectionClass;
9+
use ReflectionClassConstant;
10+
use UnexpectedValueException;
11+
12+
use function constant;
13+
use function defined;
14+
use function in_array;
15+
16+
abstract class Enum
17+
{
18+
private mixed $value;
19+
20+
/**
21+
* @psalm-var array<class-string, array<string, mixed>>
22+
*/
23+
private static array $cache = [];
24+
25+
/**
26+
* @psalm-var array<class-string, array<string, static>>
27+
*/
28+
private static array $instances = [];
29+
30+
final protected function __construct(mixed $value)
31+
{
32+
if (!self::isValid($value)) {
33+
throw new UnexpectedValueException("Value '$value' is not part of the enum " . static::class . '.');
34+
}
35+
36+
$this->value = $value;
37+
}
38+
39+
final public function getValue(): mixed
40+
{
41+
return $this->value;
42+
}
43+
44+
final public function __toString(): string
45+
{
46+
return (string)$this->value;
47+
}
48+
49+
/**
50+
* @return static
51+
*/
52+
final public static function from(mixed $value): self
53+
{
54+
return new static($value);
55+
}
56+
57+
/**
58+
* @return static
59+
*/
60+
final public static function __callStatic(string $name, array $arguments): self
61+
{
62+
$class = static::class;
63+
if (!isset(self::$instances[$class][$name])) {
64+
$constant = $class . '::' . $name;
65+
if (!defined($constant)) {
66+
$message = "No static method or enum constant '$name' in class " . static::class . '.';
67+
throw new BadMethodCallException($message);
68+
}
69+
return self::$instances[$class][$name] = new static(constant($constant));
70+
}
71+
return clone self::$instances[$class][$name];
72+
}
73+
74+
final public static function toValues(): array
75+
{
76+
$class = static::class;
77+
78+
if (!isset(static::$cache[$class])) {
79+
/** @psalm-suppress TooManyArguments Remove this after fix https://github.com/vimeo/psalm/issues/5837 */
80+
static::$cache[$class] = (new ReflectionClass($class))->getConstants(ReflectionClassConstant::IS_PUBLIC);
81+
}
82+
83+
return static::$cache[$class];
84+
}
85+
86+
/**
87+
* @return static[]
88+
*/
89+
final public static function toObjects(): array
90+
{
91+
$class = static::class;
92+
93+
$objects = [];
94+
/**
95+
* @var string $key
96+
* @var mixed $value
97+
*/
98+
foreach (self::toValues() as $key => $value) {
99+
if (isset(self::$instances[$class][$key])) {
100+
$objects[$key] = clone self::$instances[$class][$key];
101+
} else {
102+
$objects[$key] = self::$instances[$class][$key] = new static($value);
103+
}
104+
}
105+
106+
return $objects;
107+
}
108+
109+
final public static function isValid(mixed $value): bool
110+
{
111+
return in_array($value, static::toValues(), true);
112+
}
113+
114+
/**
115+
* @psalm-return array<array-key, array<string, mixed>>
116+
*/
117+
protected static function data(): array
118+
{
119+
return [];
120+
}
121+
122+
final protected function getPropertyValue(string $key, mixed $default = null): mixed
123+
{
124+
/** @psalm-suppress MixedArrayOffset */
125+
return static::data()[$this->value][$key] ?? $default;
126+
}
127+
}

‎tests/BaseTest.php

+102-26
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,125 @@
22

33
declare(strict_types=1);
44

5-
namespace vjik\enum\tests;
5+
namespace Vjik\Enum\Tests;
66

7+
use BadMethodCallException;
78
use PHPUnit\Framework\TestCase;
8-
use vjik\enum\tests\enums\Pure;
9-
use vjik\enum\tests\enums\WithData;
10-
use vjik\enum\tests\enums\WithName;
9+
use UnexpectedValueException;
10+
use Vjik\Enum\Tests\Support\Pure;
11+
use Vjik\Enum\Tests\Support\WithData;
1112

1213
final class BaseTest extends TestCase
1314
{
14-
protected Pure $pure;
15-
protected WithName $withName;
16-
protected WithData $withData;
15+
public function testCreateViaFrom(): void
16+
{
17+
$foo = Pure::from(Pure::FOO);
18+
self::assertSame(Pure::FOO, $foo->getValue());
19+
}
20+
21+
public function testCreateViaFunction(): void
22+
{
23+
$foo = Pure::FOO();
24+
self::assertSame(Pure::FOO, $foo->getValue());
25+
}
1726

18-
protected function setUp(): void
27+
public function testImmutabilityCreateViaFunction(): void
1928
{
20-
$this->pure = new Pure(Pure::FOO);
21-
$this->withName = new WithName(WithName::FOO);
22-
$this->withData = new WithData(WithData::ONE);
29+
self::assertNotSame(Pure::FOO(), Pure::FOO());
2330
}
2431

25-
public function testGetId(): void
32+
public function testCreateWithInvalidValue(): void
2633
{
27-
$this->assertEquals(Pure::FOO, $this->pure->id);
28-
$this->assertEquals((string)Pure::FOO, $this->pure);
34+
$this->expectException(UnexpectedValueException::class);
35+
$this->expectExceptionMessage('Value \'9\' is not part of the enum Vjik\Enum\Tests\Support\Pure.');
36+
Pure::from(9);
37+
}
2938

30-
$this->assertEquals(WithName::FOO, $this->withName->id);
31-
$this->assertEquals((string)WithName::FOO, $this->withName);
39+
public function testCreateWithInvalidFunction(): void
40+
{
41+
$this->expectException(BadMethodCallException::class);
42+
$this->expectExceptionMessage(
43+
'No static method or enum constant \'NOT_EXISTS\' in class Vjik\Enum\Tests\Support\Pure.'
44+
);
45+
Pure::NOT_EXISTS();
46+
}
3247

33-
$this->assertEquals(WithData::ONE, $this->withData->id);
34-
$this->assertEquals((string)WithData::ONE, $this->withData);
48+
public function testToString(): void
49+
{
50+
self::assertSame('foo', (string)Pure::FOO());
51+
self::assertSame('1', (string)Pure::ONE());
52+
}
53+
54+
public function testGetData(): void
55+
{
56+
$one = WithData::ONE();
57+
self::assertSame('One', $one->getName());
58+
}
59+
60+
public function testGetNotExistsData(): void
61+
{
62+
$three = WithData::THREE();
63+
self::assertNull($three->getName());
3564
}
3665

37-
public function testGetName(): void
66+
public function testGetDataWithoutData(): void
3867
{
39-
$this->assertEquals(Pure::FOO, $this->pure->name);
40-
$this->assertEquals('Foo Name', $this->withName->name);
41-
$this->assertEquals('One', $this->withData->name);
68+
$two = Pure::TWO();
69+
self::assertNull($two->getName());
4270
}
4371

44-
public function testCreate(): void
72+
public function dataIsValid(): array
4573
{
46-
$this->assertSame(1, (new Pure(Pure::ONE))->id);
47-
$this->assertSame(1, Pure::get(Pure::ONE)->id);
48-
$this->assertSame(1, Pure::ONE()->id);
74+
return [
75+
[true, 1],
76+
[false, '1'],
77+
[true, 'foo'],
78+
[false, null],
79+
[false, ''],
80+
];
81+
}
82+
83+
/**
84+
* @dataProvider dataIsValid
85+
*/
86+
public function testIsValid(bool $expected, mixed $value): void
87+
{
88+
self::assertSame($expected, Pure::isValid($value));
89+
}
90+
91+
public function testToValues(): void
92+
{
93+
self::assertSame(
94+
[
95+
'FOO' => Pure::FOO,
96+
'BAR' => Pure::BAR,
97+
'ONE' => Pure::ONE,
98+
'TWO' => Pure::TWO,
99+
],
100+
Pure::toValues()
101+
);
102+
}
103+
104+
public function testToObjects(): void
105+
{
106+
$objects = Pure::toObjects();
107+
108+
self::assertSame(['FOO', 'BAR', 'ONE', 'TWO'], array_keys($objects));
109+
self::assertInstanceOf(Pure::class, $objects['FOO']);
110+
self::assertInstanceOf(Pure::class, $objects['BAR']);
111+
self::assertInstanceOf(Pure::class, $objects['ONE']);
112+
self::assertInstanceOf(Pure::class, $objects['TWO']);
113+
self::assertSame(Pure::FOO, $objects['FOO']->getValue());
114+
self::assertSame(Pure::BAR, $objects['BAR']->getValue());
115+
self::assertSame(Pure::ONE, $objects['ONE']->getValue());
116+
self::assertSame(Pure::TWO, $objects['TWO']->getValue());
117+
}
118+
119+
public function testImmutabilityToObjects(): void
120+
{
121+
$objects1 = Pure::toObjects();
122+
$objects2 = Pure::toObjects();
123+
124+
self::assertNotSame($objects1, $objects2);
49125
}
50126
}

‎tests/PureTest.php

-119
This file was deleted.

‎tests/Support/Pure.php

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Vjik\Enum\Tests\Support;
6+
7+
use Vjik\Enum\Enum;
8+
9+
/**
10+
* @method static self FOO()
11+
* @method static self BAR()
12+
* @method static self ONE()
13+
* @method static self TWO()
14+
*/
15+
final class Pure extends Enum
16+
{
17+
public const FOO = 'foo';
18+
public const BAR = 'bar';
19+
public const ONE = 1;
20+
public const TWO = 2;
21+
22+
public function getName(): mixed
23+
{
24+
return $this->getPropertyValue('name');
25+
}
26+
}

‎tests/Support/WithData.php

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Vjik\Enum\Tests\Support;
6+
7+
use Vjik\Enum\Enum;
8+
9+
/**
10+
* @method static self ONE()
11+
* @method static self TWO()
12+
* @method static self THREE()
13+
*/
14+
final class WithData extends Enum
15+
{
16+
public const ONE = 1;
17+
public const TWO = 2;
18+
public const THREE = 3;
19+
20+
protected static function data(): array
21+
{
22+
return [
23+
self::ONE => [
24+
'name' => 'One',
25+
'number' => 101,
26+
],
27+
self::TWO => [
28+
'name' => 'Two',
29+
'number' => 102,
30+
],
31+
];
32+
}
33+
34+
public function getName(): ?string
35+
{
36+
return $this->getPropertyValue('name');
37+
}
38+
39+
public function getNumber(): ?int
40+
{
41+
return $this->getPropertyValue('number');
42+
}
43+
}

‎tests/WithDataTest.php

-152
This file was deleted.

‎tests/WithNameTest.php

-98
This file was deleted.

‎tests/enums/Pure.php

-15
This file was deleted.

‎tests/enums/WithData.php

-52
This file was deleted.

‎tests/enums/WithName.php

-21
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.