Phương thức phép thuật PHP __get và __set


85

Trừ khi tôi hoàn toàn nhầm lẫn, các phương thức __get__setđược cho là cho phép nạp chồng cho → getset.

Ví dụ, các câu lệnh sau sẽ gọi __getphương thức:

echo $foo->bar;
$var = $foo->bar;

Và sau đây nên sử dụng __setphương pháp:

$foo->bar = 'test';

Điều này không hoạt động trong mã của tôi và có thể lặp lại bằng ví dụ đơn giản sau:

class foo {

    public $bar;
    public function __get($name) {

        echo "Get:$name";
        return $this->$name;
    }

    public function __set($name, $value) {

        echo "Set:$name to $value";
        $this->$name = $value;
    }
}


$foo = new foo();

echo $foo->bar;
$foo->bar = 'test';

echo "[$foo->bar]";

Điều này chỉ dẫn đến:

[test]

Đưa một số die()cuộc gọi vào đó cho thấy rằng nó không đánh nó chút nào.

Hiện tại, tôi chỉ nói vặn nó và đang sử dụng theo cách thủ công __getkhi nó cần, nhưng điều đó không năng động lắm và cần phải biết rằng mã 'quá tải' trên thực tế không được gọi trừ khi được gọi cụ thể. Tôi muốn biết liệu điều này có được cho là không hoạt động theo cách mà tôi đã hiểu rằng nó nên hay không hoặc tại sao điều này không hoạt động.

Điều này đang chạy php 5.3.3.

Câu trả lời:


164

__get, __set, __call__callStaticđược gọi khi các phương pháp hay tài sản là không thể tiếp cận. Của bạn $barlà công khai và không thể truy cập được.

Xem phần Tải trọng tài sản trong sách hướng dẫn:

  • __set() được chạy khi ghi dữ liệu vào các thuộc tính không thể truy cập.
  • __get() được sử dụng để đọc dữ liệu từ các thuộc tính không thể truy cập.

Các phương pháp ma thuật không thay thế cho getters và setters. Chúng chỉ cho phép bạn xử lý các cuộc gọi phương thức hoặc quyền truy cập thuộc tính mà nếu không sẽ dẫn đến lỗi. Như vậy, có nhiều thứ liên quan đến xử lý lỗi. Cũng lưu ý rằng chúng chậm hơn đáng kể so với việc sử dụng getter và setter hoặc các cuộc gọi phương thức trực tiếp thích hợp.


5
Để giải thích rõ hơn về vấn đề này, chỉ cần loại bỏ "public $ bar" để tài sản không tồn tại nữa và nó sẽ hoạt động như một chiếc bùa.
Steffen Müller

Cảm ơn. Đó là những gì tôi nhận được vì không đọc kỹ hướng dẫn sử dụng và thay vào đó xem một số blog ví dụ về nó;) Tuy nhiên, vẫn đang chờ nạp chồng toán tử thực sự trong PHP.
airbear

@airbear có một gói PECL cũ của Sara Golemon cho phép bạn quá tải các toán tử . Tuy nhiên, không biết nó tương thích với PHP.current như thế nào.
Gordon

1
@Pooya Đó nodekhông phải là tài sản của $ foo mà là tài sản của doesNotExist. Vì vậy, trừ khi 'doesNotExist' là một đối tượng (thực hiện __set hoặc có thuộc tính công khai được gọi là nút) thì nó sẽ không hoạt động.
Tivie

1
@Allen có, nó có.
Gordon

34

Tôi khuyên bạn nên sử dụng một mảng để lưu trữ tất cả các giá trị qua __set().

class foo {

    protected $values = array();

    public function __get( $key )
    {
        return $this->values[ $key ];
    }

    public function __set( $key, $value )
    {
        $this->values[ $key ] = $value;
    }

}

Bằng cách này, bạn đảm bảo rằng bạn không thể truy cập các biến theo cách khác (lưu ý rằng đã $valuesđược bảo vệ), để tránh xung đột.


19

Từ hướng dẫn sử dụng PHP :

  • __set () được chạy khi ghi dữ liệu vào các thuộc tính không thể truy cập.
  • __get () được sử dụng để đọc dữ liệu từ các thuộc tính không thể truy cập.

Điều này chỉ được gọi trên các thuộc tính không thể truy cập đọc / ghi . Tuy nhiên, tài sản của bạn là công khai, có nghĩa là nó có thể truy cập được. Thay đổi công cụ sửa đổi quyền truy cập thành được bảo vệ sẽ giải quyết được vấn đề.


7

Để mở rộng câu trả lời của Berry, rằng việc thiết lập mức truy cập thành protected cho phép __get và __set được sử dụng với các thuộc tính được khai báo rõ ràng (ít nhất là khi được truy cập bên ngoài lớp) và tốc độ chậm hơn đáng kể, tôi sẽ trích dẫn nhận xét từ một câu hỏi khác về chủ đề này và vẫn đưa ra trường hợp sử dụng nó:

Tôi đồng ý rằng __get chậm hơn đối với hàm get tùy chỉnh (thực hiện những điều tương tự), đây là 0,0124455 thời gian cho __get () và 0,0024445 này là cho get () tùy chỉnh sau 10000 vòng. - Melsi 23 tháng 11 '12 lúc 22:32 Phương pháp hay nhất: Phương pháp ma thuật PHP __set và __get

Theo các thử nghiệm của Melsi, chậm hơn đáng kể là chậm hơn khoảng 5 lần. Điều đó chắc chắn chậm hơn đáng kể, nhưng cũng lưu ý rằng các thử nghiệm cho thấy rằng bạn vẫn có thể truy cập thuộc tính bằng phương pháp này 10.000 lần, tính thời gian lặp lại vòng lặp, trong khoảng 1/100 giây. Nó chậm hơn đáng kể so với các phương thức get và set thực tế được định nghĩa và đó là một cách nói ngắn gọn, nhưng trong sơ đồ tổng thể của mọi thứ, thậm chí chậm hơn 5 lần cũng không bao giờ thực sự chậm.

Thời gian tính toán của hoạt động vẫn không đáng kể và không đáng để xem xét trong 99% các ứng dụng thế giới thực. Lần duy nhất nó thực sự nên tránh là khi bạn thực sự sẽ truy cập các thuộc tính hơn 10.000 lần trong một yêu cầu duy nhất. Các trang web có lưu lượng truy cập cao đang làm điều gì đó thực sự sai nếu họ không đủ khả năng cung cấp thêm một vài máy chủ để giữ cho ứng dụng của họ hoạt động. Một quảng cáo văn bản một dòng trên chân trang của một trang web có lưu lượng truy cập cao, nơi tỷ lệ truy cập trở thành một vấn đề có thể phải trả cho một nhóm 1.000 máy chủ có dòng văn bản đó. Người dùng cuối sẽ không bao giờ gõ ngón tay của họ để tự hỏi điều gì đang khiến trang tải quá lâu vì quyền truy cập thuộc tính của ứng dụng của bạn mất một phần triệu giây.

Tôi nói điều này với tư cách là một nhà phát triển đến từ nền tảng .NET, nhưng các phương thức nhận và thiết lập vô hình cho người tiêu dùng không phải là phát minh của .NET. Đơn giản là chúng không phải là thuộc tính nếu không có chúng, và những phương thức kỳ diệu này là ân huệ của nhà phát triển PHP khi thậm chí còn gọi phiên bản thuộc tính của chúng là "thuộc tính". Ngoài ra, phần mở rộng Visual Studio dành cho PHP không hỗ trợ intellisense với các thuộc tính được bảo vệ, tôi nghĩ vậy với mẹo đó. Tôi nghĩ rằng với đủ các nhà phát triển sử dụng các phương thức __get và __set kỳ diệu theo cách này, các nhà phát triển PHP sẽ điều chỉnh thời gian thực thi để phục vụ cho cộng đồng nhà phát triển.

Chỉnh sửa: Về lý thuyết, các thuộc tính được bảo vệ có vẻ như nó sẽ hoạt động trong hầu hết các tình huống. Trong thực tế, có rất nhiều lần bạn sẽ muốn sử dụng getters và setters của mình khi truy cập vào các thuộc tính trong định nghĩa lớp và các lớp mở rộng. Một giải pháp tốt hơn là một lớp cơ sở và giao diện khi mở rộng các lớp khác, vì vậy bạn có thể chỉ cần sao chép một vài dòng mã từ lớp cơ sở vào lớp thực thi. Tôi đang làm nhiều hơn một chút với lớp cơ sở của dự án của mình, vì vậy tôi không có giao diện để cung cấp ngay bây giờ, nhưng đây là định nghĩa lớp đã rút gọn chưa được thử nghiệm với việc nhận và thiết lập thuộc tính ma thuật bằng cách sử dụng phản chiếu để xóa và di chuyển các thuộc tính đến một mảng được bảo vệ:

/** Base class with magic property __get() and __set() support for defined properties. */
class Component {
    /** Gets the properties of the class stored after removing the original
     * definitions to trigger magic __get() and __set() methods when accessed. */
    protected $properties = array();

    /** Provides property get support. Add a case for the property name to
     * expand (no break;) or replace (break;) the default get method. When
     * overriding, call parent::__get($name) first and return if not null,
     * then be sure to check that the property is in the overriding class
     * before doing anything, and to implement the default get routine. */
    public function __get($name) {
        $caller = array_shift(debug_backtrace());
        $max_access = ReflectionProperty::IS_PUBLIC;
        if (is_subclass_of($caller['class'], get_class($this)))
            $max_access = ReflectionProperty::IS_PROTECTED;
        if ($caller['class'] == get_class($this))
            $max_access = ReflectionProperty::IS_PRIVATE;
        if (!empty($this->properties[$name])
            && $this->properties[$name]->class == get_class()
            && $this->properties[$name]->access <= $max_access)
            switch ($name) {
                default:
                    return $this->properties[$name]->value;
            }
    }

    /** Provides property set support. Add a case for the property name to
     * expand (no break;) or replace (break;) the default set method. When
     * overriding, call parent::__set($name, $value) first, then be sure to
     * check that the property is in the overriding class before doing anything,
     * and to implement the default set routine. */
    public function __set($name, $value) {
        $caller = array_shift(debug_backtrace());
        $max_access = ReflectionProperty::IS_PUBLIC;
        if (is_subclass_of($caller['class'], get_class($this)))
            $max_access = ReflectionProperty::IS_PROTECTED;
        if ($caller['class'] == get_class($this))
            $max_access = ReflectionProperty::IS_PRIVATE;
        if (!empty($this->properties[$name])
            && $this->properties[$name]->class == get_class()
            && $this->properties[$name]->access <= $max_access)
            switch ($name) {
                default:
                    $this->properties[$name]->value = $value;
            }
    }

    /** Constructor for the Component. Call first when overriding. */
    function __construct() {
        // Removing and moving properties to $properties property for magic
        // __get() and __set() support.
        $reflected_class = new ReflectionClass($this);
        $properties = array();
        foreach ($reflected_class->getProperties() as $property) {
            if ($property->isStatic()) { continue; }
            $properties[$property->name] = (object)array(
                'name' => $property->name, 'value' => $property->value
                , 'access' => $property->getModifier(), 'class' => get_class($this));
            unset($this->{$property->name}); }
        $this->properties = $properties;
    }
}

Tôi xin lỗi nếu có bất kỳ lỗi nào trong mã.


Tôi đã tìm thấy sự cố trên 'quyền truy cập' => $ property-> getModifier ()
macki

5

Đó là vì $ bar là tài sản công cộng.

$foo->bar = 'test';

Không cần gọi phương thức ma thuật khi chạy phần trên.

Xóa public $bar;khỏi lớp học của bạn sẽ sửa lỗi này.


1

Sử dụng tốt nhất các phương thức Magic set / get với các Phương thức set / get tùy chỉnh được xác định trước như trong ví dụ bên dưới. Bằng cách này, bạn có thể kết hợp tốt nhất hai thế giới. Về tốc độ, tôi đồng ý rằng chúng chậm hơn một chút nhưng bạn thậm chí có thể cảm nhận được sự khác biệt. Ví dụ bên dưới cũng xác thực mảng dữ liệu dựa trên các bộ thiết lập được xác định trước.

"Các phương thức ma thuật không thay thế cho getters và setters. Chúng chỉ cho phép bạn xử lý các lệnh gọi phương thức hoặc quyền truy cập thuộc tính mà nếu không sẽ dẫn đến lỗi."

Đây là lý do tại sao chúng ta nên sử dụng cả hai.

VÍ DỤ MỤC LỚP

    /*
    * Item class
    */
class Item{
    private $data = array();

    function __construct($options=""){ //set default to none
        $this->setNewDataClass($options); //calling function
    }

    private function setNewDataClass($options){
        foreach ($options as $key => $value) {
            $method = 'set'.ucfirst($key); //capitalize first letter of the key to preserve camel case convention naming
            if(is_callable(array($this, $method))){  //use seters setMethod() to set value for this data[key];      
                $this->$method($value); //execute the setters function
            }else{
                $this->data[$key] = $value; //create new set data[key] = value without seeters;
            }   
        }
    }

    private function setNameOfTheItem($value){ // no filter
        $this->data['name'] = strtoupper($value); //assign the value
        return $this->data['name']; // return the value - optional
    }

    private function setWeight($value){ //use some kind of filter
        if($value >= "100"){ 
            $value = "this item is too heavy - sorry - exceeded weight of maximum 99 kg [setters filter]";
        }
        $this->data['weight'] = strtoupper($value); //asign the value
        return $this->data['weight']; // return the value - optional
    }

    function __set($key, $value){
        $method = 'set'.ucfirst($key); //capitalize first letter of the key to preserv camell case convention naming
        if(is_callable(array($this, $method))){  //use seters setMethod() to set value for this data[key];      
            $this->$method($value); //execute the seeter function
        }else{
            $this->data[$key] = $value; //create new set data[key] = value without seeters;
        }
    }

    function __get($key){
        return $this->data[$key];
    }

    function dump(){
        var_dump($this);
    }
}

INDEX.PHP

$data = array(
    'nameOfTheItem' => 'tv',
    'weight' => '1000',
    'size' => '10x20x30'
);

$item = new Item($data);
$item->dump();

$item->somethingThatDoNotExists = 0; // this key (key, value) will trigger magic function __set() without any control or check of the input,
$item->weight = 99; // this key will trigger predefined setter function of a class - setWeight($value) - value is valid,
$item->dump();

$item->weight = 111; // this key will trigger predefined setter function of a class - setWeight($value) - value invalid - will generate warning.
$item->dump(); // display object info

ĐẦU RA

object(Item)[1]
  private 'data' => 
    array (size=3)
      'name' => string 'TV' (length=2)
      'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
      'size' => string '10x20x30' (length=8)
object(Item)[1]
  private 'data' => 
    array (size=4)
      'name' => string 'TV' (length=2)
      'weight' => string '99' (length=2)
      'size' => string '10x20x30' (length=8)
      'somethingThatDoNotExists' => int 0
object(Item)[1]
  private 'data' => 
    array (size=4)
      'name' => string 'TV' (length=2)
      'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
      'size' => string '10x20x30' (length=8)
      'somethingThatDoNotExists' => int 0

0

Bỏ public $bar;khai báo và nó sẽ hoạt động như mong đợi.


-6

Intenta con:

__GET($k){
 return $this->$k;
}

_SET($k,$v){
 return $this->$k = $v;
}
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.