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