12/08/2018, 16:33

Metaprogramming in PHP

Bạn vào Viblo, bạn search từ khóa Metaprogramming, các bạn sẽ thấy kết quả chỉ có Ruby. Hôm nay mình sẽ giới thiệu về Metaprogramming (MP) trong PHP. Metaprogramming là gì? Trước hết, đây là 1 kỹ thuật được áp dụng khi chúng ta cốt, nó đại khái là dùng code để cổt ra 1 code khác, mà cái code ...

Bạn vào Viblo, bạn search từ khóa Metaprogramming, các bạn sẽ thấy kết quả chỉ có Ruby. Hôm nay mình sẽ giới thiệu về Metaprogramming (MP) trong PHP.

Metaprogramming là gì?

Trước hết, đây là 1 kỹ thuật được áp dụng khi chúng ta cốt, nó đại khái là dùng code để cổt ra 1 code khác, mà cái code này mới là cái chúng ta cần chạy nó để ra kết quả mong muốn. Mình thì không biết trong Ruby thì metaprogramming nó kỳ diệu như thế nào, nhưng trong PHP, chúng ta có 1 thứ gọi mà magic method khiến cho việc triển khai MP rất là thú vị. Chúng ta có thể thay đổi các dòng code ngay trong khi chúng đang chạy, có thể thêm mới, thay đổi các phương thức của 1 class, có thể xử lý được cả những method không tồn tại trong class luôn. Chúng ta sẽ xem qua 1 số ví dụ sau.

Tìm hiểu

Ví dụ 1

function example1($array = [], $add = true, $multiply = true) { 
    foreach ($array as $item) { 
        if ($add) { 
            $item += 1; 
        } 
        if ($multiply) { 
            $item *= 2; 
        } 
        echo $item; 
    }
}

Ở đây, chúng ta có 1 hàm xử lý 1 array, với đầu vào là array, flag add thì sẽ cộng thêm 1, flag multiply thì nhân thêm 2 vào mỗi phần tử, đại loại là thế. Như vậy là mỗi lần duyệt qua 1 phần tử, chúng ta phải kiểm tra 2 lần, rồi sau mỗi lần kiểm tra chúng ta sẽ thực hiện phép tính hoặc không. Chúng ta sẽ thay đổi một chút cái hàm này như sau:

function example1($array = [], $add = true, $multiply = true) { 
    $code = 'foreach ($array as $item) {'; 
    if ($add) { 
        $code .= '$item += 1;'; 
    }
    if ($multiply) { 
        $code .= '$item *= 2;'; 
    }
    $code .= 'echo $item;'; 
    $code .= '}'; 
 
    // Execute the value of $code variable 
    eval($code); 
} 

Các bạn có thể thấy là, chúng ta sẽ check các flag trước, nếu có thì chúng ta sẽ thêm 1 string code vào biến $code, và sau đó, trước khi tới eval() thì đoạn code của chúng ta sẽ như thế này

foreach ($array as $item) { 
    $item += 1; 
    $item *= 2; 
    echo $item; 
}

Đoạn code trên chỉ có phép cộng hoặc nhân hoặc cả cộng và nhân (trong trường hợp này, các flag đều là true), và hàm eval() sẽ thực hiện đoạn code trên, thay vì chúng ta phải chạy hàm example1 ban đầu.

** eval() trong PHP có chức năng xem 1 chuỗi string đầu vào là 1 đoạn code php. Tuy nhiên, PHP có cảnh báo trước với chúng ta:

The eval() language construct is very dangerous because it allows execution of arbitrary PHP code. 

Sử dụng metaprogramming có khiến code của chúng ta nhanh hơn không?

Chúng ta thay đổi ví dụ trên một chút, thay đổi cách tính toán một chút và tăng vòng lặp lên 100 triệu lần nhé:

function example1($add = true, $multiply = true) {
	$results = 1;
    for ($item = 1; $item <= 100000000; $item++) {
        if ($add) {
            $results += $item;
        }
        if ($multiply) {
            $results /= 2;
        }
    }
    echo 'Results: ' . $results . "
";
}

Và đây là kết quả:

Thấy nó nhanh hơn đúng không ạ? và chúng ta kết luận code theo MP khiến code chạy nhanh hơn cách thông thường? Không phải đâu ạ, đừng hiểu nhầm, đây là 1 trong số ít trường hợp mà code của chúng ta chạy nhanh hơn, và trong hầu hết các trường hợp còn lại, code của chúng ta chạy vẫn trong thời gian như nhau hoặc lâu hơn cách thông thường.

Ví dụ 2

$searchCode = 'if ($i == $item) { $found = true; $searchCode = ""; }'; 
// found $item, then no need to check the rest of items

$item = 7;

for ($i = 0; $i < 10000; $i++) { 
    eval($searchCode); 
    echo $i;
}

Ở đây, chúng ta có 1 đoạn code, nhiệm vụ của nó là tìm số 7 trong 10000 số tự nhiên đầu tiên, đồng thời in ra các số đó (ví dụ có vẻ hơi ngu 1 tý, nhưng mà kiểu gì chúng ta chả phải gặp trường hợp như vầy). Thì thông thường, chúng ta cứ code là if then là xong, nhưng khi tìm đc số cần tìm rồi, if then vẫn chạy, thật là lãng phí. Đoạn code trên giúp ta giải quyết vấn đề trên: khi chưa tìm thấy, eval() sẽ thực hiện đoạn code tìm kiếm, khi chạy đoạn code tìm kiếm, nếu tìm thấy, đoạn code tìm kiếm sẽ bị gán thành empty, và ở những vòng lặp sau, eval sẽ không cần phải thực hiện vì đoạn code tìm kiếm bây giờ là rỗng. Thật thú vị.

MP khiến code của chúng ta khó hiểu hơn?

Qua 2 ví dụ vừa rồi, chắc hẳn đây là 1 nhận xét chính xác đúng không? nếu không, chúng ta sẽ đi tiếp qua ví dụ khác nữa, đến bao giờ thật sự khó hiểu thì thôi. Ngoài ra thì, việc thực hiện "code that writing code" như thế kia cũng khiến chúng ta khó debug hơn. Các bạn cứ thử viết sai 1 tý code trong string kia, xem nó báo lỗi ở dòng bao nhiêu là biết ngay.             </div>
            
            <div class=

0