SmartObject
SmartObject poprawiał zachowanie obiektów na wiele sposobów, ale dzisiejszy PHP zawiera już większość tych ulepszeń natywnie. Wciąż jednak dodaje wsparcie dla property.
Instalacja:
composer require nette/utils
Właściwości, gettery i settery
W nowoczesnych językach obiektowych (np. C#, Python, Ruby, JavaScript) termin property odnosi się do specjalnych członków klas, które wyglądają jak zmienne, ale w rzeczywistości są reprezentowane przez metody. Kiedy wartość tej „zmiennej“ jest przypisywana lub odczytywana, wywoływana jest odpowiednia metoda (zwana getterem lub setterem). Jest to bardzo przydatna rzecz, daje nam pełną kontrolę nad dostępem do zmiennych. Możemy zatwierdzić dane wejściowe lub wygenerować wyniki tylko wtedy, gdy właściwość zostanie odczytana.
Właściwości PHP nie są obsługiwane, ale traita Nette\SmartObject
może je imitować. Jak to zrobić?
- Dodaj adnotację do klasy w postaci
@property <type> $xyz
- Utwórz getter o nazwie
getXyz()
lubisXyz()
, setter o nazwiesetXyz()
- Getter i setter muszą być public lub protected i są opcjonalne, więc może być właściwość read-only lub write-only.
Wykorzystamy własność dla klasy Circle, aby zapewnić, że do zmiennej $radius
wstawiane są tylko liczby
nieujemne. Zamień public $radius
na własność:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // není publiczne!
// getter pro właściwość $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter pro właściwość $radius
protected function setRadius(float $radius): void
{
// hodnotu před uložením sanitizujeme
$this->radius = max(0.0, $radius);
}
// getter pro właściwość $visible
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // ve skutečnosti volá setRadius(10)
echo $circle->radius; // volá getRadius()
echo $circle->visible; // volá isVisible()
Właściwości to przede wszystkim cukier syntaktyczny, który ma uczynić życie programisty słodszym poprzez uproszczenie kodu. Jeśli ich nie chcesz, nie musisz z nich korzystać.
Rzut oka na historię
SmartObject udoskonalał zachowanie obiektów na wiele sposobów, ale dzisiejszy PHP zawiera już większość tych ulepszeń natywnie. Poniższy tekst jest nostalgicznym spojrzeniem wstecz na historię, przypominającym nam, jak rzeczy ewoluowały.
Od samego początku model obiektowy PHP cierpiał na niezliczoną ilość poważnych niedociągnięć i braków. Doprowadziło
to do stworzenia klasy Nette\Object
(w 2007 roku), która miała na celu naprawienie tych problemów i zwiększenie
komfortu korzystania z PHP. Wystarczyło tylko, by inne klasy po niej dziedziczyły, a zyskałyby oferowane przez nią korzyści.
Gdy w PHP 5.4 wprowadzono obsługę cech, klasa Nette\Object
została zastąpiona cechą
Nette\SmartObject
. Wyeliminowało to potrzebę dziedziczenia po wspólnym przodku. Co więcej, cecha mogła być
używana w klasach, które już dziedziczyły po innej klasie. Ostateczny koniec Nette\Object
nastąpił wraz
z wydaniem PHP 7.2, które zabroniło klasom nazywania się Object
.
W miarę rozwoju PHP, jego model obiektowy i możliwości językowe ulegały poprawie. Różne funkcje klasy
SmartObject
stały się zbędne. Od wydania PHP 8.2 pozostała tylko jedna funkcja nieobsługiwana bezpośrednio w
PHP: możliwość korzystania z tak zwanych właściwości.
Jakie funkcje oferowały Nette\Object
i, rozszerzając, Nette\SmartObject
? Oto przegląd. (W
przykładach użyto klasy Nette\Object
, ale większość funkcji dotyczy również cechy
Nette\SmartObject
).
Niespójne błędy
PHP zachowywał się niespójnie podczas dostępu do niezadeklarowanych członków. Stan w momencie wejścia na stronę
Nette\Object
był następujący:
echo $obj->undeclared; // E_NOTICE, później E_WARNING
$obj->undeclared = 1; // przechodzi cicho bez zgłaszania
$obj->unknownMethod(); // Błąd fatalny (nie do wychwycenia przez try/catch)
Fatal error zakończył działanie aplikacji bez możliwości reakcji. Ciche pisanie do nieistniejących członków bez
ostrzeżenia mogło prowadzić do poważnych błędów trudnych do wykrycia. Nette\Object
Wszystkie te przypadki
zostały złapane i wyjątek rzucony przez MemberAccessException
.
echo $obj->undeclared; // vyhodí Nette\MemberAccessException
$obj->undeclared = 1; // vyhodí Nette\MemberAccessException
$obj->unknownMethod(); // vyhodí Nette\MemberAccessException
Od PHP 7.0, PHP nie powoduje już nieśledzonych błędów fatalnych, a dostęp do niezadeklarowanych członków jest błędem od PHP 8.2.
Miałeś na myśli?
Jeśli został rzucony błąd Nette\MemberAccessException
, być może z powodu literówki przy dostępie do
zmiennej obiektu lub wywołaniu metody, Nette\Object
próbował w komunikacie o błędzie dać podpowiedź, jak
naprawić błąd, w postaci ikonicznego dodatku „czy miałeś na myśli?“.
class Foo extends Nette\Object
{
public static function from($var)
{
}
}
$foo = Foo::form($var);
// vyhodí Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"
Podczas gdy dzisiejszy PHP nie ma funkcji „czy miałeś na myśli?“, ta fraza może być dodawana do błędów przez Tracy. Może nawet automatycznie korygować takie błędy.
Metody rozszerzania
Zainspirowany metodami rozszerzającymi z języka C#. Dawały one możliwość dodawania nowych metod do istniejących klas.
Na przykład możesz dodać metodę addDateTime()
do formularza, aby dodać własny DateTimePicker.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Metody rozszerzania okazały się niepraktyczne, ponieważ ich nazwy nie sugerowały redaktorów, zamiast tego informowały, że dana metoda nie istnieje. Dlatego też zaprzestano ich wspierania.
Uzyskanie nazwy klasy:
$class = $obj->getClass(); // używając Nette.
$class = $obj::class; // od PHP 8.0
Dostęp do refleksji i adnotacji
Nette\Object
zaoferował dostęp do refleksji i adnotacji za pomocą metod getReflection()
i
getAnnotation()
:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // vrátí 'John Doe'
Od PHP 8.0 możliwy jest dostęp do metainformacji w postaci atrybutów:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Metoda gettery
Nette\Object
oferował elegancki sposób przekazywania metod tak, jakby były one zmiennymi:
class Foo extends Nette\Object
{
public function adder($a, $b)
{
return $a + $b;
}
}
$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5
Od PHP 8.1 można używać tzw. składni wywoływalnej pierwszej klasy:https://www.php.net/…lable_syntax:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Wydarzenia
Nette\Object
zaoferował cukier syntaktyczny do wywołania zdarzenia:
class Circle extends Nette\Object
{
public array $onChange = [];
public function setRadius(float $radius): void
{
$this->onChange($this, $radius);
$this->radius = $radius;
}
}
Kod $this->onChange($this, $radius)
jest równoważny z następującym:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Dla jasności, zalecamy unikanie metody magicznej $this->onChange()
. Praktycznym substytutem jest funkcja Nette\Utils\Arrays::invoke:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);