Skip to content
This repository was archived by the owner on Aug 27, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changes History

2.0.2
-----
Fix the following logic bug: wrong strict comparison of
the identical enum instances.

2.0.0
-----
Replace old Enum class by new Java-style Enum class.

Update predefined enums: Gender, PagingType, PaymentType.

1.0.9
-----
Add:
Expand Down
87 changes: 80 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,91 @@ Designed to be container for repeatedly used set of constants.

**Example**:
```php
class Gender extends Saritasa\Enum
class LogicOperations extends Saritasa\Enum
{
const MALE = 'Male';
const FEMALE = 'Female';
protected const AND = null;
protected const OR = null;
protected const XOR = null;
}
```
then somewere in code:
then somewhere in code:
```php
$allGenders = Gender::getConstants();
$gender = new Gender($stringValue); // Will throw UnexpectedValueException on unknown value;
function getGenderDependentValue(Gender $gender) { ... }
$operations = LogicOperations::getConstantNames(); // returns ['AND', 'OR', 'XOR']
...
function getLogicOperationDependentValue(LogicOperations $op)
{
if ($op === LogicOperations::OR()) { ... }
...
switch ($op) {
case LogicOperations::AND():
....
break;
case LogicOperations::OR():
...
break;
case LogicOperations::XOR():
...
break;
default:
...
}
}
...
$xor = LogicOperations::XOR();
echo getLogicOperationDependentValue($xor);
...
echo $xor; // will display XOR
echo json_encode($xor); // will display "XOR"
...
$foo = LogicOperations::FOO(); // will throw InvalidEnumValueException on unknown value
...
if ($xor == 'XOR') {} // the condition is TRUE because of Enum::toString()
```

The enum class body can include methods and other fields (Java style):
```php
class TeamGender extends Saritasa\Enum
{
protected const MEN = ['Men', false];
protected const WOMEN = ['Women', false];
protected const MIXED = ['Co-ed', true];

private $name = '';
private $mixed = false;

protected function __construct(string $name, bool $mixed)
{
$this->name = $name;
$this->mixed = $mixed;
}

public function getName(): string
{
return $this->name;
}

public function getIsMixed(): bool
{
return $this->mixed;
}
}
```
then somewhere in code:
```php
$genders = Gender::getConstants();
// returns ['MEN' => ['Men', false], 'WOMEN' => ['Women', false], 'MIXED' => ['Co-ed', true]]
...
echo TeamGender::MEN(); // will display MEN
echo TeamGender::WOMEN('name'); // will display Women
echo TeamGender::WOMEN()->getName(); // will display Women
echo (int)TeamGender::MIXED('isMixed'); // will display 1
echo (int)TeamGender::MIXED()->getIsMixed(); // will display 1
```

**Note:** It's recommended to make enum constants protected or private (because each enum value
is actually an object and public constants break the encapsulation). However, constant access
modifiers are only available since PHP 7.1

### Dto
A simple DTO, that can convert associative array to strong typed class with fields and back:

Expand Down
177 changes: 125 additions & 52 deletions src/Enum.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Saritasa;

use ReflectionClass;
use Saritasa\Exceptions\InvalidEnumValueException;

/**
Expand All @@ -14,107 +13,181 @@
*/
abstract class Enum implements \JsonSerializable
{
private static $constCacheArray = null;
/**
* The constants' cache.
*
* @var array
*/
private static $constants = [];

/**
* The enum instances.
*
* @var array
*/
private static $instances = [];

private $value;
/**
* The name of an enum constant associated with the given enum instance.
*
* @var string
*/
protected $constant = '';

/**
* Enum implementation for PHP, alternative to \SplEnum.
* Returns the class's constants.
*
* @param mixed $value String representation of enum value (must be valid enum value or exception will be thrown)
* @return array
* @throws \ReflectionException
*/
public function __construct($value)
final public static function getConstants(): array
{
if (!static::isValidValue($value)) {
throw new \UnexpectedValueException('Value not a const in enum ' . get_class($this));
$class = static::class;
if (isset(self::$constants[$class])) {
return self::$constants[$class];
}
return self::$constants[$class] = (new \ReflectionClass($class))->getConstants();
}

$this->value = $value;
/**
* Returns the available constant names.
*
* @return array
* @throws \ReflectionException
*/
final public static function getConstantNames(): array
{
return array_keys(self::getConstants());
}

/**
* Returns scalar value of this enum.
* Checks if the given constant name is in the enum type.
*
* @return mixed
* @param string $name
* @return bool
* @throws \ReflectionException
*/
public function getValue()
public static function isValidConstantName($name): bool
{
return $this->value;
return array_key_exists($name, self::getConstants());
}

/**
* Compares given enum value to another value
* Checks if the given value is in the enum type.
*
* @param Enum|mixed $value A scalar value or another Enum to compare with
* @return boolean true if values are equal, false otherwise
* @param mixed $value
* @param bool $strict Determines whether to search for identical elements.
* @return bool
* @throws \ReflectionException
*/
public function equalsTo($value)
public static function isValidConstantValue($value, $strict = false): bool
{
if (is_object($value) && $value instanceof Enum) {
return $value->getValue() == $this->value;
}
return $value == $this->value;
return in_array($value, self::getConstants(), $strict);
}

/**
* An array of all constants in this enum (keys are constant names).
* Validates the constant name.
*
* @return array
* @param string $name The constant name.
* @return void
* @throws InvalidEnumValueException
* @throws \ReflectionException
*/
public static function getConstants() : array
public static function validate($name)
{
if (self::$constCacheArray == null) {
self::$constCacheArray = [];
if (!static::isValidConstantName($name)) {
throw new InvalidEnumValueException($name, array_keys(self::getConstants()));
}
$calledClass = get_called_class();
if (!array_key_exists($calledClass, self::$constCacheArray)) {
$reflect = new ReflectionClass($calledClass);
self::$constCacheArray[$calledClass] = $reflect->getConstants();
}
return self::$constCacheArray[$calledClass];
}

/**
* Checks if given value is valid for this enum class.
* Returns value by constant name.
*
* @param mixed $value A value to check
* @param bool $strict If strict comparison should be used or not
* @return boolean True of value is valid, false otherwise
* @param string $name The constant name.
* @return mixed
* @throws InvalidEnumValueException
* @throws \ReflectionException
*/
public static function isValidValue($value, $strict = true) : bool
public static function getConstantValue($name)
{
$values = array_values(self::getConstants());
return in_array($value, $values, $strict);
static::validate($name);
return self::getConstants()[$name];
}

/**
* Returns validated value for this enum class or throws exception if not.
* Creates an enum instance that associated with the given enum constant name.
*
* @param mixed $value value to be checked
* @param bool $strict If strict comparison should be used or not
* @return mixed validated value
* @param string $name The constant name.
* @param array $arguments
* @return mixed
* @throws InvalidEnumValueException
* @throws \BadMethodCallException
* @throws \ReflectionException
*/
public static function validate($value, $strict = true)
final public static function __callStatic(string $name, array $arguments)
{
if (static::isValidValue($value, $strict) === false) {
throw new InvalidEnumValueException(static::getConstants());
$value = static::getConstantValue($name);
$value = is_array($value) ? $value : [$value];
$instance = self::getInstance($name, $value);
$instance->constant = $name;
if ($arguments) {
$method = 'get' . ucfirst(reset($arguments));
if (method_exists($instance, $method)) {
return $instance->{$method}(...$arguments);
}
throw new \BadMethodCallException("Method $method does not exist.");
}
return $value;
return $instance;
}

/**
* Converts value to a string
* Returns the name of the constant that associated with the current enum instance.
*
* @return string
*/
public function __toString()
public function getConstantName(): string
{
return (string)$this->value;
return $this->constant;
}

public function jsonSerialize()
/**
* Converts the enum instance to a string.
*
* @return string
*/
public function __toString(): string
{
return $this->__toString();
return $this->getConstantName();
}

/**
* Returns data which should be serialized to JSON.
*
* @return string
*/
public function jsonSerialize(): string
{
return $this->getConstantName();
}

/**
* Creates the enum instance.
*
* @param string $constant
* @param array $value
* @return static
*/
private static function getInstance(string $constant, array $value)
{
$class = static::class;
if (isset(self::$instances[$class][$constant])) {
return self::$instances[$class][$constant];
}
return self::$instances[$class][$constant] = new $class(...$value);
}

/**
* Forbids the implicit creation of enum instances without own constructors.
*/
private function __construct() {}
}
6 changes: 3 additions & 3 deletions src/Enums/Gender.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace Saritasa\Enums;

use Saritasa\Enum;
use Saritasa\NamedEnum;

/**
* Human gender - Male or Female
*/
class Gender extends Enum
class Gender extends NamedEnum
{
const MALE = 'Male';
const FEMALE = 'Female';
}
}
6 changes: 3 additions & 3 deletions src/Enums/PagingType.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class PagingType extends Enum
{
const NONE = 'NONE';
const PAGINATOR = 'PAGINATOR';
const CURSOR = 'CURSOR';
const NONE = null;
const PAGINATOR = null;
const CURSOR = null;
}
Loading