Why is my php array being saved to MongoDB as an object, then being retrieved as an associative array with stringified keys? - php

Background:
I have a class in this application I'm building whose job is:
__construct: $this->data = $this->mongoDB->collection->findOne();
Intermediate functions are employed to manipulate the data in tens of different ways each request. One manipulation could trigger one which would trigger another. This allows me to do unlimited updates to the mongo document with just one query, as long as $this->data['_id'] remains the same. This is the only place where data manipulation of this specific collection is allowed.
__destruct: $this->monboDB->collection->save($data)
Data is then read back, json_encode'd and sent to Javascript to draw the page
Intention:
I intended to delete a member of an array by looping through said array, matching a value within it, and unsetting that. Example:
foreach($this->data['documents'] as $key => $val){
if($val == $toBeDeleted){
unset($this->data['documents'][$key];
}
}
Then, this would be saved to the DB when the script finishes.
Problem:
When javascript reads back the data, rather than having ['a', 'b', 'd'], I had {'0': 'a', '1': 'b', '3': 'd'} - which can't be treated like an array and would pretty much break things.

I had this question half typed out before my a-hah! moment, so I figured I'd post my own answer to it too for future reference.
In php, an associative array and an array are all the same. You can have out of order keys, nonconsecutive keys, and almost any key that you'd like to use in calling your array member. Most, if not all, php array functions work with any array key. Objects are a totally different thing.
That being said, Javascript doesn't share the same rules for arrays. A javascript array must have consecutive keys starting at zero, otherwise it is an object. MongoDB is similar to Javascript in this way.
When php converts an object to be used in MongoDB or in Javascript, if the php array doesn't follow that rule, it becomes a Javascript object.
The problem was after unsetting an array index, it left a gap, causing nonconsecutive array keys, causing it to become an object. Simple fix would either be array_slice($array, $key, 1) or $array = array_values($array)

Related

Finding the length of an associative array encoded by PHP

I have a multidiminsional array that I have created in php that is passed back to a jQuery script. I need to iterate through this array and process the data.
In Firebug I can see that the data is located at data.data.items. I've tried finding the length of the array using data.data.items.length, but it comes back as undefined. Interestingly, this worked prior to my php portion working correctly when it passed back an array of 8 empty items. Now that it's populated (and the indexes are strings), length doesn't work. There is also an object in each of the items. What's breaking this?
An Array in JavaScript is an object nonetheless. When setting values using strings (or anything that isn't an integer), you are actually setting a property of the object (you are actually doing this when setting it with integer keys as well, but it's handled slightly differently).
To the issue of its sudden breakage after using strings as keys, I would expect that PHP realizes when you have an honest-to-goodness array versus an associative array, thus it sends arrays (surrounded by []) when all keys are integers, and objects (surrounded by {}) otherwise. I believe in the string-keyed case, PHP is generating objects, and thus .length becomes undefined (rather than 0 as in an empty array).
To answer your question, there is a simple way to count the "length" of this data:
var i = 0;
for (var item in data.data.items) {
i++;
}
Which will iterate through each property of data.data.items and count them. Note that if you (or any library you include) adds a property to the Object prototype, this will not produce expected results. This is fairly uncommon, but you must be aware of it when using for..in.
You can address this by using the method Nagh suggested, which ignores properties not defined on that particular object:
var i = 0;
for (var item in data.data.items) {
if(data.data.items.hasOwnProperty(item)) {
i++;
}
}
You can always use "foreach" kind of loop. In this case, you don't need to know what array length is there or even is it array or not, since you can iterate over object properties aswell.
As Joe already has pointed, javascript doesn't have associative arrays and when you trying to use one - you end up with object with properties. However, if u sure, that only properties this object got - is your array you can use code like that:
for (i in arr) {
if (arr.hasOwnProperty(i)) {
//do something with arr[i]
}
}
However if you really need an array, consider using integer as an array index.

Why would one want to pass primitive-type parameters by reference in PHP?

One thing that's always bugged me (and everyone else, ever) about PHP is its inconsistency in function naming and parameters. Another more recent annoyance is its tendency to ask for function parameters by reference rather than by value.
I did a quick browse through the PHP manual, and found the function sort() as an example. If I was implementing that function I'd take an array by value, sort it into a new array, and return the new value. In PHP, sort() returns a boolean, and modifies the existing array.
How I'd like to call sort():
$array = array('c','a','b');
$sorted_array = sort($array);
How PHP wants me to call sort():
$array = array('c','a','b');
sort($array);
$sorted_array = $array;
And additionally, the following throws a fatal error: Fatal error: Only variables can be passed by reference
sort(array('c','a','b');
I'd imagine that part of this could be a legacy of PHP's old days, but there must have been a reason things were done this way. I can see the value in passing an object by reference ID like PHP 5+ does (which I guess is sort of in between pass by reference and pass by value), but not in the case of strings, arrays, integers and such.
I'm not an expert in the field of Computer Science, so as you can probably gather I'm trying to grasp some of these concepts still, and I'm curious as to whether there's a reason things are set up this way, or whether it's just a leftover.
The main reason is that PHP was developed by C programmers, and this is very much a C-programming paradigm. In C, it makes sense to pass a pointer to a data structure you want changed. In PHP, not so much (Among other things, because references are not the same as a pointer).
I believe this is done for speed-reason.
Most of the time you need the array you are working on to be sorted, not a copy.
If sort should have returned a new copy of the array then for each time you call sort(); the PHP engine should have copied the array into new one (lowering speed and increasing space cost) and you would have no way to control this behaviour.
If you need the original array to be not sorted (and this doesn't happen so often) then just do:
$copy = $yourArray;
sort($yourArray);

Unify variable types of array elements

After hours of debugging, I found an error in one of my scripts. For saving different event types in a database, I have an array of unique data for each event that can be used to identify the event.
So I basically have some code like
$key = md5(json_encode($data));
to generate a unique key for each event.
Now, in some cases, a value in the $data array is an integer, sometimes a string (depending on where it comes from - database or URL). That causes the outputs of json_encode() to be different from each other, though - once including quotes, once not.
Does anybody know a way to "unify" the variable types in the $data array? That would probably mean converting all strings that only contain an integer value to integer. Anything else I have to take care of when using json_encode()?
array_walk_recursive combined with a function you have written to the effect of maybe_intval which performs the conversion you talk about on a single element.
EDIT: having read the documentation for array_walk_recursive more closely you'll actually want to write your own recursive function
function to_json($obj){
if(is_object($obj))
$obj=(array)$obj;
if(is_array($obj))
return array_map('to_json',$obj);
return "$obj"; // or return is_int($obj)?intval($obj):$obj;
}

Will the Order of my Associative Array be maintained from PHP to Javascript?

In PHP I'm running a mysql_query that has an ORDER BY clause. I'm then iterating through the results to build an associative array, with the row_id as the key.
Then, I'm calling json_encode on that array and outputting the result.
This page is loaded with AJAX, and defined in a Javascript variable. When I iterate through that Javascript variable, will I still have the order that was returned from the mysql_query?
PHP arrays are somewhat unique in their property of maintaining insertion order. Javascript doesn't have associative arrays per se. It has objects, which are often used as associative arrays. These do not guarantee any particular key order.
Why not output them as an array? That will have a particular order. If you want some sort of key lookup why does the order matter?
What cletus says is correct, but in my experience, most browsers will maintain the order. That being said, you should consider using an Array. If you need to sort it once you receive it on the client-side, just use the .sort() function in JavaScript:
rows.sort(function(a, b) {
return a.row_id - b.row_id;
}
Though it seems like it works, the order of properties in an object can't be counted on. See the many comments below for more info (smarter eyes than mine). However, this was the code I used to test the behavior in my own limited testing:
var test = {
one: 'blah',
two: 'foo',
another: 'bar'
};
for (prop in test) {
document.write(prop + "<br />");
}
Prints (in Firefox 3.6.3 and Chrome 5.0.375.9):
one
two
another
Also, you may want to be sure you're getting the type of JSON encoding you're needing back from json_encode(), such as an object (uses {} curly braces) and not an array ([] braces). You may need to pass JSON_FORCE_OBJECT to json_encode() to force it.
Edited to clarify that the Array approach is preferred)
Edited again (sorry), as I had overlooked pcorcoran's comment, which has a link to an issue in Chromium's issue tracker regarding this. Suffice to say, the order an object's properties is not reliable.

Is json_decode in PHP guaranteed to preserve ordering of elements when returning an array?

You can pass a boolean to json_decode to return an array instead of an object
json_decode('{"foo", "bar", "baz"}', true); // array(0 => 'foo', 1 => 'bar', 2 => 'baz')
My question is this. When parsing object literals, does this guarantee that the ordering of the items will be preserved? I know JSON object properties aren't ordered, but PHP arrays are. I can't find anywhere in the PHP manual where this is addressed explicitly. It probably pays to err on the side of caution, but I would like to avoid including some kind of "index" sub-property if possible.
Wouldn't it make more sense in this case to use an array when you pass the JSON to PHP. If you don't have any object keys in the JSON (which become associative array keys in PHP), just send it as an array. That way you will be guaranteed they will be in the same order in PHP as in javascript.
json_decode('{["foo", "bar", "baz"]}');
json_decode('["foo", "bar", "baz"]'); //I think this would work
If you need associative arrays (which is why you are passing the second argument as true), you will have to come up with some way to maintain their order when passing. You will pry have to do some post-processing on the resulting array after you decode it to format it how you want it.
$json = '{[ {"key" : "val"}, {"key" : "val"} ]}';
json_decode($json, true);
Personally, I've never trusted any system to return an exact order unless that order is specifically defined. If you really need an order, then use a dictionary aka 2dimension array and assigned a place value (0,1,2,3...) to each value in the list.
If you apply this rule to everything, you'll never have to worry about the delivery/storage of that array, be it XML, JSON or a database.
Remember, just because something happens to work a certain way, doesn't mean it does so intentionally. It's akin to thinking rows in a database have an order, when in fact they don't unless you use an ORDER BY clause. It's unsafe to think ID 1 always comes before ID 2 in a SELECT.
I've used json_decode() some times, and the results order was kept intact with PHP client apps. But with Python for instance it does not preserve the order.
One way to be reassured is to test it over with multiple examples.
Lacking an explicit statement I'd say, by definition, no explicit order will be preserved.
My primary line of thought it what order, exactly, would this be preserving? The json_decode function takes a string representation of a javascript object literal as it's argument, and then returns either an object or an array. The function's input (object literal as string) has no explicit ordering, which means there's no clear order for the json_decode function to maintain.

Categories