[PHP]コンストラクタ内で例外を投げるのは危険?

PHP に限らず他の言語でも言われていることですが、クラスのコンストラクタで例外を投げるような構造を取らざるを得ない場合、気をつけて置かなければならないことがあります。

例外の発生によってコンストラクタでの処理が中断され、インスタンスが生成されなかった場合、そのクラスのデストラクタは実行されません。

<?php
class Sample
{
	function __construct(){
		throw new Exception("error");
	}
	
	function __destruct(){
		echo "destruct";
	}
}

try {
	$sample = new Sample();
} catch (Exception $e){
	echo $e->getMessage();
}

生成に失敗したインスタンス変数は NULL のままになってしまうので、コンストラクタで利用した通信の切断やロックの解除をデストラクタやクラスメソッドから行うことはできなくなります。

コンストラクタ内で例外を投げる場合は、エラー発生時の終了処理を完結させた後でスローしたほうが良さそうです。

[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');