PHPでメンバ変数の管理をガチガチにしてみた
PHPは型が違っていても問答無用で突っ込めたり、メンバ変数に宣言されていなくても動的に作成できたりして、適当な人がコードを書くと途端にスパゲッティ化してしまう。
他人が書いたコード内のバグを追っている時とか、C++出身の自分としてはこれが非常にもどかしかったりする。
なのでガチガチにしてみた。
https://gist.github.com/HogeTatu/5735002
<?php abstract class ObjectBase { private $_fieldValueList = array(); private $_fieldTypeList = array(); private $_lock = false; public final function __construct() { $privateFieldList = self::_getPrivateFieldList(); foreach ($this as $label => $value) { if (isset($privateFieldList[$label])) continue; $this->_fieldTypeList[$label] = gettype($value); $this->_fieldValueList[$label] = $value; unset($this->{$label}); } if (method_exists($this, '_initialize')) { call_user_func_array(array($this, '_initialize'), func_get_args()); } } private static final function _getPrivateFieldList() { static $privateFieldList = null; if ($privateFieldList !== null) return $privateFieldList; $privateFieldList = array(); $classVars = get_class_vars(get_class()); foreach ($classVars as $label => $value) { $privateFieldList[$label] = $label; } return $privateFieldList; } public final function __get($label) { if (isset($this->_fieldTypeList[$label]) === false) { throw new Exception("{$label} is not found in field list."); } return $this->_fieldValueList[$label]; } public final function __set($label, $value) { if ($this->_lock) { throw new Exception("field list is locked."); } if (isset($this->_fieldTypeList[$label]) === false) { throw new Exception("{$label} is not found in field list."); } $oldType = $this->_fieldTypeList[$label]; $newType = gettype($value); if ($oldType !== 'NULL' && $oldType !== $newType) { throw new Exception("type of {$label} is {$oldType} rather than {$newType}."); } $this->_fieldTypeList[$label] = $newType; $this->_fieldValueList[$label] = $value; } public function lock() { $this->_lock = true; } public function toArray() { return $this->_fieldValueList; } } //========================================================= class Hoge extends ObjectBase { public $foo = 1; public $bar = null; protected function _initialize($a, $b) { echo "a = {$a}\n"; echo "b = {$b}\n"; } } //========================================================= $hoge = new Hoge(1, 2); $hoge->foo = 2; // OK //$hoge->foo = 2.5; // NG echo "{$hoge->foo}\n"; $hoge->bar = 1.5; // OK //$hoge->bar = 1; // NG echo "{$hoge->bar}\n"; $hoge->lock(); //$hoge->foo = 3; // NG var_dump($hoge->toArray()); /* a = 1 b = 2 2 1.5 array(2) { ["foo"]=> int(2) ["bar"]=> float(1.5) } */
メンバ変数に何かをSETしようとした時に、型が違った場合はエラーにする。
NULLで初期化されている時は自由な型で一度だけSETできる。
宣言されていないメンバ変数のGET/SETは認めない。
これでバグを追う時に余計なことを気にしなくて済む。
もちろん動的に何かをやりたくて__getや__setを使いたいクラスでは使えない。
使える場面で使うといった感じで。
【追記】
上記実装ではアクセス制限に関して問題があるため、そこを考慮した実装に修正しなければならない。
また、__getや__setはpublicでの宣言しか許されないため、どこからアクセスされたのかをbacktraceを見て判断しなければならない。
うーむ、ボツだな。