可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Why the following
$a = new SplFixedArray(5);
$a[0] = array(1, 2, 3);
$a[0][0] = 12345; // here
var_dump($a);
produces
Notice: Indirect modification of overloaded element of SplFixedArray has no effect in <file> on line <indicated>
Is it a bug? How do you deal with multidimensional SplFixedArrays then? Any workarounds?
回答1:
First, the problem is related to all classes which implement ArrayAccess
it is not a special problem of SplFixedArray
only.
When you accessing elements from SplFixedArray
using the []
operator it behaves not exactly like an array. Internally it's offsetGet()
method is called, and will return in your case an array - but not a reference to that array. This means all modifications you make on $a[0]
will get lost unless you save it back:
Workaround:
$a = new SplFixedArray(5);
$a[0] = array(1, 2, 3);
// get element
$element = $a[0];
// modify it
$element[0] = 12345;
// store the element again
$a[0] = $element;
var_dump($a);
Here is an example using a scalar which fails too - just to show you that it is not related to array elements only.
回答2:
This is actually fixable if you slap a &
in front of offsetGet
(assuming you have access to the internals of your ArrayAccess
implementation):
class Dict implements IDict {
private $_data = [];
/**
* @param mixed $offset
* @return bool
*/
public function offsetExists($offset) {
return array_key_exists(self::hash($offset), $this->_data);
}
/**
* @param mixed $offset
* @return mixed
*/
public function &offsetGet($offset) {
return $this->_data[self::hash($offset)];
}
/**
* @param mixed $var
* @return string
*/
private static function hash($var) {
return is_object($var) ? spl_object_hash($var) : json_encode($var,JSON_UNESCAPED_SLASHES);
}
/**
* @param mixed $offset
* @param mixed $value
*/
public function offsetSet($offset, $value) {
$this->_data[self::hash($offset)] = $value;
}
/**
* @param mixed $offset
*/
public function offsetUnset($offset) {
unset($this->_data[self::hash($offset)]);
}
}
回答3:
Adding my experience with the same error, in case it helps anyone:
I recently imported my code into a framework with a low error-tolerance (Laravel). As a result, my code now throws an exception when I try to retrieve a value from an associative array using a non-existent key. In order to deal with this I tried to implement my own dictionary using the ArrayAccess interface. This works fine, but the following syntax fails:
$myDict = new Dictionary();
$myDict[] = 123;
$myDict[] = 456;
And in the case of a multimap:
$properties = new Dictionary();
$properties['colours'] = new Dictionary();
$properties['colours'][] = 'red';
$properties['colours'][] = 'blue';
I managed to fix the problem with the following implementation:
<?php
use ArrayAccess;
/**
* Class Dictionary
*
* DOES NOT THROW EXCEPTIONS, RETURNS NULL IF KEY IS EMPTY
*
* @package fnxProdCrawler
*/
class Dictionary implements ArrayAccess
{
// FOR MORE INFO SEE: http://alanstorm.com/php_array_access
protected $dict;
function __construct()
{
$this->dict = [];
}
// INTERFACE IMPLEMENTATION - ArrayAccess
public function offsetExists($key)
{
return array_key_exists($key, $this->dict);
}
public function offsetGet($key)
{
if ($this->offsetExists($key))
return $this->dict[$key];
else
return null;
}
public function offsetSet($key, $value)
{
// NOTE: THIS IS THE FIX FOR THE ISSUE "Indirect modification of overloaded element of SplFixedArray has no effect"
// NOTE: WHEN APPENDING AN ARRAY (E.G. myArr[] = 5) THE KEY IS NULL, SO WE TEST FOR THIS CONDITION BELOW, AND VOILA
if (is_null($key))
{
$this->dict[] = $value;
}
else
{
$this->dict[$key] = $value;
}
}
public function offsetUnset($key)
{
unset($this->dict[$key]);
}
}
Hope it helps.
回答4:
I did a workaround to this problem extending SplFixedArray
and overriding offsetGet()
to return a reference*
But as this hek2mgl mentioned, it could lead to side effects.
I shared the code to do it, because I couldn't find it in other place. Note is not serious implementation, because I'm not even checking the offset exists (I will be glad if someone propose enhancements), but it works:
class mySplFixedArray extends SplFixedArray{
public function &offsetGet($offset) {
return $this->array[$offset];
}
}
I was changing native PHP hash like arrays for these less memory consuming fixed length arrays, and some of the other things I have to change too (either as a consequence of the lazy extending of the class SplFixedArray, or just for not using native arrays) were:
- Creating a manual method for copying my class objects property by property.
clone
didn't worked anymore.
- Use
a[i]!==NULL
to check if the element exists, because isset()
didn't worked anymore.
- Add an
offsetSet()
method to the extended class too:
public function offsetSet($offset,$value) { $this->array[$offset]=$value; }
(*) I think this overriding is only possible after some PHP version between 5.2.6 and 5.3.4. I couldn't find too much info or code about this problem, but I want to share the solution for other people anyway.
回答5:
I guess SplFixedArray is incomplete/buggy.
If i wrote an own class and it works like a charm:
$a = new \myArrayClass();
$a[0] = array(1, 2, 3);
$a[0][0] = 12345;
var_dump($a->toArray());
Output (no notices/warnings here, in strict mode too):
array (size=1)
0 =>
array (size=3)
0 => int 12345
1 => int 2
2 => int 3
Using the [] operator is not a problem (for assoc/mixed arrays too). A right implementation of offsetSet should do the job:
public function offsetSet($offset, $value) {
if ($offset === null) {
$offset = 0;
if (\count($this->array)) {
$keys = \preg_grep( '#^(0|([1-9][0-9]*))$#', \array_keys($this->array));
if (\count($keys)) {
$offset = \max($keys) + 1;
}
}
}
...
But there is only one exception. Its not possible to use the [] operator for offset which does not exist. In our example:
$a[1][] ='value'; // Notice: Indirect modification of overloaded...
It would throw the warning above because ArrayAccess calls offsetGet and not offsetSet for [1] and the later [] fails. Maybe there is a solution out there, but i did not find it yet. But the following is working without probs:
$a[] ='value';
$a[0][] ='value';
I would write an own implementation instead of using SplFixedArray. Maybe its possible to overload some methods in SplFixedArray to fix it, but i am not sure because i never used and checked SplFixedArray.