[翻訳] PHPのマジックメソッド

もしあなたがオープンソースプロジェクトのPHPコードを研究したことがあるなら、二重アンダースコアで始まるメソッドに出くわすかもしれません。 これらは、インスタンスを使用したさまざまな操作中にオブジェクトの動作を決定できる非常に魔法のメソッドです。



かなり一般的な方法があるため、すでにそれらのいくつかに出くわしたと思いますが、有能なPHPプログラマーは言語のすべての機能に自信を持つ必要があると思います。

これは、魔法の方法の世界における一種の出発点と考えることができると思います。



はじめに



私がこの資料を自分で勉強したとき、私はかなり愚かな、または一般に役に立たない例を示したすべての種類の教科書と記事を使用しました。 何かを理解するためには、実際のタスクのコンテキストでこれを試す必要があると思います。 ここから始めます。



Tweeter Apiを使用してすべてのツイートを取得したいと想像してください。 現在のユーザーのすべてのツイートのJSONを取得し、特定の操作を許可するメソッドを使用して、各ツイートをオブジェクトに変換します。



以下に、基本のTweetクラスを紹介しました。

class Tweet { }
      
      







オブジェクトを作成したので、メソッド自体の学習を開始できます。 (注翻訳者-一部の設計は、各メソッドの役割と機能を強調するために省略される場合があります)



コンストラクターとデストラクター



おそらく最も一般的な魔法のメソッドの1つはコンストラクター(__construct())です。 あなたが私のブログでCribbbアプリケーションを十分に追っていれば、この方法をよく知っているでしょう。



__construct()メソッドは、オブジェクトがインスタンス化されると自動的に呼び出されます。 その中で、オブジェクトの初期プロパティを設定したり、依存関係を設定したりできます。



使用例:



 public function __construct($id, $text) { $this->id = $id; $this->text = $text; } $tweet = new Tweet(123, 'Hello world');
      
      







Tweetクラスをインスタンス化するときに、__ construct()メソッドに渡すパラメーターを渡すことができます。 上記の例から、このメソッドを呼び出さず、呼び出すべきではないことがわかります。このメソッドは自動的に呼び出されます。



時間が経つにつれて、クラスを継承して拡張する必要があります。 親クラスには、特定のアクションを実行する__construct()メソッドもある場合があります。そのため、親クラスの機能を失わないようにするには、コンストラクターを呼び出す必要があります。



 class Entity { protected $meta; public function __construct(array $meta) { $this->meta = $meta; } } class Tweet extends Entity { protected $id; protected $text; public function __construct($id, $text, array $meta) { $this->id = $id; $this->text = $text; parent::__construct($meta); } }
      
      







オブジェクトを削除しようとすると、__ destruct()メソッドが呼び出されます。 繰り返しますが、コンストラクタとの類推により、これは呼び出す必要のあるものではありません。PHPがすべてを処理するからです。 このメソッドを使用すると、データベース接続など、オブジェクトで使用したすべてをクリアできます。



 public function __destruct() { $this->connection->destroy(); }
      
      







正直に言うと、上記の__destruct()メソッドのほとんどをあなたから隠しました。 PHPは、実際にプロセスが十分に長い間存在する言語の1つではないため、デストラクタが必要になる可能性があるものはないと思います。 PHP自体のリクエストライフサイクルは非常に小さいため、この方法は良いというよりも面倒です。



ゲッターとセッター



PHPでオブジェクトを操作する場合、次のようにオブジェクトのプロパティを参照したいでしょう。



 $tweet = new Tweet(123, 'hello world'); echo $tweet->text; // 'hello world'
      
      







ただし、textプロパティに保護されたアクセス修飾子が設定されている場合、そのような呼び出しはエラーを引き起こします。

__get()マジックメソッドは、非パブリックプロパティへの呼び出しをキャッチします。



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







__get()メソッドは、アクセスするプロパティの名前を引数として受け取ります。 上記の例では、オブジェクト内のプロパティの存在が最初にチェックされ、存在する場合はその値が返されます。



上記の例のように、このメソッドを直接呼び出すべきではありません。PHPは、非パブリッククラスプロパティにアクセスしようとするたびにこのメソッドを呼び出します。



逆の状況-パブリックではないプロパティの値を設定しようとすると、エラーが発生します。 また、PHPには、非パブリックフィールドに値を設定しようとしたときに呼び出されるメソッドがあります。 このメソッドは、引数として2つのパラメーターを受け取ります。値を書き込むプロパティと値自体です。



このメソッドを使用する場合、クラスは次のようなプロパティを取得します。



 public function __set($property, $value) { if (property_exists($this, $property)) { $this->$property = $value; } } $tweet->text = 'Setting up my twttr'; echo $tweet->text; // 'Setting up my twttr'
      
      







上記の例では、パブリックアクセス修飾子を持たないプロパティの値を取得または設定する方法を示しました。 ただし、これらの魔法の方法で作業することが常に最良のアイデアとは限りません。 この場合、特定のAPIを形成し、ストレージまたは処理メソッドを変更してもコードが破損しないため、プロパティを取得および書き込むための多くのメソッドを用意することをお勧めします。



ただし、__ get()および__set()メソッドが発生することもあります。これらはそれぞれ、一般にゲッターおよびセッターと呼ばれています。 値を変更したり、ビジネスロジックを少し追加したりする場合は、これは非常に良いソリューションです。



存在チェック



PHPに精通している場合、isset()関数の存在を知っている可能性が高く、これは通常、配列を操作するときに使用されます。 プロパティがオブジェクトに設定されているかどうかを理解するために、この関数を使用することもできます。 マジック__isset()メソッドを定義して、パブリックプロパティだけでなく他のプロパティもチェックできるようにすることができます。



 public function __isset($property) { return isset($this->$property); } isset($tweet->text); // true
      
      







上記のように、__ isset()メソッドは関数呼び出しを追跡して存在を確認し、引数としてプロパティの名前を受け取ります。 次に、このメソッドでisset()関数を使用して存在を確認できます。



可変クリーニング

isset()関数との類推により、配列を操作する場合は通常unset()関数が使用されます。 繰り返しますが、unset()関数を使用して、非パブリックプロパティの値をクリアできます。 このメソッドを非パブリックプロパティに適用するには、__ unset()メソッドが必要です。このメソッドは、非パブリッククラスプロパティをクリアする試みを追跡します。



 public function __unset($property) { unset($this->$property); }
      
      







文字列にキャスト



__toString()メソッドを使用すると、オブジェクトを文字列型にキャストしようとしたときに、アプリケーションのロジックを決定できます。

例:



 public function __toString() { return $this->text; } $tweet = new Tweet(1, 'hello world'); echo $tweet; // 'hello world'
      
      







たとえば、エコーを使用する場合など、オブジェクトを文字列として参照しようとすると、__ toString()メソッドで定義したとおりにオブジェクトが返されます。



この場合の良い例は、LaravelフレームワークのEloquent Modelsです。 オブジェクトを文字列にキャストしようとすると、jsonが返されます。 Laravelがこれをどのように行うかを知りたい場合は、 ソースコードを参照することをお勧めします



睡眠と覚醒



シリアル化関数(serialize())は、オブジェクトを格納するかなり一般的な方法です。 たとえば、オブジェクトをデータベースに保存する場合は、最初にオブジェクトをシリアル化してから保存し、再度必要になったときに取得して逆シリアル化する必要があります(非シリアル化())。



__sleep()メソッドを使用すると、保存するプロパティを決定できます。 たとえば、接続や外部リソースを保持したくない場合。



オブジェクトを作成するとき、その保存のメカニズムを決定したいと想像してください。



 $tweet = new Tweet(123, 'Hello world', new PDO ('mysql:host=localhost;dbname=twttr', 'root'));
      
      







オブジェクトを保存する準備をするとき、将来的には無意味になるので、データベースへの接続を保存する必要はありません。

したがって、__sleep()メソッドでは、保存する必要があるプロパティの配列を定義します。



 public function __sleep() { return array('id', 'text'); }
      
      







そして、オブジェクトを目覚めさせる時が来たら、シリアライズ中に保存しなかったすべてのものが必要になるかもしれません。 特定の例では、データベースへの接続を確立する必要があります。 これは、マジック__wakeup()メソッドを使用して実行できます。



 public function __wakeup() { $this->storage->connect(); }
      
      







メソッド呼び出し



__call()マジックメソッドは、非パブリックメソッドを呼び出すすべての試行をインターセプトします。 たとえば、変更するデータの配列がある場合があります。



 class Tweet { protected $id; protected $text; protected $meta; public function __construct($id, $text, array $meta) { $this->id = $id; $this->text = $text; $this->meta = $meta; } protected function retweet() { $this->meta['retweets']++; } protected function favourite() { $this->meta['favourites']++; } public function __get($property) { var_dump($this->$property); } public function __call($method, $parameters) { if (in_array($method, array('retweet', 'favourite'))) { return call_user_func_array(array($this, $method), $parameters); } } } $tweet = new Tweet(123, 'hello world', array('retweets' => 23, 'favourites' => 17)); $tweet->retweet(); $tweet->meta; // array(2) { ["retweets"]=> int(24) ["favourites"]=> int(17) }
      
      







別の典型的な例は、オブジェクトでの別のパブリックAPIの使用です。



 class Location { protected $latitude; protected $longitude; public function __construct($latitude, $longitude) { $this->latitude = $latitude; $this->longitude = $longitude; } public function getLocation() { return array( 'latitude' => $this->latitude, 'longitude' => $this->longitude, ); } } class Tweet { protected $id; protected $text; protected $location; public function __construct($id, $text, Location $location) { $this->id = $id; $this->text = $text; $this->location = $location; } public function __call($method, $parameters) { if(method_exists($this->location, $method)) { return call_user_func_array(array($this->location, $method), $parameters); } } } $location = new Location('37.7821120598956', '-122.400612831116'); $tweet = new Tweet(123, 'Hello world', $location); var_dump($tweet->getLocation()); // array(2) { ["latitude"]=> string(16) "37.7821120598956" ["longitude"]=> string(17) "-122.400612831116" }
      
      







上記の例では、TweetクラスのオブジェクトでgetLocationメソッドを呼び出すことができますが、実際にはLocationクラスに委任します。

静的メソッドを呼び出そうとしている場合は、__ callStatic()マジックメソッドも使用できます。 覚えておくべき主なことは、静的メソッドを呼び出すときにのみ機能することです。



クローニング



PHPでオブジェクトのコピーを作成すると、実際には、新しいオブジェクトが変数に書き込まれるのではなく、元のオブジェクトを参照する識別子が書き込まれます。 つまり、参照元オブジェクトの変更は元のオブジェクトの変更を伴いますが、オブジェクトを削除しても他のオブジェクトの存在には影響しません。



 $sheep1 = new stdClass; $sheep2 = $sheep1; $sheep2->name = "Polly"; $sheep1->name = "Dolly"; echo $sheep1->name; // Dolly echo $sheep2->name; // Dolly
      
      







 $a = new StdClass; $b = $a; $a = null; var_dump($b); // object(stdClass)#1 (0) { }
      
      







オブジェクトのコピーを作成するには、cloneキーワードを使用する必要があります。



 $sheep1 = new stdClass; $sheep2 = clone $sheep1; $sheep2->name = "Polly"; $sheep1->name = "Dolly"; echo $sheep1->name; // Dolly echo $sheep2->name; // Polly
      
      







ただし、複数の関連オブジェクトがある場合、それらに含まれる依存関係もコピーされます。



 class Notification { protected $read = false; public function markAsRead() { $this->read = true; } public function isRead() { return $this->read == true; } } class Tweet { protected $id; protected $text; protected $notification; public function __construct($id, $text, Notification $notification) { $this->id = $id; $this->text = $text; $this->notification = $notification; } public function __call($method, $parameters) { if(method_exists($this->notification, $method)) { return call_user_func_array(array($this->notification, $method), $parameters); } } } $tweet1 = new Tweet(123, 'Hello world', new Notification); $tweet2 = clone $tweet1; $tweet1->markAsRead(); var_dump($tweet1->isRead()); // true var_dump($tweet2->isRead()); // true
      
      







この問題を解決するために、__clone()メソッドを定義して正しい動作を決定できます。



 class Tweet { protected $id; protected $text; protected $notification; public function __construct($id, $text, Notification $notification) { $this->id = $id; $this->text = $text; $this->notification = $notification; } public function __call($method, $parameters) { if(method_exists($this->notification, $method)) { return call_user_func_array(array($this->notification, $method), $parameters); } } public function __clone() { $this->notification = clone $this->notification; } } $tweet1 = new Tweet(123, 'Hello world', new Notification); $tweet2 = clone $tweet1; $tweet1->markAsRead(); var_dump($tweet1->isRead()); // true var_dump($tweet2->isRead()); // false
      
      







オブジェクトを関数として呼び出す



魔法のメソッド__invoke()を使用すると、オブジェクトに関数としてアクセスしようとしたときに、オブジェクトのロジックを判別できます。



 class User { protected $name; protected $timeline = array(); public function __construct($name) { $this->name = $name; } public function addTweet(Tweet $tweet) { $this->timeline[] = $tweet; } } class Tweet { protected $id; protected $text; protected $read; public function __construct($id, $text) { $this->id = $id; $this->text = $text; $this->read = false; } public function __invoke($user) { $user->addTweet($this); return $user; } } $users = array(new User('Ev'), new User('Jack'), new User('Biz')); $tweet = new Tweet(123, 'Hello world'); $users = array_map($tweet, $users); var_dump($users);
      
      







この例では、$ tweetオブジェクトをコールバック関数として$ users配列のすべての値に適用します。 この例では、各ユーザーにツイートを追加します。 私は同意します、この例は少し人工的ですが、この方法のアプリケーションは本当に見つかると確信しています。



おわりに



ご覧のとおり、PHPはマジックメソッドを使用して、オブジェクトのメソッドまたはプロパティで特定のアクションに応答します。 これらの各メソッドは自動的に動作し、何をすべきかを判断するだけで、残りはPHPが処理します。



かなり長い間、私は魔法の方法の本当の意味を理解していませんでした。 オブジェクトを使って面白いことをするためだけに必要だと思いました。 そして最終的に彼らの本当の目的を理解したとき、私はより深刻なアプリケーションのフレームワークでより強力なオブジェクトを書くことができました。



このガイドに示されている各例で、日常のタスクで魔法の手法がどのように役立つかを示すことができたと思います。 そして、私があなたがいくつかの資料を説明するとき、その実際的な応用を説明しないとき、それが本当に迷惑であることに同意する最初ではありません。



投稿者:Philip Brown

オリジナル: culttt.com/2014/04/16/php-magic-methods

ありがとう: werdenderHighQuality



PS申し訳ありませんが、私はいくつかの場所での翻訳がかなり不器用であることを知っています。 あなたがそれがより良く聞こえる方法を知っているなら-私に書いて、私はそれを修正しようとします。



All Articles