[Laravel][5.4][Có gì hot] Đôi nét về Laravel HighOrderCollections

Spread the love

Laravel 5.4 cũng đã release được gần hơn 2 tháng (2017.01.24), và nó cung cấp thêm rất nhiều thứ tiện ích khác cho người dùng. Cụ thể trong đó là phần High Order Messages mà mình cảm thấy rất là hứng thú và sẽ cùng chia sẻ với các bạn thông qua bài viết hôm nay

  1. High Oder Messages là gì vậy ?
    Đây cũng là khái niệm lần đầu tiên mình gặp , đại khái nó như vầy: hiện tại trong lập trình hướng đối tượng thì việc chuẩn hóa vòng lặp cũng khá là khó khăn (thường thì các bạn phải for, foreach, while, do while v..v ) nên dẫn tới khái niệm High Order Messages
  2. Curry function là gì :
  3. High Order Messages được implements trong laravel như thế nào vậy ?
  4. Các ví dụ cụ thể
I. High Order Messages là gì ?
Khái niệm về  HighOrdering Messaging:
. Cho bảng Nhân viên, tìm các nhân viên với lương < 1000
Tiếp cận : đơn giản là chúng ta sẽ dùng vòng loop bên dưới
for ($i = 0; $i < count($arrEmployee); $i++){ if ($ arrEmployee [$i]->salary > 1000 ){
          print_r($employee);
     }
}

Cách thứ 2 là dùng foreach

foreach ($arrEmloyee as $employee){
     if ($ employee->salary > 1000 ){
          print_r($employee);
     }
}

Cách dùng foreach đã làm cho code ngắn hơn (chúng ta không còn phải khai báo biến đếm cũng như 2 cận ) vì những hạn chế này nên dẫn đến sự ra đời của High Order messages
Trước khi tìm hiểu HighOrderMessages là gì thì ta cần phải biết sơ về khái niệm curry function

II. Curry function là gì ?

Hàm currry sẽ được định nghĩa theo ví dụ bên dưới

var greetCurried = function(greeting) {
  return function(name) {
    console.log(greeting + ", " + name);
  };};
var greetHello = greetCurried("Hello");
greetHello("Heidi"); //"Hello, Heidi"
greetHello("Eddie"); //"Hello, Eddie"

Nếu như ban đầu chúng ta có hàm greet(“Hello”, “Heidi”) thì các step bên dưới sẽ thực hiện curry

  1. Tạo hàm greetCurried nhận tham số là lời chào (greeting) và trả về 1 hàm => đây là Curry Function
  2. Hàm vô danh này sẽ nhận tham số là tên người sẽ chào và trả lại kết quả : Lời chào + tên người chào
  3. Ta có thể gán hàm greetHello như trên hoặc có thể gọi thẳng luôn : greetCurried(“Hello”)(“Heidi”) cũng cho cùng một kết quả.

Bạn có thể tham khảo thêm hàm curry (được dùng trong javascript như ở bên trong link

III. High Order Messages trong laravel

High Order Messages chỉ được implements từ laravel 5.4 trở lên (các bạn check laravel version của mình trước khi thực hiện theo tuts )

>  php artisan --version
Laravel Framework 5.4.15

Trong laravel thì High Order Messages được implement bằng class HighOrderCollectionProxy. Ngoài ra thì không phải hàm nào của Collections cũng có thể sử dụng High Order Messages.

Nếu tìm trong class Illuminate\Support\Collection thì ta tìm thấy các đoạn code sau

Bên dưới là đoạn code mô tả các hàm collection có thể trả về HighOrderCollectionProxy

 /**
 * The methods that can be proxied.
 *
 * @var array
 */
 protected static $proxies = [
 'contains', 'each', 'every', 'filter', 'first', 'map',
 'partition', 'reject', 'sortBy', 'sortByDesc', 'sum',
 ];
 /**
 * Add a method to the list of proxied methods.
 *
 * @param string $method
 * @return void
 */
 public static function proxy($method)
 {
 static::$proxies[] = $method;
 }

 /**
 * Dynamically access collection proxies.
 *
 * @param string $key
 * @return mixed
 *
 * @throws \Exception
 */
 public function __get($key)
 {
 if (! in_array($key, static::$proxies)) {
 throw new Exception("Property [{$key}] does not exist on this collection instance.");
 }

 return new HigherOrderCollectionProxy($this, $key);
 }

Vậy ta thử gọi contains, each… như là property của Collection thì có get được 1 instance HigherOrderCollection không nhé

Psy Shell v0.8.2 (PHP 5.6.24 窶・cli) by Justin Hileman
>>> $higherCollection = collect([1,2], [3,4,5])->map;
=> Illuminate\Support\HigherOrderCollectionProxy {#662}
>>> $higherCollection2 = collect([1,2], [3,4,5])->map2;
Exception with message 'Property [map2] does not exist on this collection instance.

đúng là với hàm map thì Laravel trả về 1 instance của class HigherOrderCollectionProxy, nếu chúng ta set sai hoặc set các hàm không nằm trong list thì sẽ bị báo lỗi như bên trên

IV. Các ví dụ cụ thể

Giả sử chúng ta muốn tính trung bình cộng của riêng rẽ từng array ở trên , ta thử như bên dưới

>>> $higherCollection = collect([collect([1, 2]), collect([2, 3, 4])])->map
=> Illuminate\Support\HigherOrderCollectionProxy {#673}
>>> $higherCollection->average()
=> Illuminate\Support\Collection {#672
 all: [
 1.5,
 3,
 ],
 }
>>>

Ta có thể thấy là hàm average sẽ tính average được cho từng phần tử bên trong hàm map. Ồ điều kì diệu này là như thế nào vậy 😕

Chúng ta cùng phân tích bằng cách tìm hiểu đoạn code trong class HighOrderCollectionProxy.php

Đầu tiên là __construct

 /**
 * Create a new proxy instance.
 *
 * @param \Illuminate\Support\Collection $collection
 * @param string $method
 * @return void
 */
 public function __construct(Collection $collection, $method)
 {
 $this->method = $method;
 $this->collection = $collection;
 }

Constructor sẽ nhận hàm collection và method tương ứng với collection đó, như ví dụ trên của ta thì

$this->collection = collect([collect([1, 2]), collect([2, 3, 4])]);
$this->method = "map";

Sau đó nếu bạn gọi câu lệnh

$higherCollection->average();

thì điều gì sẽ xảy ra, nào hãy cùng nhìn vào hàm __call của HighOrderCollectionProxy.php như bên dưới

    
    /**
     * Proxy a method call onto the collection items.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        return $this->collection->{$this->method}(function ($value) use ($method, $parameters) {
            return $value->{$method}(...$parameters);
        });
    }

Vậy thì ta có thể viết lại câu lệnh trên thành

$higherCollection->average() = collect([collect([1, 2]), collect([2, 3, 4])])->map(function($value) use ("average"){
                                                                                        return $value->average();
});

Theo đoạn code trên thì chúng ta có thể hiểu được tại sao chúng ta có thể get được giá trị trung bình của từng array bên trong collection
(Lưu ý là do hàm average() chỉ áp dụng cho collection class, nên ở đoạn trên, chúng ta đã cố tình cast từ array thành collection qua câu lệnh : collect([..]))
Tiếp theo chúng ta cùng qua một ví dụ bên dưới là hàm pluck trong collection, bây giờ chúng ta thử viết lại hàm này thông qua highOrderCollection nhé

>>> $user1 = new App\User
=> App\User {#645}
>>> $user1->name = "Khanh"
=> "Khanh"
>>> $user1->age = 18
=> 18}
>>> $user2 = new App\User
=> App\User {#642}
>>> $user2->name = "Honda"
=> "Honda"
>>> $user2->age = 22
=> 22
>>> $highMap = collect([$user1, $user2])->map
=> Illuminate\Support\HigherOrderCollectionProxy {#654}
>>> $highMap->name->toArray()
=> [
 "Khanh",
 "Honda",
 ]

Ồ hãy xem này, điều gì đã xẩy ra vậy , chúng ta cùng phân tích, hãy nhìn vào hàm => __get nằm bên trong class HighOrderCollectionProxy.php

    /**
     * Proxy accessing an attribute onto the collection items.
     *
     * @param  string  $key
     * @return mixed
     */
    public function __get($key)
    {
        return $this->collection->{$this->method}(function ($value) use ($key) {
            return is_array($value) ? $value[$key] : $value->{$key};
        });
    }

Ta thử viết lại câu lệnh bên trên

$highMap->name = collect([$user1, $user2])->map(function ($value) use ("name"){
                                              return $value->name;
});
}

qua câu lệnh trên ta sẽ nhận lại được 1 collection đã extract field name như chỉ định .
Thật tuyệt đúng không nhỉ 😕
Nào thử tiếp với filter (mục đích của mình là loại bỏ các object mà không có keys tương ứng để tránh trường hợp phải check key và phát sinh lỗi undefined index đáng ghét
Hãy thử xem mình làm như thế nào nhé
Giả sử mình khai báo thêm 1 user như bên dưới

>>> $user3 = new App\User
=> App\User {#655}
>>> $user3->age = 8
=> 8

Và mình muốn loại bỏ $user3 trong kết quả (do nó không có name ) mình sẽ làm như bên dưới

>>> $highMapNoKeys = collect([$user1, $user2, $user3])->filter->name
=> Illuminate\Support\Collection {#675
     all: [
       App\User {#645
         name: "Khanh",
         age: 18,
       },
       App\User {#642
         name: "Honda",
         age: 22,
       },
     ],
   }

Ồ kết quả là $user3 đã bị remove khỏi collections do $user3 không có key(“name”) tương ứng 🙂

Vậy là chúng ta có thể viết code nhanh hơn, ý nghĩa hơn

Thật ra cấu trúc loops, if , case..switch bất kì ngôn ngữ nào cũng có hỗ trợ, có phải vì thế nên chúng ta lúc nào cũng sử dụng mindset đó ở hầu hết ngôn ngữ của mình (dù là ngôn ngữ mới học v..v) , nào hãy thử lật lại các vấn đề xưa như trái đất kia và khám phá thêm những cách mới nhé ^^

 

■ Các trích dẫn được dùng trong bài :

  1. Understanding Laravels HighOrderCollections
  2. A beginner’s guide to Currying in Functional Javascript
  3. Laravel 5.4 Release Notes
  4. Higher Order Messaging in Ruby

Leave a Reply

Your email address will not be published. Required fields are marked *