Geliştirici olarak projelerinizde bir değişkenin alabileceği değerlerin önceden belirlenmiş olan değerlerle sınırlandırıldığı durumlarla karşılaşmışsınızdır. Buna örnek olarak WordPress’in post_status
değerini düşünebiliriz. Yazıların durumlarının alabileceği değerler önceden belirlenmiştir. Bunları “published”, “draft” ve “deleted” olarak düşünebiliriz.
PHP 8.1 ile gelen ENUM’lar sayesinde artık bunları daha derli toplu ve düzgün bir şekilde projelerimizde tanımlayabiliyoruz. 8.1’den önce ben bu işlemi Interface kullanarak yapıyordum. Bu yazıyı hazırlarken yaptığım araştırmada karşıma ilginçte bir örnek çıktı, örneği sevgili Doğan Uçar hazırlamış, blog yazısına buradan ulaşabilirsiniz. Gitmeye üşenirseniz de aşağıda örneği sizinle paylaşıyorum, kıyaslama yaparken hatalı bir geri bildirim almanızı sağlayacak bir durumu gösteriyor aslında:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
interface EnumInterface { public const ACTIVE = 'active'; public const INACTIVE = 'inactive'; public const PENDING = 'pending'; } class MyEnum implements EnumInterface { private string $value; public function __construct(string $value) { $this->value = $value; } } $a = new MyEnum(EnumInterface::ACTIVE); $b = new MyEnum(EnumInterface::ACTIVE); var_dump($a === $b); // false |
Tabii dediğim gibi bunu deneyimlemedim hiç.
Kabaca Enum’ları gösterelim. Bunun içinde yine gördüğüm güzel bir örnek üzerinden gitmek istiyorum. Detaylı yazıya buradan ulaşabilirsiniz.
PHP’deki Enums’un temellerini keşfetmek için Harry Potter’ın büyülü dünyasından yararlanmaya karar verdim.
Hayal edelim ki, Hogwarts Cadılık ve Büyücülük Okulu için kod yazıyoruz ve her yeni öğrenci, sanal Seçmen Şapka tarafından bir binaya yerleştirilmeli.
Hogwarts’ta yalnızca dört bina vardır: Gryffindor, Hufflepuff, Ravenclaw ve Slytherin.
Bu durum, PHP’de bir Enum olarak şöyle temsil edilebilir:
1 2 3 4 5 6 7 8 9 |
<?php enum House { case Gryffindor; case Hufflepuff; case Ravenclaw; case Slytherin; } |
Bu örnekte, Gryffindor, Hufflepuff, Ravenclaw ve Slytherin olmak üzere dört olası değeri olan bir House Enum’u oluşturduk.
Şimdi, her öğrencinin bir $house özelliğine sahip olduğu bir Student sınıfı oluşturalım.
1 2 3 4 |
class Student { public ?House $house = null; } |
Ayrıca,
sort yöntemi olan bir
SortingHat sınıfımız var. Bu yöntem, öğrenciye bir bina önerisi sunulmasını kabul edebilir ya da öğrenciye rastgele bir bina atayabilir. Aynı Harry Potter’daki gibi, Seçmen Şapka Harry’nin tercihini dikkate aldı!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class SortingHat { public function sort(Student $student, ?House $suggestedHouse = null) { if ($suggestedHouse) { $student->house = $suggestedHouse; return; } $houses = [ House::Gryffindor, House::Hufflepuff, House::Ravenclaw, House::Slytherin, ]; $index = array_rand($houses); $student->house = $houses[$index]; } } |
Gördüğünüz gibi, $suggestedHouse değişkeni ve $house özelliği değerlerini yalnızca dört Hogwarts binasından biriyle sınırladık, bunu House Enum’u kullanarak yaptık.
Bir öğrenciyi var olmayan bir binaya yerleştirmeye çalışırsan, PHP bunu yakalar ve çalıştırmaz.
İşte bu, Enums’un sihirli yanı!
Bu senaryoda, Hogwarts binalarıyla ilişkili herhangi bir veri olmayan durumlar “Pure Cases” olarak adlandırılır ve yalnızca Pure Cases içeren bir Enum, bizim House Enum’umuz gibi, “Pure Enum” olarak adlandırılır.
Örnek bu kadar. Bu örnek dediğim gibi yukarıda paylaştığım bağlantıdan alıntı. Ama durumu son derece iyi açıklıyor, o nedenle kaynak göstererek paylaşmakta hiç bir sakınca görmedim. Umarım problem olmaz :)
Pure Enum’ları bu örnekle son derece iyi bir şekilde açıklandığını düşünüyorum. Şimdi geriye kaldı Backed Enum kavramı.
Backed Enum, bir skaler değerle desteklenen Enum’tur. Bu yüzden, “pure” olarak kabul edilmezler.
PHP’de skaler bir değer, bool
, float
, int
veya string
türünde olabilir.
Kod bazen kelimelerden daha fazla şey ifade eder, bu yüzden az önceki örneğin devamı niteliğinde bir örnek ile Backed Enum kavramına ışık tutalım:
1 2 3 4 5 6 7 8 9 |
<?php enum House: string { case Gryffindor = 'Gryffindor'; case Hufflepuff = 'Hufflepuff'; case Ravenclaw = 'Ravenclaw'; case Slytherin = 'Slytherin'; } |
Gördüğünüz gibi, Enum’u destekleyen türü (bu durumda string) tanımladık ve her duruma bir değer atadık. Daha basit olamazdı. Ama neden Backed Enum kullanmalısın?
İşte harika bir kullanım örneği:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Bu değeri veritabanından aldığımızı varsayalım. $house = 'Gryffindor'; $student = new Student( House::from($house) ); var_dump($student); // object(Student)#1 (1) { // ["house"]=> // enum(House::Gryffindor) // } |
Bu örnekte:
- Veritabanından bir veri aldığımızı varsayıyoruz ve yeni bir Student nesnesi oluşturuyoruz.
- $house özelliğini bir string değerden House::from() statik yöntemiyle başlatıyoruz (çünkü House artık string türünde bir Backed Enum’dur).
- Bu işlem başarısız olursa, bir istisna atılır. (
Uncaught ValueError: "Foo" is not a valid backing value for enum "House"
)
Bazı durumlarda, bir exception atmak yerine varsayılan bir değere geri dönmek isteyebilirsin. Bunu, başarısızlık durumunda null döndüren House::tryFrom() statik yöntemiyle nasıl yapabileceğini gösteren bir örnek:
1 2 3 |
$student = new Student( House::tryFrom('Slytherin') ?? House::Gryffindor ); |
Benjamin Crozat tarafından hazırlanmış bu örnekler harika bir şekilde anlatıyor. Daha da derin bir yazıyı kendisinin bloğunda okuyabilirsiniz. Bağlantısını tekrar paylaşmak istiyorum :)
Enum’lardan Laravel’de Nasıl Faydalanabiliriz?
Laravel içerisinde Enum’lardan nasıl faydalandığımı kendi tecrübem ile yola çıkarak paylaşacağım. Eğer daha fazla örnek varsa lütfen yorumlarda benimle paylaşmaktan çekinmeyin.
Ben Enum’ları Model ve FormRequest’lerde kullanıyorum. Çalıştığım proje itibariyle farklı tipte mağazalarla entegrasyon hazırlamış durumdayım. Bunlar şimdilik Shopify ve WooCommerce. Bunlar için öncelikle projemin app/ dizini içerisinde Enums isminde bir dizinim var. app/Enums/StoreTypeEnum.php isimli PHP dosyamın içeriği ise şu şekilde:
1 2 3 4 5 6 7 8 9 |
<?php namespace App\Enums\Ecommerce; enum StoreType: string { case SHOPIFY = 'shopify'; case WOOCOMMERCE = 'woocommerce'; } |
Enum case isimlendirirken snake case kullanmayı tercih ediyorum. Hatta screaming snake case’i tercih ediyorum. Farklı isimlendirme kuralları ile ilgili bir yazı yakın zamanda hazırlayacağım, bu tarz isimlendirme kurallarınızın olması projenizin sürdürülebilirliği açısından oldukça önemlidir.
Bu StoreType
enum’unu daha sonra Store
modelimde $casts
içerisinde tanımlıyorum. Böylelikle modelim type özelliği her zaman bu enum oluyor. Örnek:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php namespace App\Models; use App\Enums\StoreType; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Store extends Model { use HasFactory; protected $casts = [ 'type' => StoreType::class, ]; } |
Artık
$store->type bir StoreType
varlığı haline dönüşüyor. Resource’larda bunu hazırlarken
$this->type->value şeklinde yazdırmanıza gerek yok.
$this->type olarak kullanabilirsiniz ve Laravel sizin yerinize value
‘yu otomatik olarak yanıtta gösterecektir.
Benzer şekilde bir form’da kabul ettiğiniz verileri bu enumlarla sınırlayabilirsiniz. Ben tüm form işlemleri için FormRequest kullanmayı tercih ediyorum. Yeni bir Store kaydetmek için hazırlanan formda type için kabul edilebilecek değerleri normalde ‘in:woocommerce,shopify’ ile sağlıyordum fakat bunu artık enum sınıfımla yapıyorum. Önce in: ile nasıl yapıldığına sonra enum ile nasıl yapıldığına bir bakış atalım:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class StoreStoreRequest extends FormRequest { /** * Get the validation rules that apply to the request. * * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string> */ public function rules(): array { return [ 'type' => 'required|in:woocommerce,shopify', 'name' => 'required', ]; } } |
Fakat store tiplerinizin sayısını çeşitlendikçe bunu düzenli bir şekilde tutmanız da zorlaşacaktır. İşte şimdi Enum ile nasıl yapacağınız:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?php namespace App\Http\Requests\Ecommerce; use App\Enums\StoreType; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rules\Enum; class StoreStoreRequest extends FormRequest { /** * Get the validation rules that apply to the request. * * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string> */ public function rules(): array { return [ 'type' => [ 'required', new Enum(StoreType::class), ], 'name' => 'required|string', ]; } } |
Tüm bunları göz önüne aldığımıza PHP 8.1 ile hayatımıza dahil olan Enum’ların ne kadar kıymetli olduğunu bir parçada olsa anlatabilmişimdir umarım.
Güncelleme
Laravel’de kullanımıyla ilgili bazı makalelerde migration’larda kullanıldığını gördüm fakat bunun bu şekilde kullanılmasını tavsiye etmiyorum. Çünkü veritabanında enum tipinde bunu sınırlandırmak her değişiklik yapmak istediğinizde veritabanına da müdahale etmeniz demek ki bu bana çok sağlıklı gelmiyor. Onun yerine validation sürecinde ve model casting ile bu süreci yönetmeniz çoook daha sağlıklı olacaktır.
Yorumlar