İçeriğe geç
Muhammet Şafak
Diller 3 dk okuma

PHP'de enum ihtiyacı: 8.1 öncesi çözümler

PHP 8.1 native enum getirmeden önce sabit kümeleri nasıl temsil ediyorduk ve her yaklaşımın trade-off'ları nelerdi.


PHP 8.1 enum desteği getiriyor — ama henüz çıkmadı. Bu arada yıllardır “sipariş durumu”, “ödeme yöntemi”, “kullanıcı rolü” gibi sabit kümeleri (fixed sets) temsil etmek için farklı yöntemler kullandım. Her birinin bir bedeli var. Bu boşluğu nasıl doldurduğumuza bakmak, native enum geldiğinde neyi kazanacağımızı da gösteriyor.

Neden enum’a ihtiyaç duyuyoruz

Sorun şu: bir siparişin durumu pending, processing, shipped, cancelled değerlerinden birini alabilir. Bunu düz string ile temsil ederseniz kodun her yerinde bu dört string tekrar eder. Bir yerde "canceld" yazılırsa fark etmek zor. Veritabanında hangi değerlerin geçerli olduğu dağınık.

İhtiyaç şu: belirli değerlerden oluşan kapalı bir küme, bu kümenin dışına çıkıldığında hata, tip sistemi tarafından anlaşılabilen bir temsil.

PHP bu ihtiyacı uzun süre native olarak karşılamadı. Geliştirici topluluğu farklı çözümler geliştirdi.

Yaklaşım 1: Sınıf sabitleri (class constants)

En yaygın ve en basit yol:

<?php

class OrderStatus
{
    const PENDING    = 'pending';
    const PROCESSING = 'processing';
    const SHIPPED    = 'shipped';
    const CANCELLED  = 'cancelled';

    public static function values(): array
    {
        return [
            self::PENDING,
            self::PROCESSING,
            self::SHIPPED,
            self::CANCELLED,
        ];
    }

    public static function isValid(string $status): bool
    {
        return in_array($status, self::values(), true);
    }
}

Kullanımı:

<?php

$order->status = OrderStatus::PENDING;

if (!OrderStatus::isValid($order->status)) {
    throw new InvalidArgumentException("Geçersiz sipariş durumu: {$order->status}");
}

Ne iyi: Basit, sıfır bağımlılık, IDE otomatik tamamlamayı destekler.

Ne kötü: Tip güvenliği (type safety) yok. Bir metot OrderStatus::PENDING bekliyorsa imzasına string yazmak zorundasınız. Herhangi bir string geçirilebilir; derleme ya da başlatma anında değil, çalışma anında hata alırsınız.

Yaklaşım 2: myclabs/php-enum paketi

Bu paket enum benzeri nesneler üretmeyi sağlıyor:

<?php

use MyCLabs\Enum\Enum;

class OrderStatus extends Enum
{
    const PENDING    = 'pending';
    const PROCESSING = 'processing';
    const SHIPPED    = 'shipped';
    const CANCELLED  = 'cancelled';
}

Kullanımı:

<?php

function processOrder(Order $order, OrderStatus $status): void
{
    $order->status = $status->getValue();
}

processOrder($order, OrderStatus::PENDING());  // Nesne döner
processOrder($order, 'pending');               // TypeError fırlatır

Ne iyi: Artık tip imzasına OrderStatus yazabilirsiniz. Yanlış bir değer geçirilirse PHP hata fırlatır. Dahası karşılaştırma için equals() metodu geliyor.

Ne kötü: Her enum değeri aslında bir nesne. Karşılaştırmalarda dikkatli olmak gerekiyor — === değil equals() kullanılmalı. Ve sonuçta bu bir Composer paketi; bir dil özelliği değil.

Yaklaşım 3: Backed sınıflar (manuel)

Bazı projelerde myclabs/php-enum’a bağımlı olmak yerine kendi değer nesnemi (value object) yazıyordum:

<?php

final class OrderStatus
{
    private string $value;

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

    public static function pending(): self
    {
        return new self('pending');
    }

    public static function processing(): self
    {
        return new self('processing');
    }

    public static function shipped(): self
    {
        return new self('shipped');
    }

    public static function cancelled(): self
    {
        return new self('cancelled');
    }

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

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

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

Ne iyi: Sıfır bağımlılık. İmzaya OrderStatus yazabilirsiniz. Sınıfın bir metodu isCancellable() gibi davranışsal mantık içerebilir.

Ne kötü: Boilerplate (ortak kalıp kodu) fazla. Her enum için bu yapıyı tekrar yazmak yorucu. Özellikle 10-15 enum olan bir projede bakımı ağırlaşıyor.

Hangisini ne zaman kullanırdım

Küçük projeler ve prototipler için sınıf sabitleri yeterli. Bir ekiple çalışıyorsam veya iş mantığı gerçekten bu sabit kümeler üzerine kuruluysa myclabs/php-enum veya manuel değer nesnesi.

Manuel değer nesnesini enum mantığının ötesinde davranış da taşıması gerektiğinde tercih ettim. Bir OrderStatus nesnesi canBeRefunded() veya isTerminal() metoduna sahip olabilir — bu paketten gelmez, sizin yazdığınız mantıktır.

Native enum ne getirecek

PHP 8.1 dil düzeyinde enum getiriyor. Yukarıdaki bütün yaklaşımlar birer geçici çözüm. Native enum gelince tip sistemi enum’u doğrudan anlayacak, IDE desteği tam olacak, karşılaştırma operatörleri doğal çalışacak ve paket gerekmeyecek.

Ama şu an 2021’in başındayız ve o sürüm henüz çıkmadı. Bu arada seçeneklerden birini seçmek gerekiyor. Benim tercihim: proje boyutu ve ekip büyüklüğüne göre değişiyor; bir proje standardı belirleyin ve tutarlı kalın.

Etiketler: #PHP
Paylaş:

İlgili Yazılar

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi