PHPの__getおよび__setマジックメソッド


85

私が完全に間違えない限り、__getand__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ます。

回答:


164

__get__set__callおよび__callStaticメソッドやプロパティにアクセスできないときに呼び出されます。あなた$barは公開されているため、アクセスできません。

マニュアルのプロパティのオーバーロードに関するセクションを参照してください

  • __set() アクセスできないプロパティにデータを書き込むときに実行されます。
  • __get() アクセスできないプロパティからデータを読み取るために使用されます。

魔法の方法は、ゲッターとセッターの代わりにはなりません。これらを使用すると、エラーが発生するメソッド呼び出しやプロパティアクセスを処理できます。そのため、エラー処理に関連するものは他にもたくさんあります。また、適切なゲッターとセッターまたは直接メソッド呼び出しを使用するよりもかなり遅いことに注意してください。


5
これについて詳しく説明するには、「public $ bar」を削除するだけで、プロパティが存在しなくなり、チャームのように機能します。
ステファン・ミュラー

ありがとう。これは、マニュアルを完全に読んでおらず、代わりにその例をブログで見ていることで得られるものです;)ただし、PHPでの真の演算子オーバーロードをまだ待っています。
エアベア2011年

@airbearには、演算子をオーバーロードできるようにする、SaraGolemonによる古いPECLパッケージがあります。PHP.currentとの互換性はわかりませんが。
ゴードン

1
@Pooyaこれnodeは、が$ fooのプロパティではなく、のプロパティであるためですdoesNotExist。したがって、「doesNotExist」がオブジェクト(__setを実装するか、nodeというパブリックプロパティを持つ)でない限り、機能しません。
Tivie 2014

1
@アレンはい、そうです。
ゴードン

34

を介してすべての値を格納するには、配列を使用することをお勧めします__set()

class foo {

    protected $values = array();

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

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

}

このようにして、$values衝突を回避するために、別の方法で変数にアクセスできないことを確認します(保護されていることに注意してください)。


19

PHPマニュアルから:

  • __set()は、アクセスできないプロパティにデータを書き込むときに実行されます。
  • __get()は、アクセスできないプロパティからデータを読み取るために使用されます。

これは、アクセスできないプロパティの読み取り/書き込み時にのみ呼び出されます。ただし、あなたの財産は公開されているため、アクセス可能です。アクセス修飾子を保護に変更すると、問題が解決します。


7

ベリーの答えを拡張するために、アクセスレベルを保護に設定すると、明示的に宣言されたプロパティで__getと__setを使用でき(少なくともクラスの外部でアクセスした場合)、速度がかなり遅くなります。別の質問からのコメントを引用しますこのトピックについて、とにかくそれを使用するためのケースを作ります:

__getはカスタムget関数(同じことを行う)よりも遅いことに同意します。これは__get()の時間0.0124455であり、この0.0024445は10000ループ後のカスタムget()の時間です。– melsi 2012年11月23日22:32ベストプラクティス:PHPマジックメソッド__setおよび__get

Melsiのテストによると、かなり遅いのは約5倍遅いです。これは間違いなくかなり遅いですが、テストでは、このメソッドを使用してプロパティに10,000回アクセスでき、ループ反復の時間を約1/100秒でカウントできることにも注意してください。定義されている実際のgetおよびsetメソッドと比較するとかなり遅く、それは控えめな表現ですが、物事の壮大なスキームでは、5倍遅い場合でも実際に遅くなることはありません。

操作の計算時間はまだごくわずかであり、実際のアプリケーションの99%では考慮する価値がありません。実際に回避する必要があるのは、1回のリクエストで実際に10,000回を超えるプロパティにアクセスする場合のみです。トラフィックの多いサイトは、アプリケーションを実行し続けるためにさらにいくつかのサーバーを起動する余裕がない場合、本当に悪いことをしています。アクセス率が問題となるトラフィックの多いサイトのフッターにある1行のテキスト広告は、おそらくそのテキスト行を持つ1,000台のサーバーのファームに支払う可能性があります。アプリケーションのプロパティアクセスには100万分の1秒かかるため、エンドユーザーはページの読み込みに何がそんなに時間がかかるのか疑問に思うことは決してありません。

私はこれを.NETのバックグラウンドから来た開発者として話すと言いますが、消費者に見えないgetおよびsetメソッドは.NETの発明ではありません。それらは単にそれらのないプロパティではなく、これらの魔法のメソッドは、プロパティのバージョンを「プロパティ」と呼ぶことさえできるPHPの開発者の節約の恩恵です。また、PHP用のVisual Studio拡張機能は、保護されたプロパティを備えたインテリセンスをサポートしています。そのトリックを念頭に置いて、私は思います。このように魔法の__getメソッドと__setメソッドを使用する十分な数の開発者がいると、PHP開発者は開発者コミュニティに対応するために実行時間を調整すると思います。

編集:理論的には、保護されたプロパティはほとんどの状況で機能するように見えました。実際には、クラス定義および拡張クラス内のプロパティにアクセスするときに、ゲッターとセッターを使用したいと思うことがよくあります。より良い解決策は、他のクラスを拡張するときの基本クラスとインターフェースです。したがって、基本クラスから実装クラスに数行のコードをコピーするだけです。プロジェクトの基本クラスでもう少し作業を行っているので、現時点で提供するインターフェイスはありませんが、リフレクションを使用してプロパティを削除および移動する魔法のプロパティを取得および設定する、テストされていないストリップダウンクラス定義を次に示します。保護されたアレイ:

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

コードにバグがある場合はお詫び申し上げます。


'access' => $ property-> getModifier()
macki 2016年

5

$ barは公共の財産だからです。

$foo->bar = 'test';

上記を実行するときにマジックメソッドを呼び出す必要はありません。

public $bar;クラスから削除すると、これが修正されます。


1

以下の例のように、事前定義されたカスタムset / getメソッドを使用してマジックセット/ getメソッドを最適に使用します。このようにして、2つの世界の最良のものを組み合わせることができます。速度に関しては少し遅いと思いますが、違いを感じることもできます。以下の例では、事前定義されたセッターに対してデータ配列も検証します。

「魔法のメソッドは、ゲッターやセッターの代わりにはなりません。エラーが発生するメソッド呼び出しやプロパティアクセスを処理できるようにするだけです。」

これが、両方を使用する必要がある理由です。

クラスアイテムの例

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

出力

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


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.