PHP de Trait Nedir? Ne İçin Kullanılır?

PHP, PHP5 ile birlikte OOP (Nesne-yönelimli programlama) desteğini duyurdu. Eğer OOP nedir ne değildir tam olarak fikir ve bilgi sahibi değilseniz; daha önce yazdığım Programlama'da OOP içeriğime göz atmanız daha iyi olacaktır. O yazıda bir programlama dilinin OOP desteklediğini söyleyebilmemiz için öncelikle 4 temel kavramı desteklemesi gerektiğini söylemiştim.

Bu 4 temel kavramdan ikincisi olan Inheritance (Kalıtım) kavramının kendi içinde Single ve Multi gibi farklı tipleri olduğunu yine o yazıda belirtmiştim. Oysa biz (yani PHP gelişticileri) sınıflarımıza sadece tek bir sınıftan genişletebiliyoruz. Yani Single Inheritance yapabiliyoruz ama OOP için Multi Inheritance da önemli ve vazgeçilemez bir konudur. Ama PHP tarafından desteklenmemektedir.

Hemen heyecanlamayın; PHP'nin Multi Inheritance desteklememesinin geçerli bir sebebi bulunuyor. Multi Inheritance gerçekten iyi tasarlanması gereken bir konudur ve iyi tasarlanmadığında programlama dilinin "Diamond Problem" (Elmas Problemi) ya da "Ölümcül Ölüm Elması" olarak bilinen bir çıkmaza girmesine sebep olur. PHP bu problem ile karşılaşmamak için problemin çevresinden dolaşarak "trait"leri kullanmayı önerir.

Soru : PHP'de Trait nedir?
Cevap : PHP'nin elmas problemi ile karşılaşmadan multi inheritance imkanı sağlamak amacıyla duyurduğu kavramdır.

Yapısı gereği sınıflara benzer ancak kendisinden bir nesne oluşturulamaz. Sınıfların kullanacağı ortak metotları ve özellikleri tanımlamak için kullanılır.

Trait Ne İçin Kullanılır?

Traitler soyutlama (abstraction) değil, kalıtım (inheritance) kavramının bir parçasıdır ve buna uygun olarak kullanılmalıdır.

Örnekler üzerinden devam etmemin konuyu anlamanız için daha iyi olacağını düşünüyorum.

Bir proje geliştirdiğinizi düşünelim! Bu projede bir HTTP için hem server hem de client sınıfları geliştireceksiniz.

<?php

trait Header
{
protected $headers = [];

public function setHeader($key, $value)
{
$this->headers[$key] = $value;
}

public function getHeader($key)
{
return array_key_exists($key, $this->headers) ? $this->headers[$key] : false;
}
}

trait Body
{
protected $body = '';

public function setBody($body)
{
$this->body = $body;
}

public function getBody()
{
return $this->body;
}
}

class ServerHTTP
{
}

class ClientHTTP
{
public function push()
{
$client = curl_init('http://example.com');
curl_setopt_array($client, [
CURLOPT_RETURNTRANSFER => true,
]);
$res = curl_exec($client);
curl_close($client);

return $res;
}
}

class Server extends ServerHTTP
{
use Header, Body;

public function response()
{
echo $this->getBody();
}
}

class Client extends ClientHTTP
{
use Header, Body;
}

HTTP de server ve client tarafında header ve body aynı kurallara tabidir. Doğal olarak bunlar için metotlar ilgili traitler de oluşturup, sınıflar içinde kullanılır.

OOP konusunda tam adapte olamadıysanız; aklınızda bunları tek bir sınıf içerisinde toplayıp, o sınıfı kalıtım almak yani şöyle bir kod yazmak gelebilir.

<?php

class CommonHTTP
{
protected $headers = [];
protected $body = '';

public function setHeader($key, $value)
{
$this->headers[$key] = $value;
}

public function getHeader($key)
{
return array_key_exists($key, $this->headers) ? $this->headers[$key] : false;
}

public function setBody($body)
{
$this->body = $body;
}

public function getBody()
{
return $this->body;
}
}

class Server extends CommonHTTP
{
public function response()
{
echo $this->getBody();
}
}

class Client extends CommonHTTP
{
public function push()
{
$client = curl_init('http://example.com');
curl_setopt_array($client, [
CURLOPT_RETURNTRANSFER => true,
]);
$res = curl_exec($client);
curl_close($client);

return $res;
}
}

Evet bu da bir önceki gibi düzgün çalışacaktır ama hatalı bir yaklaşımdır.

Farklı bir örnek olarak şöyle bir şey yapalım.

<?php
trait Pushable
{

public function toMail(string $to)
{
if (!method_exists($this, '__toString')) {
throw new Exception(get_class($this) . ' bir dizeye çevrilemiyor.');
}

return @mail($to, get_class($this), $this->__toString());
}

}

Yukarıdaki kodu okuduğunuzda bir nesneyi dizeye çevirip belli bir mail adresine gönderen basit bir metod görüyorsunuz.

Şimdi bu traiti dilediğiniz sınıf içerisinde kullanıp, ilgili sınıftan türetilmiş nesneyi bir mail adresine gönderebileceğiniz bir özelliğe sahip oldunuz. :) Buna benzer şeyler ile neler tasarlayabileceğiniz hayal gücünüze ve programlama bilginize kalıyor.

Burada bir kaç örnekle PHP de Trait nedir? Ne için kullanılır? anlatmaya çalıştım. Son olarak belirtmeden geçmek istemiyorum; Traitler çöp tenekesi değildir, onlara o şekilde muamele yapmayın. Her trait olabildiğince dar bir alanda tek bir iş yapacak şekilde tasarlanmalı ve kullanılmalıdır.

Okuduğunuz için teşekkür ederim.
Muhammet ŞAFAK - Software Developer