SmartObject

SmartObject, yıllarca PHP'deki nesne davranışını geliştirdi. PHP 8.4 sürümünden itibaren tüm özellikleri PHP'nin doğal bir parçası haline geldi ve böylece modern nesne yönelimli yaklaşımın öncüsü olma tarihi misyonunu tamamladı.

Kurulum:

composer require nette/utils

SmartObject, 2007 yılında PHP'nin o zamanki nesne modelinin eksikliklerine devrimci bir çözüm olarak ortaya çıktı. PHP'nin nesne yönelimli tasarımda birçok sorunla karşı karşıya olduğu bir dönemde, geliştiriciler için önemli iyileştirmeler ve basitleştirmeler getirdi. Nette framework'ünün efsanevi bir parçası haline geldi. PHP'nin yıllar sonra kazanacağı işlevselliği sundu – nesne özelliklerine erişim doğrulamasından sofistike hata yönetimine kadar. PHP 8.4'ün gelişiyle, tüm özellikleri dilin doğal bir parçası haline geldiğinde tarihi misyonunu tamamladı. PHP'nin gelişimini dikkat çekici bir şekilde 17 yıl önceden gösterdi.

SmartObject ilginç bir teknik evrim geçirdi. Başlangıçta, diğer sınıfların gerekli işlevselliği miras aldığı Nette\Object sınıfı olarak uygulandı. PHP 5.4 ile trait desteğinin gelmesiyle önemli bir değişiklik yaşandı. Bu, Nette\SmartObject trait'ine dönüşümü sağladı ve daha fazla esneklik getirdi – geliştiriciler işlevselliği başka bir sınıftan miras alan sınıflarda bile kullanabilir hale geldi. Orijinal Nette\Object sınıfı PHP 7.2 ile (sınıfların ‘Object’ kelimesiyle adlandırılmasını yasaklayan) sona ererken, Nette\SmartObject trait'i yaşamaya devam ediyor.

Nette\Object ve daha sonra Nette\SmartObject'in sunduğu özellikleri inceleyelim. Bu işlevlerin her biri, zamanında PHP nesne yönelimli programlamada önemli bir adımı temsil ediyordu.

Tutarlı Hata Durumları

Erken dönem PHP'nin en önemli sorunlarından biri, nesnelerle çalışırken tutarsız davranışlardı. Nette\Object bu kargaşaya düzen ve öngörülebilirlik getirdi. PHP'nin orijinal davranışına bakalım:

echo $obj->undeclared;    // E_NOTICE, daha sonra E_WARNING
$obj->undeclared = 1;     // sessizce uyarısız geçer
$obj->unknownMethod();    // Fatal error (try/catch ile yakalanamaz)

Fatal error uygulamayı herhangi bir tepki verme olasılığı olmadan sonlandırırdı. Var olmayan üyelere sessizce yazma, tespit edilmesi zor ciddi hatalara yol açabilirdi. Nette\Object tüm bu durumları yakalayıp bir MemberAccessException fırlatırdı, bu da programcıların hatalara tepki vermesini ve onları ele almasını sağlardı:

echo $obj->undeclared;   // Nette\MemberAccessException fırlatır
$obj->undeclared = 1;    // Nette\MemberAccessException fırlatır
$obj->unknownMethod();   // Nette\MemberAccessException fırlatır

PHP 7.0'dan beri dil artık yakalanamayan fatal error'lar oluşturmuyor ve PHP 8.2'den beri tanımlanmamış üyelere erişim hata olarak kabul ediliyor.

“Did you mean?” Yardımcısı

Nette\Object çok kullanışlı bir özellikle geldi: yazım hatalarında akıllı öneriler. Bir geliştirici bir metot veya değişken adında hata yaptığında, sadece hatayı bildirmekle kalmaz, aynı zamanda doğru adı önererek yardım ederdi. “Did you mean?” olarak bilinen bu ikonik mesaj, programcıların yazım hatalarını aramak için harcadıkları saatleri kurtardı:

class Foo extends Nette\Object
{
	public static function from($var)
	{
	}
}

$foo = Foo::form($var);
// Nette\MemberAccessException fırlatır
// "Call to undefined static method Foo::form(), did you mean from()?"

Günümüzde PHP'nin kendisinde “did you mean?” özelliği yok, ancak bu özellik Tracy tarafından sağlanıyor. Hatta bu tür hataları otomatik düzeltebiliyor.

Kontrollü Erişimli Properties

SmartObject'in PHP'ye getirdiği önemli yeniliklerden biri kontrollü erişimli property'lerdi. C# veya Python gibi dillerde yaygın olan bu konsept, geliştiricilerin nesne verilerine erişimi zarif bir şekilde kontrol etmelerini ve tutarlılıklarını sağlamalarını sağladı. Property'ler nesne yönelimli programlamanın güçlü bir aracıdır. Değişkenler gibi çalışırlar ancak aslında metodlarla (getter ve setter) temsil edilirler. Bu, girişlerin doğrulanmasını veya okuma anında değerlerin oluşturulmasını sağlar.

Property'leri kullanmak için şunları yapmalısınız:

  • Sınıfa @property <type> $xyz şeklinde bir annotation ekleyin
  • getXyz() veya isXyz() adında bir getter, setXyz() adında bir setter oluşturun
  • Getter ve setter'ın public veya protected olmasını sağlayın. Bunlar isteğe bağlıdır – yani read-only veya write-only property'ler olabilir

Circle sınıfında yarıçapın her zaman negatif olmayan bir sayı olmasını sağlamak için property'leri kullanan pratik bir örneğe bakalım. public $radius yerine bir property kullanalım:

/**
 * @property float $radius
 * @property-read bool $visible
 */
class Circle
{
	use Nette\SmartObject;

	private float $radius = 0.0; // public değil!

	// $radius property'si için getter
	protected function getRadius(): float
	{
		return $this->radius;
	}

	// $radius property'si için setter
	protected function setRadius(float $radius): void
	{
		// kaydetmeden önce değeri temizle
		$this->radius = max(0.0, $radius);
	}

	// $visible property'si için getter
	protected function isVisible(): bool
	{
		return $this->radius > 0;
	}
}

$circle = new Circle;
$circle->radius = 10;  // aslında setRadius(10)'u çağırır
echo $circle->radius;  // getRadius()'u çağırır
echo $circle->visible; // isVisible()'ı çağırır

PHP 8.4'ten beri, aynı işlevselliğe property hooks kullanılarak ulaşılabilir, bu da çok daha zarif ve özlü bir sözdizimi sunar:

class Circle
{
	public float $radius = 0.0 {
		set => max(0.0, $value);
	}

	public bool $visible {
		get => $this->radius > 0;
	}
}

Extension Methods

Nette\Object, modern programlama dillerinden esinlenen başka bir ilginç konsepti PHP'ye getirdi – extension methods. C#'tan alınan bu özellik, geliştiricilerin var olan sınıfları değiştirmeden veya onlardan miras almadan yeni metodlarla genişletmelerini sağladı. Örneğin, bir forma özel bir DateTimePicker ekleyen bir addDateTime() metodu ekleyebilirdiniz:

Form::extensionMethod(
	'addDateTime',
	fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);

$form = new Form;
$form->addDateTime('date');

Extension metodları, editörlerin isimlerini önermemesi ve bunun yerine metodun var olmadığını bildirmesi nedeniyle pratik olmadığını kanıtladı. Bu nedenle destekleri sonlandırıldı. Günümüzde sınıf işlevselliğini genişletmek için composition veya inheritance kullanmak daha yaygın.

Sınıf Adını Alma

SmartObject sınıf adını almak için basit bir metod sunuyordu:

$class = $obj->getClass(); // Nette\Object kullanarak
$class = $obj::class;      // PHP 8.0'dan beri

Reflection ve Annotation Erişimi

Nette\Object, getReflection() ve getAnnotation() metodları aracılığıyla reflection ve annotation'lara erişim sağlıyordu. Bu yaklaşım sınıf meta-bilgileriyle çalışmayı önemli ölçüde basitleştirdi:

/**
 * @author John Doe
 */
class Foo extends Nette\Object
{
}

$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // 'John Doe' döndürür

PHP 8.0'dan beri, meta-bilgilere özellikler (attributes) aracılığıyla erişmek mümkün, bu da daha fazla olanak ve daha iyi tip kontrolü sunuyor:

#[Author('John Doe')]
class Foo
{
}

$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];

Method Getters

Nette\Object metodları değişkenlermiş gibi aktarmanın zarif bir yolunu sunuyordu:

class Foo extends Nette\Object
{
	public function adder($a, $b)
	{
		return $a + $b;
	}
}

$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5

PHP 8.1'den beri first-class callable syntax kullanabilirsiniz, bu da konsepti daha da ileri taşıyor:

$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5

Olaylar (Events)

SmartObject olaylarla çalışmak için basitleştirilmiş bir sözdizimi sunar. Olaylar, nesnelerin durumlarındaki değişiklikler hakkında uygulamanın diğer kısımlarını bilgilendirmesini sağlar:

class Circle extends Nette\Object
{
	public array $onChange = [];

	public function setRadius(float $radius): void
	{
		$this->onChange($this, $radius);
		$this->radius = $radius;
	}
}

$this->onChange($this, $radius) kodu aşağıdaki döngüye eşdeğerdir:

foreach ($this->onChange as $callback) {
	$callback($this, $radius);
}

Netlik için $this->onChange() sihirli metodundan kaçınmanızı öneririz. Pratik bir alternatif Nette\Utils\Arrays::invoke fonksiyonudur:

Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);
versiyon: 4.0