I had this question earlier and it was concluded it was a bug in 5.2.5. Well, it's still broken in 5.2.6, at least for me:
Please let me know if it is broken or works for you:
$obj = new stdClass();
$obj->{"foo"} = "bar";
$obj->{"0"} = "zero";
$arr = (array)$obj;
//foo -- bar
//0 -- {error: undefined index}
foreach ($arr as $key=>$value){
echo "$key -- " . $arr[$key] . "\n";
}
Any ways to "fix" the array after it has been cast from a stdClass?
Definitely seems like a bug to me (PHP 5.2.6).
You can fix the array like this:
$arr = array_combine(array_keys($arr), array_values($arr));
It's been reported in this bug report but marked as bogus... the documentation says:
The keys are the member variable
names, with a few notable exceptions:
integer properties are unaccessible;
A bit of experimentation shows phps own functions don't persist this fubarity.
function noopa( $a ){ return $a; }
$arr = array_map('noopa', $arr );
$arr[0]; # no error!
This in effect, just creates a copy of the array, and the fix occurs during the copy.
Ultimately, its a design failure across the board, try using array_merge in the way you think it works on an array with mixed numeric and string keys?
All numbered elements get copied and some get re-numbered, even if some merely happen to be string-encapsulated-numbers, and as a result, there are dozens of homebrew implementations of array_merge just to solve this problem.
Back when php tried to make a clone of perl and failed, they didn't grasp the concept of arrays and hashes being distinct concepts, an instead globbed them together into one universal umbrella. Good going!.
For their next trick, they manage to break namespace delimiters because of some technical problem that no other language has for some reason encountered.
Thanks RoBorg.. I just discovered that as well :)
Here's another solution, not sure if it's faster or not:
unserialize(serialize($arr));
Related
I had this question earlier and it was concluded it was a bug in 5.2.5. Well, it's still broken in 5.2.6, at least for me:
Please let me know if it is broken or works for you:
$obj = new stdClass();
$obj->{"foo"} = "bar";
$obj->{"0"} = "zero";
$arr = (array)$obj;
//foo -- bar
//0 -- {error: undefined index}
foreach ($arr as $key=>$value){
echo "$key -- " . $arr[$key] . "\n";
}
Any ways to "fix" the array after it has been cast from a stdClass?
Definitely seems like a bug to me (PHP 5.2.6).
You can fix the array like this:
$arr = array_combine(array_keys($arr), array_values($arr));
It's been reported in this bug report but marked as bogus... the documentation says:
The keys are the member variable
names, with a few notable exceptions:
integer properties are unaccessible;
A bit of experimentation shows phps own functions don't persist this fubarity.
function noopa( $a ){ return $a; }
$arr = array_map('noopa', $arr );
$arr[0]; # no error!
This in effect, just creates a copy of the array, and the fix occurs during the copy.
Ultimately, its a design failure across the board, try using array_merge in the way you think it works on an array with mixed numeric and string keys?
All numbered elements get copied and some get re-numbered, even if some merely happen to be string-encapsulated-numbers, and as a result, there are dozens of homebrew implementations of array_merge just to solve this problem.
Back when php tried to make a clone of perl and failed, they didn't grasp the concept of arrays and hashes being distinct concepts, an instead globbed them together into one universal umbrella. Good going!.
For their next trick, they manage to break namespace delimiters because of some technical problem that no other language has for some reason encountered.
Thanks RoBorg.. I just discovered that as well :)
Here's another solution, not sure if it's faster or not:
unserialize(serialize($arr));
Suppose that we have this code (simplified example):
$propertyName = 'items';
$foo = new \stdClass;
$foo->$propertyName = array(42);
At this point I 'd like to write an expression that evaluates to a reference to the value inside the array.
Is it possible to do this? If so, how?
An answer that will "do the job" but is not what I 'm looking for is this:
// Not acceptable: two statements
$temp = &$foo->$propertyName;
$temp = &$temp[0];
But why write it as two statements? Well, because this will not work:
// Will not work: PHP's fantastic grammar strikes again
// This is actually equivalent to $temp = &$foo->i
$temp = &$foo->$propertyName[0];
Of course &$foo->items[0] is another unacceptable solution because it fixes the property name.
In case anyone is wondering about the strange requirements: I 'm doing this inside a loop where $foo itself is a reference to some node inside a graph. Any solution involving $temp would require an unset($temp) afterwards so that setting $temp on the next iteration does not completely mess up the graph; this unset requirement can be overlooked if you are not ultra careful around references, so I was wondering if there is a way to write this code such that there are less possibilities to introduce a bug.
Ambiguous expressions like that need some help for the parser:
$temp = &$foo->{$propertyName}[0];
Btw, this is the same regardless if you're looking for the variable alias (a.k.a. reference) or just the value. Both need it if you use the array-access notation.
It took some googling, but I found the solution:
$propertyName = 'items';
$foo = new \stdClass;
$foo->$propertyName = array(42);
$temp = &$foo->{$propertyName}[0]; // note the brackets
$temp++;
echo $temp;
print_r($foo->$propertyName);
This will print 43 array(43).
I found the solution in this article: Referencing an array in a variable object property by Jeff Beeman
I ran into this php syntax the other day and I am not familiar with it. I *guessed it might doing a push, but I really don't know. Is this *exactly the same as array_push($b). If it is accomplishing something *similar, please explain how it is different.
$foo = array();
foreach($bar as $b)
{
$foo[] = $b; //push?
}
The only difference is the tiny bit of extra overhead involved in making a function call to array_push() vs making use of the language construct [] to append onto an array. They are functionally equivalent.
The difference between them from that function call is going to be utterly miniscule to the degree that you needn't worry about it unless you are doing it millions of times.
$foo[] = $b will be slightly faster due to the overhead of a function call (as Michael stated below).
Additionally, as stated in the manual, if the first argument to array_push is not an array a notice is raised. Using the array bracket notation will simply create a new array if it does not already exist.
I'm creating a wrapper function around mysqli so that my application needn't be overly complicated with database-handling code. Part of that is a bit of code to parameterize the SQL calls using mysqli::bind_param(). bind_param(), as you may know, requires references. Since it's a semi-general wrapper, I end up making this call:
call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs);
and I get an error message:
Parameter 2 to mysqli_stmt::bind_param() expected to be a reference, value given
The above discussion is to forstall those who would say "You don't need references at all in your example".
My "real" code is a bit more complicated than anyone wants to read, so I've boiled the code leading up to this error into the following (hopefully) illustrative example:
class myclass {
private $myarray = array();
function setArray($vals) {
foreach ($vals as $key => &$value) {
$this->myarray[] =& $value;
}
$this->dumpArray();
}
function dumpArray() {
var_dump($this->myarray);
}
}
function myfunc($vals) {
$obj = new myclass;
$obj->setArray($vals);
$obj->dumpArray();
}
myfunc(array('key1' => 'val1',
'key2' => 'val2'));
The problem appears to be that, in myfunc(), in between the call to setArray() and the call to dumpArray(), all the elements in $obj->myarray stop being references and become just values instead. This can be easily seen by looking at the output:
array(2) {
[0]=>
&string(4) "val1"
[1]=>
&string(4) "val2"
}
array(2) {
[0]=>
string(4) "val1"
[1]=>
string(4) "val2"
}
Note that the array is in the "correct" state in the first half of the output here. If it made sense to do so, I could make my bind_param() call at that point, and it would work. Unfortunately, something breaks in the latter half of the output. Note the lack of the "&" on the array value types.
What happened to my references? How can I prevent this from happening? I hate to call "PHP bug" when I'm really not a language expert, but could this be one? It does seem very odd to me. I'm using PHP 5.3.8 for my testing at the moment.
Edit:
As more than one person pointed out, the fix is to change setArray() to accept its argument by reference:
function setArray(&$vals) {
I'm adding this note to document WHY this seems to work.
PHP generally, and mysqli in particular, appear to have a slightly odd concept of what a "reference" is. Observe this example:
$a = "foo";
$b = array(&$a);
$c = array(&$a);
var_dump($b);
var_dump($c);
First of all, I'm sure you're wondering why I'm using arrays instead of scalar variables -- it's because var_dump() doesn't show any indication of whether a scalar is a reference, but it does for array members.
Anyway, at this point, $b[0] and $c[0] are both references to $a. So far, so good. Now we throw our first wrench into the works:
unset($a);
var_dump($b);
var_dump($c);
$b[0] and $c[0] are both still references to the same thing. If we change one, both will still change. But what are they references to? Some unnamed location in memory. Of course, garbage collection insures that our data is safe, and will remain so, until we stop refering to it.
For our next trick, we do this:
unset($b);
var_dump($c);
Now $c[0] is the only remaining reference to our data. And, whoa! Magically, it's no longer a "reference". Not by var_dump()'s measure, and not by mysqli::bind_param()'s measure either.
The PHP manual says that there's a separate flag, 'is_ref' on every piece of data. However, this test appears to suggest that 'is_ref' is actually equivalent to '(refcount > 1)'
For fun, you can modify this toy example as follows:
$a = array("foo");
$b = array(&$a[0]);
$c = array(&$a[0]);
var_dump($a);
var_dump($b);
var_dump($c);
Note that all three arrays have the reference mark on their members, which backs up the idea that 'is_ref' is functionally equivalent to '(refcount > 1)'.
It's beyond me why mysqli::bind_param() would care about this distinction in the first place (or perhaps it's call_user_func_array()... either way), but it would appear that what we "really" need to ensure is that the reference count is at least 2 for each member of $this->bindArgs in our call_user_func_array() call (see the very beginning of the post/question). And the easiest way to do that (in this case) is to make setArray() pass-by-reference.
Edit:
For extra fun and games, I modified my original program (not shown here) to leave its equivalent to setArray() pass-by-value, and to create a gratuitous extra array, bindArgsCopy, containing exactly the same thing as bindArgs. Which means that, yes, both arrays contained references to "temporary" data which was deallocated by the time of the second call. As predicted by the analysis above, this worked. This demonstrates that the above analysis is not an artifact of var_dump()'s inner workings (a relief to me, at least), and it also demonstrates that it's the reference count that matters, not the "temporary-ness" of the original data storage.
So. I make the following assertion: In PHP, for the purpose of call_user_func_array() (and probably more), saying that a data item is a "reference" is the same thing as saying that the item's reference count is greater than or equal to 2 (ignoring PHP's internal memory optimizations for equal-valued scalars)
Administrivia note: I'd love to give mario the site credit for the answer, as he was the first to suggest the correct answer, but since he wrote it in a comment, not an actual answer, I couldn't do it :-(
Pass the array as a reference:
function setArray(&$vals) {
foreach ($vals as $key => &$value) {
$this->myarray[] =& $value;
}
$this->dumpArray();
}
My guess (which could be wrong in some details, but is hopefully correct for the most part) as to why this makes your code work as expected is that when you pass as a value, everything's cool for the call to dumpArray() inside of setArray() because the reference to the $vals array inside setArray() still exist. But when control returns to myfunc() then that temporary variable is gone as are all references to it. So PHP dutifully changes the array to string values instead of references before deallocating the memory for it. But if you pass it as a reference from myfunc() then setArray() is using references to an array that lives on when control returns to myfunc().
Adding the & in the argument signature fixed it for me. This means the function will receive the memory address of the original array.
function setArray(&$vals) {
// ...
}
CodePad.
I just encountered this same problem in almost exactly the same situation (I'm writing a PDO wrapper for my own purposes). I suspected that PHP was changing the reference to a value once no other variables were referencing the same data, and Rick, your comments in the edit to your question confirmed that suspicion, so thank you for that.
Now, for anybody else who comes across something like this, I believe I have the simplest solution: simply set each relevant array element to a reference to itself before passing the array to call_user_func_array().
I'm not sure exactly what happens internally because it seems like that shouldn't work, but the element becomes a reference again (which you can see with var_dump()), and call_user_func_array() then passes the argument by reference as expected. This fix seems to work even if the array element is still a reference already, so you don't have to check first.
In Rick's code, it would be something like this (everything after the first argument for bind_param is by reference, so I skip the first one and fix all of the ones after that):
for ($i = 1, $count = count($this->bindArgs); $i < $count; $i++) {
$this->bindArgs[$i] = &$this->bindArgs[$i];
}
call_user_func_array(array($stmt, 'bind_param'), $this->bindArgs);
In php, I often need to map a variable using an array ... but I can not seem to be able to do this in a one liner. c.f. example:
// the following results in an error:
echo array('a','b','c')[$key];
// this works, using an unnecessary variable:
$variable = array('a','b','c');
echo $variable[$key];
This is a minor problem, but it keeps bugging every once in a while ... I don't like the fact, that I use a variable for nothing ;)
The technical answer is that the Grammar of the PHP language only allows subscript notation on the end of variable expressions and not expressions in general, which is how it works in most other languages. I've always viewed it as a deficiency in the language, because it is possible to have a grammar that resolves subscripts against any expression unambiguously. It could be the case, however, that they're using an inflexible parser generator or they simply don't want to break some sort of backwards compatibility.
Here are a couple more examples of invalid subscripts on valid expressions:
$x = array(1,2,3);
print ($x)[1]; //illegal, on a parenthetical expression, not a variable exp.
function ret($foo) { return $foo; }
echo ret($x)[1]; // illegal, on a call expression, not a variable exp.
This is called array dereferencing. It has been added in php 5.4.
http://www.php.net/releases/NEWS_5_4_0_alpha1.txt
update[2012-11-25]: as of PHP 5.5, dereferencing has been added to contants/strings as well as arrays
I wouldn't bother about that extra variable, really. If you want, though, you could also remove it from memory after you've used it:
$variable = array('a','b','c');
echo $variable[$key];
unset($variable);
Or, you could write a small function:
function indexonce(&$ar, $index) {
return $ar[$index];
}
and call this with:
$something = indexonce(array('a', 'b', 'c'), 2);
The array should be destroyed automatically now.
This might not be directly related.. But I came to this post finding solution to this specific problem.
I got a result from a function in the following form.
Array
(
[School] => Array
(
[parent_id] => 9ce8e78a-f4cc-ff64-8de0-4d9c1819a56a
)
)
what i wanted was the parent_id value "9ce8e78a-f4cc-ff64-8de0-4d9c1819a56a".
I used the function like this and got it.
array_pop( array_pop( the_function_which_returned_the_above_array() ) )
So, It was done in one line :)
Hope It would be helpful to somebody.
function doSomething()
{
return $somearray;
}
echo doSomething()->get(1)->getOtherPropertyIfThisIsAnObject();
actually, there is an elegant solution:) The following will assign the 3rd element of the array returned by myfunc to $myvar:
$myvar = array_shift(array_splice(myfunc(),2));
Or something like this, if you need the array value in a variable
$variable = array('a','b','c');
$variable = $variable[$key];
There are several oneliners you could come up with, using php array_* functions. But I assure you that doing so it is total redundant comparing what you want to achieve.
Example you can use something like following, but it is not an elegant solution and I'm not sure about the performance of this;
array_pop ( array_filter( array_returning_func(), function($key){ return $key=="array_index_you_want"? TRUE:FALSE; },ARRAY_FILTER_USE_KEY ) );
if you are using a php framework and you are stuck with an older version of php, most frameworks has helping libraries.
example: Codeigniter array helpers
though the fact that dereferencing has been added in PHP >=5.4 you could have done it in one line using ternary operator:
echo $var=($var=array(0,1,2,3))?$var[3]:false;
this way you don't keep the array only the variable. and you don't need extra functions to do it...If this line is used in a function it will automatically be destroyed at the end but you can also destroyed it yourself as said with unset later in the code if it is not used in a function.