Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
  • v0.1.x
  • 0.1.0
  • 0.1.1
  • 0.1.2
  • 0.2.0
  • 0.2.0-alpha1
  • 0.2.1
8 results

Target

Select target project
  • jkuchar1/grifart-enum
1 result
Select Git revision
  • master
  • v0.1.x
  • 0.1.0
  • 0.1.1
  • 0.1.2
  • 0.2.0
  • 0.2.0-alpha1
  • 0.2.1
8 results
Show changes
Commits on Source (45)
Showing
with 546 additions and 111 deletions
/vendor/
composer.lock
.idea
**/output/*
\ No newline at end of file
# grifart/enum
repositories: [GRIFART GitLab](https://gitlab.grifart.cz/jkuchar1/grifart-enum), [GitHub](https://github.com/grifart/enum)
Enumeration value object. Enumerate values and behaviour with type-safety.
Repositories [gitlab.grifart.cz](https://gitlab.grifart.cz/jkuchar1/grifart-enum)
and [github.com](https://github.com/grifart/enum).
[![pipeline status](https://gitlab.grifart.cz/jkuchar1/grifart-enum/badges/master/pipeline.svg)](https://gitlab.grifart.cz/jkuchar1/grifart-enum/commits/master)
Sponsored by [grifart.com](https://grifart.com).
## Introduction
Enums represent predefined set of values. The available values are defined statically by each enum class. Each value is represented by an instance of this class in a flyweight manner.
- This enum allows you to add individal behaviour to every enum value (as in Java). This allows you to transform your `switch`es/`if`s into more readable composition. (see example bellow)
- Checks enum annotations if phpdoc-declared methods are properly declared (will generate docblock for you in exception)
- This enum allows you to add individual behaviour for every enum value (as in Java). This allows you to transform your `switch`es/`if`s into more readable composition. (see example bellow)
- Checks enum annotations if phpdoc-declared methods are properly declared (will generate docblock for you when not specified or incorrect)
- `===`, `==` and usage of `switch`es is supported
- string or integer scalar keys are supported
- Easily access scalar value of enum `DayOfWeek::MONDAY()->toScalar()` or `(string) DayOfWeek::MONDAY()`
Also includes:
- It is type safe. By annotating your enumeration type, you are guaranteed that there will be no other values then you declared. `function translateTo(DayOfWeek $day) { ...`
- You can get a list of all the possible values `Enum::getAvailableValues()`
## Installation
```bash
composer require grifart/enum
```
This library uses [**semantic versioning 2.0**](https://semver.org/spec/v2.0.0.html).
You can safely use `^` constrain in you `composer.json`.
## Requirements
This library requires PHP 7.1 and later.
## Project status & release process
While this library is still under development, it is well tested and should be stable enough to use in production environments.
The current releases are numbered 0.x.y. When a non-breaking change is introduced (adding new methods, optimizing existing code, etc.), y is incremented.
When a breaking change is introduced, a new 0.x version cycle is always started.
It is therefore safe to lock your project to a given release cycle, such as 0.1.*.
If you need to upgrade to a newer release cycle, check the release history for a list of changes introduced by each further 0.x.0 version.
## Overview
### Static methods
- fromScalar() - returns enum instance (value) for given scalar
- getAvailableValues() - returns all values for given type
- provideInstances() - implement to return enum instances or automatically implemented by `Grifart\Enum\AutoInstances` trait.
### Instance methods
- toScalar() - return scalar value identifier
- equals() - returns true if the same enum value is passed
- scalarEquals() - returns true if passed scalar value is equal to current value
### Basic enumeration
```php
/**
* @method static DayOfWeek MONDAY()
* @method static DayOfWeek TUESDAY()
*/
final class DayOfWeek extends \Grifart\Enum\Enum
{
use Grifart\Enum\AutoInstances;
private const MONDAY = 'monday';
private const TUESDAY = 'tuesday';
// ...
Common with other enum implementations:
public function process(self $value): void { /* ... */ }
- You can type-hint: `function setCurrentDay(DayOfWeek $day) {`
- You can get a list of all the possible values
}
$monday = DayOfWeek::MONDAY();
$monday->process(DayOfWeek::TUESDAY());
````
## Let code speak: individual behaviour for each value
### Migrating from class constants
This guide show how to migrate from classes with constants to `\Grifart\Enum` in few simple steps. [Continue to example](tests/Example/MigratingLegacyCode/readme.md)
### Adding behaviour to values
Let's **refactor** following existing code:
......@@ -35,13 +112,15 @@ $monday = DayOfWeek::MONDAY();
function nextDay(DayOfWeek $dayOfWeek): DayOfWeek
{
if($dayOfWeek === DayOfWeek::MONDAY()) {
return DayOfWeek::TUESDAY();
} else if (...) {
...
}
throw new ShouldNotHappenException();
switch($dayOfWeek) {
case DayOfWeek::MONDAY():
return DayOfWeek::TUESDAY();
case DayOfWeek::TUESDAY():
// ...
}
throw new ShouldNotHappenException();
}
$tuesday = nextDay($monday);
......@@ -64,12 +143,15 @@ final class DayOfWeek extends \Grifart\Enum\Enum
public function nextDay(): self
{
if($this === self::MONDAY()) {
return self::TUESDAY();
} else if (...) {
...
switch($this) {
case self::MONDAY():
return self::TUESDAY();
case self::TUESDAY():
// ...
}
throw new ShouldNotHappenException();
}
......@@ -106,14 +188,14 @@ abstract class DayOfWeek extends \Grifart\Enum\Enum
protected const TUESDAY = 'tuesday';
// ...
public abstract function nextDay(): self;
abstract public function nextDay(): self;
/** @return static[] */
protected static function provideInstances(): array
{
return [
self::MONDAY => new class extends DayOfWeek
new class(self::MONDAY) extends DayOfWeek
{
public function nextDay(): DayOfWeek
{
......@@ -121,11 +203,11 @@ abstract class DayOfWeek extends \Grifart\Enum\Enum
}
},
self::TUESDAY => new class extends DayOfWeek
new class(self::TUESDAY) extends DayOfWeek
{
public function nextDay(): DayOfWeek
{
return return DayOfWeek::WEDNESDAY();
return DayOfWeek::WEDNESDAY();
}
},
];
......@@ -142,6 +224,11 @@ More use cases:
- day of week
- tracking life-cycle
## Big thanks
- [David Grudl](https://github.com/dg) for making [Nette Tester](https://github.com/nette/tester)
- [Onřej Mirtes](https://github.com/ondrejmirtes) for making [PHP Stan](https://github.com/phpstan/phpstan).
## More reading
- [consistence/consistence enum](https://github.com/consistence/consistence/blob/master/docs/Enum/enums.md)
......
......@@ -15,7 +15,7 @@
"@phpstan",
"@test"
],
"phpstan": "vendor/bin/phpstan analyze -l 7 src tests --no-interaction --ansi --no-progress",
"phpstan": "vendor/bin/phpstan analyze -l 7 -c phpstan.neon --error-format compact --no-interaction --ansi --no-progress -- src",
"test": "vendor/bin/tester tests --colors 1"
},
......@@ -25,15 +25,22 @@
"autoload": {
"psr-4": {
"Grifart\\Enum\\": "src"
}
},
"classmap": [
"src/exceptions.php"
]
},
"require-dev": {
"nette/tester": "^1.7.0",
"phpstan/phpstan": "^0.8.5"
"nette/tester": "^2.1.0",
"phpstan/phpstan": "^0.10.7",
"phpstan/phpstan-strict-rules": "^0.10.1",
"grifart/phpstan-oneline": "^0.2.0"
},
"autoload-dev": {
"files": [
"src/exceptions.php"
]
}
}
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/grifart/phpstan-oneline/config.neon
@echo off
vendor\bin\phpstan analyze src tests --level 7
\ No newline at end of file
......@@ -2,16 +2,22 @@
namespace Grifart\Enum;
/**
* Allows you to use you defined constants automatically as enum value.
* Without explicitly implementing each enum value.
*/
trait AutoInstances
{
// todo: better define this interface
abstract protected static function getConstantToScalar(): array;
/** @param string|int $scalar */
abstract public function __construct($scalar);
protected static function provideInstances(): array
{
$instances = [];
foreach (static::getConstantToScalar() as $constantName => $primitiveValue) {
$instances[$primitiveValue] = new static();
foreach (static::getConstantToScalar() as $scalarValue) {
$instances[] = new static($scalarValue);
}
return $instances;
}
......
......@@ -4,39 +4,51 @@ namespace Grifart\Enum;
use Grifart\Enum\Internal\InstanceRegister;
use Grifart\Enum\Internal\Meta;
use PHPStan\Reflection\ClassReflection;
/**
* Enum
* Enumeration class with support for strong-typing support and behaviour-rich values.
*
* Three concepts:
* - constant name = used to refer to enum value
* - value = the enum instance
* - scalar value = scalar value of enum, must be unique, used for serialization
* Three basic concepts:
* - **value** = the enum instance
* - **scalar** = scalar identifier of enum value; typically used in persistence layer to refer to particular value
* - **constant** = each value has associated class constant, which is used to refer to value from code.
* Constant name is used to generate static method for each of them. Constants are therefore usually not public.
*/
abstract class Enum
{
protected function __construct()
{
}
/**
* Provide values for given enum.
* @return array
* Provide values for given enum, never call this method directly.
* @return static[]
*/
abstract protected static function provideInstances(): array;
/**
* @return string[]|int[]
* @return static[] Order and array keys are not guaranteed.
* For further value introspection use returned enum instances.
*/
final public static function getAvailableValues(): array
{
return self::getMeta()->getValues();
}
/**
* @return array<string,string|int>
*/
protected static function getConstantToScalar(): array
{ // todo: move this to Meta?
return (new \ReflectionClass(static::class))->getConstants();
{
try {
return (new \ReflectionClass(static::class))->getConstants();
} catch (\ReflectionException $e) {
throw new ReflectionFailedException($e);
}
}
/**
* Builds enumeration from its scalar value.
* @param string|int $scalar
* @return static
* @throws MissingValueDeclarationException if there is no value for given scalar
*/
public static function fromScalar($scalar): Enum
{
......@@ -46,21 +58,34 @@ abstract class Enum
/**
* Provides access to values using ::CONSTANT_NAME() interface.
* @return static
* @throws MissingValueDeclarationException
*/
public static function __callStatic(string $constantName, array $arguments): Enum
{
\assert(empty($arguments));
\assert(\count($arguments) === 0);
return self::getMeta()->getValueForConstantName($constantName);
$value = self::getMeta()->getValueForConstantName($constantName);
if($value === NULL) {
throw new \Error('Call to undefined method ' . static::class . '::' . $constantName . '(). Please check that you have provided constant, annotation and value.');
}
return $value;
}
private static function getMeta(): Meta
private static function getMeta(bool $checkIfAccessingRootDirectly = true): Meta
{
$rootClass = static::getRootClass();
if ($checkIfAccessingRootDirectly && $rootClass !== static::class) {
throw new UsageException(
'You have accessed static enum method on non-root class '
. "('$rootClass' is a root class)"
);
}
return InstanceRegister::get(
static::getEnumClassName(),
function (): Meta {
$rootClass,
function () use ($rootClass): Meta {
return Meta::from(
static::getEnumClassName(),
$rootClass,
static::getConstantToScalar(),
static::provideInstances()
);
......@@ -68,44 +93,70 @@ abstract class Enum
);
}
private static function getEnumClassName(): string
private static function getRootClass(): string
{
$ref = new \ReflectionClass(static::class);
if ($ref->isAnonymous()) { // anonymous objects are used for values
$ref = $ref->getMethod('provideInstances')->getDeclaringClass();
try {
return (new \ReflectionClass(static::class))
->getMethod('provideInstances')
->getDeclaringClass()
->getName();
} catch (\ReflectionException $e) {
throw new ReflectionFailedException($e);
}
}
// -------- INSTANCE IMPLEMENTATION ---------
/** @var int|string */
private $scalarValue;
return $ref->getName();
/**
* @param int|string $scalarValue
*/
protected function __construct($scalarValue)
{
$this->scalarValue = $scalarValue;
}
/**
* Provides scalar representation of enum value.
* Returns scalar representation of enum value.
* @return int|string
*/
public function getScalarValue()
public function toScalar()
{
return $this->scalarValue;
}
public function __toString(): string
{
return self::getMeta()->getScalarForValue($this);
// as enum does not allow mixed key types (all must be int or all string),
// we can safely convert integers to strings without worrying introducing
// value conflicts
return (string) $this->toScalar();
}
/**
* Retrieves constant name that is used to access enum value.
*
* Note: do not depend on this values, as it can change anytime. This value can be
* @internal Do not depend on this values, as it can change anytime. This value can be
* subject of refactorings of user-defined enums.
*/
public function getConstantName(): string
{
return self::getMeta()->getConstantNameForScalar(
self::getMeta()->getScalarForValue($this)
return $this::getMeta(FALSE)->getConstantNameForScalar(
$this->toScalar()
);
}
/**
* @param mixed $that the other object we are comparing to
* @param Enum $that the other object we are comparing to
* @return bool if current value equals to the other value
* If value is non-enum value, returns false (as they are also not equal).
*/
public function equals($that): bool
public function equals(Enum $that): bool
{
return $this === $that;
}
......@@ -114,8 +165,8 @@ abstract class Enum
* @param int|string $theOtherScalarValue
* @return bool true if current scalar representation of value equals to given scalar value
*/
public function equalsScalarValue($theOtherScalarValue): bool
public function scalarEquals($theOtherScalarValue): bool
{
return self::getMeta()->getScalarForValue($this) === $theOtherScalarValue;
return $this->toScalar() === $theOtherScalarValue;
}
}
......@@ -2,18 +2,24 @@
namespace Grifart\Enum\Internal;
use Grifart\Enum\UsageException;
/**
* Checks if registering enum does not contain error.
*/
final class ConsistencyChecker
{
public static function checkAnnotations(Meta $enumMeta): void
{
$enumReflection = new \ReflectionClass($enumMeta->getClass());
self::checkCallStaticAnnotations($enumMeta, $enumReflection);
self::checkAllInstancesProvided($enumMeta, $enumReflection->getName());
self::checkCallStaticAnnotations($enumMeta);
self::checkAllInstancesProvided($enumMeta);
self::checkAbstractAndFinal($enumMeta);
}
private static function checkCallStaticAnnotations(Meta $enumMeta, \ReflectionClass $enumReflection): void
private static function checkCallStaticAnnotations(Meta $enumMeta): void
{
$enumReflection = $enumMeta->getClassReflection();
$docBlock = $enumReflection->getDocComment();
$className = $enumReflection->getShortName();
if ($docBlock === false) {
......@@ -28,22 +34,35 @@ final class ConsistencyChecker
}
}
if (!empty($missingAnnotations)) {
if (\count($missingAnnotations) !== 0) {
$properDoc = "/**\n * " . implode("\n * ", $missingAnnotations) . "\n */\n";
throw new \LogicException("You have forgotten to add @method annotations for enum '{$enumReflection->getName()}'. Documentation block should contain: \n$properDoc");
throw new UsageException("You have forgotten to add @method annotations for enum '{$enumReflection->getName()}'. Documentation block should contain: \n$properDoc");
}
// todo: @method annotations without constants
}
private static function checkAllInstancesProvided(Meta $enumMeta, string $className): void
private static function checkAllInstancesProvided(Meta $enumMeta): void
{
// todo: instances without constants
foreach ($enumMeta->getScalarValues() as $scalarValue) {
if (!$enumMeta->hasValueForScalar($scalarValue)) {
$constantName = $enumMeta->getConstantNameForScalar($scalarValue);
throw new \LogicException("You have forgotten to provide instance for $constantName.");
throw new UsageException("You have forgotten to provide instance for $constantName.");
}
}
}
private static function checkAbstractAndFinal(Meta $enumMeta): void
{
$enumReflection = $enumMeta->getClassReflection();
if (!$enumReflection->isFinal() && !$enumReflection->isAbstract()) {
throw new UsageException(
"Enum root class must be either abstract or final.\n"
. "Final is used when one type is enough for all enum instance values.\n"
. 'Abstract is used when enum values are always instances of child classes of enum root class.'
);
}
}
}
......@@ -2,28 +2,33 @@
namespace Grifart\Enum\Internal;
/**
* Keeps track of all enum instances organized by enum root classes.
*/
final class InstanceRegister
{
/** @var \Grifart\Enum\Internal\Meta[] */
private static $instances = [];
public static function get(string $enumClass, callable $registrator = null): Meta
public static function get(string $enumClass, callable $registrar): Meta
{
if (!isset(self::$instances[$enumClass]) && $registrator !== null) {
self::register($registrator());
if (!isset(self::$instances[$enumClass])) {
self::register($enumClass, $registrar());
}
return self::$instances[$enumClass];
}
public static function register(Meta $meta): void
public static function register(string $className, Meta $meta): void
{
// check consistency of enum when assertions are enabled
\assert($meta->getClass() === $className, 'Provided Meta object is for different enum class that was originally registered.');
// check consistency of enum when assertions are enabled (typically non-production code)
assert(
(function () use ($meta): bool {
ConsistencyChecker::checkAnnotations($meta);
return true;
})()
);
self::$instances[$meta->getClass()] = $meta;
self::$instances[$className] = $meta;
}
}
......@@ -3,26 +3,75 @@
namespace Grifart\Enum\Internal;
use Grifart\Enum\Enum;
use Grifart\Enum\MissingValueDeclarationException;
use Grifart\Enum\ReflectionFailedException;
use Grifart\Enum\UsageException;
final class Meta
{
/** @var string */
private $class;
/** @var array */
/** @var array<string,int|string> */
private $constantToScalar;
/** @var \Grifart\Enum\Enum[] */
/** @var array<int|string,Enum> */
private $scalarToValue;
private function __construct(string $class, array $constantToScalar, array $scalarToValue)
/**
* @param string $class
* @param array<string,string|int> $constantToScalar
* @param Enum[] $values
*/
private function __construct(string $class, array $constantToScalar, array $values)
{
$this->class = $class;
$this->constantToScalar = $constantToScalar;
$this->scalarToValue = $scalarToValue;
$this->scalarToValue = $this->buildScalarToValueMapping($values); // requires constantToScalar to be already set!
}
/**
* @param Enum[] $values
* @return array<string|int,Enum>
*/
private function buildScalarToValueMapping(array $values): array {
$scalarToValues = [];
// check type of all scalar values
$keyType = null;
foreach($values as $value) {
$scalar = $value->toScalar();
if ($keyType === NULL) {
$keyType = \gettype($scalar);
}
if ($keyType !== \gettype($scalar)) {
throw new UsageException('Mixed types of scalar value. Keys must either all string or all int.');
}
}
foreach($values as $value) {
$scalar = $value->toScalar();
if (isset($scalarToValues[$scalar])) {
throw new UsageException('You have provided duplicated scalar values.');
}
if(!$this->hasConstantForScalar($scalar)) {
throw new UsageException("Provided instance contains scalar value '$scalar'. But no corresponding constant was found.");
}
$scalarToValues[$scalar] = $value;
}
return $scalarToValues;
}
public static function from(string $class, array $constantToScalar, array $scalarToValues): self
/**
* @param string $class
* @param array<string,string|int> $constantToScalar
* @param Enum[] $values
* @return self
*/
public static function from(string $class, array $constantToScalar, array $values): self
{
return new self($class, $constantToScalar, $scalarToValues);
return new self($class, $constantToScalar, $values);
}
public function getClass(): string
......@@ -30,54 +79,101 @@ final class Meta
return $this->class;
}
/** @return string[] */
public function getClassReflection(): \ReflectionClass
{
try {
return new \ReflectionClass($this->getClass());
} catch (\ReflectionException $e) {
throw new ReflectionFailedException($e);
}
}
/**
* @return string[]
*/
public function getConstantNames(): array
{
return \array_keys($this->constantToScalar);
}
/**
* @return string[]|int[]
*/
public function getScalarValues(): array
{
return \array_values($this->constantToScalar);
}
/** @return \Grifart\Enum\Enum[] */
/**
* @return Enum[]
*/
public function getValues(): array
{
return \array_values($this->scalarToValue);
}
public function getValueForConstantName($constantName): Enum
/**
* @param string $constantName
* @throws MissingValueDeclarationException
*/
public function getValueForConstantName($constantName): ?Enum
{
if(!isset($this->constantToScalar[$constantName])) {
return NULL;
}
$scalar = $this->constantToScalar[$constantName];
return $this->scalarToValue[$scalar];
return $this->getValueForScalar($scalar);
}
/**
* @param string|int $scalarValue
*/
public function hasValueForScalar($scalarValue): bool
{
return isset($this->scalarToValue[$scalarValue]);
}
/**
* @param string|int $scalarValue
*/
public function getConstantNameForScalar($scalarValue): string
{
$result = \array_search($scalarValue, $this->constantToScalar, true);
if ($result === false) {
throw new \RuntimeException("Could not find constant name for $scalarValue.");
throw new UsageException("Could not find constant name for $scalarValue.");
}
return $result;
}
/**
* @return int|string
*/
public function getScalarForValue(Enum $enum)
{
$result = \array_search($enum, $this->scalarToValue, true);
if ($result === false) {
throw new \RuntimeException("Could not find scalar value given value.");
throw new UsageException("Could not find scalar for given instance.");
}
return $result;
}
/**
* @param int|string $scalar
* @throws MissingValueDeclarationException if there is no value for given scalar
*/
public function getValueForScalar($scalar): Enum
{
if (!isset($this->scalarToValue[$scalar])) {
throw new MissingValueDeclarationException("There is no value for enum '{$this->class}' and scalar value '$scalar'.");
}
return $this->scalarToValue[$scalar];
}
/**
* @param string|int $scalar
*/
private function hasConstantForScalar($scalar): bool
{
return \in_array($scalar, $this->constantToScalar, TRUE);
}
}
<?php declare(strict_types=1);
/**
* Contains all exceptions used in project.
*
* - Usage exceptions: leads directly to fix by programmer. They are never caught and should never happen on production.
* - Runtime exception: they represent valid case in domain logic. They should be handled at runtime and caught by user.
* Therefore every error should have separate exception type (they can create inheritance tree)
*/
namespace Grifart\Enum;
// Project root exceptions:
class UsageException extends \LogicException {}
abstract class RuntimeException extends \RuntimeException {}
final class MissingValueDeclarationException extends RuntimeException {}
final class ReflectionFailedException extends UsageException {
public function __construct(\ReflectionException $previous)
{
parent::__construct('PHP reflection failed.', 0, $previous);
}
};
\ No newline at end of file
<?php
declare(strict_types=1);
require __DIR__ . '/../bootstrap.php';
/**
* @method static EnumString VALUE1()
* @method static EnumString VALUE2()
*/
final class EnumString extends \Grifart\Enum\Enum
{
use Grifart\Enum\AutoInstances;
protected const VALUE1 = 'value1';
protected const VALUE2 = 'value2';
}
\Tester\Assert::same('value1', EnumString::VALUE1()->toScalar());
\Tester\Assert::same('value1', (string) EnumString::VALUE1());
\Tester\Assert::same('value2', EnumString::VALUE2()->toScalar());
\Tester\Assert::same('value2', (string) EnumString::VALUE2());
/**
* @method static EnumInt VALUE1()
* @method static EnumInt VALUE2()
*/
final class EnumInt extends \Grifart\Enum\Enum
{
use Grifart\Enum\AutoInstances;
protected const VALUE1 = 1;
protected const VALUE2 = 2;
}
\Tester\Assert::same(1, EnumInt::VALUE1()->toScalar());
\Tester\Assert::same('1', (string) EnumInt::VALUE1());
\Tester\Assert::same(2, EnumInt::VALUE2()->toScalar());
\Tester\Assert::same('2', (string) EnumInt::VALUE2());
......@@ -6,7 +6,7 @@ require __DIR__ . '/../bootstrap.php';
* @method static OrderState ACTIVE()
* @method static OrderState DELIVERED()
*/
class OrderState extends \Grifart\Enum\Enum
final class OrderState extends \Grifart\Enum\Enum
{
use Grifart\Enum\AutoInstances;
......@@ -23,5 +23,5 @@ class OrderState extends \Grifart\Enum\Enum
\Tester\Assert::notSame(OrderState::NEW(), OrderState::DELIVERED());
\Tester\Assert::notSame(OrderState::ACTIVE(), OrderState::DELIVERED());
\Tester\Assert::same('active', OrderState::ACTIVE()->getScalarValue());
\Tester\Assert::same('active', OrderState::ACTIVE()->toScalar());
\Tester\Assert::same(OrderState::ACTIVE(), OrderState::fromScalar('active'));
......@@ -5,7 +5,7 @@ require __DIR__ . '/../bootstrap.php';
* @method static EqualsState NEW()
* @method static EqualsState ACTIVE()
*/
class EqualsState extends \Grifart\Enum\Enum
final class EqualsState extends \Grifart\Enum\Enum
{
use Grifart\Enum\AutoInstances;
......@@ -17,5 +17,5 @@ class EqualsState extends \Grifart\Enum\Enum
\Tester\Assert::true(EqualsState::NEW()->equals(EqualsState::NEW()));
\Tester\Assert::false(EqualsState::NEW()->equals(EqualsState::ACTIVE()));
\Tester\Assert::true(EqualsState::NEW()->equalsScalarValue('new'));
\Tester\Assert::false(EqualsState::NEW()->equalsScalarValue('active'));
\Tester\Assert::true(EqualsState::NEW()->scalarEquals('new'));
\Tester\Assert::false(EqualsState::NEW()->scalarEquals('active'));
<?php
declare(strict_types=1);
require __DIR__ . '/../bootstrap.php';
/**
* @method static Enum1 VALUE1()
* @method static Enum1 VALUE2()
*/
final class Enum1 extends \Grifart\Enum\Enum
{
use Grifart\Enum\AutoInstances;
protected const VALUE1 = 'value1';
protected const VALUE2 = 'value2';
}
$val1 = Enum1::VALUE1();
$val2 = Enum1::VALUE2();
/** intentionally non-strict @noinspection TypeUnsafeComparisonInspection PhpNonStrictObjectEqualityInspection */
\Tester\Assert::true($val1 == Enum1::VALUE1());
/** intentionally non-strict @noinspection TypeUnsafeComparisonInspection PhpNonStrictObjectEqualityInspection */
\Tester\Assert::true($val2 == Enum1::VALUE2());
/** intentionally non-strict @noinspection TypeUnsafeComparisonInspection PhpNonStrictObjectEqualityInspection */
\Tester\Assert::true($val1 != Enum1::VALUE2());
/** intentionally non-strict @noinspection TypeUnsafeComparisonInspection PhpNonStrictObjectEqualityInspection */
\Tester\Assert::true($val2 != Enum1::VALUE1());
$switchResult = 0;
switch ($val1) {
case Enum1::VALUE1():
$switchResult = 1;
break;
case Enum1::VALUE2():
$switchResult = 2;
break;
default:
$switchResult = 3;
break;
}
\Tester\Assert::same(1, $switchResult);
......@@ -16,14 +16,14 @@ abstract class StateMachine extends \Grifart\Enum\Enum
protected static function provideInstances(): array
{
return [
self::STATE_A => new class extends StateMachine
new class(self::STATE_A) extends StateMachine
{
public function canDoTransitionTo(StateMachine $targetState): bool
{
return $targetState === StateMachine::STATE_B();
}
},
self::STATE_B => new class extends StateMachine
new class(self::STATE_B) extends StateMachine
{
public function canDoTransitionTo(StateMachine $targetState): bool
{
......
<?php
declare(strict_types=1);
require __DIR__ . '/../bootstrap.php';
/**
* @method static Enum1 VALUE1()
* @method static Enum1 VALUE2()
*/
final class Enum1 extends \Grifart\Enum\Enum
{
use Grifart\Enum\AutoInstances;
protected const VALUE1 = 'value1';
protected const VALUE2 = 'value2';
}
$val1 = Enum1::VALUE1();
$val2 = Enum1::VALUE2();
\Tester\Assert::true($val1 === Enum1::VALUE1());
\Tester\Assert::true($val2 === Enum1::VALUE2());
\Tester\Assert::true($val1 !== Enum1::VALUE2());
\Tester\Assert::true($val2 !== Enum1::VALUE1());
<?php declare(strict_types=1);
namespace Tests\Grifart\Enum\Consistency;
use Grifart\Enum\UsageException;
use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
class UndecidedEnum extends \Grifart\Enum\Enum
{
protected static function provideInstances(): array
{
return [];
}
}
Assert::exception(
function () {
UndecidedEnum::getAvailableValues();
},
UsageException::class,
"Enum root class must be either abstract or final.\n"
. "Final is used when one type is enough for all enum instance values.\n"
. 'Abstract is used when enum values are always instances of child classes of enum root class.'
);
<?php declare(strict_types=1);
require __DIR__ . '/../bootstrap.php';
class MethodAnnotationsMissing extends \Grifart\Enum\Enum
final class MethodAnnotationsMissing extends \Grifart\Enum\Enum
{
use Grifart\Enum\AutoInstances;
......@@ -14,7 +14,7 @@ class MethodAnnotationsMissing extends \Grifart\Enum\Enum
function () {
MethodAnnotationsMissing::fromScalar('a');
},
\LogicException::class,
\Grifart\Enum\UsageException::class,
"You have forgotten to add @method annotations for enum 'MethodAnnotationsMissing'. Documentation block should contain:\n"
. "/**\n"
. " * @method static MethodAnnotationsMissing STATE_A()\n"
......
......@@ -2,6 +2,7 @@
namespace Tests\Grifart\Enum\Consistency;
use Grifart\Enum\UsageException;
use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
......@@ -10,16 +11,15 @@ require __DIR__ . '/../bootstrap.php';
* @method static MissingInstanceEnum STATE_A()
* @method static MissingInstanceEnum STATE_B()
*/
class MissingInstanceEnum extends \Grifart\Enum\Enum
abstract class MissingInstanceEnum extends \Grifart\Enum\Enum
{
protected const STATE_A = 'a';
protected const STATE_B = 'b';
protected static function provideInstances(): array
{
return [
self::STATE_A => new class extends MissingInstanceEnum
new class(self::STATE_A) extends MissingInstanceEnum
{
},
];
......@@ -29,16 +29,16 @@ class MissingInstanceEnum extends \Grifart\Enum\Enum
$message = 'You have forgotten to provide instance for STATE_B.';
Assert::exception(function () {
MissingInstanceEnum::STATE_A();
}, \LogicException::class, $message);
}, UsageException::class, $message);
Assert::exception(function () {
MissingInstanceEnum::STATE_B();
}, \LogicException::class, $message);
}, UsageException::class, $message);
Assert::exception(function () {
MissingInstanceEnum::fromScalar('a');
}, \LogicException::class, $message);
}, UsageException::class, $message);
Assert::exception(function () {
MissingInstanceEnum::fromScalar('b');
}, \LogicException::class, $message);
\ No newline at end of file
}, UsageException::class, $message);
\ No newline at end of file