Gõ gợi ý cho các thuộc tính trong PHP 7?


80

Php 7 có hỗ trợ kiểu gợi ý cho các thuộc tính của lớp không?

Ý tôi là, không chỉ cho setters / getters mà cho chính tài sản.

Cái gì đó như:

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

$fooInstance = new Foo();
$fooInstance->bar = new NotBar(); //Error

1
Không phải là tôi biết. Tuy nhiên, nói chung, bất kỳ ràng buộc nào đối với giá trị của thuộc tính cũng nên được thực hiện thông qua một bộ định nghĩa. Vì bộ cài đặt có thể dễ dàng có kiểu chữ cho đối số "giá trị", bạn nên thực hiện.
Niet the Dark Absol

Nhiều khuôn khổ ngoài kia sử dụng các thuộc tính được bảo vệ (chủ yếu dành cho bộ điều khiển). Đối với những trường hợp cụ thể, nó sẽ rất hữu ích.
CarlosCarucce

Câu trả lời:


134

PHP 7.4 sẽ hỗ trợ các thuộc tính được gõ như sau:

class Person
{
    public string $name;
    public DateTimeImmutable $dateOfBirth;
}

PHP 7.3 trở về trước không hỗ trợ điều này, nhưng có một số lựa chọn thay thế.

Bạn có thể tạo một thuộc tính riêng chỉ có thể truy cập thông qua getters và setters có khai báo kiểu:

class Person
{
    private $name;
    public function getName(): string {
        return $this->name;
    }
    public function setName(string $newName) {
        $this->name = $newName;
    }
}

Bạn cũng có thể tạo thuộc tính công khai và sử dụng docblock để cung cấp thông tin loại cho những người đọc mã và sử dụng IDE, nhưng điều này không cung cấp kiểm tra loại thời gian chạy:

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

Và thực sự, bạn có thể kết hợp getters và setters và một docblock.

Nếu bạn mạo hiểm hơn, bạn có thể làm cho một tài sản với các giả __get, __set, __isset__unsetma thuật phương pháp , và kiểm tra các loại chính mình. Tuy nhiên, tôi không chắc liệu tôi có khuyên bạn nên dùng nó hay không.


Nghe rât hâp dân. Không thể chờ đợi để xem những gì sẽ xảy ra trên các bản phát hành tiếp theo!
CarlosCarucce

Một vấn đề quan trọng khác là việc xử lý các tham chiếu không thực sự tương tác tốt với khai báo kiểu và có thể phải bị vô hiệu hóa đối với các thuộc tính như vậy. Ngay cả khi không có vấn đề về hiệu suất, không thể làm, nói, array_push($this->foo, $bar)hoặc sort($this->foobar)sẽ là một vấn đề lớn.
Andrea

Việc cưỡng chế các kiểu sẽ diễn ra như thế nào? Ví dụ: (new Person())->dateOfBirth = '2001-01-01';... Với điều kiện declare(strict_types=0);là. Nó sẽ ném hay sử dụng hàm DateTimeImmutabletạo? Và nếu đúng như vậy, loại lỗi nào sẽ được ném ra nếu chuỗi là ngày không hợp lệ? TypeError?
lmerino

@Imerino Không có chuyển đổi ngầm thành DateTime (Bất biến) và chưa bao giờ xảy ra
Andrea

12

7.4+:

Tin tốt là nó sẽ được triển khai trong các bản phát hành mới, như @Andrea đã chỉ ra. Tôi sẽ chỉ để lại giải pháp này ở đây trong trường hợp ai đó muốn sử dụng nó trước 7.4


7,3 trở xuống

Dựa trên các thông báo tôi vẫn nhận được từ chủ đề này, tôi tin rằng nhiều người ngoài kia đã / đang gặp phải vấn đề tương tự như tôi. Giải pháp của tôi cho trường hợp này là kết hợp setters + __setmagic method bên trong một đặc điểm để mô phỏng hành vi này. Nó đây:

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;
        }
    }
}

Và đây là minh chứng:

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

Giải trình

Trước hết, hãy định nghĩa barlà một thuộc tính riêng để PHP sẽ truyền __set tự động .

__setsẽ kiểm tra nếu có một số setter được khai báo trong đối tượng hiện tại ( method_exists($this, $setter)). Nếu không, nó sẽ chỉ đặt giá trị của nó như bình thường.

Khai báo phương thức setter (setBar) nhận đối số gợi ý kiểu ( setBar(Bar $bar)).

Miễn là PHP phát hiện ra rằng một cái gì đó không phải là Barphiên bản đang được chuyển đến setter, nó sẽ tự động kích hoạt Lỗi nghiêm trọng: Uncaught TypeError: Đối số 1 được chuyển tới Foo :: setBar () phải là một phiên bản của Bar, phiên bản NotBar đã cho


4

Chỉnh sửa cho PHP 7.4:

Kể từ PHP 7.4, bạn có thể nhập các thuộc tính ( Tài liệu / Wiki ), nghĩa là bạn có thể làm:

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

Theo wiki, tất cả các giá trị có thể chấp nhận được là:

  • bool, int, float, string, array, object
  • có thể lặp lại
  • bản thân, cha mẹ
  • bất kỳ tên lớp hoặc giao diện nào
  • ? type // trong đó "type" có thể là bất kỳ phần nào ở trên

PHP <7.4

Nó thực sự không thể và bạn chỉ có 4 cách để thực sự mô phỏng nó:

  • Giá trị mặc định
  • Người trang trí trong các khối nhận xét
  • Giá trị mặc định trong hàm tạo
  • Getters và setters

Tôi kết hợp tất cả chúng ở đây

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;
    }
}

Lưu ý rằng bạn thực sự có thể nhập trả về là? Bar kể từ php 7.1 (nullable) vì nó có thể là null (không có sẵn trong php7.0.)

Bạn cũng có thể nhập trả về là void kể từ php7.1


1

Bạn có thể sử dụng setter

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);

Đầu ra:

TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of Foo given, called in ...
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.