リフレクションと__get/__setメソッドを使ってtypesafeを実現する
リフレクションと__get/__setメソッドをうまく使って実現する方法が紹介されています。
掲載されているコードをざっとまとめてみました。PHP5.2.1で動作確認しています。リフレクションのgetDocCommentメソッドを使って、コード内のコメントに定義された型情報を取得しています。この辺は参考になります :-)
I always disliked the way PHP handles Objects. There is no way to assign a type to properties. Validators have to be glued against the fields externally and you can't just generate a Object-Description (like WSDL) from a object either.
なお、PHPでは多重継承ができないので、そこが難点になるんじゃないでしょうか。
<?php /** * @see http://jan.kneschke.de/2007/2/19/typesafe-objects-in-php */ class POPO { function hasProperty($k) { $r = new ReflectionObject($this); return $r->hasProperty($k); } function getPropertyType($k) { $o = new ReflectionObject($this); $p = $o->getProperty($k); $dc = $p->getDocComment(); if (!preg_match("#@var\s+([a-z]+)#", $dc, $a)) { return false; } return $a[1]; } } class POPOTypeSafe extends POPO { function __get($k) { if (!$this->hasProperty($k)) { throw new Exception(sprintf("'%s' has no property '%s'", get_class($this), $k)); } if (!isset($this->$k)) { return NULL; } return $this->$k; } function __set($k, $v) { if (!$this->hasProperty($k)) { throw new Exception(sprintf("'%s' has no property '%s'", get_class($this), $k)); } if (!($type = $this->getPropertyType($k))) { throw new Exception(sprintf("'%s'.'%s' has no type set", get_class($this), $k)); } if (!$this->isValid($k, $v, $type)) { throw new Exception(sprintf("'%s'.'%s' = %s is not valid for '%s'", get_class($this), $k, $v, $type)); } $this->$k = $v; } function isValid($k, $v, $type) { if (!isset($v)) return false; if (is_null($v)) return false; switch ($type) { case "int": case "integer": case "timestamp": return (is_numeric($v)); case "string": return true; default: throw new Exception(sprintf("'%s'.'%s' has invalid type: '%s'", get_class($this), $k, $type)); } } } class Employee extends POPOTypeSafe { /** @var int*/ protected $employee_id; /** * @var string */ protected $name; /** @var string */ protected $surname; /** @var timestamp */ protected $since; } $employee = new Employee(); var_dump($employee); /** * nameに対する操作 */ var_dump($employee->name); try { $employee->name = 'Jan'; } catch (Exception $e) { echo $e->getMessage(); } var_dump($employee->name); /** * employee_idに対する操作 */ try { /** * 型が合っていない */ $employee->employee_id = "foobar"; } catch (Exception $e) { echo $e->getMessage(); } try { /** * 型が合っている */ $employee->employee_id = 1; } catch (Exception $e) { echo $e->getMessage(); } var_dump($employee->employee_id); /** * 存在しないプロパティ */ try { $employee->unknown = 1; } catch (Exception $e) { echo $e->getMessage(); }