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