Page MenuHomePhabricator

D14481.id35023.diff
No OneTemporary

D14481.id35023.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -168,6 +168,8 @@
'PhutilEmailAddress' => 'parser/PhutilEmailAddress.php',
'PhutilEmailAddressTestCase' => 'parser/__tests__/PhutilEmailAddressTestCase.php',
'PhutilEmptyAuthAdapter' => 'auth/PhutilEmptyAuthAdapter.php',
+ 'PhutilEnum' => 'object/PhutilEnum.php',
+ 'PhutilEnumTestCase' => 'object/__tests__/PhutilEnumTestCase.php',
'PhutilErrorHandler' => 'error/PhutilErrorHandler.php',
'PhutilErrorHandlerTestCase' => 'error/__tests__/PhutilErrorHandlerTestCase.php',
'PhutilErrorTrap' => 'error/PhutilErrorTrap.php',
@@ -200,9 +202,11 @@
'PhutilIPAddressTestCase' => 'ip/__tests__/PhutilIPAddressTestCase.php',
'PhutilInRequestKeyValueCache' => 'cache/PhutilInRequestKeyValueCache.php',
'PhutilInteractiveEditor' => 'console/PhutilInteractiveEditor.php',
+ 'PhutilInvalidEnumImplementationException' => 'object/PhutilInvalidEnumImplementationException.php',
'PhutilInvalidRuleParserGeneratorException' => 'parser/generator/exception/PhutilInvalidRuleParserGeneratorException.php',
'PhutilInvalidStateException' => 'exception/PhutilInvalidStateException.php',
'PhutilInvalidStateExceptionTestCase' => 'exception/__tests__/PhutilInvalidStateExceptionTestCase.php',
+ 'PhutilInvalidTestEnum' => 'object/__tests__/PhutilInvalidTestEnum.php',
'PhutilInvisibleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilInvisibleSyntaxHighlighter.php',
'PhutilIrreducibleRuleParserGeneratorException' => 'parser/generator/exception/PhutilIrreducibleRuleParserGeneratorException.php',
'PhutilJIRAAuthAdapter' => 'auth/PhutilJIRAAuthAdapter.php',
@@ -338,6 +342,7 @@
'PhutilSystem' => 'utils/PhutilSystem.php',
'PhutilSystemTestCase' => 'utils/__tests__/PhutilSystemTestCase.php',
'PhutilTerminalString' => 'xsprintf/PhutilTerminalString.php',
+ 'PhutilTestEnum' => 'object/__tests__/PhutilTestEnum.php',
'PhutilTestPhobject' => 'object/__tests__/PhutilTestPhobject.php',
'PhutilTortureTestDaemon' => 'daemon/torture/PhutilTortureTestDaemon.php',
'PhutilTranslation' => 'internationalization/PhutilTranslation.php',
@@ -690,6 +695,7 @@
'PhutilEmailAddress' => 'Phobject',
'PhutilEmailAddressTestCase' => 'PhutilTestCase',
'PhutilEmptyAuthAdapter' => 'PhutilAuthAdapter',
+ 'PhutilEnumTestCase' => 'PhutilTestCase',
'PhutilErrorHandler' => 'Phobject',
'PhutilErrorHandlerTestCase' => 'PhutilTestCase',
'PhutilErrorTrap' => 'Phobject',
@@ -722,9 +728,11 @@
'PhutilIPAddressTestCase' => 'PhutilTestCase',
'PhutilInRequestKeyValueCache' => 'PhutilKeyValueCache',
'PhutilInteractiveEditor' => 'Phobject',
+ 'PhutilInvalidEnumImplementationException' => 'Exception',
'PhutilInvalidRuleParserGeneratorException' => 'PhutilParserGeneratorException',
'PhutilInvalidStateException' => 'Exception',
'PhutilInvalidStateExceptionTestCase' => 'PhutilTestCase',
+ 'PhutilInvalidTestEnum' => 'PhutilEnum',
'PhutilInvisibleSyntaxHighlighter' => 'Phobject',
'PhutilIrreducibleRuleParserGeneratorException' => 'PhutilParserGeneratorException',
'PhutilJIRAAuthAdapter' => 'PhutilOAuth1AuthAdapter',
@@ -859,6 +867,7 @@
'PhutilSystem' => 'Phobject',
'PhutilSystemTestCase' => 'PhutilTestCase',
'PhutilTerminalString' => 'Phobject',
+ 'PhutilTestEnum' => 'PhutilEnum',
'PhutilTestPhobject' => 'Phobject',
'PhutilTortureTestDaemon' => 'PhutilDaemon',
'PhutilTranslation' => 'Phobject',
diff --git a/src/object/PhutilEnum.php b/src/object/PhutilEnum.php
new file mode 100644
--- /dev/null
+++ b/src/object/PhutilEnum.php
@@ -0,0 +1,217 @@
+<?php
+
+/**
+ * A basic implementation of an "enum".
+ *
+ * This class is largely based on [[https://github.com/myclabs/php-enum |
+ * myclabs/php-enum]], which aims to be a "PHP Enum implementation inspired
+ * from [[http://php.net/manual/en/class.splenum.php | SplEnum]]".
+ *
+ * @task assert Assertions
+ * @task construct Construction
+ * @task enum Enumeration
+ */
+abstract class PhutilEnum {
+
+ private $key;
+ private $value;
+
+ /**
+ * Cache enumeration values.
+ *
+ * This class follows a singleton pattern so that we can do this:
+ *
+ * ```lang=php
+ * $enum === SomeEnum::SOME_VALUE();
+ * ```
+ */
+ protected static $cache = array();
+
+ /**
+ * A flag to indicate whether a @{class:PhutilEnum} implementation is valid.
+ *
+ * See @{method:assertValidImplementation} for further details.
+ */
+ protected static $validImplementations = array();
+
+ /**
+ * Creates a new enumeration value.
+ *
+ * @param wild Enum value.
+ * @task construct
+ */
+ final private function __construct($value) {
+ if (!idx(self::$validImplementations, get_called_class())) {
+ try {
+ static::assertValidImplementation();
+ } catch (PhutilInvalidEnumImplementationException $ex) {
+ static::$validImplementations[get_called_class()] = false;
+ throw $ex;
+ }
+
+ static::$validImplementations[get_called_class()] = true;
+ }
+
+ $this->key = static::search($value);
+ $this->value = $value;
+ }
+
+ /**
+ * Returns a @{class:PhutilEnum} instance from a class constant.
+ *
+ * TODO
+ *
+ * @param string
+ * @param list<wild>
+ * @return static
+ * @task construct
+ */
+ final public static function __callStatic($name, $arguments) {
+ $array = static::toArray();
+
+ if (isset($array[$name])) {
+ return static::initializeEnum($array[$name]);
+ }
+
+ throw new BadMethodCallException(
+ pht(
+ 'No static method or enumeration constant "%s" in %s',
+ $name,
+ get_called_class()));
+ }
+
+ /**
+ * Returns the enumeration value, as a string.
+ *
+ * @return string
+ */
+ final public function __toString() {
+ return (string)$this->value;
+ }
+
+ /**
+ * TODO
+ *
+ * @param wild
+ * @return ???
+ * @task construct
+ */
+ final private static function initializeEnum($value) {
+ if (!array_key_exists($value, static::$cache)) {
+ static::$cache[$value] = new static($value);
+ }
+
+ return static::$cache[$value];
+ }
+
+ /**
+ * Returns the enumeration key.
+ *
+ * @return wild
+ * @task enum
+ */
+ final public function getKey() {
+ return $this->key;
+ }
+
+ /**
+ * Returns the enumeration value.
+ *
+ * @return wild
+ * @task enum
+ */
+ final public function getValue() {
+ return $this->value;
+ }
+
+ /**
+ * Returns the names (keys) of all constants in the enumerator class.
+ *
+ * @return array
+ * @task enum
+ */
+ final public static function keys() {
+ return array_keys(static::toArray());
+ }
+
+ /**
+ * Returns instances of the @{class:PhutilEnum} class for all enumeration
+ * constants.
+ *
+ * @return map<wild, PhutilEnum> Constant name in key, @{class:PhutilEnum}
+ * instance in value.
+ * @task enum
+ */
+ final public static function values() {
+ $values = array();
+
+ foreach (static::toArray() as $key => $value) {
+ $values[$key] = static::initializeEnum($value);
+ }
+
+ return $values;
+ }
+
+ /**
+ * Returns all possible enumeration values as an array.
+ *
+ * @return map<wild, wild> Constant name in key, constant value in value.
+ * @task enum
+ */
+ final public static function toArray() {
+ $class = get_called_class();
+ return id(new ReflectionClass($class))->getConstants();
+ }
+
+ /**
+ * Return the enumeration key for a specified value.
+ *
+ * NOTE: This method assumes that enumeration values are unique, see
+ * @{method:assertValidImplementation}.
+ *
+ * @param wild The specification value to search for.
+ * @return wild The enumeration key, or `null` is the specified value does
+ * not exist.
+ * @task enum
+ */
+ final private static function search($value) {
+ $key = array_search($value, static::toArray(), true);
+
+ if ($key === false) {
+ $key = null;
+ }
+
+ return $key;
+ }
+
+ /**
+ * Asserts that a @{class:PhutilEnum} implementation is valid.
+ *
+ * Specifically, this method asserts that enumeration values are unique. For
+ * example, the following @{class:PhutilEnum} implementation is invalid:
+ *
+ * ```lang=php, counterexample
+ * final class PhutilInvalidEnum extends PhutilEnum {
+ * const SOMETHING = 'value';
+ * const SOMETHING_ELSE = 'value';
+ * }
+ * ```
+ *
+ * @return void
+ * @task assert
+ */
+ final private static function assertValidImplementation() {
+ $array = static::toArray();
+
+ if (array_values($array) != array_unique(array_values($array))) {
+ throw new PhutilInvalidEnumImplementationException(
+ pht(
+ '%s is not a valid %s implementation. Valid %s implementation '.
+ 'must have unique enumeration value.',
+ get_called_class(),
+ __CLASS__,
+ __CLASS__));
+ }
+ }
+
+}
diff --git a/src/object/PhutilInvalidEnumImplementationException.php b/src/object/PhutilInvalidEnumImplementationException.php
new file mode 100644
--- /dev/null
+++ b/src/object/PhutilInvalidEnumImplementationException.php
@@ -0,0 +1,3 @@
+<?php
+
+final class PhutilInvalidEnumImplementationException extends Exception {}
diff --git a/src/object/__tests__/PhutilEnumTestCase.php b/src/object/__tests__/PhutilEnumTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/object/__tests__/PhutilEnumTestCase.php
@@ -0,0 +1,65 @@
+<?php
+
+final class PhutilEnumTestCase extends PhutilTestCase {
+
+ public function testCallStatic() {
+ $enum = PhutilTestEnum::ONE();
+ $this->assertEqual('one', (string)$enum);
+
+ $enum = PhutilTestEnum::TWO();
+ $this->assertEqual('two', (string)$enum);
+
+ $caught = null;
+ try {
+ PhutilTestEnum::THREE();
+ } catch (BadMethodCallException $ex) {
+ $caught = $ex;
+ }
+ $this->assertTrue($caught instanceof BadMethodCallException);
+ }
+
+ public function testSingleton() {
+ $this->assertEqual(
+ PhutilTestEnum::ONE(),
+ PhutilTestEnum::ONE());
+ }
+
+ public function testGetKey() {
+ $this->assertEqual('ONE', PhutilTestEnum::ONE()->getKey());
+ }
+
+ public function testGetValue() {
+ $this->assertEqual('one', PhutilTestEnum::ONE()->getValue());
+ }
+
+ public function testKeys() {
+ $keys = array('ONE', 'TWO');
+ $this->assertEqual($keys, PhutilTestEnum::keys());
+ }
+
+ public function testValues() {
+ $values = array(
+ 'ONE' => PhutilTestEnum::ONE(),
+ 'TWO' => PhutilTestEnum::TWO(),
+ );
+ $this->assertEqual($values, PhutilTestEnum::values());
+ }
+
+ public function testToArray() {
+ $array = array('ONE' => 'one', 'TWO' => 'two');
+ $this->assertEqual($array, PhutilTestEnum::toArray());
+ }
+
+ public function testInvalidImplementation() {
+ $caught = null;
+ try {
+ PhutilInvalidTestEnum::ONE();
+ } catch (Exception $ex) {
+ $caught = $ex;
+ }
+
+ $this->assertTrue(
+ $caught instanceof PhutilInvalidEnumImplementationException);
+ }
+
+}
diff --git a/src/object/__tests__/PhutilInvalidTestEnum.php b/src/object/__tests__/PhutilInvalidTestEnum.php
new file mode 100644
--- /dev/null
+++ b/src/object/__tests__/PhutilInvalidTestEnum.php
@@ -0,0 +1,6 @@
+<?php
+
+final class PhutilInvalidTestEnum extends PhutilEnum {
+ const ONE = 'test';
+ const TWO = 'test';
+}
diff --git a/src/object/__tests__/PhutilTestEnum.php b/src/object/__tests__/PhutilTestEnum.php
new file mode 100644
--- /dev/null
+++ b/src/object/__tests__/PhutilTestEnum.php
@@ -0,0 +1,6 @@
+<?php
+
+final class PhutilTestEnum extends PhutilEnum {
+ const ONE = 'one';
+ const TWO = 'two';
+}

File Metadata

Mime Type
text/plain
Expires
Sat, May 11, 10:18 AM (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6285177
Default Alt Text
D14481.id35023.diff (11 KB)

Event Timeline