>  기사  >  백엔드 개발  >  PHP를 사용하여 열거형을 구현하는 방법은 무엇입니까?

PHP를 사용하여 열거형을 구현하는 방법은 무엇입니까?

不言
不言앞으로
2019-04-12 10:23:093783검색

이 기사의 내용은 PHP를 사용하여 열거형을 구현하는 방법에 관한 것입니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

열거

수학과 컴퓨터 과학 이론에서 집합의 열거는 특정 유한 수열 집합의 모든 구성원 또는 특정 유형의 개체 수를 나열하는 프로그램입니다. 두 가지 유형은 종종(항상은 아니지만) 중복됩니다.

열거는 정수 상수의 명명된 모음입니다. 열거는 일상 생활에서 매우 일반적입니다. 예를 들어, 주를 나타내는 SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY 및 SATURDAY가 열거형입니다. ——Wikipedia

비즈니스 시나리오

실제 개발 과정에서 우리는 열거형을 접하기가 매우 쉽습니다. 그러나 PHP의 기본 열거형 지원이 좋지 않기 때문에 개발자들은 열거형에 주의를 기울이지 않는 경우가 많습니다. 대신 전역 상수나 클래스 상수를 사용하세요. 원칙적으로 이 두 데이터는 여전히 문자열이므로 유형 판단에 사용할 수 없습니다. 字符串 并不能用来做类型判断。

业务

  • 订单状态 待支付/待发货/待收货/待评价
  • 会员状态 激活/未激活
  • ....

等等 ,很多时候我们都会用简单的 1/2/3/4 或者0/1 这样的方式去代表,然后在文档或者注释中规定这些东西。

更高级一点儿的就是定义成常量,然后方便统一存取,但是常量的值还是是字符串,无法进行类型判断。

这里就要看一下 PHP 对枚举的支持,虽然 PHP 对枚举没有完美的支持,但是在 SPL 中还是有一个基础的枚举类

SPL 枚举

SplEnum extends SplType {/ Constants /
const NULL __default = NULL ;
/ 方法 /
public getConstList ([ bool $include_default = FALSE ] ) : array
/ 继承的方法 /
SplType::__construct ( [mixed $initial_value [, bool $strict ]] )
}

但是!这个需要额外的安装 PECL 用 PECL 安装 Spl_Types,无意间增加了使用成本,那有没有其他解决方案?答案是肯定的。

直接手写一个。

开始准备

首先定一个枚举

class Enum
{
    // 默认值
    const __default = self::WAIT_PAYMENT;
    // 待付款
    const WAIT_PAYMENT = 0;
    // 待发货
    const WAIT_SHIP = 1;
    // 待收货
    const WAIT_RECEIPT = 2;
    // 待评价
    const WAIT_COMMENT = 3;
}

这样似乎就完成了,我们直接使用 Enum::WAIT_PAYMENT 就可以拿到里面的值了,但是传参的地方我们并没法校验他。

function setStatus(Enum $status){
    // TODO
}
setStatus(Enum::WAIT_PAYMENT);
// Error 显然这是不行的 因为上面常量的值时一个 int 并不是 Enum 类型。

这里我们就需要用到 PHP 面向对象中的一个魔术方法 __toString()

public __toString ( void ) : string

__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

现在我们来完善一下这个方法。

class OrderStatus extends Enum
{
    // 默认值
    const __default = self::WAIT_PAYMENT;
    // 待付款
    const WAIT_PAYMENT = 0;
    // 待发货
    const WAIT_SHIP = 1;
    // 待收货
    const WAIT_RECEIPT = 2;
    // 待评价
    const WAIT_COMMENT = 3;

    public function __toString()
    {
        return '233';
    }
}
// object
echo gettype($orderStatus) . PHP_EOL;
// boolean true
var_dump($orderStatus instanceof Enum);
// 233
echo $orderStatus;

初具模型

这里似乎实现了一部分,那我们应该怎么样让他做的更好?再来改造一下。

class OrderStatus extends Enum
{
    // 默认值
    const __default = self::WAIT_PAYMENT;
    // 待付款
    const WAIT_PAYMENT = 0;
    // 待发货
    const WAIT_SHIP = 1;
    // 待收货
    const WAIT_RECEIPT = 2;
    // 待评价
    const WAIT_COMMENT = 3;
    /**
     * @var string
     */
    protected $value;

    public function __construct($value = null)
    {
        $this->value = is_null($value) ? self::__default : $value;
    }

    public function __toString()
    {
        return (string)$this->value;
    }
}

// 1️⃣
$orderStatus = new OrderStatus(OrderStatus::WAIT_SHIP);

// object
echo gettype($orderStatus) . PHP_EOL;
// boolean true
var_dump($orderStatus instanceof Enum);
// 1
echo $orderStatus . PHP_EOL;
// 2️⃣
$orderStatus = new OrderStatus();
// object
echo gettype($orderStatus) . PHP_EOL;
// boolean true
var_dump($orderStatus instanceof Enum);
// 0
echo $orderStatus;
// 3️⃣
$orderStatus = new OrderStatus('意外的参数');
// object
echo gettype($orderStatus) . PHP_EOL;
// boolean true
var_dump($orderStatus instanceof Enum);
// 意外的参数
echo $orderStatus;

在这一次,我们加入了 构造函数 并且允许他传入一个可选的值,然后来作为 __toString 方法的输出值,这次看起来不错,功能都已经实现了,如果传入的参数否和我们的预期的话。但是 万一不符合呢?看看,第 3️⃣ 个那里,就已经成了意外了,哪还有没有办法补救?答案当然是 有的 ,在这里我们会用到 PHP 另一个好东西 反射类 ,当然这个不是 PHP 特有的,其他语言也有。
当然,除了反射,我们还会用到另外一个东西 方法重载 里面的 __callStatic

비즈니스

  • 주문 상태 결제 보류/배송 예정/수령 예정/평가 예정
  • 회원 상태 활성화/비활성화
  • ....
잠깐만요, 우리는 이를 표현하기 위해 간단한 1/2/3/4 또는 0/1을 사용하고 문서나 댓글에서 이러한 사항을 지정하는 경우가 많습니다.

더 발전된 접근 방식은 이를 상수로 정의한 다음 통합 액세스를 용이하게 하는 것입니다. 그러나 상수의 값은 여전히 ​​문자열이므로 유형 판단을 수행할 수 없습니다.

여기서 PHP의 열거형 지원을 살펴봐야 합니다. 비록 PHP가 열거형을 완벽하게 지원하지는 않지만 SPL

SPL 열거형

class Enum
{
    const __default = null;
    /**
     * @var string
     */
    protected static $value;

    // 注意这里 将构造函数的 修饰符改成了 受保护的 即 外部无法直接 new
    protected function __construct($value = null)
    {
        // 很常规
        self::$value = is_null($value) ? static::__default : $value;
    }

    /**
     * @param $name
     * @param $arguments
     * @return mixed
     * @throws ReflectionException
     */
    public static function __callStatic($name, $arguments)
    {
        // 实例化一个反射类 static::class 表示调用者
        $reflectionClass = new ReflectionClass(static::class);
        // 这里我们要有一个约定, 就是类常量成员的名字必须的大写。
        // 这里就是取出来调用的静态方法名对应的常量值 虽然这里有个 getValue 方法
        // 但是因为其返回值不可靠 我们就依赖于他原本的隐式的 __toString 方法来帮我们输出字符串即可。
        $constant = $reflectionClass->getConstant(strtoupper($name));
        // 获取调用者的 构造方法
        $construct = $reflectionClass->getConstructor();
        // 设置成可访问 因为我们把修饰符设置成了受保护的 这里需要访问到,所以就需要设置成可访问的。
        $construct->setAccessible(true);
        // 因为现在类已经是可以访问的了所以我们直接实例化即可,实例化之后 PHP 会自动调用 __toString 方法 使得返回预期的值。
        $static = new static($constant);
        return $static;
    }

    public function __toString()
    {
        return (string)self::$value;
    }

}

class OrderStatus extends Enum
{
    // 默认值
    const __default = self::WAIT_PAYMENT;
    // 待付款
    const WAIT_PAYMENT = 0;
    // 待发货
    const WAIT_SHIP = 1;
    // 待收货
    const WAIT_RECEIPT = 2;
    // 待评价
    const WAIT_COMMENT = 3;

}

$WAIT_SHIP = OrderStatus::WAIT_SHIP();
var_dump($WAIT_SHIP . '');
var_dump($WAIT_SHIP instanceof Enum);
에는 여전히 기본 열거형 클래스가 있습니다. PECL을 추가로 설치해야 합니다. PECL을 사용하여 Spl_Types를 설치하면 의도치 않게 사용 비용이 증가합니까? 대답은 '예'입니다.

하나만 손으로 ​​쓰세요.

준비 시작

먼저 열거형을 정의합니다

class Enum
{
    const __default = null;
    /**
     * @var string
     */
    protected static $value;
    /**
     * @var ReflectionClass
     */
    protected static $reflectionClass;

    // 注意这里 将构造函数的 修饰符改成了 受保护的 即 外部无法直接 new
    protected function __construct($value = null)
    {
        // 很常规
        self::$value = is_null($value) ? static::__default : $value;
    }

    /**
     * @param $name
     * @param $arguments
     * @return mixed
     */
    public static function __callStatic($name, $arguments)
    {
        // 实例化一个反射类 static::class 表示调用者
        $reflectionClass = self::getReflectionClass();
        // 这里我们要有一个约定, 就是类常量成员的名字必须的大写。
        // 这里就是取出来调用的静态方法名对应的常量值 虽然这里有个 getValue 方法
        // 但是因为其返回值不可靠 我们就依赖于他原本的隐式的 __toString 方法来帮我们输出字符串即可。
        $constant = $reflectionClass->getConstant(strtoupper($name));
        // 获取调用者的 构造方法
        $construct = $reflectionClass->getConstructor();
        // 设置成可访问 因为我们把修饰符设置成了受保护的 这里需要访问到,所以就需要设置成可访问的。
        $construct->setAccessible(true);
        // 因为现在类已经是可以访问的了所以我们直接实例化即可,实例化之后 PHP 会自动调用 __toString 方法 使得返回预期的值。
        $static = new static($constant);
        return $static;
    }

    /**
     * 实例化一个反射类
     * @return ReflectionClass
     * @throws ReflectionException
     */
    protected static function getReflectionClass()
    {
        if (!self::$reflectionClass instanceof ReflectionClass) {
            self::$reflectionClass = new ReflectionClass(static::class);
        }
        return self::$reflectionClass;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return (string)self::$value;
    }

    /**
     * 判断一个值是否有效 即是否为枚举成员的值
     * @param $val
     * @return bool
     * @throws ReflectionException
     */
    public static function isValid($val)
    {
        return in_array($val, self::toArray());
    }

    /**
     * 转换枚举成员为键值对输出
     * @return array
     * @throws ReflectionException
     */
    public static function toArray()
    {
        return self::getEnumMembers();
    }

    /**
     * 获取枚举的常量成员数组
     * @return array
     * @throws ReflectionException
     */
    public static function getEnumMembers()
    {
        return self::getReflectionClass()
            ->getConstants();
    }

    /**
     * 获取枚举成员值数组
     * @return array
     * @throws ReflectionException
     */
    public static function values()
    {
        return array_values(self::toArray());
    }

    /**
     * 获取枚举成员键数组
     * @return array
     * @throws ReflectionException
     */
    public static function keys()
    {
        return array_keys(self::getEnumMembers());
    }

    /**
     * 判断 Key 是否有效 即存在
     * @param $key
     * @return bool
     * @throws ReflectionException
     */
    public static function isKey($key)
    {
        return in_array($key, array_keys(self::getEnumMembers()));
    }

    /**
     * 根据 Key 去获取枚举成员值
     * @param $key
     * @return static
     */
    public static function getKey($key)
    {
        return self::$key();
    }

    /**
     * 格式枚举结果类型
     * @param null|bool|int $type 当此处的值时什么类时 格式化输出的即为此类型
     * @return bool|int|string|null
     */
    public function format($type = null)
    {
        switch (true) {
            // 当为纯数字 或者类型处传入的为 int 值时 转为 int
            case ctype_digit(self::$value) || is_int($type):
                return (int)self::$value;
                break;
            // 当 type 传入 true 时 返回 bool 类型
            case $type === true:
                return (bool)filter_var(self::$value, FILTER_VALIDATE_BOOLEAN);
                break;
            default:
                return self::$value;
                break;
        }
    }

}

class OrderStatus extends Enum
{
    // 默认值
    const __default = self::WAIT_PAYMENT;
    // 待付款
    const WAIT_PAYMENT = 0;
    // 待发货
    const WAIT_SHIP = 1;
    // 待收货
    const WAIT_RECEIPT = 2;
    // 待评价
    const WAIT_COMMENT = 3;

}

$WAIT_SHIP = OrderStatus::WAIT_SHIP();
// 直接输出是字符串
echo $WAIT_SHIP;
// 判断类型是否存在
var_dump($WAIT_SHIP instanceof OrderStatus);
// 格式化输出一下 是要 字符串 、还是 bool 还是整形
// 自动
var_dump($WAIT_SHIP->format());
// 整形
var_dump($WAIT_SHIP->format(1));
// bool
var_dump($WAIT_SHIP->format(true));
// 判断这个值是否有效的枚举值
var_dump(OrderStatus::isValid(2));
// 判断这个值是否有效的枚举值
var_dump(OrderStatus::isValid(8));
// 获取所有枚举成员的 Key
var_dump(OrderStatus::keys());
// 获取所有枚举成员的值
var_dump(OrderStatus::values());
// 获取枚举成员的键值对
var_dump(OrderStatus::toArray());
// 判断枚举 Key 是否有效
var_dump(OrderStatus::isKey('WAIT_PAYMENT'));
// 判断枚举 Key 是否有效
var_dump(OrderStatus::isKey('WAIT_PAYMENT_TMP'));
// 根据 Key 取去 值 注意 这里取出来的已经不带有类型了
// 更加建议直接使用 取类常量的方式去取 或者在高版本的 直接使用类常量修饰符 
// 将类常量不可见最佳,但是需要额外处理了
var_dump(OrderStatus::getKey('WAIT_PAYMENT')
    ->format(1));
이렇게 완료된 것 같습니다. Enum::WAIT_PAYMENT를 직접 사용하여 내부 값을 가져올 수 있지만 통과할 수는 없습니다. 참조한 곳입니다.

rrreee

여기서 PHP 객체 지향 __toString()

public __toString ( void )에서 매직 메서드를 사용해야 합니다. string

__toString() 메서드는 클래스가 문자열로 처리될 때 클래스가 어떻게 응답해야 하는지 결정하는 데 사용됩니다. . 예를 들어 echo $obj; 는 뭔가를 표시해야 합니다. 이 메서드는 문자열을 반환해야 합니다. 그렇지 않으면 E_RECOVERABLE_ERROR 수준의 치명적인 오류가 발생합니다.

이제 이 방법을 개선해 보겠습니다. rrreee

모델이 구체화되기 시작했습니다

🎜🎜일부 구현된 것 같은데 어떻게 하면 더 잘하게 만들 수 있을까요? 다시 개편해 보겠습니다. 🎜rrreee🎜이번에는 생성자를 추가하고 선택적 값을 전달하도록 허용했습니다. 그런 다음 이 값은 __toString 메서드의 출력 값으로 사용됩니다. 예, 전달된 매개변수가 예상한 것과 다른 경우 함수가 구현된 것 같습니다. 하지만 일치하지 않으면 어떻게 되나요? 봐요, 3️⃣ 시점에서는 이미 사고가 났습니다. 해결할 수 있는 방법이 없을까요? 대답은 물론 입니다. 여기서는 PHP의 또 다른 장점인 리플렉션 클래스를 사용하겠습니다. 물론 이것은 PHP에만 있는 것이 아니며 다른 언어에서도 사용할 수 있습니다.
물론 리플렉션 외에도 메서드 오버로딩__callStatic 메서드도 사용할 것입니다. 🎜🎜🎜한 단계 더 나아가세요🎜🎜🎜public static __callStatic ( string $name , array $arguments ) : Mixed🎜🎜 __callStatic()은 액세스할 수 없는 메서드가 정적 컨텍스트에서 호출될 때 호출됩니다. 🎜$name 매개변수는 호출할 메소드의 이름입니다. $arguments 매개변수는 $name 메소드에 전달될 매개변수를 포함하는 열거형 배열입니다. 🎜🎜🎜계속 변신하세요. 🎜rrreee🎜간단한 열거형 클래스가 완성되었습니다. 🎜🎜🎜Complete🎜🎜🎜값이 열거 범위 내에 있는지 확인하는 등 다른 요구 사항이 있는 경우 어떻게 해야 합니까? 모든 열거형 값을 가져오시겠습니까? 모든 열거 키를 가져오고 열거 키가 유효한지 확인하시겠습니까? 자동 서식 지정 "🎜__toString 메서드는 문자열 반환만 허용하지만 때로는 정수, 부울 및 기타 유형이 필요할 때도 있기 때문입니다🎜"🎜rrreee🎜이제 완전한 열거가 완료되었습니다~🎜

위 내용은 PHP를 사용하여 열거형을 구현하는 방법은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제