尝试更改它时,抛出异常。
7 回答
我想对于类属性的解决方案是:
- 不使用您感兴趣的名称定义属性
- 使用魔术
__get
方法访问该属性,使用“假”名称 - 定义该
__set
方法,以便在尝试设置该属性时引发异常。 - 有关魔术方法的更多信息,请参阅重载。
对于变量,我认为不可能有一个只读变量,当您尝试写入它时,PHP 会为其抛出异常。
例如,考虑这个小类:
class MyClass {
protected $_data = array(
'myVar' => 'test'
);
public function __get($name) {
if (isset($this->_data[$name])) {
return $this->_data[$name];
} else {
// non-existant property
// => up to you to decide what to do
}
}
public function __set($name, $value) {
if ($name === 'myVar') {
throw new Exception("not allowed : $name");
} else {
// => up to you to decide what to do
}
}
}
实例化类并尝试读取属性:
$a = new MyClass();
echo $a->myVar . '<br />';
将为您提供预期的输出:
test
在尝试写入属性时:
$a->myVar = 10;
会给你一个例外:
Exception: not allowed : myVar in /.../temp.php on line 19
class test {
const CANT_CHANGE_ME = 1;
}
你把它称为test::CANT_CHANGE_ME
使用常数。关键词const
简短的回答是您不能在 PHP 中创建只读对象成员变量。
事实上,大多数面向对象的语言都认为公开公开成员变量是一种糟糕的形式……(C# 是其属性结构的大而丑陋的例外)。
如果您想要一个类变量,请使用const
关键字:
class MyClass {
public const myVariable = 'x';
}
可以访问此变量:
echo MyClass::myVariable;
无论您创建了多少不同类型的对象,此变量都将存在于一个版本中MyClass
,并且在大多数面向对象的场景中,它几乎没有用处。
但是,如果您想要一个每个对象可以具有不同值的只读变量,则应该使用私有成员变量和访问器方法(又名 getter):
class MyClass {
private $myVariable;
public function getMyVariable() {
return $this->myVariable;
}
public function __construct($myVar) {
$this->myVariable = $myVar;
}
}
该变量在构造函数中设置,并且由于没有设置器而被设置为只读。但是 的每个实例都MyClass
可以有自己的值myVariable
。
$a = new MyClass(1);
$b = new MyClass(2);
echo $a->getMyVariable(); // 1
echo $b->getMyVariable(); // 2
$a->setMyVariable(3); // causes an error - the method doesn't exist
$a->myVariable = 3; // also error - the variable is private
我也使用一个特征制作了一个版本。
尽管在这种情况下,该属性仍然可以由其声明类设置。
声明一个类,如:
class Person {
use Readonly;
protected $name;
//simply declaring this means "the 'name' property can be read by anyone"
private $r_name;
}
这就是我创造的特质:
trait Readonly {
public function readonly_getProperty($prop){
if (!property_exists($this,$prop)){
//pretty close to the standard error if a protected property is accessed from a public scope
// throw new \Error("Property '{$prop}' on class '".get_class($this)."' does not exist");
trigger_error('Undefined property: '.get_class($this).'::\$'.$prop,E_USER_NOTICE);
}
$allow_read = property_exists($this, 'r_'.$prop );
if ($allow_read){
$actual = $this->$prop;
return $actual;
}
throw new \Error("Cannot access non-public property '{$prop}' of class '".get_class($this)."'");
}
public function __get($prop){
return $this->readonly_getProperty($prop);
}
}
在我的 gitlab上查看源代码和测试
我制作了另一个@readonly
在 docblock 中使用的版本,而不是private $r_propname
. 这仍然不会阻止声明类设置属性,但适用于公共只读访问。
样本类:
class Person {
use Readonly;
/**
* @readonly
*/
protected $name;
protected $phoneNumber;
public function __construct($name){
$this->name = $name;
$this->phoneNumber = '123-555-1234';
}
}
ReadOnly
特质_
trait Readonly {
public function readonly_getProperty($prop){
if (!property_exists($this,$prop)){
//pretty close to the standard error if a protected property is accessed from a public scope
trigger_error('Undefined property: '.get_class($this).'::\$'.$prop,E_USER_NOTICE);
}
$refProp = new \ReflectionProperty($this, $prop);
$docblock = $refProp->getDocComment();
// a * followed by any number of spaces, followed by @readonly
$allow_read = preg_match('/\*\s*\@readonly/', $docblock);
if ($allow_read){
$actual = $this->$prop;
return $actual;
}
throw new \Error("Cannot access non-public property '{$prop}' of class '".get_class($this)."'");
}
public function __get($prop){
return $this->readonly_getProperty($prop);
}
}
在我的 gitlab上查看源代码和测试
我知道这是一个老问题,但 PASCAL 的回答确实帮助了我,我想补充一点。
__get() 不仅会触发不存在的属性,还会触发“不可访问”的属性,例如受保护的属性。这使得制作只读属性变得容易!
class MyClass {
protected $this;
protected $that;
protected $theOther;
public function __get( $name ) {
if ( isset( $this->$name ) ) {
return $this->$name;
} else {
throw new Exception( "Call to nonexistent '$name' property of MyClass class" );
return false;
}
}
public function __set( $name ) {
if ( isset( $this->$name ) ) {
throw new Exception( "Tried to set nonexistent '$name' property of MyClass class" );
return false;
} else {
throw new Exception( "Tried to set read-only '$name' property of MyClass class" );
return false;
}
}
}