-
-
Save nextat/71ceb285ad856e430d9afd48f22da984 to your computer and use it in GitHub Desktop.
My Enum
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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