Initial commit

This commit is contained in:
Jérémy Munsch 2021-03-01 07:55:10 +01:00
commit f6d042b16c
20 changed files with 7949 additions and 0 deletions

18
.editorconfig Normal file
View file

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.json]
indent_size = 4
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
workspace.code-workspace
vendor

2
.styleci.yml Normal file
View file

@ -0,0 +1,2 @@
php:
preset: laravel

7
changelog.md Normal file
View file

@ -0,0 +1,7 @@
# Changelog
All notable changes to `Enum` will be documented in this file.
## Version 1.0
### Initial release

36
composer.json Normal file
View file

@ -0,0 +1,36 @@
{
"name": "kwaadpepper/enum",
"description": "Enums to use for any project",
"license": "MIT",
"authors": [
{
"name": "Jérémy Munsch",
"email": "github@jeremydev.ovh",
"homepage": "https://jeremydev.ovh"
}
],
"homepage": "https://github.com/kwaadpepper/enum",
"keywords": [
"Laravel",
"Enum"
],
"require": {
"illuminate/support": "~7|~8",
"laravel/framework": "~7|~8"
},
"require-dev": {
"phpunit/phpunit": "~9.0",
"squizlabs/php_codesniffer": "^3.5",
"orchestra/testbench": "^6.13"
},
"autoload": {
"psr-4": {
"Kwaadpepper\\Enum\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Kwaadpepper\\Enum\\Tests\\": "tests"
}
}
}

7187
composer.lock generated Normal file

File diff suppressed because it is too large Load diff

7
contributing.md Normal file
View file

@ -0,0 +1,7 @@
# Contributing
Contributions are welcome and will be fully credited.
Contributions are accepted via Pull Requests on [Github](https://github.com/kwaadpepper/enum).
**Happy coding**!

9
license.md Normal file
View file

@ -0,0 +1,9 @@
# The license
Copyright (c) 28/02/2021, Jérémy Munsch <github@jeremydev.ovh>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

11
phpcs.xml Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="RealWorld" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
<arg name="colors"/>
<arg name="extensions" value="php"/>
<file>src/</file>
<file>tests/</file>
<rule ref="PSR1"/>
<rule ref="PSR2"/>
<rule ref="PSR12"/>
<rule ref="PSR2"/>
</ruleset>

13
phpunit.xml Normal file
View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" backupStaticAttributes="false" colors="true" verbose="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage>
<include>
<directory>src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Package">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

57
readme.md Normal file
View file

@ -0,0 +1,57 @@
# Enum
[![Latest Version on Packagist][ico-version]][link-packagist]
[![Total Downloads][ico-downloads]][link-downloads]
[![Build Status][ico-travis]][link-travis]
[![StyleCI][ico-styleci]][link-styleci]
This is where your description should go. Take a look at [contributing.md](contributing.md) to see a to do list.
## Installation
Via Composer
``` bash
$ composer require kwaadpepper/enum
```
## Usage
## Change log
Please see the [changelog](changelog.md) for more information on what has changed recently.
## Testing
``` bash
$ composer test
```
## Contributing
Please see [contributing.md](contributing.md) for details and a todolist.
## Security
If you discover any security related issues, please email github@jeremydev.ovh instead of using the issue tracker.
## Credits
- [Jérémy Munsch][link-author]
- [All Contributors][link-contributors]
## License
MIT. Please see the [license file](license.md) for more information.
[ico-version]: https://img.shields.io/packagist/v/kwaadpepper/enum.svg?style=flat-square
[ico-downloads]: https://img.shields.io/packagist/dt/kwaadpepper/enum.svg?style=flat-square
[ico-travis]: https://img.shields.io/travis/kwaadpepper/enum/master.svg?style=flat-square
[ico-styleci]: https://styleci.io/repos/12345678/shield
[link-packagist]: https://packagist.org/packages/kwaadpepper/enum
[link-downloads]: https://packagist.org/packages/kwaadpepper/enum
[link-travis]: https://travis-ci.org/kwaadpepper/enum
[link-styleci]: https://styleci.io/repos/12345678
[link-author]: https://github.com/kwaadpepper
[link-contributors]: ../../contributors

334
src/BaseEnum.php Normal file
View file

@ -0,0 +1,334 @@
<?php
namespace Kwaadpepper\Enum;
use BadMethodCallException;
use Closure;
use Illuminate\Contracts\Routing\UrlRoutable;
use JsonSerializable;
use Kwaadpepper\Enum\Exceptions\DuplicateLabelsException;
use Kwaadpepper\Enum\Exceptions\DuplicateValuesException;
use Kwaadpepper\Enum\Exceptions\NotImplementedException;
use Kwaadpepper\Enum\Exceptions\UnknownEnumProperty;
use ReflectionClass;
use TypeError;
abstract class BaseEnum implements JsonSerializable, UrlRoutable
{
/**
* @var string|int
* @readonly
*/
protected $value;
/** @readonly */
protected $label;
/** @var array<string, array<string, App\Enums\BaseEnumDefinition>> */
private static $definitionCache = [];
/**
* @return string[]
* @return array<string|int, string>
*/
public static function toArray(): array
{
$array = [];
foreach (static::resolveDefinition() as $definition) {
$array[$definition->value] = $definition->label;
}
return $array;
}
/**
* @return string[]|int[]
*/
public static function toValues(): array
{
return array_keys(static::toArray());
}
/**
* @return string[]
*/
public static function toLabels(): array
{
return array_values(static::toArray());
}
/**
* Get the enum definition
*
* @return mixed|null
*/
public function getDefinition()
{
$className = static::class;
foreach (self::$definitionCache[$className] as $definition => $enum) {
if ($enum->value === $this->value) {
return $definition;
}
}
return null;
}
/**
* @param string|int $value
* @throws TypeError
* @throws BadMethodCallException
* @return static
*/
public static function make($value): BaseEnum
{
if (!(is_string($value) || is_int($value))) {
$enumClass = static::class;
throw new TypeError("Only string and integer are allowed values for enum {$enumClass}.");
}
$definition = static::findDefinition($value);
if ($definition === null) {
$enumClass = static::class;
throw new BadMethodCallException(
"There's no value {$value} defined for enum {$enumClass}," .
"consider adding it in the docblock definition."
);
}
$obj = new static();
$obj->value = $definition->value;
$obj->label = $definition->label;
return $obj;
}
/**
* @param string $name
*
* @return int|string
*
* @throws UnknownEnumProperty
*/
public function __get(string $name)
{
if ($name === 'label') {
return $this->label;
}
if ($name === 'value') {
return $this->value;
}
throw new UnknownEnumProperty($name);
}
/**
* @param string $name
* @param array $arguments
*
* @return static
*/
public static function __callStatic(string $name, array $arguments)
{
return static::make($name);
}
/**
* @param string $name
* @param array $arguments
*
* @return bool|mixed
*
* @throws UnknownEnumMethod
*/
public function __call(string $name, array $arguments)
{
if (strpos($name, 'is') === 0) {
$other = static::make(substr($name, 2));
return $this->equals($other);
}
return self::__callStatic($name, $arguments);
}
public function equals(BaseEnum ...$others): bool
{
foreach ($others as $other) {
// @phpcs:disable PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
if (
get_class($this) === get_class($other)
&& $this->value === $other->value
) {
return true;
}
}
return false;
}
/**
* @return string[]|int[]|Closure
* @return array<string, string|int> | Closure(string):(int|string)
*/
protected static function values()
{
return [];
}
/**
* @return string[]|Closure
* @return array<string, string> | Closure(string):string
*/
protected static function labels()
{
return [];
}
/**
* @param string|int $input
*
* @return App\Enums\BaseEnumDefinition|null
*/
private static function findDefinition($input): ?BaseEnumDefinition
{
foreach (static::resolveDefinition() as $definition) {
if ($definition->equals($input)) {
return $definition;
}
}
return null;
}
/**
* @return App\Enums\BaseEnumDefinition[]
*/
private static function resolveDefinition(): array
{
$className = static::class;
if (self::$definitionCache[$className] ?? null) {
return self::$definitionCache[$className];
}
$reflectionClass = new ReflectionClass($className);
$docComment = $reflectionClass->getDocComment();
preg_match_all('/@method\s+static\s+self\s+([\w_]+)\(\)/', $docComment, $matches);
$definition = [];
$valueMap = static::values();
if ($valueMap instanceof Closure) {
$valueMap = array_map($valueMap, array_combine($matches[1], $matches[1]));
}
$labelMap = static::labels();
if ($labelMap instanceof Closure) {
$labelMap = array_map($labelMap, array_combine($matches[1], $matches[1]));
}
foreach ($matches[1] as $methodName) {
$value = $valueMap[$methodName] = $valueMap[$methodName] ?? $methodName;
$label = $labelMap[$methodName] = $labelMap[$methodName] ?? $methodName;
$definition[$methodName] = new BaseEnumDefinition($methodName, $value, $label);
}
if (self::arrayHasDuplicates($valueMap)) {
throw new DuplicateValuesException();
}
if (self::arrayHasDuplicates($labelMap)) {
throw new DuplicateLabelsException();
}
return self::$definitionCache[$className] = self::$definitionCache[$className] ?? $definition;
}
private static function arrayHasDuplicates(array $array): bool
{
return count($array) > count(array_unique($array));
}
/**
* @return int|string
*/
public function jsonSerialize()
{
return $this->value;
}
public function __toString(): string
{
return (string) $this->value;
}
/**
* Get the value of the model's route key.
*
* @return mixed
*/
public function getRouteKey()
{
throw new NotImplementedException('getRouteKey not implemented, should not be used');
}
/**
* Get the route key for the model.
*
* @return string
*/
public function getRouteKeyName()
{
throw new NotImplementedException('getRouteKeyName not implemented, should not be used');
}
/**
* Retrieve the model for a bound value.
*
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value, $field = null)
{
try {
return static::make(is_numeric($value) ? intval($value) : $value);
} catch (BadMethodCallException $e) {
return null;
}
}
/**
* Retrieve the child model for a bound value.
*
* @param string $childType
* @param mixed $value
* @param string|null $field
* @return \Illuminate\Database\Eloquent\Model|null
* @throws NotImplementedException
*/
public function resolveChildRouteBinding($childType, $value, $field)
{
throw new NotImplementedException('resolveChildRouteBinding not implemented, should not be used');
}
/**
* Get the Enum name from class name
*
* @return string
*/
public static function getEnumName()
{
$cls = explode('\\', static::class);
return array_pop($cls);
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Kwaadpepper\Enum;
class BaseEnumDefinition
{
/** @var string|int */
public $value;
/** @var string */
public $label;
/** @var string */
private $methodName;
/**
* @param string $methodName
* @param string|int $value
* @param string $label
*/
public function __construct(string $methodName, $value, string $label)
{
$this->methodName = strtolower($methodName);
$this->value = $value;
$this->label = $label;
}
/**
* @param string|int $input
*
* @return bool
*/
public function equals($input): bool
{
if ($this->value === $input) {
return true;
}
if (is_string($input) && $this->methodName === strtolower($input)) {
return true;
}
return false;
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace Kwaadpepper\Enum\Exceptions;
use Exception;
class DuplicateLabelsException extends Exception
{
//
}

View file

@ -0,0 +1,10 @@
<?php
namespace Kwaadpepper\Enum\Exceptions;
use Exception;
class DuplicateValuesException extends Exception
{
//
}

View file

@ -0,0 +1,10 @@
<?php
namespace Kwaadpepper\Enum\Exceptions;
use Exception;
class NotImplementedException extends Exception
{
//
}

View file

@ -0,0 +1,10 @@
<?php
namespace Kwaadpepper\Enum\Exceptions;
use Exception;
class UnknownEnumProperty extends Exception
{
//
}

100
tests/EnumTest.php Normal file
View file

@ -0,0 +1,100 @@
<?php
namespace Kwaadpepper\Enum\Tests;
use Kwaadpepper\Enum\Tests\Enums\ContactFormCivility;
use Kwaadpepper\Enum\Tests\Enums\Days;
use Orchestra\Testbench\TestCase;
class EnumTest extends TestCase
{
protected function getPackageAliases($app)
{
$app->alias(Days::class, 'Days');
$app->alias(ContactFormCivility::class, 'ContactFormCivility');
}
/**
* Define routes setup.
*
* @param \Illuminate\Routing\Router $router
*
* @return void
*/
protected function defineRoutes($router)
{
$router->get('/days/{day}', function (Days $day) {
return response()->json([$day]);
})->middleware('bindings');
$router->get('/civilities/{civility}', function (ContactFormCivility $civility) {
return response()->json([$civility]);
})->middleware('bindings');
}
public function testEnum()
{
$mon = Days::mon();
$tue = Days::tue();
Days::toArray();
Days::toValues();
Days::toLabels();
json_encode($tue);
$mon->getDefinition();
$this->assertClassHasAttribute('value', Days::class);
$this->assertClassHasAttribute('label', Days::class);
$this->assertFalse($mon->equals($tue));
$this->assertTrue($mon->equals($mon));
$this->assertIsArray(Days::toArray());
$this->assertIsArray(Days::toValues());
$this->assertIsArray(Days::toLabels());
$this->assertEquals([
0 => 0,
1 => 1,
2 => 2,
3 => 4,
4 => 8,
5 => 16,
6 => 32,
7 => 64
], Days::toValues());
$this->assertEquals([
0 => 'None',
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
7 => 'Sunday'
], Days::toLabels());
$this->assertEquals([
0 => 'None',
1 => 'Monday',
2 => 'Tuesday',
4 => 'Wednesday',
8 => 'Thursday',
16 => 'Friday',
32 => 'Saturday',
64 => 'Sunday'
], Days::toArray());
$this->assertEquals('Monday', (string)$mon);
}
public function testRoutesDays()
{
$response = $this->call('GET', '/days/1');
$response->assertOk();
$response->assertExactJson([Days::mon()]);
$response = $this->call('GET', '/days/2');
$response->assertOk();
$response->assertExactJson([Days::tue()]);
}
public function testRoutesCivilities()
{
$response = $this->call('GET', '/civilities/mme');
$response->assertOk();
$response->assertExactJson([ContactFormCivility::mme()]);
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Kwaadpepper\Enum\Tests\Enums;
use BadMethodCallException;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self m()
* @method static self mme()
* @method static self mlle()
*/
final class ContactFormCivility extends BaseEnum
{
public function __toString(): string
{
return (string) $this->label;
}
}

62
tests/Enums/Days.php Normal file
View file

@ -0,0 +1,62 @@
<?php
namespace Kwaadpepper\Enum\Tests\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self none()
* @method static self mon()
* @method static self tue()
* @method static self wed()
* @method static self thu()
* @method static self fri()
* @method static self sat()
* @method static self sun()
*/
final class Days extends BaseEnum
{
protected static function values(): array
{
return [
'none' => 0,
'mon' => 1,
'tue' => 1 << 1,
'wed' => 1 << 2,
'thu' => 1 << 3,
'fri' => 1 << 4,
'sat' => 1 << 5,
'sun' => 1 << 6,
];
}
protected static function labels(): array
{
return [
'none' => 'None',
'mon' => 'Monday',
'tue' => 'Tuesday',
'wed' => 'Wednesday',
'thu' => 'Thursday',
'fri' => 'Friday',
'sat' => 'Saturday',
'sun' => 'Sunday'
];
}
public function __toString(): string
{
return (string) $this->label;
}
/**
* Checks if days contain this day
*
* @param int $days
* @return boolean
*/
public function has(int $days)
{
return (bool)($this->value & $days);
}
}