Реализация перечислений (Enum) в PHP с проверкой типа

в 14:34, , рубрики: enum, php, Программирование

Иногда в коде приходится использовать строго типизированные параметры, однако сам язык PHP не является строго типизированным (как, например, C#, в котором присутствует такой тип данных, как перечисления – Enum). Однако выход из данной ситуации все равно можно найти. Порывшись по просторам интернета я так и не нашел подходящего мне решения. Предлагаю вам мое решение данной проблемы.

Проблема заключалась в следующем. Необходимо реализовать функцию, которая бы на вход принимала строго типизированный объект (класс), однако в теле функций необходим перебор значений данного класса и сравнение его с предопределенными константами (этого же класса):

function test(Data $data)
{
    switch ((string)$data) {
        case Data::ID:
            echo 'This is ID' . PHP_EOL;
            break;
        case Data::STRING:
            echo 'This is a STRING' . PHP_EOL;
            break;

    }
}

Однако, сразу становится очевидным, что PHP выдаст предупреждение о том, что невозможно использовать объект в качестве параметра оператора switch.

Решение проблемы состоит в том, чтобы реализовать абстрактный класс, в качестве базового для всех классов, где будут использоваться предопределенные константы, с которыми и будет происходить сравнение в операторе switch.

Код данного класса:

/**
 * Base class, witch implements enumeration in child classes.
 *
 * PHP version 5
 *
 * @author Andrey Klimenko
 * @license 2012, Andrey Klimenko
 * @version 1.0.0
 */
abstract class AbstractEnum
{
    /**
     * @var AbstractEnum Class instance
     */
    protected static $instance = null;
    /**
     * @var mixed Value to compare with class constants
     */
    protected static $value;

    /**
     * Protected constructor (realize singleton pattern).
     *
     * @final
     */
    protected final function __construct()
    {
    }

    /**
     * Protect from object cloning (realize singleton pattern).
     *
     * @final
     */
    protected final function __clone()
    {
    }

    /**
     * Protect from reconstruct resources that the object may have (realize singleton pattern).
     *
     * @final
     */
    protected final function __wakeup()
    {
    }

    /**
     * Return instance of this object.
     *
     * @static
     *
     * @param mixed $value Constant value
     *
     * @return AbstractEnum
     */
    public static function getInstance($value)
    {

        if (self::$instance === null) {
            self::$instance = new static();
        }

        self::setConstant($value); // Set value of constant

        return self::$instance;
    }

    /**
     * Prepare to return constant value, given in getInstance() function.
     *
     * @return string
     */
    public final function __toString()
    {

        return (string)static::$value;

    }

    /**
     * Set constant value.
     *
     * @static
     *
     * @param mixed $value Constant value
     *
     */
    protected static function setConstant($value)
    {
        $class = new ReflectionClass(static::$instance); // Get this class reflection
        $constants = array_flip($class->getConstants()); // Get constants of this object

        // Check if constant with given value exist
        if (array_key_exists($value, $constants)) {
            $constantName = $constants[$value];
            static::$value = $class->getConstant($constantName); // Set constant value
        } else {
            trigger_error('Class does not have constant with this value: `' . $value . '`', E_USER_ERROR);
        }
    }
}

Теперь реализуем класс, который наследуется от абстрактного класса, описанного выше. Данный класс является классом, который включает в себя константы, с которыми мы и будем производить сравнение.

/**
 * Sample class, witch implement AbstractEnum abstract class.
 *
 * PHP version 5
 *
 * @author Andrey Klimenko
 * @license 2012, Andrey Klimenko
 * @version 1.0.0
 */
class Data extends AbstractEnum
{
    const ID = 1;       // First constant
    const STRING = 2;   // Second constant
}

Теперь реализуем функцию, в которой проверим работу нашего класса. Основной проблемой, как я уже сказал была строгая типизация параметра, передаваемого в нее. В качестве типа мы и указываем наш класс Data.

/**
 * Test function.
 *
 * @param Data $data Class to check value with.
 *
 * @return void
 */
function test(Data $data)
{
    // First - convert object to string
    switch ((string)$data) {
    // compare needed values
        case Data::ID:
            echo 'This is ID' . PHP_EOL;
            break;
        case Data::STRING:
            echo 'This is a STRING' . PHP_EOL;
            break;

    }
}

И последнее – проверим нашу функцию:

for ($i = 1; $i < 3; $i++) {
    test(Data::getInstance($i));
}

Все работает, как мы и хотели. На этом все. Надеюсь кому-то данная конструкция пригодится.

Автор: andreyklimenko

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js