SmartObject

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

Kurulum:

composer require nette/utils

SmartObject, 2007 yılında o zamanki PHP nesne modelinin eksikliklerine devrim niteliğinde bir çözüm olarak ortaya çıktı. PHP'nin nesne tasarımıyla ilgili bir dizi sorun yaşadığı bir zamanda, geliştiriciler için işleri önemli ölçüde iyileştirdi ve basitleştirdi. Nette framework'ünün efsanevi bir parçası haline geldi. PHP'nin ancak yıllar sonra kazandığı işlevselliği sundu – nesne özelliklerine erişim kontrolünden sofistike sözdizimsel şekerlemelere kadar. PHP 8.4'ün gelişiyle, tüm fonksiyonları dilin yerel bir parçası haline geldiği için tarihi misyonunu tamamladı. PHP'nin gelişimini dikkat çekici bir şekilde 17 yıl geride bıraktı.

Teknik olarak SmartObject ilginç bir gelişim geçirdi. Başlangıçta, diğer sınıfların gerekli işlevselliği miras aldığı Nette\Object sınıfı olarak uygulandı. Önemli bir değişiklik, trait desteğini getiren PHP 5.4 ile geldi. Bu, Nette\SmartObject trait'ine dönüşümü mümkün kıldı ve daha fazla esneklik getirdi – geliştiriciler işlevselliği zaten başka bir sınıftan miras alan sınıflarda da kullanabildiler. Orijinal Nette\Object sınıfı PHP 7.2'nin (sınıfların Object kelimesiyle adlandırılmasını yasaklayan) gelişiyle ortadan kalkarken, Nette\SmartObject trait'i yaşamaya devam ediyor.

Bir zamanlar Nette\Object ve daha sonra Nette\SmartObject'ın sunduğu özelliklere bir göz atalım. Bu fonksiyonların her biri, kendi zamanında PHP'de nesne yönelimli programlama alanında önemli bir adım temsil ediyordu.

Tutarlı Hata Durumları

Erken PHP'nin en can sıkıcı sorunlarından biri, nesnelerle çalışırken tutarsız davranıştı. Nette\Object bu kaosa düzen ve öngörülebilirlik getirdi. PHP'nin orijinal davranışına bir göz atalım:

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

Fatal error, herhangi bir şekilde tepki verme olasılığı olmadan uygulamayı sonlandırdı. Var olmayan üyelere uyarı olmadan sessizce yazmak, tespit edilmesi zor ciddi hatalara yol açabilirdi. Nette\Object tüm bu durumları yakaladı ve MemberAccessException istisnası fırlattı, bu da programcıların hatalara tepki vermesine ve bunları çözmesine olanak tanıdı.

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 itibaren dil artık yakalanamayan fatal error'lara neden olmuyor ve PHP 8.2'den itibaren bildirilmemiş üyelere erişim bir hata olarak kabul ediliyor.

“Did you mean?” Yardımı

Nette\Object çok hoş bir özellik getirdi: yazım hatalarında akıllı yardım. Geliştirici bir metot veya değişken adında hata yaptığında, sadece hatayı bildirmekle kalmadı, aynı zamanda doğru ad önerisi şeklinde yardımcı bir el de uzattı. “did you mean?” olarak bilinen bu ikonik mesaj, programcıların yazım hatalarını aramak için saatler harcamasını engelledi:

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üz PHP'sinin “did you mean?” benzeri bir özelliği olmasa da, Tracy bu eki hatalara ekleyebilir. Hatta bu tür hataları otomatik olarak düzeltme yeteneğine sahiptir.

Kontrollü Erişime Sahip Özellikler

SmartObject'ın PHP'ye getirdiği önemli bir yenilik, kontrollü erişime sahip özelliklerdi. 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ı. Özellikler, nesne yönelimli programlamanın güçlü bir aracıdır. Değişkenler gibi çalışırlar, ancak aslında metotlarla (getter'lar ve setter'lar) temsil edilirler. Bu, girdileri doğrulamayı veya değerleri okuma anında oluşturmayı mümkün kılar.

Özellikleri kullanmak için şunları yapmalısınız:

  • Sınıfa @property <type> $xyz şeklinde bir ek açıklama ekleyin
  • getXyz() veya isXyz() adında bir getter, setXyz() adında bir setter oluşturun
  • Getter ve setter'ın public veya protected olduğundan emin olun. İsteğe bağlıdırlar – yani salt okunur veya salt yazılır özellik olarak var olabilirler

Yarıçapın her zaman negatif olmayan bir sayı olmasını sağlamak için özellikleri kullanacağımız Circle sınıfında pratik bir örnek gösterelim. Orijinal public $radius öğesini bir özellikle değiştireceğiz:

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

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

	// $radius özelliği için getter
	protected function getRadius(): float
	{
		return $this->radius;
	}

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

	// $visible özelliği 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 itibaren, çok daha zarif ve kısa bir sözdizimi sunan property hook'ları kullanarak aynı işlevselliğe ulaşılabilir:

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

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

Uzantı Metotları

Nette\Object, modern programlama dillerinden esinlenen başka bir ilginç konsepti PHP'ye getirdi – uzantı metotları. C#'dan alınan bu özellik, geliştiricilerin mevcut sınıfları değiştirmeden veya onlardan miras almadan yeni metotlarla zarif bir şekilde genişletmelerini sağladı. Örneğin, formunuza özel bir DateTimePicker ekleyen addDateTime() metodunu ekleyebilirsiniz:

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

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

Uzantı metotları pratik olmadıklarını kanıtladılar çünkü adları editörler tarafından önerilmiyordu, aksine metodun var olmadığını bildiriyorlardı. Bu nedenle destekleri sona erdirildi. Bugün, sınıfların işlevselliğini genişletmek için kompozisyon veya kalıtım kullanmak daha yaygındır.

Sınıf Adını Alma

Sınıf adını almak için SmartObject basit bir metot sunuyordu:

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

Yansıma ve Ek Açıklamalara Erişim

Nette\Object, getReflection() ve getAnnotation() metotlarını kullanarak yansıma ve ek açıklamalara erişim sunuyordu. Bu yaklaşım, sınıfların 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 itibaren, meta bilgilere nitelikler şeklinde erişmek mümkündür, bu da daha da fazla olanak ve daha iyi tür kontrolü sunar:

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

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

Metot Getters

Nette\Object, metotları sanki 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 itibaren, bu konsepti daha da ileri götüren first-class callable syntax kullanmak mümkündür:

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

Olaylar

SmartObject, olaylar ile çalışmak için basitleştirilmiş bir sözdizimi sunar. Olaylar, nesnelerin durumlarındaki değişiklikler hakkında uygulamanın diğer bölümlerini bilgilendirmelerini 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);
}

Anlaşılırlık nedeniyle sihirli $this->onChange() metodundan kaçınmanızı öneririz. Pratik bir alternatif, örneğin Nette\Utils\Arrays::invoke fonksiyonudur:

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