Enums PHP

No âmbito da descrição da área disciplinar, são comuns conceitos com um número limitado de significados. Enums são os melhores para isso. O PHP não possui construções especiais para descrever uma enumeração, mas podem ser imitadas usando uma abordagem orientada a objetos.



Implementação mais simples



No caso mais simples, você pode implementar uma enumeração como um objeto wrapper sobre um tipo simples, limitando programaticamente os argumentos de entrada. Como exemplo, você pode pegar as estações, das quais existem quatro e apenas quatro.



class Season
{
    public const SUMMER = 'summer';
    public const AUTUMN = 'autumn';
    public const WINTER = 'winter';
    public const SPRING = 'spring';

    private string $value;

    public function __construct(string $value)
    {
        if (
            self::SUMMER !== $value &&
            self::AUTUMN !== $value &&
            self::WINTER !== $value &&
            self::SPRING !== $value
        ) {
            throw new InvalidArgumentException(sprintf(
                "Wrong season value. Awaited '%s', '%s', '%s' or '%s'.",
                self::SUMMER,
                self::AUTUMN,
                self::WINTER,
                self::SPRING
            ));
        }

        $this->value = $value;
    }


Você pode demonstrar o processo de criação de uma enumeração com um teste.



    public function testCreation(): void
    {
        $summer = new Season(Season::SUMMER);
        $autumn = new Season(Season::AUTUMN);
        $winter = new Season(Season::WINTER);
        $spring = new Season(Season::SPRING);

        $this->expectException(InvalidArgumentException::class);

        $wrongSeason = new Season('Wrong season');
    }


-. toValue() getValue(). , , __toString().



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


__toString() - .



    public function testStringConcatenation(): void
    {
        $autumn = new Season(Season::AUTUMN);
        $spring = new Season(Season::SPRING);
        $value = $autumn . ' ' . $spring;

        $this->assertIsString($value);
        $this->assertSame(Season::AUTUMN . ' ' . Season::SPRING, $value);
    }


PHP , . , , , .



    public function testEquality(): void
    {
        $firstSummer = new Season(Season::SUMMER);
        $secondSummer = new Season(Season::SUMMER);
        $winter = new Season(Season::WINTER);

        $this->assertTrue($firstSummer == $secondSummer);
        $this->assertFalse($firstSummer == $winter);
        $this->assertFalse($firstSummer === $secondSummer);
        $this->assertFalse($firstSummer === $winter);
    }


equals.



    public function equals(Season $season): bool
    {
        return $this->value === $season->value;
    }


    public function testEquals(): void
    {
        $firstSummer = new Season(Season::SUMMER);
        $secondSummer = new Season(Season::SUMMER);
        $firstWinter = new Season(Season::WINTER);
        $secondWinter = new Season(Season::WINTER);

        $this->assertTrue($firstSummer->equals($secondSummer));
        $this->assertTrue($firstWinter->equals($secondWinter));
        $this->assertFalse($firstSummer->equals($secondWinter));
    }


: Season . .



: . xlr 3pin, jack, mini jack usb .



class MicrophoneConnector
{
    public const XLR_3PIN = 'xlr_3pin';
    public const JACK = 'jack';
    public const MINI_JACK = 'mini_jack';
    public const USB = 'usb';

    private string $value;

    private function __construct(string $value)
    {
        $this->value = $value;
    }

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


. . .



    public static function xlr3pin(): self
    {
        return new self(self::XLR_3PIN);
    }


jack, miniJack usb.



    public function testEquality(): void
    {
        $firstJack = MicrophoneConnector::jack();
        $secondJack = MicrophoneConnector::jack();
        $xlr3pin = MicrophoneConnector::xlr3pin();

        $this->assertTrue($firstJack == $secondJack);
        $this->assertFalse($firstJack == $xlr3pin);
        $this->assertFalse($firstJack === $secondJack);
        $this->assertFalse($firstJack === $xlr3pin);
    }




, . .



: . , , , . agree, disagree hold.



class Decision
{
    public const AGREE = 'agree';
    public const DISAGREE = 'disagree';
    public const HOLD = 'hold';

    private string $value;

    private function __construct(string $value)
    {
        $this->value = $value;
    }

    private function __clone() { }

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


__clone(). .



    private static $agreeInstance = null;

    public static function agree(): self
    {
        if (null === self::$agreeInstance) {
            self::$agreeInstance = new self(self::AGREE);
        }

        return self::$agreeInstance;
    }


c agree disagree hold.



    public function testEquality(): void
    {
        $firsAgree = Decision::agree();
        $secondAgree = Decision::agree();
        $firstDisagree = Decision::disagree();
        $secondDisagree = Decision::disagree();

        $this->assertTrue($firsAgree == $secondAgree);
        $this->assertTrue($firstDisagree == $secondDisagree);
        $this->assertFalse($firsAgree == $secondDisagree);
        $this->assertTrue($firsAgree === $secondAgree);
        $this->assertTrue($firstDisagree === $secondDisagree);
        $this->assertFalse($firsAgree === $secondDisagree);
    }


. __toString() . : . .



    public static function from($value): self
    {
        switch ($value) {
            case self::AGREE:
                return self::agree();

            case self::DISAGREE:
                return self::disagree();

            case self::HOLD:
                return self::hold();

            default:
                throw new InvalidArgumentException(sprintf(
                    "Wrong decision value. Awaited '%s', '%s' or '%s'.",
                    self::AGREE,
                    self::DISAGREE,
                    self::HOLD
                ));
        }
    }


: . . : __sleep() __wakeup(). __sleep() . __wakeup() from() Decision.



class Order
{
    private Decision $decision;
    private string $description;

    public function getDecision(): Decision
    {
        return $this->decision;
    }

    public function getDescription(): string
    {
        return $this->description;
    }

    public function __construct(Decision $decision, string $description)
    {
        $this->decision = $decision;
        $this->description = $description;
    }

    public function __sleep(): array
    {
        return ['decision', 'description'];
    }

    public function __wakeup(): void
    {
        $this->decision = Decision::from($this->decision);
    }
}


/ .



    public function testSerialization(): void
    {
        $order = new Order(Decision::hold(), 'Some order description');

        $serializedOrder = serialize($order);
        $this->assertIsString($serializedOrder);

        /** @var Order $unserializedOrder */
        $unserializedOrder = unserialize($serializedOrder);
        $this->assertInstanceOf(Order::class, $unserializedOrder);

        $this->assertTrue($order->getDecision() === $unserializedOrder->getDecision());
    }




, , . , , . , .



class Season
{
    public const SEASONS = ['summer', 'autumn', 'winter', 'spring'];

    private string $value;

    public function __construct(string $value)
    {
        if (!in_array($value, self::SEASONS)) {
            throw new InvalidArgumentException(sprintf(
                "Wrong season value. Awaited one from: '%s'.",
                implode("', '", self::SEASONS)
            ));
        }

        $this->value = $value;
    }


.



: . .



class Size
{
    public const SIZES = ['xxs', 'xs', 's', 'm', 'l', 'xl', 'xxl'];

    private string $value;

    private function __construct(string $value)
    {
        $this->value = $value;
    }

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

    public static function __callStatic($name, $arguments)
    {
        $value = strtolower($name);
        if (!in_array($value, self::SIZES)) {
            throw new BadMethodCallException("Method '$name' not found.");
        }

        if (count($arguments) > 0) {
            throw new InvalidArgumentException("Method '$name' expected no arguments.");
        }

        return new self($value);
    }
}


    public function testEquality(): void
    {
        $firstXxl = Size::xxl();
        $secondXxl = Size::xxl();
        $firstXxs = Size::xxs();
        $secondXxs = Size::xxs();

        $this->assertTrue($firstXxl == $secondXxl);
        $this->assertTrue($firstXxs == $secondXxs);
        $this->assertFalse($firstXxl == $secondXxs);
        $this->assertFalse($firstXxl === $secondXxl);
        $this->assertFalse($firstXxs === $secondXxs);
        $this->assertFalse($firstXxl === $secondXxs);
    }


, , IDE __callStatic(). DocBlock', .



/**
 * @method static Size xxs()
 * @method static Size xs()
 * @method static Size s()
 * @method static Size m()
 * @method static Size l()
 * @method static Size xl()
 * @method static Size xxl()
 */
class Size
{




, , , .



PECL- SPL SplEnum. SPL ( ).





O PHP não possui construções especiais para descrever uma enumeração, mas podem ser imitadas usando uma abordagem orientada a objetos. Embora o uso de enumeração como um singleton forneça algumas vantagens, tem uma desvantagem crítica que complica significativamente a implementação. É importante notar que este não é tanto um problema de implementação de enumeração, mas um problema geral de implementação do padrão "singleton" em PHP. Soluções generalizadas praticamente não oferecem vantagens, uma vez que métodos específicos ainda precisam ser descritos no DocBlock.



Todos os exemplos estão no GitHub .




All Articles