Framework: Map method modifies original collection

Created on 26 Jun 2018  Â·  6Comments  Â·  Source: laravel/framework

  • Laravel Version: 5.6.26
  • PHP Version: 7.1.10

Description:

Hello guys

The following code is about a product filter system im building, this is the part where the product object is generated. Before that i have pulled from the db all the options of the products and items of those products.

Im using the option_list and option_items_list objects to structure the options of the products and where its neccessary using the map,transform,each etc.

`$options_list = DB::table('options as o')->join('options_of_products as oop', function ($join) {

                $join->on('oop.options_entity_id', '=', 'o.entity_id');
                $join->on('oop.store_id', '=', 'o.store_id');
                $join->on('oop.locale', '=', 'o.locale');

            })
            ->select('o.*','oop.products_entity_id')
            ->where('o.locale', session(Tools::gou().'language_code'))
            ->where('o.store_id', session(Tools::gou().'store_id'))
            ->where('oop.filter', 1)
            ->where('o.status', 1)
            ->whereIn('oop.products_entity_id', $products->pluck('entity_id'))
            ->groupBy('oop.products_entity_id')
            ->orderBy('o.sort_order')
            ->get();


        //get option items
        $option_items_list = DB::table('option_items as oi')

            ->join('option_items_of_products as oiop', function ($join) {

                $join->on('oiop.option_items_entity_id', '=', 'oi.entity_id');
                $join->on('oiop.store_id', '=', 'oi.store_id');
                $join->on('oiop.locale', '=', 'oi.locale');

            })
            ->select('oi.id', 'oi.entity_id', 'oi.title', 'oiop.image', 'oi.option_id','oiop.products_entity_id')
            ->where('oi.locale', session(Tools::gou().'language_code'))
            ->where('oi.store_id', session(Tools::gou().'store_id'))
            ->where('oiop.quantity', '>', 0)
            ->whereIn('oiop.products_entity_id', $products->pluck('entity_id'))
            ->groupBy('oi.entity_id')
            ->get();


        $products->transform(function ($product) use ($discounts,$brands,$options_list,$option_items_list,$options) {

            //add discount
            $product->discount = $discounts->where('products_entity_id', $product->entity_id)->first();

            //add brand
            $product->brand = $brands->where('entity_id',$product->brands_id)->first();

            $product_options = $options_list->where('products_entity_id',$product->entity_id)->map(function ($option) use ($product,$option_items_list){

                    $result_items = $option_items_list->where('products_entity_id',$product->entity_id)->where('option_id',$option->entity_id);

                    if(!is_null($result_items)){

                        $option->items = $result_items;

                    } else {

                        $option->items = null;
                    }

                    return $product;
            });

            //add options
            $product->options = $product_options ?? null;

            return $product;
        });

`

According to the docs the map method passes the item to the fallback function where i can modify it.
My understating is that this function dose not edit the currect collection but every edit i made is saved to the new collection the method creates.

If i wanted to edit the currect collection that would be the job of transform method.

The problem im facing is that when i use the map method on the options_list the method also changes the original collection.

Example with the above code.

This is what i get when i do a dd($option_list) just after the query which is also what i want to keep.

Collection {#1143 â–¼ #items: array:2 [â–¼ 0 => {#1215 â–¼ +"id": 1 +"entity_id": 1 +"store_id": 1 +"locale": "el" +"option_type_id": 4 +"title": "Option" +"icon_image": null +"status": 1 +"sort_order": 1 +"products_entity_id": 3801 } 1 => {#1220 â–¼ +"id": 1 +"entity_id": 1 +"store_id": 1 +"locale": "el" +"option_type_id": 4 +"title": "Option" +"icon_image": null +"status": 1 +"sort_order": 1 +"products_entity_id": 3800 } ] }

And this is the collection after the map proccess inside the product object.

Collection {#1143 â–¼ #items: array:2 [â–¼ 0 => {#1215 â–¼ +"id": 1 +"entity_id": 1 +"store_id": 1 +"locale": "el" +"option_type_id": 4 +"title": "Option" +"icon_image": null +"status": 1 +"sort_order": 1 +"products_entity_id": 3801 +"items": Collection {#1293 â–¼ #items: array:1 [â–¼ 1 => {#1252 â–¼ +"id": 2 +"entity_id": 2 +"title": "Item-2" +"image": "" +"option_id": 1 +"products_entity_id": 3801 } ] } } 1 => {#1220 â–¼ +"id": 1 +"entity_id": 1 +"store_id": 1 +"locale": "el" +"option_type_id": 4 +"title": "Option" +"icon_image": null +"status": 1 +"sort_order": 1 +"products_entity_id": 3800 +"items": Collection {#1295 â–¼ #items: array:1 [â–¼ 0 => {#1259 â–¼ +"id": 1 +"entity_id": 1 +"title": "item-1" +"image": "" +"option_id": 1 +"products_entity_id": 3800 } ] } } ] }

What i miss here? Any help would be appreciated.

Most helpful comment

You have to clone the individual items:

$object_not_to_change = DB::table('table')->get();

$new_object = $object_not_to_change->map(function($item) {
    $copy_of_item = clone $item;

    return $copy_of_item;
});

All 6 comments

map() does in fact not the change collection itself, it returns a new collection.

But: The values in your collection are objects. So if you change an object in the new collection, you also change this object in the original collection. That's how PHP works.

If $option_list has to remain unchanged, you have to clone the objects.

I see i didnt know that about objects. Thanks mate but still trying to clone still has the references

What code are you using?

Im trying something like this

$object_not_to_change = DB::table('table')->get();

$copy_of_object = clone $object_not_to_change;

$new_object = copy_of_object->map(function($item){

return $item;
});

And the old object still changes

As i did a deeper digging for this to work there must be a magic method __clone() inside the class, and most the cases people are using models, BUT im using only the query builder. Is there a way inject the method on certain cases?

You have to clone the individual items:

$object_not_to_change = DB::table('table')->get();

$new_object = $object_not_to_change->map(function($item) {
    $copy_of_item = clone $item;

    return $copy_of_item;
});

@staudenmeir yeap that worked many thanks!!!!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

shopblocks picture shopblocks  Â·  3Comments

lzp819739483 picture lzp819739483  Â·  3Comments

Fuzzyma picture Fuzzyma  Â·  3Comments

felixsanz picture felixsanz  Â·  3Comments

iivanov2 picture iivanov2  Â·  3Comments