SmartObject и StaticClass
SmartObject добавляет поддержку свойств в классы PHP. StaticClass используется для обозначения статических классов.
Установка:
composer require nette/utils
Свойства, геттери и сеттери
В современных объектно-ориентированных языках (например, C#, Python, Ruby, JavaScript) термин свойство относится к специальным членам классов, которые выглядят как переменные, но на самом деле представлены методами. Когда значение такой “переменной” присваивается или считывается, вызывается соответствующий метод (называемый getter или setter). Это очень удобная вещь, она дает нам полный контроль над доступом к переменным. Мы можем проверять вводимые данные или генерировать результаты только тогда, когда свойство прочитано.
Свойства PHP не поддерживаются, но trait Nette\SmartObject
может их
имитировать. Как это использовать?
- Добавьте аннотацию к классу в виде
@property <type> $xyz
- Создайте геттер с именем
getXyz()
илиisXyz()
, сеттер с именемsetXyz()
- Геттер и сеттер должны быть публичными или защищенными и необязательными, поэтому может быть свойство только для чтения или только для записи.
Мы будем использовать свойство для класса Circle, чтобы гарантировать,
что в переменную $radius
будут помещаться только неотрицательные
числа. Замените public $radius
на property:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // not public
// getter for property $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter for property $radius
protected function setRadius(float $radius): void
{
// sanitizing value before saving it
$this->radius = max(0.0, $radius);
}
// getter for property $visible
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // actually calls setRadius(10)
echo $circle->radius; // calls getRadius()
echo $circle->visible; // calls isVisible()
Свойства – это прежде всего синтаксический сахар, который призван сделать жизнь программиста слаще за счет упрощения кода. Если они вам не нужны, вы не обязаны их использовать.
Статические классы
Статические классы, т.е. классы, которые не предназначены для
инстанцирования, могут быть помечены признаком Nette\StaticClass
:
class Strings
{
use Nette\StaticClass;
}
Когда вы пытаетесь создать экземпляр, возникает исключение
Error
, указывающее на то, что класс является статическим.
Взгляд в историю
SmartObject использовался для улучшения и исправления поведения класса во многих отношениях, но развитие PHP сделало большинство первоначальных функций излишними. Поэтому ниже мы рассмотрим историю развития этих функций.
С самого начала объектная модель PHP страдала от ряда серьезных
недостатков и неэффективности. Это послужило причиной создания класса
Nette\Object
(в 2007 году), который попытался устранить их и улучшить
опыт использования PHP. Этого оказалось достаточно, чтобы другие классы
унаследовали от него и получили те преимущества, которые он принес.
Когда в PHP 5.4 появилась поддержка трейтов, класс Nette\Object
был
заменен на Nette\SmartObject
. Таким образом, больше не было
необходимости наследоваться от общего предка. Кроме того, trait можно
было использовать в классах, которые уже наследовались от другого
класса. Окончательный конец 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, later E_WARNING
$obj->undeclared = 1; // passes silently without reporting
$obj->unknownMethod(); // Fatal error (not catchable by 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, возможно, не имеет формы “Вы имели в виду?”, но Tracy добавляет это дополнение к ошибкам. И он даже может сам исправлять такие ошибки.
Методы расширения
Вдохновлен методами расширения из C#. Они дают возможность добавлять
новые методы к существующим классам. Например, вы можете добавить
метод addDateTime()
к форме, чтобы добавить свой собственный
DateTimePicker.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Методы расширения оказались непрактичными, поскольку их имена не автозаполнялись редакторами, вместо этого они сообщали, что метод не существует. Поэтому их поддержка была прекращена.
Получение имени класса
$class = $obj->getClass(); // using Nette\Object
$class = $obj::class; // since PHP 8.0
Доступ к размышлениям и аннотациям
Nette\Object
предложен доступ к размышлениям и аннотациям с помощью
методов getReflection()
и getAnnotation()
:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // returns 'John Doe
Начиная с PHP 8.0, появилась возможность доступа к мета-информации в виде атрибутов:
#[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
Начиная с PHP 8.1, вы можете использовать так называемый первоклассный синтаксис вызываемых методов:
$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);