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()
veyaisXyz()
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);