SmartObject и StaticClass
SmartObject добавя поддръжка на свойства към класовете на PHP. StaticClass се използва за обозначаване на статични класове.
Настройка:
composer require nette/utils
Свойства, getteri и setteri
В съвременните обектно-ориентирани езици (напр. C#, Python, Ruby, JavaScript) терминът собственост се отнася до специални членове на класа, които изглеждат като променливи, но всъщност са представени като методи. Когато се присвоява или чете стойността на такава „променлива“, се извиква съответният метод (наречен getter или setter). Това е много удобно, тъй като ни дава пълен контрол върху достъпа до променливите. Можем да проверяваме входни данни или да генерираме резултати само когато свойството е прочетено.
PHP свойствата не се поддържат, но чертите Nette\SmartObject
могат да ги
симулират. Как да го използвате?
- Добавяне на анотация към класа под формата на
@property <type> $xyz
- Създайте getter с име
getXyz()
илиisXyz()
, setter с имеsetXyz()
- Getter и setter трябва да са публични или защитени и незадължителни, така че може да има свойство само за четене или само за писане.
Ще използваме свойството за класа Circle, за да гарантираме, че в
променливата $radius
се поставят само неотрицателни числа.
Заменете public $radius
с имота:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // не е публичен
// getter за свойството $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter за свойството $radius
protected function setRadius(float $radius): void
{
// обработване на стойността, преди да я запишем
$this->radius = max(0.0, $radius);
}
// getter за свойството $visible
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // всъщност се извиква setRadius(10)
echo $circle->radius; // извиква getRadius()
echo $circle->visible; // извиква isVisible()
Свойствата са преди всичко „синтактична захар“ и са предназначени да направят живота на програмиста по-сладък, като опростят кода. Ако нямате нужда от тях, не е нужно да ги използвате.
Статични класове
Статичните класове, т.е. класовете, които не са предназначени да бъдат
инстанцирани, могат да бъдат маркирани с Nette\StaticClass
:
class Strings
{
use Nette\StaticClass;
}
Когато се опитате да създадете инстанция, се появява изключение
Error
, което показва, че класът е статичен.
Поглед към историята
SmartObject е използван за подобряване и коригиране на поведението на класовете по много начини, но развитието на PHP направи повечето от първоначалните функции излишни. Затова по-долу ще разгледаме историята на развитието на тези функции.
От самото начало обектният модел на PHP страдаше от някои сериозни
недостатъци и неефективност. Това доведе до създаването на класа
Nette\Object
(през 2007 г.), който се опита да коригира тези проблеми и да
подобри работата на потребителите на PHP. Това беше достатъчно, за да
могат други класове да го наследят и да се възползват от предимствата
му. Когато в PHP 5.4 беше въведена поддръжка на черти, класът
Nette\Object
беше заменен с Nette\SmartObject
. По този начин вече не е
било необходимо да се наследява от общ прародител. Освен това чертата
може да се използва в класове, които вече са наследени от друг клас.
Окончателният край на Nette\Object
настъпи с излизането на PHP 7.2, който
забрани именуването на класове Object
.
С развитието на PHP обектният модел и функциите на езика се
усъвършенстваха. Някои характеристики на класа SmartObject
станаха
излишни. След излизането на PHP 8.2 единствената функция, която все още не
се поддържа директно в PHP, е възможността за използване на т.нар. свойства.
Какви функции предлагат Nette\Object
и Nette\Object
? Ето един
преглед. (В примерите е използван класът Nette\Object
, но повечето от
свойствата се отнасят и за Nette\SmartObject
).
Непоследователни грешки
PHP имаше непоследователно поведение при достъп до недекларирани
членове. Към момента на публикуване на Nette\Object
статусът е
следният:
echo $obj->undeclared; // E_NOTICE, по-късно E_WARNING
$obj->undeclared = 1; // преминава безшумно, без да съобщава
$obj->unknownMethod(); // Фатална грешка (която не може да бъде уловена чрез try/catch)
Фатална грешка ще прекрати приложението без възможност за реакция.
Тихото записване на несъществуващи членове без предупреждение можеше
да доведе до сериозни грешки, които трудно се откриваха. Nette\Object
Всички тези случаи бяха уловени и беше хвърлено изключение на адрес
MemberAccessException
.
echo $obj->undeclared; // throw Nette\MemberAccessException
$obj->undeclared = 1; // throw Nette\MemberAccessException
$obj->unknownMethod(); // throw Nette\MemberAccessException
От PHP 7.0 PHP вече не причинява фатални грешки, които не могат да бъдат прихванати, а достъпът до необявени членове е грешка от PHP 8.2 насам.
Какво имаш предвид?
Ако се появи грешка в Nette\MemberAccessException
, вероятно поради печатна
грешка при достъп до променлива на обект или извикване на метод,
Nette\Object
се опитва да подскаже в съобщението за грешка как да се
поправи грешката под формата на емблематичното допълнение „Искахте
ли да кажете?“.
class Foo extends Nette\Object
{
public static function from($var)
{
}
}
$foo = Foo::form($var);
// throw Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"
Съвременният PHP може и да не разполага с формата „Искахте ли да кажете?“, но Трейси добавя тази добавка към грешките. Той дори може сам да коригира такива грешки.
Методи за разширение
Вдъхновено от методите за разширение от C#. Те ви позволяват да
добавяте нови методи към съществуващи класове. Например можете да
добавите метод addDateTime()
към формуляр, за да добавите свой
собствен DateTimePicker.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Методите за разширение се оказаха непрактични, тъй като имената им не се попълваха автоматично от редакторите, а вместо това те съобщаваха, че методът не съществува. Поради това тяхната подкрепа беше преустановена.
Получаване на името на класа
$class = $obj->getClass(); // използване на Nette\Object
$class = $obj::class; // от PHP 8.0
Достъп до рефлексии и анотации
Nette\Object
Достъп до размишленията и анотациите чрез методите
getReflection()
и getAnnotation()
:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // връща 'John Doe
От версия 8.0 на PHP вече е възможен достъп до метаинформация под формата на атрибути:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Получатели на методи
Nette\Object
предлагат елегантен начин за работа с методите, сякаш
са променливи:
class Foo extends Nette\Object
{
public function adder($a, $b)
{
return $a + $b;
}
}
$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5
От версия 8.1 на PHP можете да използвате така наречения синтаксис на извикване на метод от първи клас:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Събития
Nette\Object
предлага синтактична захар за задействане на събитие:
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)
е еквивалентен на следното:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
За по-голяма яснота препоръчваме да избягвате магическия метод
$this->onChange()
. Добър заместител би бил Nette\Utils\Arrays::invoke:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);