Framework: (L5.2) hasMany() - strange collection-keys behaviour

Created on 4 Aug 2016  路  8Comments  路  Source: laravel/framework

Dear community,

After digging through the core for quite some time & trying to find similiar issues, I'm starting to think that this might be a fw-related issue rather than a bug in my code - hopefully, I'm wrong.

Story time: A Device might have (hasMany) Variations, whereas one Variation belongsTo a specific device. Foreign keys are set, I'm accessing variations through $device->variations. This works fine for 90% of the device objects, as I just stumbled accross a few objects which lead to bugs in our Code.

Most of the time, $device->variations returns a collection without associative keys - using dd($device->variations), the Collection->array key is red (sf-dump-index). Using $device->variations->toJson() returns a valid json array.

Example:

[{"id":84,"device_id":19,"storage_size":32,"color":"black","retail_price":"280.00"},{"id":86,"device_id":19,"storage_size":32,"color":"gold","retail_price":"300.00"},{"id":85,"device_id":19,"storage_size":32,"color":"white","retail_price":"280.00"}]

For "some" $devices, the relationship returns an associative array with integer keys rather than integer-indices - by dd()ing, those keys are orange. Converting them through toJson leads to a json-object with sub-objects.

Example:

{"2":{"id":82,"device_id":18,"storage_size":32,"color":"gold","retail_price":"460.00"},"0":{"id":80,"device_id":18,"storage_size":32,"color":"grey","retail_price":"460.00"},"3":{"id":83,"device_id":18,"storage_size":32,"color":"pink","retail_price":"460.00"},"1":{"id":81,"device_id":18,"storage_size":32,"color":"silver","retail_price":"460.00"}}

For me, the issue is "solved" by using $device->variations->values()->toJson(), still I thought that this might not be the expected behaviour, at least it doesn't seem intuitive. Again, there's no obvious difference between those device-objects returning assoc. arrays & those returning index-arrays.

A screenshot of two dd'ed device objects (right: works, left: doesn't work -> json object) is available here..

(PHP7 / Postgresql 9.4 / Ubuntu 14.04 Homestead / L5.2)

BR,

Maximilian

Most helpful comment

Looks like the collection was mutated in a way that some of the items were removed or the sort was changed leaving the indices not sequential e.g. 0, 1, 4, 10, in that case PHP creates a JSON object, otherwise it creates a JSON array.

In PHP all arrays are associative, in JSON arrays are not keyed, so PHP takes the above distinction to generate a JSON string from a given array and decide if it should be a JSON object or a JSON array.

Conclusion

  • json_encode([0 => 'a', 1 => 'b']) will result ["a", "b"].
  • json_encode([0 => 'a', 2 => 'b']) will result {"0": "a", "2": "b"}.
  • json_encode([1 => 'a', 0 => 'b']) will result {"1": "a", "0": "b"}.

What to do

Use $collection->values() before you cast it to JSON.

All 8 comments

I'm unsure if this will help but below should help do the trick. It'll won't fix the underlying issues with how Eloquent serves relationship data without the additional functions, however, I believe ->get() is the intended way to return it.
$device->variations()->get()

or
Response::json($device->variations)

Is the query the same for both variations of the result?

@chrismpettyjohn - according to the doc., my way of accessing it should be fine.

Once the relationship has been defined, we can access the collection of comments by accessing the comments property. Remember, since Eloquent provides "dynamic properties", we can access relationship functions as if they were defined as properties on the model:

$comments = AppPost::find(1)->comments;

foreach ($comments as $comment) {
//
}

@themsaid - I'd have to have a look how to access the query itself, but I can't see any reason why it should not.

Did you get the chance to check the query?

@Huggyduggy Can you please share your relation definition for $device->variations ?

Closing for lack of activity.

Looks like the collection was mutated in a way that some of the items were removed or the sort was changed leaving the indices not sequential e.g. 0, 1, 4, 10, in that case PHP creates a JSON object, otherwise it creates a JSON array.

In PHP all arrays are associative, in JSON arrays are not keyed, so PHP takes the above distinction to generate a JSON string from a given array and decide if it should be a JSON object or a JSON array.

Conclusion

  • json_encode([0 => 'a', 1 => 'b']) will result ["a", "b"].
  • json_encode([0 => 'a', 2 => 'b']) will result {"0": "a", "2": "b"}.
  • json_encode([1 => 'a', 0 => 'b']) will result {"1": "a", "0": "b"}.

What to do

Use $collection->values() before you cast it to JSON.

if you always want the keys you can do:

json_encode([0 => 'a', 1 => 'b'], JSON_FORCE_OBJECT) which will provide {"0":"a","1":"b"}

Was this page helpful?
0 / 5 - 0 ratings

Related issues

iivanov2 picture iivanov2  路  3Comments

lzp819739483 picture lzp819739483  路  3Comments

SachinAgarwal1337 picture SachinAgarwal1337  路  3Comments

shopblocks picture shopblocks  路  3Comments

Anahkiasen picture Anahkiasen  路  3Comments