Введите намек на свойства в PHP 7?

#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 ...