#php #oop
Вопрос:
Поддерживает ли php 7 указание типа для свойств класса?
Я имею в виду, не только для сеттеров/геттеров, но и для самой собственности.
Что-то вроде:
class Foo {
/**
*
* @var Bar
*/
public $bar : Bar;
}
$fooInstance = new Foo();
$fooInstance->bar = new NotBar(); //Error
Комментарии:
1. Насколько мне известно, нет. Однако, вообще говоря, любые ограничения на значение свойства в любом случае должны выполняться через сеттера. Поскольку у сеттера может легко быть тип для аргумента «значение», вы можете идти.
2. Многие фреймворки используют защищенные атрибуты (в основном для контроллеров). В частности, для этих случаев это было бы очень полезно.
Ответ №1:
PHP 7.4 будет поддерживать типизированные свойства, такие как:
class Person
{
public string $name;
public DateTimeImmutable $dateOfBirth;
}
PHP 7.3 и более ранние версии не поддерживают это, но есть некоторые альтернативы.
Вы можете создать частное свойство, доступное только через геттеры и сеттеры, которые имеют объявления типов:
class Person
{
private $name;
public function getName(): string {
return $this->name;
}
public function setName(string $newName) {
$this->name = $newName;
}
}
Вы также можете создать общедоступное свойство и использовать docblock для предоставления информации о типе людям, читающим код и использующим IDE, но это не обеспечивает проверку типов во время выполнения:
class Person
{
/**
* @var string
*/
public $name;
}
И действительно, вы можете комбинировать геттеры, сеттеры и док-блок.
Если вы более предприимчивы, вы могли бы создать поддельное свойство с помощью методов __get
, __set
, __isset
и __unset
магии, и проверить типы самостоятельно. Хотя я не уверен, что рекомендовал бы это делать.
Комментарии:
1. Звучит действительно хорошо. Не могу дождаться, чтобы увидеть, что будет в следующих выпусках!
2. Еще одной важной проблемой является обработка ссылок, которые на самом деле плохо взаимодействуют с объявлениями типов и могут быть отключены для таких свойств. Даже без проблем с производительностью, невозможность сделать, скажем,
array_push($this->foo, $bar)
илиsort($this->foobar)
было бы большой проблемой.3. Как будет работать принуждение типов? Например:
(new Person())->dateOfBirth = '2001-01-01';
… При условииdeclare(strict_types=0);
, что это так. Будет ли он бросать или использоватьDateTimeImmutable
конструктор? И если это так, то какая ошибка возникнет, если строка содержит недопустимую дату?TypeError
?4. @Imerino Нет неявного преобразования в DateTime(неизменяемое) и никогда не было
Ответ №2:
7.4 :
Хорошая новость в том, что это будет реализовано в новых версиях, как отметил @Andrea. Я просто оставлю это решение здесь на случай, если кто-то захочет использовать его до 7.4
7,3 или менее
Основываясь на уведомлениях, которые я все еще получаю от этой темы, я считаю, что у многих людей была/есть та же проблема, что и у меня. Мое решение для этого случая состояло в объединении сеттеров __set
магического метода внутри признака, чтобы смоделировать это поведение. Вот он:
trait SettersTrait
{
/**
* @param $name
* @param $value
*/
public function __set($name, $value)
{
$setter = 'set'.$name;
if (method_exists($this, $setter)) {
$this->$setter($value);
} else {
$this->$name = $value;
}
}
}
А вот и демонстрация:
class Bar {}
class NotBar {}
class Foo
{
use SettersTrait; //It could be implemented within this class but I used it as a trait for more flexibility
/**
*
* @var Bar
*/
private $bar;
/**
* @param Bar $bar
*/
protected function setBar(Bar $bar)
{
//(optional) Protected so it wont be called directly by external 'entities'
$this->bar = $bar;
}
}
$foo = new Foo();
$foo->bar = new NotBar(); //Error
//$foo->bar = new Bar(); //Success
Объяснение
Прежде всего, определите bar
как частную собственность, чтобы PHP выполнял автоматическое __set
приведение.
__set
проверит, есть ли какой-либо сеттер, объявленный в текущем объекте ( method_exists($this, $setter)
). В противном случае он будет устанавливать свое значение только так, как обычно.
Объявите метод задатчика (setBar), который получает аргумент с намеком на тип ( setBar(Bar $bar)
).
Пока PHP обнаруживает, что сеттеру передается что-то, что не Bar
является экземпляром, он автоматически вызовет фатальную ошибку: Неучтенная ошибка типа: Аргумент 1, переданный Foo::setBar (), должен быть экземпляром Bar, экземпляр NotBar задан
Ответ №3:
Редактирование для PHP 7.4 :
Начиная с PHP 7.4 вы можете вводить атрибуты (Документация / Вики), что означает, что вы можете делать :
class Foo
{
protected ?Bar $bar;
public int $id;
...
}
Согласно вики, все приемлемые значения являются :
- bool, int, float, строка, массив, объект
- повторяемый
- я, родитель
- имя любого класса или интерфейса
- ?тип // где «тип» может быть любым из вышеперечисленных
PHP
На самом деле это невозможно, и у вас есть только 4 способа фактически смоделировать это :
- Значения по умолчанию
- Декораторы в блоках комментариев
- Значения по умолчанию в конструкторе
- Добытчики и сеттеры
Я объединил их все здесь
class Foo
{
/**
* @var Bar
*/
protected $bar = null;
/**
* Foo constructor
* @param Bar $bar
**/
public function __construct(Bar $bar = null){
$this->bar = $bar;
}
/**
* @return Bar
*/
public function getBar() : ?Bar{
return $this->bar;
}
/**
* @param Bar $bar
*/
public function setBar(Bar $bar) {
$this->bar = $bar;
}
}
Обратите внимание, что на самом деле вы можете ввести возврат как ?Панель начиная с php 7.1 (обнуляется), потому что она может быть нулевой (недоступна в php7.0.)
Вы также можете ввести возвращаемое значение как недействительное, начиная с php7.1
Ответ №4:
Вы можете использовать сеттер
class Bar {
public $val;
}
class Foo {
/**
*
* @var Bar
*/
private $bar;
/**
* @return Bar
*/
public function getBar()
{
return $this->bar;
}
/**
* @param Bar $bar
*/
public function setBar(Bar $bar)
{
$this->bar = $bar;
}
}
$fooInstance = new Foo();
// $fooInstance->bar = new NotBar(); //Error
$fooInstance->setBar($fooInstance);
Выход:
TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of Foo given, called in ...