PHP只读属性?

66

我使用PHP的DOM类(DOMNode,DOMEElement等)时注意到它们具有真正的只读属性。例如,我可以读取DOMNode的$nodeName属性,但我不能写入它(如果我这样做,PHP会抛出致命错误)。

我如何在PHP中创建自己的只读属性?


10
似乎 "readonly" 是一个特殊关键字,只能用于编译成PHP的类。这很不幸,因为 "readonly public" 将是避免使用 __get() 和 __set() 的绝佳方式。 - shadowhand
2
这在2014年被视为RFC(https://wiki.php.net/rfc/readonly_properties),但在经过相当多的争议后被撤回(http://markmail.org/message/7l3ci3sboma2nlzq)。我很想看到`readonly`作为属性的关键字,这将使生活变得更轻松,不用一遍又一遍地定义getter或使用代理模式。 - e_i_pi
如何在 PHP 中实现只读成员变量? - Reed
目前(2020年6月27日)有一份草案RFC,旨在为PHP 8.0添加只读特性:“这是一个早期的草案,目前正在寻求反馈意见。”作者的电子邮件已列出,您可以通过电子邮件向他们提出建议。 - Reed
2
readonly 在 PHP8.1 中被正式添加,可以像 @shadowhand 建议的那样使用,甚至可以在属性的构造函数样式定义中使用 - 请参见下面的 https://dev59.com/l3RC5IYBdhLWcg3wG9bp#68376398。 - jave.web
7个回答

48
你可以像这样做:
class Example {
    private $__readOnly = 'hello world';
    function __get($name) {
        if($name === 'readOnly')
            return $this->__readOnly;
        user_error("Invalid property: " . __CLASS__ . "->$name");
    }
    function __set($name, $value) {
        user_error("Can't set property: " . __CLASS__ . "->$name");
    }
}

只在你确实需要时使用它 - 它比普通的属性访问要慢。对于PHP,最好采用一种只使用setter方法从外部更改属性的策略。


6
的确!__get 方法非常慢。我有一个配置类使用了类似这样的方法,以便能够访问每个私有变量但不能改变它们。当我通过分析器运行代码时,我对它消耗的时间感到惊讶 :( 真的很遗憾。我希望 PHP 有 readonly 属性。 - AntonioCS
没有__get,这只能在内部(扩展中)实现。 - Artefacto
奇怪的是,他们将它们记录为“readonly public”而不是私有的。 - Rafael Barros
5
现在已经过去了一些年,新的PHP版本也发布了。在PHP中,__get方法仍然太慢吗?我正在使用PHP 7.0。 - rineez
1
如果所有属性都是私有的,那么不需要__set()方法,因为PHP会自动捕获私有属性。另一种方法是将属性设置为public,然后仅使用__set()方法来捕获任何尝试设置属性的操作。此时不需要__get()方法,因为可以直接读取属性。 - Jason

36

自PHP 8.1起,实现了本地只读属性

文档

您只能在属性声明期间初始化只读属性。

class Test {
    public readonly string $prop;

    public function __construct(string $prop) {
        $this->prop = $prop;
    }
}
class Test {
    public function __construct(
        public readonly string $prop,
    ) {}
}
尝试修改只读属性会导致以下错误:
Error: Cannot modify readonly property Test::$prop

更新 PHP 8.2

自 PHP 8.2 版本起,您可以将整个类定义为 readonly

readonly class Test {
    public string $prop;

    public function __construct(string $prop) {
        $this->prop = $prop;
    }
}

4
在使用新的readonly关键字和const关键字时,最大的区别在于继承方面。在8.1之前,常量不会被继承,因此只能由其自身类访问。任何继承的类必须重新实现此常量或使用parent关键字调用父类,并且不能在初始化时设置不同的值。 - Alexander Behling
2
常规文档链接:https://www.php.net/manual/zh/language.oop5.properties.php#language.oop5.properties.readonly-properties - jave.web
1
这个答案应该在当前版本的PHP中标记为正确答案!但对于像VO、DTO和任何您想要应用整个类不可变性的东西,一些被冻结的、不可变甚至是数据关键字都很适用。 - gbrennon
@gbrennon,PHP 8.2将支持只读类。 - Jsowa

12

但是只使用__get()公开的私有属性对于枚举对象成员的函数来说是不可见的,例如json_encode()。

我经常使用json_encode()将PHP对象传递给Javascript,因为它似乎是传递从数据库填充了大量数据的复杂结构的好方法。在这些对象中,我必须使用公共属性,以便将数据传递到使用它的Javascript中,但这意味着这些属性必须是公共的(因此存在另一个程序员直接修改它们而不考虑意图的风险)。如果我将它们设置为私有并使用__get()和__set(),则json_encode()将无法看到它们。

是否有"readonly"可访问性关键字会更好呢?


如果一个变量虽然是“public”但不应该直接被编辑,PHP程序员通常使用约定 $pleaseTouch$_doNotTouch 来表示是否应该依赖于给定的属性。 - Seldom 'Where's Monica' Needy
6
实现 JsonSerializable 接口的类可以通过定义 jsonSerialize 方法来自定义要编码的属性,从而将私有属性序列化。在该方法中,您可以指定要编码的私有属性。 - Taufik Nurrohman

7
这里有一种方法可以使你的类的所有属性都变为只读,但继承该类的子类仍然具有写入权限 ;-)。
class Test {
    protected $foo;
    protected $bar;

    public function __construct($foo, $bar) {
        $this->foo = $foo;
        $this->bar = $bar;
    }

/**
 * All property accessible from outside but readonly
 * if property does not exist return null
 *
 * @param string $name
 *
 * @return mixed|null
 */
    public function __get ($name) {
        return $this->$name ?? null;
    }

/**
 * __set trap, property not writeable
 *
 * @param string $name
 * @param mixed $value
 *
 * @return mixed
 */
    function __set ($name, $value) {
        return $value;
    }
}

已在PHP7中进行测试


是的,我测试过了。你可以很好地获取和设置,但它不会通过你的魔术方法。https://dev59.com/yG445IYBdhLWcg3wwcyg - story
我编辑了public为protected,就像你说的那样,结果对我来说很奇怪,并且像预期的那样工作(我用public进行IDE内省)。 - Le Petit Monde de Purexo
@mrReiha,我认为你的问题的答案是“不”,因为如果这样做,你就无法在布尔上下文中返回评估为false的变量的值,比如0或''...你将得到null值。 - scott8035
@mrReiha,如果变量包含零长度字符串或值为零怎么办?在这种情况下,它将被评估为false并返回null而不是变量中包含的实际值。 - scott8035
3
@mrReiha,如果参数存在且不为null,则??返回左表达式。否则,它将返回右表达式。它充当了一个两部分的if()语句:如果(isset(expr1) && !is_null(expr1)) {返回expr1;} else {返回expr2; }。 - scott8035
显示剩余5条评论

6

我看到您已经得到了答案,但是对于仍在寻找答案的人:

只需将所有“readonly”变量声明为私有或受保护的,并使用魔术方法__get(),如下所示:

/**
 * This is used to fetch readonly variables, you can not read the registry
 * instance reference through here.
 * 
 * @param string $var
 * @return bool|string|array
 */
public function __get($var)
{
    return ($var != "instance" && isset($this->$var)) ? $this->$var : false;
}

如您所见,我还保护了$this->instance变量,因为此方法将允许用户读取所有声明的变量。要阻止几个变量,请使用一个包含在数组中的in_array()。


0

对于那些正在寻找一种将私有/受保护属性暴露出来以进行序列化的方法,如果您选择使用getter方法使其只读,那么这里有一种方法可以实现这一点(@Matt:以json为例):

interface json_serialize {
    public function json_encode( $asJson = true );
    public function json_decode( $value );
}

class test implements json_serialize {
    public $obj = null;
    protected $num = 123;
    protected $string = 'string';
    protected $vars = array( 'array', 'array' );
    // getter
    public function __get( $name ) {
        return( $this->$name );
    }
    // json_decode
    public function json_encode( $asJson = true ) {
        $result = array();
        foreach( $this as $key => $value )
            if( is_object( $value ) ) {
                if( $value instanceof json_serialize )
                    $result[$key] = $value->json_encode( false );
                else
                    trigger_error( 'Object not encoded: ' . get_class( $this ).'::'.$key, E_USER_WARNING );
            } else
                $result[$key] = $value;
        return( $asJson ? json_encode( $result ) : $result );
    }
    // json_encode
    public function json_decode( $value ) {
        $json = json_decode( $value, true );
        foreach( $json as $key => $value ) {
            // recursively loop through each variable reset them
        }
    }
}
$test = new test();
$test->obj = new test();
echo $test->string;
echo $test->json_encode();

3
值得注意的是,自5.4版本起,PHP现在包括了一个JsonSerializable接口。 - smdvlpr

-1
Class PropertyExample {

        private $m_value;

        public function Value() {
            $args = func_get_args();
            return $this->getSet($this->m_value, $args);
        }

        protected function _getSet(&$property, $args){
            switch (sizeOf($args)){
                case 0:
                    return $property;
                case 1:
                    $property = $args[0];
                    break;  
                default:
                    $backtrace = debug_backtrace();
                    throw new Exception($backtrace[2]['function'] . ' accepts either 0 or 1 parameters');
            }
        }


}

这是我处理属性获取/设置的方式,如果你想让Value()只读......那么你只需要将其改为以下内容:

    return $this->m_value;

现在,函数Value()要么获取数据,要么设置数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接