簡體   English   中英

PHP __get和__set魔術方法

[英]PHP __get and __set magic methods

除非我完全弄錯了,否則__get__set方法應該允許重載→ getset

例如,以下語句應調用__get方法:

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

以下應使用__set方法:

$foo->bar = 'test';

這在我的代碼中不起作用,並且可以通過這個簡單的示例重現:

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]";

這只會導致:

[test]

在那里放一些die()調用表明它根本沒有打它。

現在,我只是說它,並且我手動使用__get現在需要它,但這不是非常動態的,並且需要知道“重載”代碼實際上沒有被調用,除非特別調用。 我想知道這是不應該以我理解它應該或為什么這不起作用的方式起作用。

這是在php 5.3.3上運行的。

__get__set__call__callStatic當方法或屬性是不可訪問的被調用。 您的$bar是公開的,因此無法訪問。

請參閱手冊中有關屬性重載部分:

  • 將數據寫入不可訪問的屬性時運行__set()
  • __get()用於從不可訪問的屬性中讀取數據。

魔術方法不能替代getter和setter。 它們只允許您處理方法調用或屬性訪問,否則會導致錯誤。 因此,與錯誤處理有更多相關。 另請注意,它們比使用正確的getter和setter或直接方法調用慢得多。

我建議使用數組通過__set()存儲所有值。

class foo {

    protected $values = array();

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

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

}

這樣您就可以確保不能以其他方式訪問變量(注意$values是受保護的),以避免沖突。

PHP手冊

  • 將數據寫入不可訪問的屬性時運行__set()。
  • __get()用於從不可訪問的屬性中讀取數據。

這僅在讀/寫不可訪問的屬性時調用。 但您的財產是公開的,這意味着它是可以訪問的。 將訪問修飾符更改為受保護可解決此問題。

為了擴展Berry的答案,將訪問級別設置為protected允許__get和__set與顯式聲明的屬性一起使用(至少在類外部訪問時),並且速度相當慢,我將引用另一個問題的注釋關於這個主題,並提出使用它的案例:

我同意__get對自定義get函數更慢(做同樣的事情),這是__get()的時間是0.0124455,而這個0.0024445是10000循環后的自定義get()。 - Melsi 12年11月23日在22:32 最佳實踐:PHP魔術方法__set和__get

根據Melsi的測試,相當慢的速度大約慢5倍。 這肯定要慢得多,但同時請注意,測試顯示您仍然可以使用此方法訪問屬性10,000次,計算循環迭代的時間,大約是1/100秒。 與定義的實際get和set方法相比,它要慢得多,這是輕描淡寫的,但在宏觀方案中,即使慢5倍也從未真正變慢。

該操作的計算時間仍然可以忽略不計,並且在99%的實際應用中不值得考慮。 真正應該避免的唯一一次是你實際上要在一次請求中訪問屬性超過10,000次。 如果他們無法承擔更多的服務器以保持他們的應用程序運行,那么高流量站點正在做一些非常錯誤的事情。 高流量站點的頁腳上的單行文本廣告(訪問率成為問題)可能需要支付具有該行文本的1,000個服務器的服務器場。 最終用戶永遠不會輕拍他們的手指,因為您的應用程序的屬性訪問需要花費百萬分之一秒,因此想知道該頁面需要如此長時間才能加載。

我說這是一個來自.NET背景的開發人員,但對於消費者來說,隱形的get和set方法並不是.NET的發明。 它們不是沒有它們的屬性,而這些神奇的方法是PHP的開發人員甚至可以節省它們的屬性“屬性”的版本。 另外,PHP的Visual Studio擴展確實支持帶有受保護屬性的intellisense,我想這就是這個技巧。 我認為有足夠的開發人員以這種方式使用magic __get和__set方法,PHP開發人員會調整執行時間以迎合開發人員社區。

編輯:理論上,受保護的屬性似乎在大多數情況下都有效。 實際上,事實證明,在訪問類定義和擴展類中的屬性時,很多時候您會想要使用getter和setter。 更好的解決方案是擴展其他類時的基類和接口,因此您只需將幾行代碼從基類復制到實現類中即可。 我正在使用我的項目的基類做更多的事情,所以我現在沒有提供的接口,但這里是未經測試的剝離類定義,使用魔法屬性獲取並使用反射設置移除並將屬性移動到受保護的數組:

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

如果代碼中有任何錯誤,我很抱歉。

這是因為$ bar是公共財產。

$foo->bar = 'test';

運行上述操作時無需調用magic方法。

刪除public $bar; 從你的班級應該糾正這一點。

最好使用具有預定義自定義set / get方法的magic set / get方法,如下例所示。 這樣你就可以結合兩個世界中最好的一個。 在速度方面我同意他們有點慢,但你甚至可以感受到差異。 下面的示例還根據預定義的setter驗證數據數組。

“魔術方法不能替代getter和setter。它們只允許你處理方法調用或屬性訪問,否則會導致錯誤。”

這就是我們應該同時使用兩者的原因

課程示例

    /*
    * 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

OUTPUT

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

放棄public $bar; 聲明,它應該按預期工作。

Intenta con:

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

_SET($k,$v){
 return $this->$k = $v;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM