Laravel Collections: Simplifying your data
30.12.2015 / Code

Laravel Collections: Simplifying your data

Insights.

Home / Code / Laravel Collections: Simplifying your data

Laravel Collections: Simplifying your data

Jonathan Ive, Apple’s very own design chief once said: Simplicity, it’s about bringing order to complexity. Good programmers know how to understand complex problems, apply solid logical reasonings and solve them. But only great programmers will first disect them into smaller, more structured chunks. They simplify complex problems.

Keep it simple.

Sometimes getting the right output is just not enough. It is very common in our team to follow up a proposed solution with a question: ‘Can we simplify this further?’ or ‘Is it easy enough for others to understand?’. More often than not, we recalibrate our path of reasoning and come to a completely different solution. Both work, but the latter makes a lot more sense.

We use Laravel a lot (frankly speaking, if you’re PHP programmer but never used Laravel, you are missing out) for more customised client projects and personal ones, and there is a lot to be learned from the philosophy framework itself. At its core, Laravel brings simplicity to web development. Let me repeat that, brings simplicity. It’s not a simple framework, but it brings order to many aspects of web development that makes it feel straight forward.

And while everything in Laravel is designed to make programmers’ life easier, I find Collection stands out as being extremely useful, yet easily overlooked. It is one of the most expressive tools you can get out of Laravel, to handle common data manipulation. After exploring Collections in a bit more detail, you’ll probably think twice about using a nested array. Once you understand how Collections works, you are free to mould your data however you want without much hassle.

Collections simplify your data. In fun, elegant way.

It’s best to showcase the power of Collections using examples.
But before we jump in, let’s take a look at an array of data we will be working with.

Code

// Array of orders
$orders = [
    [
        'date'            => '2015-12-26',
        'supplier'        => 'ACME',
        'item_code'       => 'IPHONE_6S',
        'item_unit_price' => 799.0,
        'qty'             => 1,
        'customer_name'   => 'Andy'
    ], [
        'date'            => '2015-12-26',
        'supplier'        => 'ACME',
        'item_code'       => 'DVD',
        'item_unit_price' => 9.8,
        'qty'             => 5,
        'customer_name'   => 'Andy'  
    ], [
        'date'            => '2015-12-25',
        'supplier'        => 'FOOBAR',
        'item_code'       => 'IPOD',
        'item_unit_price' => 129.9,
        'qty'             => 3,
        'customer_name'   => 'Andy'  
    ], [
        'date'            => '2015-12-25',
        'supplier'        => 'ACME',
        'item_code'       => 'IMAC_RETINA_2015',
        'item_unit_price' => 2999.9,
        'qty'             => 1,
        'customer_name'   => 'Andy'  
    ], [
        'date'            => '2015-12-25',
        'supplier'        => 'ACME',
        'item_code'       => 'DVD',
        'item_unit_price' => 9.8,
        'qty'             => 2,
        'customer_name'   => 'Andy'
    ], [
        'date'            => '2015-12-25',
        'supplier'        => 'FOOBAR',
        'item_code'       => 'IPHONE_6S',
        'item_unit_price' => 799.0,
        'qty'             => 2,
        'customer_name'   => 'Mandy'
    ], [
        'date'            => '2015-12-25',
        'supplier'        => 'ACME',
        'item_code'       => 'DVD',
        'item_unit_price' => 9.8,
        'qty'             => 2,
        'customer_name'   => 'Mandy'
    ], [
        'date'            => '2015-12-25',
        'supplier'        => 'ACME',
        'item_code'       => 'CUPHOLDER',
        'item_unit_price' => 3.8,
        'qty'             => 3,
        'customer_name'   => 'Mandy'
    ]
];
// Turn them into collection
$collect = collect($orders);
// or, for non-laravel user
$collect = new Illuminate\Support\Collection($orders);

Disclaimer: The data above is in no way my representation on how data should be structured, it’s purely for exercise purposes.

Okay lets begin with something simple.

# Group all order items by date.

Easy as pie using collection.

Code

// Group by date
$result = $collect->groupBy('date')->toArray();

Result

array:2 [▼
  "2015-12-26" => array:2 [▼
    0 => array:6 [▼
      "date" => "2015-12-26"
      "supplier" => "ACME"
      "item_code" => "IPHONE_6S"
      "item_unit_price" => 799.0
      "qty" => 1
      "customer_name" => "Andy"
    ]
    1 => array:6 [▼
      "date" => "2015-12-26"
      "supplier" => "ACME"
      "item_code" => "DVD"
      "item_unit_price" => 9.8
      "qty" => 5
      "customer_name" => "Andy"
    ]
  ]
  "2015-12-25" => array:6 [▼
    0 => array:6 [▼
      "date" => "2015-12-25"
      "supplier" => "FOOBAR"
      "item_code" => "IPOD"
      "item_unit_price" => 129.9
      "qty" => 3
      "customer_name" => "Andy"
    ]
    1 => array:6 [▼
      "date" => "2015-12-25"
      "supplier" => "ACME"
      "item_code" => "IMAC_RETINA_2015"
      "item_unit_price" => 2999.9
      "qty" => 1
      "customer_name" => "Andy"
    ]
    2 => array:6 [▼
      "date" => "2015-12-25"
      "supplier" => "ACME"
      "item_code" => "DVD"
      "item_unit_price" => 9.8
      "qty" => 2
      "customer_name" => "Andy"
    ]
    3 => array:6 [▼
      "date" => "2015-12-25"
      "supplier" => "FOOBAR"
      "item_code" => "IPHONE_6S"
      "item_unit_price" => 799.0
      "qty" => 2
      "customer_name" => "Mandy"
    ]
    4 => array:6 [▼
      "date" => "2015-12-25"
      "supplier" => "ACME"
      "item_code" => "DVD"
      "item_unit_price" => 9.8
      "qty" => 2
      "customer_name" => "Mandy"
    ]
    5 => array:6 [▼
      "date" => "2015-12-25"
      "supplier" => "ACME"
      "item_code" => "CUPHOLDER"
      "item_unit_price" => 3.8
      "qty" => 3
      "customer_name" => "Mandy"
    ]
  ]
]

How about nested groups?

# Group all order items by date, and then by each supplier.

Chaining and closures make it easy.

Code

// Group by date, and then supplier
$result = $collect->groupBy('date')->map(function ($item) {
        return $item->groupBy('supplier');
    })
    ->toArray();

Result

array:2 [▼
  "2015-12-26" => array:1 [▼
    "ACME" => array:2 [▼
      0 => array:6 [▼
        "date" => "2015-12-26"
        "supplier" => "ACME"
        "item_code" => "IPHONE_6S"
        "item_unit_price" => 799.0
        "qty" => 1
        "customer_name" => "Andy"
      ]
      1 => array:6 [▼
        "date" => "2015-12-26"
        "supplier" => "ACME"
        "item_code" => "DVD"
        "item_unit_price" => 9.8
        "qty" => 5
        "customer_name" => "Andy"
      ]
    ]
  ]
  "2015-12-25" => array:2 [▼
    "FOOBAR" => array:2 [▼
      0 => array:6 [▼
        "date" => "2015-12-25"
        "supplier" => "FOOBAR"
        "item_code" => "IPOD"
        "item_unit_price" => 129.9
        "qty" => 3
        "customer_name" => "Andy"
      ]
      1 => array:6 [▼
        "date" => "2015-12-25"
        "supplier" => "FOOBAR"
        "item_code" => "IPHONE_6S"
        "item_unit_price" => 799.0
        "qty" => 2
        "customer_name" => "Mandy"
      ]
    ]
    "ACME" => array:4 [▼
      0 => array:6 [▼
        "date" => "2015-12-25"
        "supplier" => "ACME"
        "item_code" => "IMAC_RETINA_2015"
        "item_unit_price" => 2999.9
        "qty" => 1
        "customer_name" => "Andy"
      ]
      1 => array:6 [▼
        "date" => "2015-12-25"
        "supplier" => "ACME"
        "item_code" => "DVD"
        "item_unit_price" => 9.8
        "qty" => 2
        "customer_name" => "Andy"
      ]
      2 => array:6 [▼
        "date" => "2015-12-25"
        "supplier" => "ACME"
        "item_code" => "DVD"
        "item_unit_price" => 9.8
        "qty" => 2
        "customer_name" => "Mandy"
      ]
      3 => array:6 [▼
        "date" => "2015-12-25"
        "supplier" => "ACME"
        "item_code" => "CUPHOLDER"
        "item_unit_price" => 3.8
        "qty" => 3
        "customer_name" => "Mandy"
      ]
    ]
  ]
]

Something with a little bit more data crunching, like

# Total order on each date.

Code

// Total order on each date?
$result = $collect->groupBy('date')->map(function ($item) {
        return $item->sum(function ($item) {
            return ($item['qty'] * $item['item_unit_price']);
        });
    })
    ->toArray();

Result

array:2 [▼
  "2015-12-26" => 848.0
  "2015-12-25" => 5038.2
]

Let’s take it a bit further:

# Get customers who spend more than $3,000 on a single day, on each day of order.

Because sometimes you gotta reward big spenders.

Code

$result = $collect->groupBy('date')->map(function ($item) {
        return $item->groupBy('customer_name')
            ->reject(function ($item) {
                return $item->sum(function ($item) {
                   return ($item['qty'] * $item['item_unit_price']);
                }) < 3000; }) ->keys();
    })
    ->toArray();

Result

array:2 [▼
  "2015-12-26" => []
  "2015-12-25" => array:1 [▼
    0 => "Andy"
  ]
]

 

# Total sales qty for each item, by date. Show `0` for items without any sales.

Code

// Use this to pad results
$dates = $collect->groupBy('date')
    ->map(function ($item) {
        return 0;
    });
$result = $collect->groupBy('item_code')->map(function ($item) use ($dates) {   
        $innerCollect = $item->groupBy('date')
            ->map(function ($item) {
                return $item->sum('qty');
            });
        // The order of merge matters.
        return $dates->merge($innerCollect);
    })
    ->toArray();

Result

array:5 [▼
  "IPHONE_6S" => array:2 [▼
    "2015-12-26" => 1
    "2015-12-25" => 2
  ]
  "DVD" => array:2 [▼
    "2015-12-26" => 5
    "2015-12-25" => 4
  ]
  "IPOD" => array:2 [▼
    "2015-12-26" => 0
    "2015-12-25" => 3
  ]
  "IMAC_RETINA_2015" => array:2 [▼
    "2015-12-26" => 0
    "2015-12-25" => 1
  ]
  "CUPHOLDER" => array:2 [▼
    "2015-12-26" => 0
    "2015-12-25" => 3
  ]
]

Marketing department called, they need you to cross reference both Andy’s and Mandy’s spending:

# List out items that one has but the other person doesn’t, and list items that they both have.

Code

// Exclusive to each other
$allItems = $collect->pluck('item_code');
$result = $collect->groupBy('customer_name')
    ->map(function ($item) use ($allItems)
    {   
        return $allItems
            ->diff($item->pluck('item_code'));
    })
    ->toArray();

// Intersection of each other
$peopleCount = $collect->groupBy('customer_name')->count();
$result = $collect->groupBy('item_code')
    ->filter(function ($item) use ($peopleCount)
    {
        return $item->groupBy('customer_name')->count() == $peopleCount;
    })
    ->keys()
    ->toArray();

Result

array:2 [▼
  "Andy" => array:1 [▼
    7 => "CUPHOLDER"
  ]
  "Mandy" => array:2 [▼
    2 => "IPOD"
    3 => "IMAC_RETINA_2015"
  ]
]
array:2 [▼
  0 => "IPHONE_6S"
  1 => "DVD"
]

 

These are just few examples on the use of Collection but the possibilities are endless. Note that I am using Laravel 5.2 version of Collection so you might want to check if all Collection methods described here are available in your Laravel version. For example `->pluck` only exists from version 5.1+.

That’s all for now, cheers.

Jofry Sutanto

Jofry Sutanto

Coffee-fueled code writer. Need to get in touch? Shoot at jofry@trueagency.com.au .