web-dev-qa-db-ja.com

ベストプラクティス:PHPマジックメソッド__setおよび__get

可能性のある複製:
マジックメソッドはPHPでベストプラクティスですか?

これらは簡単な例ですが、クラスに2つ以上のプロパティがあることを想像してください。

ベストプラクティスは何でしょうか?

a)__getおよび__setの使用

class MyClass {
    private $firstField;
    private $secondField;

    public function __get($property) {
            if (property_exists($this, $property)) {
                return $this->$property;
            }
    }

    public function __set($property, $value) {
        if (property_exists($this, $property)) {
            $this->$property = $value;
        }
    }
}

$myClass = new MyClass();

$myClass->firstField = "This is a foo line";
$myClass->secondField = "This is a bar line";

echo $myClass->firstField;
echo $myClass->secondField;

/* Output:
    This is a foo line
    This is a bar line
 */

b)従来のセッターとゲッターの使用

class MyClass {

    private $firstField;
    private $secondField;

    public function getFirstField() {
        return $this->firstField;
    }

    public function setFirstField($firstField) {
        $this->firstField = $firstField;
    }

    public function getSecondField() {
        return $this->secondField;
    }

    public function setSecondField($secondField) {
        $this->secondField = $secondField;
    }

}

$myClass = new MyClass();

$myClass->setFirstField("This is a foo line");
$myClass->setSecondField("This is a bar line");

echo $myClass->getFirstField();
echo $myClass->getSecondField();

/* Output:
    This is a foo line
    This is a bar line
 */

この記事では: http://blog.webspecies.co.uk/2011-05-23/the-new-era-of-php-frameworks.html

著者は、魔法の方法を使用するのは良い考えではないと主張しています。

まず第一に、当時はPHPのマジック関数(__get、__ callなど)を使用することが非常に一般的でした。最初の外観からは何も問題はありませんが、実際には非常に危険です。 APIが不明瞭になり、オートコンプリートが不可能になり、最も重要なことに、APIが遅くなります。彼らのユースケースは、PHPをハックして、望まないことをすることでした。そしてそれは働いた。しかし、悪いことが起こりました。

しかし、私はこれについてもっと意見を聞きたいです。

120
rfc1484

私は過去にまさにあなたの場合でした。そして、私は魔法の方法に行きました。

これは間違いでした、あなたの質問の最後の部分はそれをすべて言っています:

  • これはslower(getter/setterより)
  • 自動補完なし(これは実際に大きな問題です)、およびタイプ管理により、リファクタリングとコード参照のためのIDEがあります(下) Zend Studio/PhpStormこれは@property phpdocアノテーションで処理できますが、それらを維持する必要があります:非常に苦痛です)
  • documentation(phpdoc)は、コードの使用方法と一致しません。また、クラスを見ても、あまり多くの答えが得られません。これは紛らわしいです。
  • 編集後に追加:プロパティのゲッターを持つことは「実際の」メソッドとより一貫性がありますここで、getXXX()はプライベートプロパティを返すだけでなく、実際のロジックを実行します。同じ名前が付いています。たとえば、$user->getName()(プライベートプロパティを返す)と$user->getToken($key)(計算済み)があります。ゲッターがゲッター以上のものを取得し、何らかのロジックを実行する必要がある日でも、すべてが一貫しています。

最後に、これがIMOの最大の問題です。これは魔法です。そして、魔法は非常に非常に悪いものです。それは、魔法を適切に使用するには、魔法がどのように機能するかを知る必要があるからです。それは私がチームで出会った問題です。あなただけではなく、誰もが魔法を理解しなければなりません。

ゲッターとセッターは書くのが苦手です(私はそれらを嫌います)が、彼らはそれだけの価値があります。

151
Matthieu Napoli

オブジェクトが実際に「魔法」である場合にのみ、魔法を使用する必要があります。プロパティが固定されたクラシックオブジェクトがある場合、setterとgetterを使用すると、それらは正常に機能します。

オブジェクトに動的プロパティがあり、たとえばデータベース抽象化レイヤーの一部であり、そのパラメーターが実行時に設定される場合、実際には便利なマジックメソッドが必要です。

113
vbence

コードを読みやすくするため、__get(およびパブリックプロパティ)をできるだけ使用します。比較:

このコードは私がやっていることを明確に述べています:

echo $user->name;

このコードは私を愚かに感じさせますが、私はそれを楽しんでいません:

function getName() { return $this->_name; }
....

echo $user->getName();

2つのプロパティの違いは、複数のプロパティに一度にアクセスする場合に特に顕著です。

echo "
    Dear $user->firstName $user->lastName!
    Your purchase:
        $product->name  $product->count x $product->price
"

そして

echo "
    Dear " . $user->getFirstName() . " " . $user->getLastName() . "
    Your purchase: 
        " . $product->getName() . " " . $product->getCount() . "  x " . $product->getPrice() . " ";

$a->bが本当にdo何かをすべきか、単に値を返すかは、呼び出し先の責任です。呼び出し元の場合、$user->name$user->accountBalanceは同じように見えますが、後者は複雑な計算を伴う場合があります。私のデータクラスでは、次の小さなメソッドを使用します。

 function __get($p) { 
      $m = "get_$p";
      if(method_exists($this, $m)) return $this->$m();
      user_error("undefined property $p");
 }

誰かが$obj->xxxを呼び出し、クラスにget_xxxが定義されている場合、このメソッドは暗黙的に呼び出されます。そのため、インターフェイスを均一かつ透明に保ちながら、必要に応じてゲッターを定義できます。追加のボーナスとして、これは計算を記憶するエレガントな方法を提供します:

  function get_accountBalance() {
      $result = <...complex stuff...>
      // since we cache the result in a public property, the getter will be called only once
      $this->accountBalance = $result;
  }

  ....


   echo $user->accountBalance; // calculate the value
   ....
   echo $user->accountBalance; // use the cached value

結論:phpは動的なスクリプト言語です。そのように使用します。JavaやC#を実行しているふりをしないでください。

82
user187291

私は、edemの答えとあなたの2番目のコードを混ぜます。このように、一般的なゲッター/セッター(IDEでのコード補完)、必要に応じたコーディングの容易さ、存在しないプロパティによる例外(タイプミスの検出に最適:$foo->naemの代わりに$foo->name)の利点があります。 )、読み取り専用プロパティと複合プロパティ。

class Foo
{
    private $_bar;
    private $_baz;

    public function getBar()
    {
        return $this->_bar;
    }

    public function setBar($value)
    {
        $this->_bar = $value;
    }

    public function getBaz()
    {
        return $this->_baz;
    }

    public function getBarBaz()
    {
        return $this->_bar . ' ' . $this->_baz;
    }

    public function __get($var)
    {
        $func = 'get'.$var;
        if (method_exists($this, $func))
        {
            return $this->$func();
        } else {
            throw new InexistentPropertyException("Inexistent property: $var");
        }
    }

    public function __set($var, $value)
    {
        $func = 'set'.$var;
        if (method_exists($this, $func))
        {
            $this->$func($value);
        } else {
            if (method_exists($this, 'get'.$var))
            {
                throw new ReadOnlyException("property $var is read-only");
            } else {
                throw new InexistentPropertyException("Inexistent property: $var");
            }
        }
    }
}
3

3番目の解決策に投票します。私は自分のプロジェクトでこれを使用し、Symfonyは次のようなものも使用します。

public function __call($val, $x) {
    if(substr($val, 0, 3) == 'get') {
        $varname = strtolower(substr($val, 3));
    }
    else {
        throw new Exception('Bad method.', 500);
    }
    if(property_exists('Yourclass', $varname)) {
        return $this->$varname;
    } else {
        throw new Exception('Property does not exist: '.$varname, 500);
    }
}

この方法では、自動化されたゲッターがあり(セッターも作成できます)、メンバー変数に特別なケースがある場合にのみ新しいメソッドを作成する必要があります。

0
Adam Arold