只读多维数组属性,PHP

发布于 2024-11-15 06:37:35 字数 3295 浏览 3 评论 0原文

我已经迷恋 ArrayAccess 和 PHP 的魔法(__get__set)有一段时间了,我'我卡住了。

我正在尝试实现一个类,其中某些属性(数组)是只读的。它们最初将由构造函数设置,但此后不应修改。

通过引用使用 __get 魔法,我可以访问属性中任意深度的数组元素,并且我认为当通过 __set 定位这些属性时,我可以抛出异常。

但问题是,当我访问数组元素的值时,PHP 正在调用 __get 来通过引用返回数组的该部分,并且我不知道它是否是读取的或写动作。

最糟糕的是我知道会发生这种情况,但一直在欺骗 ArrayAccess 作为可能的解决方案,因为属性是已实现对象的实例

简单示例:

class Test{
    public function &__get($key){
        echo "[READ:{$key}]\n";
    }
    public function __set($key, $value){
        echo "[WRITE:{$key}={$value}]\n";
    }
}

$test = new Test;

$test->foo;
$test->foo = 'bar';

$test->foo['bar'];
$test->foo['bar'] = 'zip';

输出:

[READ:foo]
[WRITE:foo=bar]
[READ:foo]
[READ:foo] // here's the problem

实际上,我只需要值 foo根据我的示例),但我需要知道这是一个写入操作,未读。

我已经一半接受了这是不可能实现的,但我仍然充满希望。有谁知道我想要实现的目标如何实现?

我正在考虑使用 ArrayAccess 的一些可能的解决方法,但据我所知,我最终会回到这个位置,因为我将使用调用 __get 的属性表示法。


更新:又是与 ArrayAccess 一起度过的有趣的一天。

这是一个不同的问题,但我想它可以工作。发帖只是为了好玩。

class Mf_Params implements ArrayAccess{

    private $_key       = null;
    private $_parent    = null;
    private $_data      = array();
    private $_temp      = array();

    public function __construct(Array $data = array(), $key = null, self $parent = null){
        $this->_parent  = $parent;
        $this->_key     = $key;
        foreach($data as $key => $value){
            $this->_data[$key] = is_array($value)
                ? new self($value, $key, $this)
                : $value;
        }
    }

    public function toArray(){
        $array = array();
        foreach($this->_data as $key => $value){
            $array[$key] = $value instanceof self
                ? $value->toArray()
                : $value;
        }
        return $array;
    }

    public function offsetGet($offset){
        if(isset($this->_data[$offset])){
            return $this->_data[$offset];
        }
        // if offset not exist return temp instance
        return $this->_temp[$offset] = new self(array(), $offset, $this);
    }

    public function offsetSet($offset, $value){
        $child = $this;
        // copy temp instances to data after array reference chain
        while(!is_null($parent = $child->_parent) && $parent->_temp[$child->_key] === $child){
            $parent->_data[$child->_key] = $parent->_temp[$child->_key];
            $child  = $parent;
        }
        // drop temp
        foreach($child->_temp as &$temp){
            unset($temp);
        }
        if(is_null($offset)){
            $this->_data[] = is_array($value)
                ? new self($value, null, $this)
                : $value;
        }else{
            $this->_data[$offset] = is_array($value)
                ? new self($value, $offset, $this)
                : $value;
        }
    }

    public function offsetExists($offset){
        return isset($this->_data[$offset]);
    }

    public function offsetUnset($offset){
        unset($this->_data[$offset]);
    }

}

I've been fooling with ArrayAccess and PHP's magic (__get, __set) for awhile now, and I'm stuck.

I'm trying to implement a class in which some properties, which are arrays, are read only. They will be set initially by the constructor, but should not be modifiable thereafter.

Using __get magic by reference, I can access array elements arbitrarily deep in the properties, and I was thinking I can throw exceptions when those properties are targeted via __set.

The problem is though, when I'm accessing the value of an array element, PHP is calling __get to return that part of the array by reference, and I have no knowledge of whether or not its a read or write action.

(The worst part is I knew this going in, but have been fooling with ArrayAccess as a possible workaround solution, given the properties were instances of an implemented object)

Simple example:

class Test{
    public function &__get($key){
        echo "[READ:{$key}]\n";
    }
    public function __set($key, $value){
        echo "[WRITE:{$key}={$value}]\n";
    }
}

$test = new Test;

$test->foo;
$test->foo = 'bar';

$test->foo['bar'];
$test->foo['bar'] = 'zip';

And the output:

[READ:foo]
[WRITE:foo=bar]
[READ:foo]
[READ:foo] // here's the problem

Realistically, I only need the value foo (as per my example) anyways, but I need to know it's a write action, not read.

I've already half accepted that this cannot be achieved, but I'm still hopeful. Does anyone have any idea how what I'm looking to accomplish can be done?

I was considering some possible workarounds with ArrayAccess, but so far as I can tell, I'll end up back at this spot, given I'm going to use the property notation that invokes __get.


Update: Another fun day with ArrayAccess.

(This is a different issue, but I suppose it works in. Posting just for kicks.)

class Mf_Params implements ArrayAccess{

    private $_key       = null;
    private $_parent    = null;
    private $_data      = array();
    private $_temp      = array();

    public function __construct(Array $data = array(), $key = null, self $parent = null){
        $this->_parent  = $parent;
        $this->_key     = $key;
        foreach($data as $key => $value){
            $this->_data[$key] = is_array($value)
                ? new self($value, $key, $this)
                : $value;
        }
    }

    public function toArray(){
        $array = array();
        foreach($this->_data as $key => $value){
            $array[$key] = $value instanceof self
                ? $value->toArray()
                : $value;
        }
        return $array;
    }

    public function offsetGet($offset){
        if(isset($this->_data[$offset])){
            return $this->_data[$offset];
        }
        // if offset not exist return temp instance
        return $this->_temp[$offset] = new self(array(), $offset, $this);
    }

    public function offsetSet($offset, $value){
        $child = $this;
        // copy temp instances to data after array reference chain
        while(!is_null($parent = $child->_parent) && $parent->_temp[$child->_key] === $child){
            $parent->_data[$child->_key] = $parent->_temp[$child->_key];
            $child  = $parent;
        }
        // drop temp
        foreach($child->_temp as &$temp){
            unset($temp);
        }
        if(is_null($offset)){
            $this->_data[] = is_array($value)
                ? new self($value, null, $this)
                : $value;
        }else{
            $this->_data[$offset] = is_array($value)
                ? new self($value, $offset, $this)
                : $value;
        }
    }

    public function offsetExists($offset){
        return isset($this->_data[$offset]);
    }

    public function offsetUnset($offset){
        unset($this->_data[$offset]);
    }

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

人海汹涌 2024-11-22 06:37:35

您需要使用第二个类(实现ArrayAccess)来代替数组。然后,您将能够使用 offsetSet() 方法控制添加到数组中的内容:

class ReadOnlyArray implements ArrayAccess {
    private $container = array();
    public function __construct(array $array) {
        $this->container = $array;
    }
    public function offsetSet($offset, $value) {
        throw new Exception('Read-only');
    }
    public function offsetExists($offset) {
        return isset($this->container[$offset]);
    }
    public function offsetUnset($offset) {
        unset($this->container[$offset]);
    }
    public function offsetGet($offset) {
        if (! array_key_exists($offset, $this->container)) {
            throw new Exception('Undefined offset');
        }
        return $this->container[$offset];
    }
}

然后您可以使用原始数组初始化 ReadOnlyArray

$readOnlyArray = new ReadOnlyArray(array('foo', 'bar'));

You need to use a second class, implementing ArrayAccess, to use instead of your arrays. Then you will be able to control what is added to the array with the offsetSet() method:

class ReadOnlyArray implements ArrayAccess {
    private $container = array();
    public function __construct(array $array) {
        $this->container = $array;
    }
    public function offsetSet($offset, $value) {
        throw new Exception('Read-only');
    }
    public function offsetExists($offset) {
        return isset($this->container[$offset]);
    }
    public function offsetUnset($offset) {
        unset($this->container[$offset]);
    }
    public function offsetGet($offset) {
        if (! array_key_exists($offset, $this->container)) {
            throw new Exception('Undefined offset');
        }
        return $this->container[$offset];
    }
}

You can then initialize your ReadOnlyArray with your original array:

$readOnlyArray = new ReadOnlyArray(array('foo', 'bar'));
ヅ她的身影、若隐若现 2024-11-22 06:37:35

您无法通过 ref 返回,这将解决可更改性问题,但不允许更改某些允许更改的值。

或者,您也需要将每个返回的数组包装在 ArrayAccess 中 - 并禁止在那里进行写访问。

You could not return by ref, which would solve the problem of changability, but would not allow changing of some values that are allowed to be changed.

Alternatively you need to wrap every returned array in ArrayAccess, too - and forbid write access there.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文