Тип підказки щодо властивостей у 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;
    }
}

Ви також можете зробити загальнодоступну власність і використовувати блок-блок, щоб надати інформацію про тип людям, які читають код та використовують IDE, але це не забезпечує перевірки типу виконання:

class Person
{
    /**
      * @var string
      */
    public $name;
}

І справді, ви можете поєднати геттери та сеттери та блок-блок.

Якщо ви боїтеся, ви могли б зробити властивість підробленого з __get, __set, __issetі __unsetмагічних методами , і перевірте типи самостійно. Але я не впевнений, чи рекомендував би я це.


Звучить дуже добре. Не можу дочекатися, що побачить у наступних релізах!
Карлос Каручче

Іншою важливою проблемою є обробка посилань, які насправді погано взаємодіють з деклараціями типів і, можливо, їх доведеться відключити для таких властивостей. Навіть без проблем із продуктивністю, неможливість зробити, скажімо, array_push($this->foo, $bar)або sort($this->foobar)було б великою справою.
Андреа

Як буде працювати примус типів? Наприклад: (new Person())->dateOfBirth = '2001-01-01';... За умови, declare(strict_types=0);що це так. Це буде кидати або використовувати DateTimeImmutableконструктор? І якщо це так, яку помилку буде видано, якщо рядок є недійсною датою? TypeError?
Лмеріно

@Imerino Немає явного перетворення в DateTime (незмінне) і ніколи не було
Андреа,

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 ви можете вводити атрибути ( Documentation / Wiki ), що означає, що ви можете робити:

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

Відповідно до wiki, усі прийнятні значення:

  • bool, int, float, string, array, object
  • ітерабельний
  • я, батько
  • будь-яке ім'я класу або інтерфейсу
  • ? 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;
    }
}

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

Ви також можете ввести повернення як void, оскільки 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.