[PHP]マジックメソッドを使ったアクセサ(__get, __set)

クラスのマジックメソッド __set, __get は、存在しないクラスプロパティや private, protected 等で保護されたプロパティにアクセスする際に自動的に呼び出されます。(ゲッターメソッド/セッターメソッド)

これを使うことでプロパティを取得する際に値を加工して返したり、セットする値が正しいかどうかを確かめたりすることができます。また、private プロパティに読み取りのための __get 処理を用意すると読み取り専用のような振る舞いをさせることができます。

下記のサンプルはクラスに幅(width)と高さ(height)を渡し、面積(area)を返すものと、読み取り専用のプロパティの値を変更しようと試みるテストです。

<?php
class Sample
{
	private $width;
	private $height;
	private $readonly = 'test';
	
	function __get($name){
		if($name === 'area'){
			return $this->width * $this->height;
		} else if($name === 'readonly'){
			return $this->readonly;
		}
	}
	
	function __set($name, $value){
		if($name === 'readonly'){
			throw new Exception('Attempted to assign to readonly property');
		} else if( $name === 'width' || $name === 'height' ){
			if(!is_numeric($value)){
				throw new Exception('Invalid value');
			}
			$this->{$name} = $value;
		}
	}
}

・面積の計算

$sample = new Sample();
$sample->width  = 30;
$sample->height = 20;
echo $sample->area;
結果: 600

インスタンス $sample に幅と高さの値をセットしています。
width と height は private であるため、通常は値の読み書きはできませんが、__set() を用意しているのでそちらを通して値をセットできます。
面積を示すプロパティ area は宣言されていませんが、__get() の中で処理を用意することで存在するプロパティであるかのように動作します。これは getArea() のような専用の関数を用意するのと同じ役割です。

幅と高さを指定する際、数字以外の値が渡されることの無いよう is_numeric() で簡易チェックをしています。これにより「abc」のような文字列をセットしようとしても例外が発生します。

・読み取り専用プロパティへのアクセス

$sample = new Sample();
try {
	echo $sample->readonly; // 「test」
	$sample->readonly = 'hello'; // 例外が発生
} catch(Exception $e){
	echo $e->getMessage();
}

プロパティ readonly も private なのでそのままでは読み書きできません。
__get()、__set() を両方用意すれば読み書きが可能になりますが、__set() にセットを禁止する処理を加えているので書き込もうとすると例外が発生します。

[PHP]メソッドチェーン

PHP でも Java などの言語同様メソッドチェーンを使った処理を書くことができます。
サンプルでは文字「<Hello, World!>」という文字に対して置換と加工を行ったものを出力しています。

<?php
$str = new MyString('<Hello, World!>');
echo $str->replace('World', 'PHP')->toUpperCase()->h();

class MyString
{
	private $str;
	
	function __construct($value){
		$this->str = $value;
	}
	
	function __toString(){
		return (string)$this->str;
	}
	
	function replace($old, $new){
		$this->str = str_replace($old, $new, $this->str);
		return $this;
	}
	
	function toUpperCase(){
		$this->str = strtoupper($this->str);
		return $this;
	}
	
	function h($encoding='UTF-8'){
		$this->str = htmlspecialchars($this->str, ENT_QUOTES, $encoding);
		return $this;
	}
}

実行結果

&lt;HELLO, PHP!&gt;

メソッドの返り値を「return $this;」とすることで処理後のオブジェクトを次のメソッドに渡しています。 行っている内容は下と同じですが順序が明確になりすっきりと書くことができます。

echo htmlspecialchars(strtoupper(str_replace('World', 'PHP', '<Hello, World!>')),
	ENT_QUOTES, 'UTF-8');

[PHP]switch()は通常では厳密に型を比較しない

プログラミングである変数の値によって処理を分岐する際、switch() を使うのは珍しいことではありませんが、PHP で switch() を使うには注意が必要です。

というのも、PHPでの比較は「===」ではなく「==」による緩やかな比較であるため、それを考慮していないと狙いと違う動作をすることになります。

次のサンプルは文字列型の変数 $sample を空文字で初期化し、switch() で処理を分けています。
型を区別していれば「Blank」の文字が出力されるのですが実際には「null」が出力されます。

<?php
$sample = '';

switch($sample){
	case null:
		echo 'null';
		break;
	case 0:
		echo '0';
		break;
	case '':
		echo 'Blank';
		break;
	default:
		echo 'Default';
		break;
}

もし型を厳密に比較するのであれば if による分岐を使うか、次のようにします。

<?php
$sample = '';

switch(true){
	case $sample === null:
		echo 'null';
		break;
	case $sample === 0:
		echo '0';
		break;
	case $sample === '':
		echo 'Blank';
		break;
	default:
		echo 'Default';
		break;
}

狙った動作が実現できるとはいえ本流から外れた使い方なので、そもそも厳密な型比較が必要なケースで switch() を使うこと自体正しくないのかもしれません。

参考サイト: http://php.net/manual/ja/control-structures.switch.php