Đăng nhập Đăng ký website 
DÙNG THỬ

Phần 4: Xây dựng Template Engine cho PHP Framework

Chúng ta đã biết "bản chất của Template Engine là thực hiện việc load file giao diện, tìm và thay thế các giá trị hoặc cấu trúc được quy định trong đó bằng giá trị tương ứng trong file điều khiển"
Trong phần này chúng ta sẽ đặt ra các quy tắc để khi lập trình cho template chúng ta sẽ tuân thủ các quy tắc này và xuất ra được giá trị theo yêu cầu.

Bạn có thể tham khảo nhiều Template Engine khác như Smarty Template, Xtemplate v.v… mỗi loại đều có cái hay riêng, bạn tham khảo để học cách đặt tên biến, cách sử dụng vòng lặp v.v…

Nhắc lại một chút là chúng ta đang xây dựng một Template Engine riêng để phục vụ cho hệ thống DFramework của chúng ta, không phải xây dựng Engine để phục vụ cho tất cả nhu cầu của mọi người vì vậy chúng ta chỉ tập trung xây dựng các chức năng chính mà chúng ta cần cho hệ thống.

Chúng ta sẽ thống nhất các quy định sau:
1. Quy định đặt tên file template:
Chúng ta có thể đặt file có bất kỳ tên mở rộng nào cũng được, ví dụ: index.tpl, index.dfr, index.name, về bản chất tất cả cùng đều là file text, để thuận tiện chúng ta sẽ thống nhất tất cả các file template trong DFramework sẽ có phần mở rộng là .htm. Ví dụ: index.htm, tintuc.htm, sanpham.htm

2. Quy định đặt tên biến:
Có nhiều cách đặt tên biến như {ten_bien}, {$ten_bien}, [$ten_bien], %ten_bien% v.v… quy tắc chung là đặt tên sao cho ngắn gọn, dễ nhớ và ít bị trùng với các nội dung cần hiển thị.
Chúng ta sẽ thống nhất đặt tên biến theo dạng {data.ten_bien}, ví dụ {data.san_pham}
Trong đó:
data. là tiếp đầu ngữ quy định
title là biến có tên title
Như vậy nếu cần hiển thị nội dung một bài viết ta sẽ có file template như sau:
html>
body>
h1>{data.tieu_de}/h1>
h2>{data.mo_ta}/h2>
h3>{data.hinh_anh}/h3>
h4>{data.noi_dung}/h4>
/body>
/html>

Cách đặt tên biến như trên có thể dài hơn các cách đặt khác nhưng nó có ưu điểm là ít bị trùng với nội dung và trong trường hợp cần thiết có thể thay thế hàng loạt tiếp đầu ngữ data. bằng tên khác một cách nhanh chóng.

3. Quy tắc đặt tên khối:
Khi lập trình chúng ta sẽ có nhu cầu lặp lại một nội dung nào đó nhiều lần, ví dụ để hiển thị 3 hình ảnh trên website, nếu không dùng vòng lặp chúng ta sẽ cần template sau:
html>
body>
img src=”xe-hoi.jpg” />
img src=”tau-hoa.jpg” />
img src=”may-bay.jpg” />
/body>
/html>

Trường hợp hình ảnh lấy từ database không biết trước số lượng bao nhiêu, tên gì thì sẽ không xây dựng được file template.
Để giải quyết vấn đề này chúng ta cần một ký hiệu để thay thế cho một đoạn nội dung trong file template.
Toàn bộ nội dung sẽ bị thay thế bằng 1 giá trị khác được gọi là 1 khối (block).
Ví dụ:
{d:block}
… Nội dung A…
… Nội dung B…
{d:/block}
{d:block}
… Nội dung 1…
… Nội dung 2…
{d:/block}

Như vậy mỗi phần nội dung bắt đầu bằng {d:block} và kết thúc bằng {d:/block} sẽ gọi là một khối và toàn bộ khối này có thể được thay thế bằng nội dung khác trong file điều khiển.
Quy tắc đặt tên này gần giống với việc mở và đóng 1 thẻ HTML, ví dụ: div>…nội dung…/div>

Kết hợp biến và khối ta có thể xây dựng template hiển thị hình ảnh như sau:
html>
body>
{d:block}
img src=”{data.hinh_anh}” />
{d:block}
/body>
/html>

4. Quy tắc đặt tên cấu trúc điều khiển
Trong lập trình PHP các cấu trúc điều khiển như if else, while, for v.v… rất thường xuyên được sử dụng, trong template ta cũng có nhu cầu sử dụng các cấu trúc như vậy.

* Lập trình cho Template Engine
Ở phần 3 chúng ta đã biết cách load template và hiển thị nội dung các biến, trong phần này chúng ta sẽ tiếp tục xây dựng các chức năng điều khiển để hoàn thiện lớp DTemplate.

1. Xây dựng chức năng điều khiển if/else
Cấu trúc if/else của chúng ta sẽ được quy định như sau:
1.1 Cú pháp:
d:if giatri1 > giatri2}
…nội dung 1…
d:/if}
Hoặc
d:if giatri1 > giatri2}
…nội dung 1…
d:else}
…nội dung 2…
d:/if}
Trong đó:
{d:if}/{d:else}/{d:/if} là cú pháp mở/đóng một khối
giatri1, giatri2 là các giá trị dùng cho biểu thức so sánh

Lưu ý cú pháp biểu thức so sánh:
+ giatri1 toantu giatri2 cần phải viết đúng định dạng vì chúng ta sẽ phân tích biểu thức này dựa trên khoảng trắng giữa các nội dung. Việc viết đúng cú pháp sẽ giúp đơn giản trong phần lập trình kiểm tra biểu thức vì không phải tính toán cho mọi trường hợp và như vậy hệ thống cũng sẽ chạy nhanh hơn.

1.2 Lập trình:
Mở file dframework/libs/dtemplates.php, trong lớp DTemplate, thêm vào các hàm sau:
private function compile_dtool_if($m_content,$m_data){
    if(preg_match_all('/{d(d?):if[^}](.*?)}(.*?){d1:/if}/ims',$m_content,$matches)){
        if(isset($matches[3],$matches[3][0]) && $matches[3][0]){
            for($i=0;$icount($matches[3]);$i++){
                $this->compile_dtool_if($matches[3][$i],$m_data);
                $mvar=trim(preg_replace('/ {2,}/',' ',$matches[2][$i]));
                $tmpVar=explode(' ',$mvar);
                if($tmpVar){/*need parse sysntax*/
                    $status=$this->checkOperation($tmpVar,$m_data);
                    if(isset($matches[3],$matches[3][$i])){
                        $tmpElse=explode('{d'.$matches[1][$i].':else}',$matches[3][$i]);
                        if(count($tmpElse)==2){/*Have else*/
                            $dataReplace=$status?$tmpElse[0]:$tmpElse[1];
                        }else{/*Not else*/
                            $dataReplace=$status?$matches[3][$i]:'';
                        }
                       $m_content=str_replace($matches[0][$i],$dataReplace,$m_content);
                    }
                }
            }
        }
    }
    return $m_content;
}
protected function checkOperation($mVar,$bData){
    if(count($mVar)==3){
        list($var1,$operation,$var2)=$mVar;
        $var1=isset($bData[$var1])?$bData[$var1]:$var1;
        $var2=isset($bData[$var2])?$bData[$var2]:$var2;
        switch($operation){
            case '==':
                return (strcmp($var1,$var2)==0?true:false);
            break;
            case '>':
                return floatval($var1)>floatval($var2);
            break;
            case '>=':case '=>':
                return ((strcmp($var1,$var2)==0?true:false) || (floatval($var1)>floatval($var2)));
            break;
            case '':
                return floatval($var1)floatval($var2);
            break;
            case '=':case '=':
                return ((strcmp($var1,$var2)==0?true:false) || (floatval($var1)floatval($var2)));
            break;
        }
    }elseif(count($mVar)==2){
        list($var1,$operation)=$mVar;
        switch($operation){
            case 'notempty':
                return (isset($bData[$var1]) && $bData[$var1] && strlen($bData[$var1])>0)?true:false;
            break;
        }
    }
    return false;
}

Trong đó:
+ Hàm checkOperation() có nhiệm vụ đọc biểu thức, phân tích các giá trị và so sánh để kiểm tra biểu thức đúng hay sai.
+ Hàm compile_dtool_if() có nhiệm vụ thay thế các khối block bằng nội dung tương ứng khi biểu thức so sánh đúng hoặc sai.

1.3 Sử dụng:
Chúng ta thực hiện Example 1 với yêu cầu sau:
1. Nếu thời gian trước 17h thì hiện hình mặt trời, nếu sau 17h thì hiện hình mặt trăng.
2. Sử dụng cấu trúc if/else trong template.
+ Trong thư mục dframework/example tạo file example1.php có nội dung:
?php
include('../libs/dframework.php');
$tpl = new DFramework;
$tpl->loadtemplate('example1.html');
$data_assign=array(
    'data.current_times' => date('G'),
    'ismorning' => date('G') 17 ? 1 : 0
);
$tpl->assign($data_assign);
$tpl->display();
?>

+ Trong thư mục dframework/example tạo file example1.htm có nội dung:
html>
body>
b>Kết quả:/b>br />
div style="display:inline-block;width:60%;text-align:center">
Thời gian hiện tại: {data.current_times}h
d:if ismorning == 1}
img src="images/morning.png" width="350" />
d:else}
img src="images/night.png" width="350" />
d:/if}
/div>
/body>
/html>

Kết quả khi xem trên trình duyệt
Cấu trúc If Else trong Template Engine

+ Xem kết quả chạy thử file example1.php tại đây
+ Download toàn bộ mã nguồn tại đây

2. Xây dựng cấu trúc điều khiển khối (block)
2.1 Cú pháp:
{d:block loop=5}
... Nội dung cần lặp ...
{d:/block}
Trong đó:
{d:block}/{d:/block}: là cú pháp mở/đóng khối
loop: là số lần lặp.

2.2 Lập trình:
Mở file dframework/libs/dtemplates.php, trong lớp DTemplate, thêm vào các hàm sau:
private function compile_dtool_block($m_content,$return_data=false){
    if(preg_match_all('/{d(d?):block[^}](.*?)}(.*?){d1:/block}/mis',$m_content,$matches)){
        if(isset($matches[3])){
            for($sDBlock=0;$sDBlock<count($matches[3]);$sDBlock++){
                $dataReplace=$matches[3][$sDBlock];
                $dCount=$sDBlock;
                $allProp=get_tag_dblock_attribute($matches[2][$sDBlock]);
                $dblock=convertToObject($allProp,$dataReplace);
                if($allProp && is_array($allProp) && count($allProp)>0 && $dblock){
                    $this->dblock=&$dblock;
                    $keyFile=strval($dblock->getAttribute('module'));
                    if($keyFile){
                        $m_file=$this->get_url_pages($keyFile,'manager/',DCONFIG_PHP_FILE);
                        if($m_file && is_file($m_file)){
                            if($output=include($m_file)){
                                $dataReplace=$output;
                            }
                        }else{
                            echo 'File '.$m_file.' of key '.$keyFile.' not exists.';
                        }
                    }else{
                        $blockname = strval($dblock->getAttribute('name'));
                        if($blockname){
                            $this->_block[$blockname]=array('content'=>$matches[3][$sDBlock],'data'=>array());
                            $m_content = str_replace($matches[0][$sDBlock],'{'.$blockname.'}',$m_content);
                        }else{/*Loop Only*/
                            $loop = $dblock->hasAttribute('loop')?strval($dblock->getAttribute('loop')):0;
                            if($loop > 0){
                                $tmpData='';
                                for($i=0;$i<$loop;$i++){
                                    $tmpData.=$dataReplace;
                                }
                                $dataReplace = $tmpData;
                            }
                        }
                    }
                }else echo 'Block Invalid';
                $this->compile_dtool_block($matches[3][$sDBlock],$return_data);
                if($return_data){
                    $m_content=str_replace($matches[0][$sDBlock],$dataReplace,$m_content);
                }else{
                    $this->set_content(str_replace($matches[0][$sDBlock],$dataReplace,$this->get_content()));
                }
            }
        }
    }
    if($return_data){
        return $m_content;
    }
}

Trong đó:
+ Hàm compile_dtool_block() sẽ load toàn bộ các khối trong template, đọc các thông số cấu hình và thực hiện các chức năng tương ứng trên block đó.

2.3 Sử dụng:
Chúng ta thực hiện Example 2 với yêu cầu sau:
1. Lặp lại nội dung theo số lần được cấu hình.
2. Sử dụng cấu trúc block trong template.
+ Trong thư mục dframework/example tạo file example2.php có nội dung:
?php
include('../libs/dframework.php');
$tpl = new DFramework;
$tpl->loadtemplate('example2.html');
$data_assign=array(
'data.current_times' => date('d/m/Y h:i:s')
);
$tpl->assign($data_assign);
$tpl->display();
?>

+ Trong thư mục dframework/example tạo file example2.htm có nội dung:
html>
link rel="stylesheet" type="text/css" href="../css/style.css">
body>
p>
b>Example 2 - Yêu cầu:/b>
br />1. Lặp lại nội dung theo số lần được cấu hình.
br />2. Sử dụng cấu trúc block trong template.
br />b>Cú pháp:/b>br />
{d:block loop=5}br />
... Nội dung cần lặp ...br />
{d:/block}
br />Trong đó:br />
{d:block}/{d:/block}: là cú pháp mở/đóng khốibr />
loop: là số lần lặp.
/p>
br />
b>Kết quả:/b>br />
div style="display:inline-block;width:60%;text-align:center">
{d:block loop=5}
    Nội dung cần lặp lại {data.current_times}br />
{d:/block}
/div>
/body>
/html>

Kết quả khi chạy
Cấu trúc Block trong Template Engine
+ Xem kết quả chạy thử file example2.php tại đây
+ Download toàn bộ mã nguồn DFramework tại đây
+ Bạn nào chưa biết cách cài đặt để chạy PHP trên localhost có thể tham khảo tại đây
+ Tham khảo loạt bài xây dựng PHP Framework riêng


Các tin khác
MẠNG TRUYỀN THÔNG THƯƠNG HIỆU ONLINE
VPGD: Số 54 Đường 26, P.Hiệp Bình Chánh, Q.Thủ Đức, Tp.HCM
Hotline: 0908.622.880 (Mr. Dũng) -  Zalo: 0908622880
Email: info@thietkeweb30s.com