Подсказка типа для свойств в PHP 7?

Поддерживает ли php 7 подсказку типа для свойств класса?

Я имею в виду не только для сеттеров/геттеров, но и для самого свойства.

Что-то типа:

class Foo {
    /**
     *
     * @var Bar
     */
    public $bar : Bar;
}

$fooInstance = new Foo();
$fooInstance->bar = new NotBar(); //Error

person CarlosCarucce    schedule 16.05.2016    source источник
comment
Не то чтобы я в курсе. Однако, вообще говоря, любые ограничения на значение свойства в любом случае должны выполняться с помощью установщика. Поскольку установщик может легко получить подсказку типа для аргумента значения, все готово.   -  person Niet the Dark Absol    schedule 16.05.2016
comment
Многие фреймворки используют атрибуты protected (в основном для контроллеров). Для тех случаев, в частности, это было бы очень полезно.   -  person CarlosCarucce    schedule 16.05.2016


Ответы (4)


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 волшебные методы и проверить типы самостоятельно. Хотя не уверен, что рекомендую.

person Andrea    schedule 16.05.2016
comment
Звучит действительно хорошо. Не могу дождаться, чтобы увидеть, что будет в следующих выпусках! - person CarlosCarucce; 17.05.2016
comment
Другой важной проблемой является обработка ссылок, которые на самом деле плохо взаимодействуют с объявлениями типов и могут быть отключены для таких свойств. Даже без проблем с производительностью невозможность выполнить, скажем, array_push($this->foo, $bar) или sort($this->foobar) будет большой проблемой. - person Andrea; 21.12.2017
comment
Как будет работать приведение типов? Например: (new Person())->dateOfBirth = '2001-01-01';... При условии declare(strict_types=0); т.е. Будет ли он вызывать или использовать конструктор DateTimeImmutable? И если это так, какая ошибка будет выдана, если строка является недопустимой датой? TypeError? - person lmerino; 11.05.2019
comment
@Imerino Нет неявного преобразования в DateTime (Immutable) и никогда не было - person Andrea; 11.05.2019

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, передается сеттеру, он автоматически вызывает фатальную ошибку: Uncaught TypeError: аргумент 1, переданный в Foo::setBar(), должен быть экземпляром Bar, экземпляр NotBar задан

person CarlosCarucce    schedule 19.06.2018

Изменить для PHP 7.4:

Начиная с PHP 7.4 вы можете вводить атрибуты (Документация / Wiki), что означает, что вы можете:

    class Foo
{
    protected ?Bar $bar;
    public int $id;
    ...
}

Согласно вики, все допустимые значения:

  • bool, int, float, строка, массив, объект
  • повторяемый
  • я, родитель
  • любое имя класса или интерфейса
  • ?type // где тип может быть любым из вышеперечисленных

PHP ‹ 7.4

На самом деле это невозможно, и у вас есть только 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;
    }
}

Обратите внимание, что вы фактически можете ввести возвращаемое значение как ?Bar, начиная с php 7.1 (с возможностью обнуления), потому что оно может быть нулевым (недоступно в php7.0.)

Вы также можете ввести return как void, начиная с php7.1.

person Bruno Guignard    schedule 04.09.2018

Вы можете использовать сеттер

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 ...
person Richard    schedule 16.05.2016