Skip to content

Instantly share code, notes, and snippets.

@nextat
Last active May 22, 2019 11:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nextat/71ceb285ad856e430d9afd48f22da984 to your computer and use it in GitHub Desktop.
Save nextat/71ceb285ad856e430d9afd48f22da984 to your computer and use it in GitHub Desktop.
My Enum
<?php
declare(strict_types=1);
/**
* Enum型を表すクラス
*/
abstract class EnumType
{
/**
* Enum値として使用するクラス
* @var string
*/
protected $valueClass;
/**
* Enum値として許可するメソッド名の配列
* @var string[]
*/
private $definedValues = [];
/**
* Enum値オブジェクトのキャッシュ
* @var array
*/
protected $caches = [];
/**
* インスタンス
* @var static
*/
protected static $instance;
/**
* コンストラクタ
*
* この型に定義された値メソッドのドキュメントから、
* Enum値として有効なメソッドの配列を生成する
*/
private function __construct()
{
$reflectionClass = new ReflectionClass(static::class);
$docComment = $reflectionClass->getDocComment();
$lines = preg_split('/\n|\r/', $docComment);
foreach ($lines as $line) {
$matches = [];
preg_match('/@method\s+\S+\s+(\w+)\s*\(\)/', $line, $matches);
if (isset($matches[1])) {
$this->definedValues[] = $matches[1];
}
}
}
/**
* インスタンス取得
*
* @return static
*/
public static function get()
{
if (static::$instance !== null) {
return static::$instance;
}
return (static::$instance = new static());
}
/**
* Enum値を返すマジックメソッド
* @param $name
* @param $arguments
* @return mixed
*/
public function __call($name, $arguments)
{
if (!in_array($name, $this->definedValues)) {
throw new \LogicException('disallowed value: '.$name);
}
if (isset($this->caches[$name])) {
return $this->caches[$name];
}
$valueClass = $this->valueClass;
$valueObject = new $valueClass($name, $this);
return ($this->caches[$name] = $valueObject);
}
}
/**
* Enum値を表すクラス
*/
abstract class EnumValue implements JsonSerializable
{
/**
* @var mixed
*/
protected $value;
/**
* @var EnumType
*/
protected $type;
/**
* コンストラクタ
* @param mixed $value
* @param EnumType $type
*/
public function __construct($value, $type)
{
$this->value = $value;
$this->type = $type;
}
/**
* 文字列化
* @return string
*/
public function __toString(): string
{
return sprintf('%s(%s)', static::class, $this->value);
}
/**
* ラップされている値を取得
*
* @return mixed
*/
public function unwrap()
{
return $this->value;
}
/**
* 比較
*
* @param static $other
* @return bool
*/
public function equalsTo($other): bool
{
if ($other instanceof static) {
return $this->value === $other->unwrap();
}
return false;
}
/**
* @inheritdoc
*/
public function jsonSerialize()
{
return $this->value;
}
}
<?php
declare(strict_types=1);
require_once __DIR__.'/MyEnum.php';
/**
* トランプのマークのEnum型
*
* @method Suit spade()
* @method Suit heart()
* @method Suit dia()
* @method Suit clover()
*/
class SuitType extends EnumType
{
/**
* @inheritdocs
*/
protected $valueClass = Suit::class;
}
class Suit extends EnumValue
{
/**
* Enum型クラス
* @var SuitType
*/
protected $type;
/**
* 自分がスペードかどうか
*
* @return bool
*/
public function isSpade(): bool
{
return $this === $this->type->spade();
}
}
<?php
declare(strict_types=1);
require_once __DIR__.'/Suit.php';
/**
* @param bool $assertion
* @param string $message
*/
function assertTrue(bool $assertion, string $message)
{
if ($assertion) {
echo 'OK: '.$message."\n";
} else {
echo 'NG: '.$message."\n";
}
}
/**
* @param bool $assertion
* @param string $message
*/
function assertFalse(bool $assertion, string $message)
{
if ($assertion) {
echo 'NG: '.$message."\n";
} else {
echo 'OK: '.$message."\n";
}
}
$suitType = SuitType::get();
echo "Testing creation\n";
assertTrue($suitType->spade() instanceof Suit, 'spade() returns a suit');
assertTrue($suitType->heart() instanceof Suit, 'heart() returns a suit');
assertTrue($suitType->dia() instanceof Suit, 'dia() returns a suit');
assertTrue($suitType->clover() instanceof Suit, 'clover() returns a suit');
echo "\n";
echo "Testing toString\n";
assertTrue("".$suitType->spade() === 'Suit(spade)', 'Suit can be converted to string');
echo "\n";
echo "Testing equality\n";
assertTrue($suitType->spade()->equalsTo($suitType->spade()), 'spade equals to spade');
assertFalse($suitType->spade()->equalsTo($suitType->heart()), 'spade does not equal to heart');
assertFalse($suitType->spade()->equalsTo('spade'), 'spade does not equal to string');
try {
$suitType->foo();
assertTrue(false, 'should not reach here');
} catch (\Exception $e) {
assertTrue(true, 'trying to get undefined value throws exception');
}
echo "\n";
echo "Testing caching\n";
assertTrue($suitType->spade() === $suitType->spade(), 'enum values are cached');
echo "\n";
echo "Testing type object\n";
assertTrue($suitType === SuitType::get(), 'enum type objects should be singleton');
echo "\n";
echo "Testing extended usage of values\n";
assertTrue($suitType->spade()->isSpade(), 'the spade value knows if itself is spade');
assertFalse($suitType->heart()->isSpade(), 'the heart value knows if itself is not spade');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment