Tìm hiểu về Collection trong Laravel (Phần 1)

2017-10-29

Collection là gì?

Collection là một class trong Laravel giúp lập trình viên làm việc với mảng dễ dàng hơn, đặc biệt khi thao tác với lượng dữ liệu từ Database vì mặc định trong Laravel kiểu dữ liệu trả về từ Laravel sẽ là Collection.

Một điều đặc biệt nữa là giá trị trong collection sẽ bất biến (immutable), nghĩa là khi ta làm cái khỉ gì với các phương thức của Collection thì nó vẫn sẽ luôn trả về Collection.

Vì số lượng phương thức trong collection rất là nhiều (80 phương thức), nên bài này mình sẽ chia ra thành 3 phần.

Tạo Collection

Việc tạo một collection trong Laravel rất là dễ, như khai báo mảng hay các kiểu khác vậy:

$collection = collect([1, 2, 3]);

Các phương thức trong Collection

Phương thức liệt kê

all()

Phương thức all() sẽ trả về một mảng tương ứng với Collection đó, ví dụ:

$collect([1, 2, 3])->all()

// [1, 2, 3]

average()

Đây là một cái tên khác được đặt ra cho dễ nhớ của phương thức avg()

avg()

Phương thức avg() sẽ trả về giá trị trung bình của các key đã cho, ví dụ:

$idols = collect([
    ['name' => 'Yui Hatano', 'age' => 29],
    ['name' => 'Takizawa Laura', 'age' => 24],
      ['name' => 'Ai Shinozaki', 'age' => 25]
    ])->avg('age');

// 26

Hoặc:

$cum_time = collect([15, 20, 10])->avg();

// 15

chunk()

Phương thức chunk() sẽ chia nhỏ Collection đã cho thành các Collection khác nằm với kích thước đã cho, ví dụ:

$ball_length = collect([1, 2, 3, 4, 5, 6, 7]);

$chunks = $ball_length->chunk(4);

Lúc này $chunks sẽ là một collection chứa 2 collection mới. Ta sẽ thử kiểm tra bằng cách chuyển nó sang một mảng:

$ball_length->toArray();

// [[1, 2, 3, 4], [5, 6, 7]]

collapse()

Phương thức này sẽ gom hết các mảng trong collection thành một mảng:

$collect =  collect([[0, 9, 8], [9, 9, 9], [9, 9, 9, 9]])

$number_phone = $collect->collapse();

$number_phone->all();

// [0, 9, 8, 9, 9, 9, 9, 9, 9, 9]

combine()

Phương thức này sẽ kết hợp Collection và một mảng khác, trong đó mảng trong Collection được coi là key còn mảng thêm vào sẽ là value (key => value):

$collect = collect(['name', 'age']);

$info = $collect->combine(['Doge', 69]);

$info->all();

// ['name' => 'Doge', 'born' => 69]

contains()

Phương thức này sẽ kiểm tra trong Collection xem có giá trị đã cho không:

$long_bi = collect(['age' => 15, 'gender' => '?', 'feedback' => 'cancer']);

$long_bi->contains('cancer');

// true

$long_bi->contains('male');

// false

count()

Cái tên đã nói lên tất cả, phương thức này sẽ trả về số phần tử trong Collection:

$hash = collect(['md5', 'sha1', 'aes']);

$hash->count();

// 3

diff()

Phương thức này sẽ so sánh mảng trong Collection với một mảng khác và trả về một collection chứa các giá trị khác nhau dựa trên value trong 2 mảng:

$collection = collect([1, 2, 3, 4, 5]);

$diff = $collection->diff([2, 4, 6, 8]);

$diff->all();

// [1, 3, 5]

diffKeys()

Phương thức này cũng giống phương thức diff() ở trên, nhưng thay vì so sánh value, nó sẽ so sánh các key với nhau:

$collection = collect([
    'one' => 10,
    'two' => 20,
    'three' => 30,
    'four' => 40,
    'five' => 50,
]);

$diff = $collection->diffKeys([
    'two' => 2,
    'four' => 4,
    'six' => 6,
    'eight' => 8,
]);

$diff->all();

// ['one' => 10, 'three' => 30, 'five' => 50]

each()

Phương thức này cũng gần gần giống cái forEach củ chuối trong JavaScript vậy, nó sẽ lặp qua từng phần tử trong collection và đưa nó vào callback

$collection = $collection->each(function ($item, $key) {
    //
});

Nếu đang lặp mà muốn dừng lại thì chỉ cần return một phát là xong:

$collection = $collection->each(function ($item, $key) {
    if (/* some condition */) {
        return false;
    }
});

every()

Phương thức này sẽ lặp qua tất cả các phần tử trong collection và kiểm tra điều kiện đã cho,
nếu tất cả đều đúng thì sẽ trả về true,còn sai một cái thì sẽ trả về false

collect([1, 2, 3, 4])->every(function ($value, $key) {
    return $value > 2;
});

// false

except()

Phương thức này sẽ loại trừ các phần tử dựa trên key được cho từ một collection và trả về một collection mới:

$collection = collect(['product_id' => 1, 'price' => 100, 'discount' => false]);

$filtered = $collection->except(['price', 'discount']);

$filtered->all();

// ['product_id' => 1]

filter()

Phương thức này hao hao every() một tí, nhưng thay vì trả về true hoặc false, nó sẽ trả về các phần tử đúng với điều kiện đã cho:

$collection = collect([1, 2, 3, null, false, '', 0, []]);

$collection->filter()->all();

// [1, 2, 3]

first()

Phương thức này giống với filter() nhưng thay vì nó trả về tất cả các phần tử thì nó sẽ trả về phần tử đầu tiên :

collect([1, 2, 3, 4])->first(function ($value, $key) {
    return $value > 2;
});

// 3

Điều khá thú vị là nếu gọi phương thức này mà không có argument nào thì nó sẽ trả về phần tử đầu tiên trong collection:

collect([1, 2, 3, 4])->first();

// 1

flatMap()

Cũng như các phương thức trên, nhưng phương thức flatMap() có thể tự do sửa đổi hay làm bất cứ thứ gì và trả về một collection mới:

$collection = collect([
    ['name' => 'Sally'],
    ['school' => 'Arkansas'],
    ['age' => 28]
]);

$flattened = $collection->flatMap(function ($values) {
    return array_map('strtoupper', $values);
});

$flattened->all();

// ['name' => 'SALLY', 'school' => 'ARKANSAS', 'age' => '28'];

flatten()

Trong mảng đa chiều, phương thức này sẽ trả về tất cả value và loại bỏ key trong mảng đó và trở thành mảng 1 chiều:

$collection = collect(['name' => 'taylor', 'languages' => ['php', 'javascript']]);

$flattened = $collection->flatten();

$flattened->all();

// ['taylor', 'php', 'javascript'];

Tuy nhiên bạn cũng có thể loại bỏ dựa theo các lớp mà bạn cần, có vẻ khá khó hiểu thì xem ví dụ sau:

$collection = collect([
    'Apple' => [
        ['name' => 'iPhone 6S', 'brand' => 'Apple'],
    ],
    'Samsung' => [
        ['name' => 'Galaxy S7', 'brand' => 'Samsung']
    ],
]);

$products = $collection->flatten(1);

$products->values()->all();

/*
    [
        ['name' => 'iPhone 6S', 'brand' => 'Apple'],
        ['name' => 'Galaxy S7', 'brand' => 'Samsung'],
    ]
*/

Như trong ví dụ trên thì mình gán một argument là 1, điều này có nghĩa là mình sẽ bỏ đi các key ở lớp ngoài cũng, nếu như mình cho nó là 2 thì nó sẽ bỏ lớp tiếp theo, lúc đó nó sẽ còn:

[
    ['iPhone 6S',  'Apple'],
    ['Galaxy S7', 'Samsung'],
]

flip()

Phương thức này sẽ hoán đổi các keyvalue tương ứng với nhau, ví dụ:

$collection = collect(['name' => 'taylor', 'framework' => 'laravel']);

$flipped = $collection->flip();

$flipped->all();

// ['taylor' => 'name', 'laravel' => 'framework']

forget()

Phương thức này sẽ bỏ đi trực tiếp một phần tử trong collection dựa trên key của phần tử đó:

$collection = collect(['name' => 'taylor', 'framework' => 'laravel']);

$collection->forget('name');

$collection->all();

// ['framework' => 'laravel']

forPage()

Phương thức này trả về một collection mới chứa các phần tử có thể có mặt trên một số trang nhất định. Phương thức chấp nhận số trang là argument đầu tiên của nó và số lượng các phần tử để hiển thị trên mỗi trang là argument thứ hai được đưa vào của nó (cái này hơi khó hiểu xíu):

$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9]);

$chunk = $collection->forPage(2, 3);

$chunk->all();

// [4, 5, 6]

get()

Phương thức này sẽ trả về phần tử dựa vào key đã cho. Nếu như key không tồn tại thì sẽ trả về null:

$collection = collect(['name' => 'taylor', 'framework' => 'laravel']);

$value = $collection->get('name');

// taylor

Ta có thể thêm giá trị mặc định bằng cách cho vào argument thứ 2 :

$collection = collect(['name' => 'taylor', 'framework' => 'laravel']);

$value = $collection->get('name');

// taylor

Hoặc thậm chí là callback:

$collection->get('email', function () {
    return 'default-value';
});

// default-value

Nếu như key không xác định thì callback sẽ được trả về.

groupBy()

Phương thức này sẽ nhóm các mảng trong collection lại dựa trên value tương ứng với key được truyền vào:

$collection = collect([
    ['account_id' => 'account-x10', 'product' => 'Chair'],
    ['account_id' => 'account-x10', 'product' => 'Bookcase'],
    ['account_id' => 'account-x11', 'product' => 'Desk'],
]);

$grouped = $collection->groupBy('account_id');

$grouped->toArray();

/*
    [
        'account-x10' => [
            ['account_id' => 'account-x10', 'product' => 'Chair'],
            ['account_id' => 'account-x10', 'product' => 'Bookcase'],
        ],
        'account-x11' => [
            ['account_id' => 'account-x11', 'product' => 'Desk'],
        ],
    ]
*/

Ngoài việc đưa key vào, bạn có thể dùng callback, nó sẽ trả về giá trị mà bạn đặt cho key khi nhóm các mảng lại:

$grouped = $collection->groupBy(function ($item, $key) {
    return substr($item['account_id'], -3);
});

$grouped->toArray();

/*
    [
        'x10' => [
            ['account_id' => 'account-x10', 'product' => 'Chair'],
            ['account_id' => 'account-x10', 'product' => 'Bookcase'],
        ],
        'x11' => [
            ['account_id' => 'account-x11', 'product' => 'Desk'],
        ],
    ]
*/

has()

Phương thức này sẽ kiểm tra xem key truyền vào có tồn tại hay không:

$collection = collect(['account_id' => 1, 'product' => 'Desk']);

$collection->has('product');

// true

implode()

Phương thức này sẽ ghép value dựa trên key (argument thứ nhất) và kí tự nối lại là argument thứ 2:

$collection = collect([
    ['account_id' => 1, 'product' => 'Desk'],
    ['account_id' => 2, 'product' => 'Chair'],
]);

$collection->implode('product', ', ');

// Desk, Chair

Nếu collection chỉ bao gồm mảng một chiều gồm chuỗi hoặc số thì ta không cần truyền key vào mà chỉ cần truyền kí tự nối lại thôi:

collect([1, 2, 3, 4, 5])->implode('-');

// '1-2-3-4-5'

intersect()

Phương thức này sẽ so sánh collection gốc và một mảng được truyền vào, rồi loại bỏ tất cả các phần tử không tồn tại trong collection gốc, sau đó trả về một collection mới:

$collection = collect(['Desk', 'Sofa', 'Chair']);

$intersect = $collection->intersect(['Desk', 'Chair', 'Bookcase']);

$intersect->all();

// [0 => 'Desk', 2 => 'Chair']

isEmpty()

Cái tên đã nói lên tất cả, phương thức này sẽ trả về true nếu collection rỗng và false nếu ngược lại

isNotEmpty()

Ngược lại với isEmpty()

Tổng kết

Có thể thấy Collections trong Laravel giúp chúng ta tiết kiệm được rất nhiều thời gian làm việc, trên đây là gần 30 trong Laravel trong 80 phương thức của Collections, các phương thức khác mình sẽ giới thiệu trong các phần sau.