SmartObject i StaticClass
SmartObject dodaje do klas PHP wsparcie dla property. StaticClass służy do oznaczania klas statycznych.
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ć.
Klasy statyczne
Klasy statyczne, czyli takie, które nie są przeznaczone do instancjonowania, mogą być oznaczone cechą
Nette\StaticClass
:
class Strings
{
use Nette\StaticClass;
}
Podczas próby utworzenia instancji rzucany jest wyjątek Error
wskazujący, że klasa jest statyczna.
Spojrzenie na historię
SmartObject kiedyś poprawiał i naprawiał zachowanie klasy na wiele sposobów, ale ewolucja PHP sprawiła, że większość oryginalnych funkcji stała się zbędna. Poniżej przedstawiamy więc spojrzenie na historię tego, jak sprawy się rozwijały.
Model obiektowy PHP od początku cierpiał na szereg poważnych wad i nieefektywności. Z tego powodu powstała klasa
Nette\Object
(w 2007 roku), która starała się im zaradzić i poprawić doświadczenia związane z używaniem
PHP. Wystarczyło, aby inne klasy dziedziczyły po niej i czerpały korzyści, które przynosiła. Kiedy w PHP 5.4 pojawiła
się obsługa cech, klasa Nette\Object
została zastąpiona przez Nette\SmartObject
. Tym samym nie było
już konieczności dziedziczenia po wspólnym przodku. Dodatkowo trait można było wykorzystać w klasach, które już
dziedziczyły po innej klasie. Ostateczny koniec Nette\Object
nastąpił wraz z wydaniem PHP 7.2, który zabronił
klasom nadawania nazw Object
.
Wraz z rozwojem PHP udoskonalano model obiektowy i możliwości języka. Poszczególne funkcje klasy SmartObject
stały się zbędne. Od wydania PHP 8.2 jedyną cechą, która pozostała, a która nie jest jeszcze bezpośrednio wspierana w
PHP, jest możliwość korzystania z tzw. właściwości.
Jakie funkcje oferowały kiedyś Nette\Object
i Nette\Object
? Oto przegląd. (W przykładach użyto
klasy Nette\Object
, ale większość właściwości 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()?"
Dzisiejszy PZP może nie ma żadnej formy “czy miałeś na myśli?”, ale Tracy może dodać ten dodatek do błędów. I może nawet sam naprawić 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);