Skip to content

Instantly share code, notes, and snippets.

@zlikavac32
Last active October 23, 2017 22:29
Show Gist options
  • Select an option

  • Save zlikavac32/71e387ac91d271a97d4c54593d0cea92 to your computer and use it in GitHub Desktop.

Select an option

Save zlikavac32/71e387ac91d271a97d4c54593d0cea92 to your computer and use it in GitHub Desktop.
WIP - better enums in PHP (Java inspired)
<?php
declare(strict_types=1);
/**
* Proof of concept for better enums in PHP. Supports type-hinting and per enum abstract method implementation
*/
abstract class Enum
{
private static $existingEnums = [];
private $ordinal = -1;
private $name = '';
final public function ordinal(): int
{
return $this->ordinal;
}
final public function name(): string
{
return $this->name;
}
final public function __clone()
{
throw new LogicException('Cloning enum object is not allowed');
}
public function __toString()
{
return $this->name;
}
/**
* @return Iterator|Enum[]
*/
public static function iterator(): Iterator
{
return new ArrayIterator(array_values(self::retrieveCurrentContextEnumerations()));
}
public static function __callStatic($name, $arguments)
{
if (count($arguments) > 0) {
throw new InvalidArgumentException(
sprintf('No argument must be provided when calling %s::%s.', static::class, $name)
);
}
$objects = self::retrieveCurrentContextEnumerations();
if (!isset($objects[$name])) {
throw new RuntimeException(sprintf('Enum object %s missing in %s', $name, static::class));
}
return $objects[$name];
}
/**
* @return Enum[]
*/
private static function retrieveCurrentContextEnumerations(): array
{
$class = static::class;
if (!isset(self::$existingEnums[$class])) {
self::$existingEnums[$class] = self::discoverEnumerationObjects();
}
return self::$existingEnums[$class];
}
private static function discoverEnumerationObjects()
{
/* @var Enum[] $objects */
$objects = static::createEnumerationObjects();
if (!is_array($objects)) {
throw new LogicException(
sprintf(
'Method %s::createEnumerationObjects() must return an array of enumeration objects',
static::class
)
);
}
if (count($objects) === 0) {
throw new LogicException(sprintf('Enumeration objects array in class %s can not be empty', static::class));
}
$i = 0;
foreach ($objects as $alias => $object) {
self::assertValidAlias($alias);
self::assertValidEnumObject($object);
$object->ordinal = $i++;
$object->name = $alias;
}
return $objects;
}
protected static function createEnumerationObjects(): array
{
throw new LogicException(
sprintf(
'You must provide protected static function createEnumerationObjects(): array method in your enum class %s',
static::class
)
);
}
private static function assertValidAlias($alias): void
{
if (!is_string($alias)) {
throw new LogicException(sprintf('Alias %d in enum class %s is not valid alias', $alias, static::class));
}
//For now we allow anything else although in the future some name patterns may arise
}
private static function assertValidEnumObject($object): void
{
if ($object instanceof Enum) {
return ;
}
if (is_object($object)) {
$resolvedType = 'an instance of ' . get_class($object);
} else {
$resolvedType = gettype($object);
}
throw new LogicException(
sprintf(
'Enum object in class %s must be an instance of %s (%s received)',
static::class,
Enum::class,
$resolvedType
)
);
}
}
/**
* @method static Gender MALE
* @method static Gender FEMALE
*/
abstract class Gender extends Enum
{
/**
* @var string
*/
private $symbol;
public function __construct(string $symbol)
{
$this->symbol = $symbol;
}
protected static function createEnumerationObjects(): array
{
return [
'MALE' => new class('M') extends Gender
{
},
'FEMALE' => new class('F') extends Gender
{
},
];
}
public function symbol(): string
{
return $this->symbol;
}
}
/**
* @method static YesNo YES
* @method static YesNo NO
*/
abstract class YesNo extends Enum
{
protected static function createEnumerationObjects(): array
{
return [
'YES' => new class extends YesNo
{
},
'NO' => new class extends YesNo
{
},
];
}
}
/**
* @method static MathematicalOperator PLUS
* @method static MathematicalOperator MINUS
* @method static MathematicalOperator TIMES
* @method static MathematicalOperator DIV
*/
abstract class MathematicalOperator extends Enum
{
protected static function createEnumerationObjects(): array
{
return [
'PLUS' => new class extends MathematicalOperator
{
public function apply(float $left, float $right): float
{
return $left + $right;
}
public function __toString()
{
return '+';
}
},
'MINUS' => new class extends MathematicalOperator
{
public function apply(float $left, float $right): float
{
return $left - $right;
}
public function __toString()
{
return '-';
}
},
'TIMES' => new class extends MathematicalOperator
{
public function apply(float $left, float $right): float
{
return $left * $right;
}
public function __toString()
{
return '*';
}
},
'DIV' => new class extends MathematicalOperator
{
public function apply(float $left, float $right): float
{
if (.0 === $right) {
throw new LogicException('Division by zero');
}
return $left / $right;
}
public function __toString()
{
return '/';
}
},
];
}
abstract public function apply(float $left, float $right): float;
}
function dumpGender(Gender $gender)
{
var_dump($gender->symbol());
}
var_dump(
Gender::FEMALE()
->symbol()
);
var_dump(Gender::MALE() === Gender::FEMALE());
var_dump(Gender::MALE() === Gender::MALE());
dumpGender(Gender::MALE());
function yesNo(YesNo $yesNo)
{
switch ($yesNo) {
case YesNo::NO():
var_dump('No');
break;
case YesNo::YES():
var_dump('Yes');
break;
}
}
yesNo(YesNo::NO());
yesNo(YesNo::YES());
var_dump(
YesNo::NO()
->ordinal(),
YesNo::YES()
->ordinal()
);
//Will throw exception
//dumpGender(YesNo::NO());
//Will throw exception
//var_dump(clone YesNo::NO());
var_dump(
MathematicalOperator::PLUS()
->apply(10, 20)
);
/* @var MathematicalOperator $operator */
foreach (MathematicalOperator::iterator() as $operator) {
$left = 10;
$right = 20;
var_dump(
sprintf(
'%.2lf %s %.2lf = %.2lf',
$left,
(string) $operator,
$right,
$operator->apply(
$left,
$right
)
)
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment