繁体   English   中英

如何在 PHP 中实现只读成员变量?

[英]How to implement a read-only member variable in PHP?

当试图改变它时,抛出一个异常。

我想对于类属性的解决方案是:

  • 不要使用您感兴趣的名称定义属性
  • 使用魔术__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

我也使用 trait 编写了一个版本。

尽管在这种情况下,该属性仍然可以由其声明类设置。

声明一个类,如:

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上查看源代码和测试

我知道这是一个老问题,但 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;
        }
    }
}

我制作了另一个版本,在 docblock 中使用@readonly而不是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上查看源代码和测试

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM