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

80

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

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

Что-то вроде:

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

$fooInstance = new Foo();
$fooInstance->bar = new NotBar(); //Error
КарлосКаручче
источник
1
Не то чтобы я в курсе. Однако, вообще говоря, любые ограничения на значение свойства в любом случае должны выполняться через сеттер. Так как установщик может легко получить подсказку типа для аргумента «значение», все готово.
Niet the Dark Absol
Многие фреймворки используют защищенные атрибуты (в основном для контроллеров). В частности, для таких случаев это было бы очень полезно.
CarlosCarucce

Ответы:

134

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

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

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 задан

КарлосКаручче
источник
4

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

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

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

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

  • bool, int, float, строка, массив, объект
  • повторяемый
  • я, родитель
  • любое имя класса или интерфейса
  • ? type // где "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;
    }
}

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

Вы также можете ввести возврат как недействительный, поскольку php7.1

Бруно Гиньяр
источник
1

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

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 ...
Ричард
источник