Php getters và setters thực hành tốt nhất

Tôi đã trải qua một chặng đường dài với PHP và việc sử dụng Getters, Setters và DocBlocks là điều dễ dàng đối với tôi trong nhiều năm. Bằng cách nào đó, chúng thuộc về PHP như thể chúng là các tính năng tích hợp sẵn. Thông thường, chúng đã (và vẫn là) chỉ đơn giản là một phần của các khuôn khổ mà chúng tôi đã sử dụng. Tuy nhiên, với việc phát hành PHP 8, đã đến lúc suy nghĩ lại về một số thói quen tốt cũ và giảm mã soạn sẵn với PHP 8

Nhìn phía sau

Mẫu mã dưới đây cho thấy cách tôi rất có thể đã lập trình một DTO (Đối tượng truyền dữ liệu) đơn giản với hai thuộc tính trong thời của PHP 5

class SampleDto
{
    /**
     * @var int
     */
    private $property1;

    /**
     * @var string
     */
    private $property2 = 'test';

    /**
     * @return int
     */
    public function getProperty1()
    {
        return $this->property1;
    }

    /**
     * @param int $property1
     * @return SampleDto
     * @throws Exception
     */
    public function setProperty1($property1)
    {
        if (!is_int($property1)) {
            throw new \Exception("property1 must be of type integer");
        }

        $this->property1 = $property1;

        return $this;
    }

    /**
     * @return string
     */
    public function getProperty2()
    {
        return $this->property2;
    }

    /**
     * @param string $property2
     * @return SampleDto
     * @throws Exception
     */
    public function setProperty2($property2)
    {
        if (!is_string($property2)) {
            throw new \Exception("property2 must be of type string");
        }

        $this->property2 = $property2;

        return $this;
    }

    /**
     * SampleDto constructor.
     * @param int $property1
     * @param string $property2
     * @throws Exception
     */
    public function __construct($property1, $property2)
    {
        $this
            ->setProperty1($property1)
            ->setProperty2($property2);
    }
}

Ồ, đó là khá nhiều mã cho một DTO đơn giản. Tại sao tất cả những thứ đó lại cần thiết? . mạng)

PHP 5 không cung cấp bất kỳ chức năng gợi ý kiểu hữu ích nào. Nếu chúng ta muốn đảm bảo rằng các thuộc tính được khởi tạo đúng cách, sử dụng Setter-Methods với một số kiểm tra là một cách thực hành phổ biến và tốt để khắc phục những thiếu sót của PHP khi nói đến an toàn kiểu

Ngay cả với kiểu gõ động, việc biết loại biến nào được cho là đã giúp ích rất nhiều trong quá trình phát triển. Vì PHP không hỗ trợ điều này nên chúng tôi phải tự thêm nó vào – DocBlocks đã được hình thành và ngay sau đó, mặc dù chưa bao giờ là một cấu trúc ngôn ngữ chính thức, được chấp nhận và hỗ trợ rộng rãi giữa các IDE

Việc thiếu gợi ý loại yêu cầu rất nhiều mã chỉ để đảm bảo rằng các thuộc tính được sử dụng chính xác

Chắc chắn, hoàn toàn có thể viết mã PHP mà không cần quan tâm đến những thứ như thế này, nhưng chỉ vì điều gì đó có thể xảy ra, điều đó không có nghĩa là bạn nên làm như vậy

Càng đơn giản càng đẹp

Nhìn lại ví dụ trên, bạn có thể thấy DocBlocks đã sử dụng rất nhiều dòng rồi. Như đã đề cập ở trên, họ có lý do chính đáng để tồn tại. Họ đã giúp chúng tôi khắc phục các vấn đề về gõ động. Với hỗ trợ Type Hinting gốc, còn lý do nào để vẫn sử dụng chúng không?

Thật thú vị, câu hỏi này vẫn còn đáng để tranh luận ở đây và ở đó, các nhà phát triển có bảo vệ việc sử dụng DocBlocks như thế này không

/**
 * @param $property1
 */
public function setProperty1($property1) { // .. }

Thường được IDE tạo tự động, anh bạn nhỏ này không phục vụ bất kỳ mục đích sử dụng thực tế nào vì nó không thêm bất kỳ thông tin có giá trị nào. Đó là một sự lãng phí không gian và làm tăng tải nhận thức của các nhà phát triển một cách không cần thiết

Điều duy nhất tồi tệ hơn DocBlocks không cần thiết là DocBlocks sai

/**
 * @var int
 */
private $property1; 

/**
 * @param string $property1
 */
public function setProperty1($property1) { 
    $this->property1 = $property1;
}

Tại một số thời điểm, “loại” (hoặc giả sử mục đích) của

/**
 * @param $property1
 */
public function setProperty1($property1) { // .. }
1 đã bị thay đổi, nhưng chúng tôi lại quên cập nhật DocBlocks của chức năng thiết lập. Không phải là một thỏa thuận lớn? . Hoặc chúng ta bỏ qua nó ngay lập tức

Tránh DocBlocks, nếu bạn có thể

Theo tôi, nên tránh bình luận nếu có thể (và tôi không phải là người duy nhất). Nhận xét không phải là một phần của mã được thực thi. Những sai lầm trong nhận xét sẽ không khiến mọi thứ trở nên tồi tệ. Đó là lý do tại sao các bình luận thường không được cập nhật trong quá trình tái cấu trúc và cuối cùng gây hại nhiều hơn lợi.

DocBlocks vẫn có các trường hợp sử dụng hợp lệ. Bạn có thể sử dụng nó để khai báo loại mục trong một mảng. Các phương thức đánh dấu là không dùng nữa cũng khá tiện dụng. Ngoài ra, một số ORM yêu cầu phân tích cú pháp DocBlocks để chú thích hoạt động. Tuy nhiên, trong trường hợp này, các lỗi mắc phải trong chú thích sẽ gây ra các hiệu ứng đáng chú ý hay còn gọi là lỗi và hy vọng sẽ được kiểm tra tự động phát hiện

Chỉ thêm DocBlocks nếu chúng thêm thông tin vào mã của bạn mà không thể cung cấp thông tin khác. Bỏ qua những phần thừa. Nếu chúng không thêm giá trị, hãy loại bỏ chúng hoàn toàn

Một ví dụ về DocBlock hữu ích là

class SampleApi
{
    /**
     * @deprecated will be removed with version 2
     */
    public function createSomething(int $param1): self 
    {
       // ....
    }
}

Các loại tham số hàm hoặc giá trị trả về không yêu cầu thêm tài liệu. Tuy nhiên, thông tin mà chức năng không được dùng nữa vẫn được sử dụng rất nhiều, vì vậy chúng tôi có thể thêm một DocBlock chỉ với thông tin đó và loại bỏ những thứ không cần thiết

Cuối cùng. Thuộc tính PHP 8

Tôi biết rằng nhiều khung công tác PHP vẫn yêu cầu chú thích trong DocBlocks, nhưng đó không phải là điểm chuẩn. Ngay cả framework phổ biến nhất trong PHP cũng sử dụng nhiều anti-pattern, vì vậy điều đó không ngăn cản chúng tôi thách thức chúng

Nếu khung của bạn (hoặc ORM hoặc bất kỳ gói nào) đang dựa vào DocBlocks để hoạt động, thì vẫn còn hy vọng. với PHP 8, các thuộc tính đã được giới thiệu. Mặc dù tôi thừa nhận rằng sẽ mất một chút thời gian cho đến khi tôi quen với cú pháp, nhưng các thuộc tính sẽ giúp chúng tôi loại bỏ DocBlocks rất nhiều và do đó giảm mã soạn sẵn

Và có vẻ như nó đã bắt đầu. như Tomas Vostruba rất được kính trọng đã lưu ý trong một trong những bài đăng gần đây của anh ấy, Symfony 5. 2 đã giới thiệu thuộc tính

/**
 * @param $property1
 */
public function setProperty1($property1) { // .. }
3 để thay thế chú thích
/**
 * @param $property1
 */
public function setProperty1($property1) { // .. }
4

________số 8

Vì vậy, cuối cùng, thông tin có giá trị sẽ không còn được lưu trữ trong các nhận xét mà trong mã PHP gốc. Đó là con đường để đi

dọn dẹp nhiều hơn

Một lợi ích thú vị khác của gợi ý kiểu là PHP 7 sẽ đưa ra lỗi nếu sử dụng sai kiểu. Đó là lý do tại sao chúng ta có thể loại bỏ hoàn toàn việc kiểm tra kiểu thủ công trong các phương thức Setter, giúp giảm lượng mã soạn sẵn hơn nữa

class SampleDto
{
    private int $property1;

    private string $property2 = 'test';

    public function getProperty1(): int
    {
        return $this->property1;
    }

    public function setProperty1(int $property1): self
    {
        $this->property1 = $property1;
        return $this;
    }

    public function getProperty2(): string
    {
        return $this->property2;
    }

    public function setProperty2(string $property2): self
    {
        $this->property2 = $property2;
        return $this;
    }

    public function __construct($property1, $property2)
    {
        $this
            ->setProperty1($property1)
            ->setProperty2($property2);
    }
}

Đó là ngắn hơn nhiều rồi. Tại thời điểm này, bạn chắc chắn có thể tranh luận liệu có cần thiết cho phép khởi tạo các thuộc tính thông qua hàm tạo hay không. Nếu bạn không cần nó, nó cũng sẽ giúp bạn tiết kiệm một số dòng

Mặc dù vậy, nhiều không gian hơn được sử dụng bởi các phương thức Getter và Setter. Với PHP7. 4, chúng tôi có gợi ý loại thuộc tính, vậy chúng tôi thực sự vẫn cần chúng để làm gì?

Được rồi, đợi một chút - điều gì sẽ xảy ra nếu chúng ta chỉ khai báo các thuộc tính này là công khai ngay từ đầu?

Tôi đã mất một thời gian để vượt qua thói quen của mình, vì vậy câu trả lời thực sự khiến tôi cũng ngạc nhiên.

Chúng tôi không cần Getters và Setters cho các thuộc tính công cộng nữa

Xin đừng hiểu lầm tôi ở điểm này. Tôi không nói rằng chúng ta không cần Getters và Setters nữa. Tương tự như đối với DocBlocks, chúng ta chỉ nên sử dụng chúng nếu chúng thực sự cần thiết

Trong ví dụ của tôi, tôi đã sử dụng một DTO. Đây là những đối tượng đơn giản chỉ phục vụ mục đích di chuyển dữ liệu xung quanh. Chúng ta thực sự có thể làm điều này

/**
 * @param $property1
 */
public function setProperty1($property1) { // .. }
0

Bây giờ ĐIỀU ĐÓ hơi điên rồ, phải không? . Chúng tôi vẫn đảm bảo an toàn loại. Tôi không thấy lợi ích gì khi sử dụng Getters và Setters ở đây cả. Nó chỉ cảm thấy lạ đối với tôi, nhưng tôi không thể giúp nó

/**
 * @param $property1
 */
public function setProperty1($property1) { // .. }
1

Lần nữa. đó là tất cả về các cách để giảm mã soạn sẵn. Nếu chúng tôi không nhận được bất kỳ lợi ích nào từ đó, tại sao lại sử dụng nó?

Giới thiệu PHP 8

Nó thậm chí còn mát hơn. với PHP 8, giờ đây chúng ta có thể sử dụng quảng cáo thuộc tính Constructor để giảm mã hơn nữa. Ví dụ tiếp theo theo tôi là giảm càng nhiều càng tốt mà không mất gì

/**
 * @param $property1
 */
public function setProperty1($property1) { // .. }
2

Xem kết quả trong giây lát. chúng tôi đã giảm số lượng dòng cần thiết cho DTO này từ 75 xuống 9. Và chúng tôi đã không hy sinh bất cứ điều gì. Trên thực tế, mã của chúng tôi mạnh mẽ hơn trước, vì giờ đây chúng tôi chỉ sử dụng các tính năng PHP gốc và không có gì khác

Xin lưu ý mặc dù. Tôi không chọn công khai tất cả các thuộc tính. Thuộc tính riêng tư và được bảo vệ vẫn cần thiết. Một ví dụ có thể là Kho lưu trữ nhận đối tượng kết nối cơ sở dữ liệu được đưa vào thông qua hàm tạo và không thể thay đổi nó sau đó

/**
 * @param $property1
 */
public function setProperty1($property1) { // .. }
3

Ví dụ này được tạo ra khá đẹp, nhưng tôi chắc rằng bạn sẽ hiểu. Tuy nhiên

Nếu bạn có một thuộc tính riêng và thêm các phương thức Getter và Setter cho nó, thì không có gì phải phản đối việc đặt nó ở vị trí công khai ngay từ đầu nữa

chống lại

Dưới đây tôi đã thêm một số đối số mà tôi gặp gần đây

1. Truyền và định dạng thuộc tính

Trường hợp sử dụng phổ biến cho Getters là nhận dữ liệu khác với dữ liệu được lưu trữ trong đối tượng, chẳng hạn như. g. , định dạng ngày hoặc chuỗi theo một cách nhất định. Theo ý kiến ​​​​của tôi, đây không phải là những gì Getters nên được sử dụng cho. Getters nên trả lại dữ liệu chưa sửa đổi. Thêm các phương pháp chuyên dụng như

/**
 * @param $property1
 */
public function setProperty1($property1) { // .. }
5 hoặc
/**
 * @param $property1
 */
public function setProperty1($property1) { // .. }
6 hoặc bất cứ thứ gì bạn thích nếu bạn cần

2. Đối tượng bất biến

Chắc bạn đã biết qua Immutable Object rồi. Giao diện tin nhắn PSR-7 HTTP là một ví dụ phổ biến về nó. sau khi được tạo, bạn không thể thay đổi phiên bản. Cách duy nhất là tạo một bản sao đã sửa đổi từ nó

Vì chưa có hỗ trợ riêng cho điều này trong PHP nên để đạt được mục tiêu này cần thực hiện thêm một số thao tác thủ công. Quyết định cho chính mình nếu nó là giá trị nỗ lực. Cá nhân tôi nghĩ là không, ít nhất là đối với tập lệnh PHP trung bình không trạng thái giữa các yêu cầu

Tuy nhiên, với sự hỗ trợ bản địa, tôi nghĩ đó sẽ là một bổ sung có lợi. Trong một bài báo xuất sắc khác của Larry Garfield, anh ấy đã khám phá chủ đề về các thuộc tính và tính bất biến của Đối tượng một cách sâu sắc. Tôi sẽ không ngạc nhiên nếu điều này, theo một cách nào đó, sẽ sớm trở thành một phần của PHP

Cập nhật. thuộc tính chỉ đọc được giới thiệu với PHP 8. 1

Với PHP8. 1, bây giờ chúng tôi cũng nhận được các thuộc tính chỉ đọc

/**
 * @param $property1
 */
public function setProperty1($property1) { // .. }
0

Các thuộc tính này chỉ có thể được viết một lần trong quá trình tạo đối tượng và sau đó chỉ được đọc khi truy cập

Phần kết luận

PHP đã đi một chặng đường dài. Nó vẫn đang thay đổi rất nhiều, và chúng tôi, những nhà phát triển cũng vậy. Chỉ vì chúng tôi đã sử dụng một số mẫu nhất định trong nhiều năm, ngay cả khi chúng phục vụ tốt cho chúng tôi, điều đó không có nghĩa là chúng tôi không nên để chúng ra đi vào một ngày nào đó

Trong những năm đầu làm nhà phát triển, tôi luôn cố gắng hết sức để thêm DocBlocks và sử dụng Getters và Setters ở mọi nơi. Lúc đó tôi nghĩ nó có lý. Tuy nhiên, với những thay đổi mới nhất của PHP, giờ đây tôi tin chắc rằng chúng ta có thể cải thiện mã của mình rất nhiều bằng cách loại bỏ chúng

PHP trưởng thành, chúng ta cũng vậy

DocBlocks vẫn có thể thêm thông tin có giá trị vào mã của chúng tôi, tuy nhiên, chúng tôi chỉ nên thêm chúng ở những nơi chúng tôi không thể thêm thông tin này bằng các tính năng ngôn ngữ

Điều này cũng đúng với Getters và Setters. Có thể có những trường hợp sử dụng vẫn sử dụng chúng cùng với Constructor injection, nhưng tôi muốn thách thức bạn suy nghĩ kỹ về nó

Với PHP 8, chúng ta có thể giảm rất nhiều mã soạn sẵn. Điều này sẽ làm cho cuộc sống của chúng ta dễ dàng hơn nhiều

Quan điểm của bạn về điều này là gì?

Bạn có nên sử dụng getters và setters trong PHP không?

Chiến lược getter và setter được sử dụng khi chúng tôi cần hạn chế quyền truy cập trực tiếp vào các biến của người dùng cuối . Getters và setters là các phương thức được sử dụng để xác định hoặc truy xuất các giá trị của các biến, thường là các biến riêng tư.

Getter và setter có thực hành tốt không?

Thực hành lập trình tốt là không sử dụng getters và setters trong các lớp dự định chứa nhiều gói dữ liệu (như cấu trúc C ). Chúng phơi bày cấu trúc bên trong của lớp, vi phạm tính đóng gói và làm tăng đáng kể sự liên kết.

Làm thế nào để gọi một getter trong PHP?

Bây giờ bạn có thể tự động gọi getters của mình, như vậy. foreach ($getters as $getter) { echo $entity->{$getter}(); . ` } Một điều cần lưu ý. afaik, get_class_methods() cũng sẽ trả về tên của các phương thức tĩnh. Có lẽ không phải là một vấn đề trong trường hợp của bạn vì bạn không có khả năng có các getters và setters tĩnh.