Подсказка типа для свойств в 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 ...
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.