Từ trước đến nay khi sử dụng framework, mình rất thích sự kế thừa view engine. Nó giúp mình rất nhiều trong việc phân chia layout, cảm giác code rất mạch lạc và tiện lợi. Tò mò và tìm tòi, hôm nay mình xin chia sẻ với mọi người cách tạo ra một template engine siêu “smart” mà đơn giản cho ae. Let’s go
Khởi tạo
Đầu tiên, chúng ta hãy tạo một project với cấu trúc như sau:
À mà project chỉ đơn giản là một file template.php
thôi.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | <span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token keyword">class</span> <span class="token class-name">Template</span> <span class="token punctuation">{</span> <span class="token comment">/** * * Thư mục views. * */</span> <span class="token keyword">private</span> <span class="token variable">$__directory</span><span class="token punctuation">;</span> <span class="token comment">/** * * Layout của views. * Thuộc tính này sẽ có giá trị là view parent - view cần kế thừa của view đang được gọi. * @default null * */</span> <span class="token keyword">private</span> <span class="token variable">$__layout</span><span class="token punctuation">;</span> <span class="token comment">/** * * Các section của layout. VD: content, sidebar, header, footer,... Nói chung ai dùng fw rồi đều sẽ biết * */</span> <span class="token keyword">private</span> <span class="token variable">$__sections</span><span class="token punctuation">;</span> <span class="token comment">/** * * section hiện tại đang xét. * @default null * */</span> <span class="token keyword">private</span> <span class="token variable">$__current_section</span><span class="token punctuation">;</span> <span class="token comment">/** * * Ham khoi tao * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">__construct</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// code</span> <span class="token punctuation">}</span> <span class="token comment">/** * * Hàm load view * * @param string $view_name Tên view cần load * @param array $args Các tham số cần truyền qua view * * @return string * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">render</span><span class="token punctuation">(</span>string <span class="token variable">$view_name</span><span class="token punctuation">,</span> <span class="token keyword">array</span> <span class="token variable">$args</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// code</span> <span class="token punctuation">}</span> <span class="token comment">/** * * Include một view trong một view * * @param string $view_name Tên view cần include (giống include file php trong php đó) * * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token keyword">include</span><span class="token punctuation">(</span>string <span class="token variable">$view_name</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// code</span> <span class="token punctuation">}</span> <span class="token comment">/** * * Hàm bắt đầu một section * * @param string $name Tên của section. * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">section</span><span class="token punctuation">(</span>string <span class="token variable">$name</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// code</span> <span class="token punctuation">}</span> <span class="token comment">/** * * Hàm kết thúc một section * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">end</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// code</span> <span class="token punctuation">}</span> <span class="token comment">/** * * Hàm kế thùa layout trong views * * @param string $layout Layout cần kế thừa * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">layout</span><span class="token punctuation">(</span>string <span class="token variable">$layout</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// code</span> <span class="token punctuation">}</span> <span class="token comment">/** * * Hàm xác định vị trí section sẽ được render trong file layout view * * @param string $name Tên section cần render * * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">renderSection</span><span class="token punctuation">(</span>string <span class="token variable">$name</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// code</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </span> |
À code trên chỉ là khởi tạo file class template.php
, mình đã giải thích kĩ các biến và phương thức trong class Template. Sau đây sẽ code chi tiết từng hàm.
Code
Hàm khởi tạo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">__construct</span><span class="token punctuation">(</span><span class="token variable">$directory</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">__setDirectory</span><span class="token punctuation">(</span><span class="token variable">$directory</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__sections</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__layout</span> <span class="token operator">=</span> <span class="token constant">null</span><span class="token punctuation">;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__current_section</span> <span class="token operator">=</span> <span class="token constant">null</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/** * * Hàm set thư mục views * * @param string $directory Thư mục views * */</span> <span class="token keyword">private</span> <span class="token keyword">function</span> <span class="token function">__setDirectory</span><span class="token punctuation">(</span>string <span class="token variable">$directory</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">is_dir</span><span class="token punctuation">(</span><span class="token variable">$directory</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Exception</span><span class="token punctuation">(</span><span class="token double-quoted-string string">"<span class="token interpolation"><span class="token variable">$directory</span></span> is not exist"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__directory</span> <span class="token operator">=</span> <span class="token variable">$directory</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/** * * Hàm kiểm tra đường dẫn của file view * * @param string $path Đường dẫn của file. Đuôi file sẽ là .php * * @return string * */</span> <span class="token keyword">private</span> <span class="token keyword">function</span> <span class="token function">__resolvePath</span><span class="token punctuation">(</span>string <span class="token variable">$path</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$file</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__directory</span> <span class="token punctuation">.</span> <span class="token single-quoted-string string">'/'</span> <span class="token punctuation">.</span> <span class="token variable">$path</span> <span class="token punctuation">.</span> <span class="token single-quoted-string string">'.php'</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">file_exists</span><span class="token punctuation">(</span><span class="token variable">$file</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Exception</span><span class="token punctuation">(</span><span class="token double-quoted-string string">"<span class="token interpolation"><span class="token variable">$file</span></span> is not exist"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token variable">$file</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Tiếp đến hàm include. Hàm này cũng khá đơn giản nhưng các bạn cần phải hiểu rõ về output buffering
. Các bạn có thể tham khảo về buffer tại php.net
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <span class="token comment">/** * * Include một view trong một view * * @param string $view_name Tên view cần include (giống include file php trong php đó) * * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token keyword">include</span><span class="token punctuation">(</span>string <span class="token variable">$view_name</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">ob_start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">include_once</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">__resolvePath</span><span class="token punctuation">(</span><span class="token variable">$view_name</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$content</span> <span class="token operator">=</span> <span class="token function">ob_get_contents</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">ob_end_clean</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">echo</span> <span class="token variable">$content</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Hàm section và end. Chúng ta sẽ lấy đoạn mã HTML (XML) ở giữa hàm section và end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <span class="token comment">/** * * Hàm bắt đầu một section * * @param string $name Tên của section. * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">section</span><span class="token punctuation">(</span>string <span class="token variable">$name</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__current_section</span> <span class="token operator">=</span> <span class="token variable">$name</span><span class="token punctuation">;</span> <span class="token function">ob_start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/** * * Hàm kết thúc một section * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">end</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">empty</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__current_section</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Exception</span><span class="token punctuation">(</span><span class="token double-quoted-string string">"There is not a section start"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token variable">$content</span> <span class="token operator">=</span> <span class="token function">ob_get_contents</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">ob_end_clean</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__sections</span><span class="token punctuation">[</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__current_section</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token variable">$content</span><span class="token punctuation">;</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__current_section</span> <span class="token operator">=</span> <span class="token constant">null</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Hàm layout và renderSection lại càng đơn giản.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <span class="token comment">/** * * Hàm kế thùa layout trong views * * @param string $layout Layout cần kế thừa * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">layout</span><span class="token punctuation">(</span>string <span class="token variable">$layout</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__layout</span> <span class="token operator">=</span> <span class="token variable">$layout</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/** * * Hàm xác định vị trí section sẽ được render trong file layout view * * @param string $name Tên section cần render * * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">renderSection</span><span class="token punctuation">(</span>string <span class="token variable">$name</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">echo</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__sections</span><span class="token punctuation">[</span><span class="token variable">$name</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Cuối cùng là hàm render một view. Hàm này sẽ gọi đến các hàm section và end trước sau đó sẽ kế thừa layout, cuối cùng trả về một string là file HTML (XML) hoàn chỉnh.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <span class="token comment">/** * * Hàm load view * * @param string $view_name Tên view cần load * @param array $args Các tham số cần truyền qua view * * @return string * */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">render</span><span class="token punctuation">(</span>string <span class="token variable">$view_name</span><span class="token punctuation">,</span> <span class="token keyword">array</span> <span class="token variable">$args</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">is_array</span><span class="token punctuation">(</span><span class="token variable">$args</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">extract</span><span class="token punctuation">(</span><span class="token variable">$args</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">ob_start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">include_once</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">__resolvePath</span><span class="token punctuation">(</span><span class="token variable">$view_name</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$content</span> <span class="token operator">=</span> <span class="token function">ob_get_clean</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">empty</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__layout</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token variable">$content</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">ob_clean</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">include_once</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">__resolvePath</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token property">__layout</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$output</span> <span class="token operator">=</span> <span class="token function">ob_get_contents</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">ob_end_clean</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token variable">$output</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
ok. vậy là xong code template engine. Bây giờ test thử xem sao.
Gọi Template Engine
Đầu tiên ta tạo một file ~/views/layout.php
.
Lưu ý: trong template engine này, mình sử dụng đuôi file view là .php. Các bạn có thể tùy chỉnh đuôi file này trong hàm __resolvePath file
template.php
ở trên
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>head</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span>Demo<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>head</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span><span class="token punctuation">></span></span> <span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">renderSection</span><span class="token punctuation">(</span><span class="token single-quoted-string string">'sidebar'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token delimiter important">?></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>br</span><span class="token punctuation">/></span></span> <span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">renderSection</span><span class="token punctuation">(</span><span class="token single-quoted-string string">'content'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token delimiter important">?></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>body</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>html</span><span class="token punctuation">></span></span> |
Tiếp đến ta tạo một file view ~/views/profile.php
1 2 3 4 5 6 7 8 9 10 11 | <span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">layout</span><span class="token punctuation">(</span><span class="token single-quoted-string string">'template'</span><span class="token punctuation">)</span> <span class="token delimiter important">?></span></span> <span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">section</span><span class="token punctuation">(</span><span class="token single-quoted-string string">'content'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token delimiter important">?></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h1</span><span class="token punctuation">></span></span>User Profile<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h1</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>Hello, <span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token keyword">echo</span> <span class="token variable">$name</span><span class="token punctuation">;</span> <span class="token delimiter important">?></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span> <span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">end</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token delimiter important">?></span></span> <span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">section</span><span class="token punctuation">(</span><span class="token single-quoted-string string">'sidebar'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token delimiter important">?></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span><span class="token punctuation">></span></span>Sidebar<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h2</span><span class="token punctuation">></span></span> <span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token variable">$this</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">end</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token delimiter important">?></span></span> |
Trong file index.php
(file bạn cần gọi view đó).
1 2 3 4 5 6 | <span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token comment">// set thư mục views là ~/views</span> <span class="token variable">$template</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Template</span><span class="token punctuation">(</span><span class="token single-quoted-string string">'/views'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">echo</span> <span class="token variable">$template</span><span class="token operator">-</span><span class="token operator">></span><span class="token function">render</span><span class="token punctuation">(</span><span class="token single-quoted-string string">'profile'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token single-quoted-string string">'name'</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token single-quoted-string string">'Jocelyn'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </span> |
Ok. Các bạn chạy thử file ~/index.php
và tận hưởng thành quả.
Tổng kết
Tự tạo một template engine không khó, quan trọng là các bạn phải hiểu rõ output buffering
trong PHP. https://www.php.net/manual/en/ref.outcontrol.php