It's me ;-)

Mô hình MVC: Vai trò của các thành phần và các vấn đề thiết kế trong các Web Framework

@author pcdinh
@since July 26, 2008
@version 1.0.0
@link http://dev.wvb.com/pubdocs/software_design/mvc.txt
@note Tài liệu được viết trên Notepad (plain text) và xem tốt trên font Courrier New và encoding UTF-8 ở chế độ Word-wrap. Firefox hỗ trợ Wordwrap trên màn hình View Source (Ctrl + U) , chọn View – Wrap long lines

CHANGE LOG
+ July 26, 2008: 1.0.0 – Viết lần đầu

Disclaimer: Bài viết này phản ánh quan điểm của tôi. Nó không phản ánh quan điểm của World’Vest Base hay các software architect mà tôi có dịp làm việc cùng.

———————————————————————-
MÔ HÌNH MVC TRUYỀN THỐNG TRONG CÁC WEB FRAMEWORK
———————————————————————-

Trong mô hình MVC truyền thống, Controller là thành phần đóng vai trò trung tâm trong việc tiếp nhận và chuyển tiếp yêu cầu của user đến các thành phần có liên quan trong đó có 2 bộ phận chính là Model và View. Trong đó Model đóng vai trò là thành phần cung cấp dữ liệu và các dịch vụ có liên quan đến dữ liệu. View đóng vai trò là thành phần cung cấp các logic và cấu trúc liên quan đến việc trình bày dữ liệu.

Tuy nhiên với mô hình này, Controller đóng vai trò là trung tâm điều khiển. Model không biết Controller nào sẽ lựa chọn nó. Nó là Passive Model. Tương tự, View không biết Controller nào sẽ gọi nó ra và cung cấp cho nó dữ liệu gì. Nó là Passive View. Bằng cách đó, View và Model không hề biết nhau. View và Model không biết đến Controller nhưng Controller thì lại biết cả 2 và thậm chí là cả sự phụ thuộc về dữ liệu giữa View và Model.

Như vậy trong các web framework hỗ trợ MVC truyền thống thì

CONTROLLER

Controller là các lớp điều khiển application flow, tiếp nhận user input thông qua HTTP header, chuyển tiếp nó đến các lớp phụ trách trực tiếp xử lý yêu cầu. Tùy theo cách thiết kế kế lớp mà chúng ta thường thấy Controller gồm
+ Front Controller
+ Dispatcher
+ Action Mapping
+ Action Filter
+ Action: lớp xử lý các sự kiện chính, nơi dẫn đến application flow chủ yếu (Main Event Handler)
+ Response
+ Request: xử lý một phần user input ở mức GET, POST và PUT
+ Session: xử lý một phần user input ở mức SESSION

Tùy theo user input, Controller sẽ thực hiện các phép lọc (với dịch vụ lấy từ Model), các tính toán lựa chọn (Action Mapping) dựa trên kiến trúc và cấu hình nhằm xác định thành phần lớp chính sẽ thực hiện yêu cầu của user. Đây chính là chức năng điều khiến application flow của Controller. Vì là nơi đón nhận user input cho nên thường thì các thành phần xử lý form (ví dụ ActionForm trong Struts) sẽ thuộc về Controller. Controller cũng sẽ có chức năng validate form đơn giản nhằm thực hiện chức năng điều khiển luồng của mình.

Một khi xuất hiện các Event phức tạp, như là một Observer, Controller cần bổ sung thêm các lớp mới vào để xử lý chúng theo các quy trình độc lập, giúp phân tán bớt code tập trung vào lớp Action. Do là thành phần điều phối, Controller không nhất thiết phải tương tác với Model rồi mới tương tác với View. Bất cứ lỗi ngoại lệ nào xảy ra hay một trạng thái request không mong muốn xuất hiện thì Controller có thể phản ứng bằng cách chọn View trực tiếp.

Khi giao tiếp với Model, Controller sẽ tiến hành 2 cách
+ extract dữ liệu hay state
+ update dữ liệu

Ví dụ:

// Trong lớp Action của Controller
$productId           = $this->request->get('product_id');
$electronicWarehouse = new ElectronicProductWarehouse();
$availableProducts   = $electronicWarehouse->findAvailableProductsById($productId);

Sau khi có được dữ liệu, theo cách hành xử thông thường, View thích hợp sẽ được lựa chọn. COntroller sẽ chuyển tiếp dữ liệu vào View để nó xử lý

Ví dụ:

// Trong lớp Action của Controller

// Load the PHP Savant2 class file and create an view instance.
require_once 'Savant2.php';
$view    = new Savant2();

if (true === $availableProducts->empty())
{
    // Assign values to the Savant instance.
    $view->assign('products', $availableProducts);

    // Display a template using the assigned values.
    $view->display('products.tpl.php');
}
else
{
    // Display a template using the assigned values.
    $view->display('products_unavailable.tpl.php');
}

CakePHP, Zend Framework, Symfony, Ruby on Rails, Struts hay Spring MVC lựa chọn cách làm này. Bằng việc xóa khỏi bộ não View kiến thức về Model, Controller có thể thay thế nhiều Model, các API trên Model mà nhưng không cần phải nhắc View về điều đó. Điều này là đặc biệt quan trọng khi mà kiến trúc ứng dụng đòi hỏi phải phù hợp với việc các lớp Model cần thay đổi 1 cách linh hoạt (ví dụ như có sự can thiệp của Dependency Injection). Nhưng có vẻ như đây là nhiệm vụ của tầng Integration. Trong bài viết sau chúng ta sẽ xem xét lại vấn đề này với MVC Pull.

Có một số người khi thiết kế ứng dụng theo MVC đã cố gắng dồn hết chức năng điều khiển luồng vào lớp Action cho nên dẫn đến hiện tượng Fat Controller. Ví dụ như những người code Rails sau khi đã định nghĩa Model dựa trên ActiveRecords để làm các công việc như extract data, update data và validate dữ liệu thì họ bỏ hết trách nhiệm xử lý business logic sang cho Controller. Thực tế là
+ Fat Controller, hay Thin Controller là các từ gọi nôm na của một thiết kế tồi. Từ Fat có lẽ đến từ thế giới Ruby on Rails khi mà nhiều người ở đó quá cứng nhắc trong việc tuân thủ theo mô hình Rails mà không biết rằng bản thân Controller cũng có thể có các sub layer giúp giải phóng các tác vụ dạng thủ tục thành các thành phần nhỏ riêng biệt, nhưng vẫn thuộc về Controller. Hoặc theo 1 hướng khác, Model tỏ ra vô tránh nhiệm với dữ liệu trả về khiến cho công việc điều khối của Controller thêm nặng nề. (xem bên dưới)
+ Tôi cho rằng Controller cần làm đúng nhiệm vụ của nó. Nó không phải là God Class. Nó không thể đẩy trách nhiệm cho Model hay cho View chỉ bởi vì nó có nhiều việc hơn 2 thành phần kia trong 1 hay 1 số user case cụ thể nếu như giao diện giữa chúng đã đủ compact.
+ Theo code mẫu trên thi View có vẻ như hơi ít trách nhiệm. Một phần trách nhiệm xác định cấu trúc để hiển thị đã được đẩy sang cho Controller. Đây cũng là vấn đề làm cho Controller trở nên nặng nề hơn.

Do tính chất công việc của Controller là khá phức tạp và đa dạng cho nên việc Event Handler Controller có thể tiếp tục tách ra nhỏ hơn cũng là điều cần tính đến thay vì thực hiện việc xử lý tất cả các signal, user input … trên cùng một lớp.

MODEL

Model là các lớp cung cấp dữ liệu, dịch vụ liên quan đến dữ liệu và business logic. Chúng có thể là

+ Đánh giá tính hợp lệ của dữ liệu.
Ví dụ kiểm tra user input có đúng với rule của hệ thống không (ví dụ user credentials…)
+ Chuyển đổi dữ liệu:
Ví dụ convert định dạng file, chuyển đổi tỉ giá, language translation ….
+ Đưa ra quyết định về nghiệp vụ:
Ví dụ đưa ra các dữ liệu, lời khuyên tư vấn đầu tư dựa trên user input và các dữ liệu đang có
Ví dụ có tính integration: thực hiện chính sách của công ty là từ ngày 20 – 24 thì áp dụng cách tính giá của phòng Sale 1. Sau thời điểm đó thì áp dụng cách tính giá của phòng Sales 2.
+ Thực hiện việc xử lý dữ liệu theo một quy trình (workflows): ví dụ controller tiếp nhận yêu cầu xuất kho 300 chiếc ô tô từ kho A từ user. Sau khi kiểm tra tính chính quy của việc gửi yêu cầu, Controller chuyển yêu cầu này thành lợi gọi hệ thống để thông báo cho Model. Tuy nhiên việc xuất kho 300 chiếc ô tô cần phải được thực hiện theo 1 workflow mà chỉ có Model biết. Tạm thời bỏ qua sự xuất hiện của một rule engine, chúng ta có thể thấy Model thực hiện một số đoạn code có tính thủ tục như: 1) Thông báo với module sales về việc bán được 300 chiếc ô tô và yêu cầu trả lời. 3) Model sales ghi nhận và kiểm tra chính sách hoa hồng và thời điểm hiện tại và chuyển tiếp yêu cầu đến module Inventory và module Accounting 4) module Accounting thực hiện việc tính toán, báo giá thanh toán theo USD và VND với tỉ giá ở thời điểm hiện tại cũng như không quên trừ triết khấu và hoa hồng và gửi kèm thông báo thanh toán theo theo yêu cầu xuất kho 5) module Inventory kiểm tra yêu cầu xuất kho từ Sales và thông báo thanh toán từ Accounting để xuất kho, và ghi nhận giao dịch bằng cách ghi lại mã số yêu cầu của Sales và mã số thanh toán của Accounting. module này tiếp tục kiểm tra số ô tô có trong kho theo chất lượng và đặc tả yêu cầu rồi trả lời: có được hay không 6) Nếu được, nó sẽ thành lập thông báo xuất kho ….. Cuối cùng Model trả lại trạng thái xử lý và dữ liệu nếu có.

Do có 2 vai trò tương đối tách biệt cho nên một Model thường được tách thành các lớp có các domain xử lý khác biệt
+ Business logic thường là xử lý rule hay policy của nghiệp vụ cũng như business workflows. Tầng này có sự góp mặt của các rule engine và các integration engine (trong đó có Spring bên Java và Flow3, Scarlet, Seasar, PHPCrafty bên PHP).
+ Domain data: Cung cấp/lưu trữ dữ liệu và việc chuyển đổi dữ liệu thành các dạng khác nhau theo yêu cầu. Các tầng như Persistent Layer nằm ở đây. Vì thế chúng ta sẽ gặp các lớp của PDO (PHP Data Object), Pear MDB2, PHP Doctrine, JDBC, JTA, JPA, Hibernate, JDO … và cả các lớp thực hiện DAO.

Trong các tình huống đơn giản, Model chỉ làm vài thao tác đơn giản như fetch dữ liệu từ database. Trong các tình huống phức tạp, việc xử lý có thể là tổ hợp của hàng trăm lớp diễn ra trên 1 hoặc vài server hoặc thậm chí dữ liệu hay quyết định được đưa ra từ Model lại là tổng hợp kết quả từ 1 vài data center nằm rải rác trên vài lục địa. Do vậy trong Model không chỉ có các thao tác trên database và có còn là file system, memory, networking I/O …

Worlflow mà Model điều khiển hoàn toàn có tính nghiệp vụ đặc thù. Nó khác với Application Flow vốn có tính hệ thống và thiên về technical analysis (phần cứng hay phần mềm) của Controller. Applicaton flow được xử lý như thế nào chủ yếu là do software architect xác định nhưng với workflows thì đó là do các business analyst xác định do nó thiên về nghiệp vụ (gần với cuộc sống và cách tổ chức quy trình làm việc của 1 tổ chức chứ không phải là quy trình kĩ thuật của thiết kế hướng đối tượng).

Model là Passive để hoạt động như là một Service Layer nhằm có thể re-use giữa các Controller nhưng nó không phải là dead-end. Thay vào đó nó cần biết giao tiếp với Controller như thế nào cho có hiệu quả. Sự hiệu quả này thể hiện ở 2 hướng

+ Signal hay message
+ Data type

Một software architect của Microsoft thường nhấn mạnh đến The Power Of Sameness trong software development trong đó bao gồm
+ Coding standard
+ Naming matters
+ Common interface

Khi Controller gọi Model thông qua API của Model, nó cần biết 1 số behavior chung của Model (tức là common interface). Ví dụ:

+ Cách Model đó gửi signal về quá trình nó xử lý yêu cầu. Có hay không có Exception, kiểu của Exception, Exception trong trường hợp nào. Những cái đó cần được comment chi tiết
+ Kiểu trả lại cần nhất quán và well-defined

Cái mà Controller quan tâm đến output của Model chính là trạng thái (message : false, true) hay dữ liệu. Vì Model thực hiện phần nghiệp vụ nên dữ liệu trả lại của nó cần phải phản ánh tính nghiệp vụ. Thông thường một kiểu trả lại được coi là well-defined nếu nó là một BO (Business Object) tức là một lớp getter/setter để nhận và xuất dữ liệu. BO thường là có kiểu phản ánh bản chất dữ liệu mà nó có ví dụ như

+ User
+ Product
+ Employee

Bằng cách trả lại các đối tượng được định nghĩa rõ ràng, Model trả tỏ ra có trách nhiệm với Controller hay các thành phần gọi nó để giúp các thành phần này bớt confused (giảm các đoạn mã if/else/switch/instanceof/try/catch) và tạo ra sức mạnh của cái gọi là The Power Of Sameness. Tuy nhiên, việc BO có thể chỉ là một data container thuần túy hay là một Domain Object thì còn đang gây tranh cãi vì getter/setter thuần túy sẽ gây sự nghi ngờ về tính hợp lệ của encapsulation. Tuy nhiên tôi vẫn có một quan điểm khác về kiểu của BO trong khi lập trình PHP.

Tương tự như trong trường hợp Controller lấy state từ Model, khi Controller cung cấp thông số cho Model để Model trả lại state theo hướng mà Controller mong muốn từ Controller cũng phải định nghĩa được 1 common interface.

VIEW

View là các lớp định nghĩa cách thức trình bày dữ liệu (không update dữ liệu). Trong các web framework, nó gồm 2 phần chính:
+ Template file định nghĩa cấu trúc và cách thức trình bày dữ liệu cho user. Ví dụ như layout, color, windows …
+ Logic xử lý cách áp dụng dữ liệu vào cấu trúc trình bày. Logic này có thể bao gồm việc kiểm tra định dạng dữ liệu, chuyển đổi định dạng dữ liệu sang một sạng dữ liệu trung gian để có thể hiển thị với cấu trúc template đang có…, kiểm tra trạng thái và đặc tính của dữ liệu để lựa chọn một cấu trúc hiện thị phù hợp. Tất nhiên là trong Passive View thì việc lựa chọn cấu trúc hiện thị đôi khi lại do Controller.

Bản thân View cũng là một tổ hợp của nhiều lớp. Và nó cũng có thể có SubView để giảm tải trên 1 số lớp chính và để sử dụng lại mã. Và do vậy tính logic của View có thể là logic của một cây phân cấp.

Trong mô hình truyền thống, View có trách nhiệm chuyển đổi dữ liệu hay state của Model thành cấu trúc visual. Do vậy dữ liệu của Model cần well-defined (xem ở trên). Sự tách biệt của 2 thành phần này sẽ giúp cho người lập trình phân định được 1 biên giới rõ ràng giữa cách thức lưu trữ/lấy dữ liệu và cách trình bày dữ liệu. Do vậy tính phức tạp của quy trình lấy dữ liệu, xử lý dữ liệu cũng như (sự thay đổi của chúng theo thời gian) trước khi trả về sẽ không làm ảnh hưởng đến việc trình bày dữ liệu. Do vậy sự khác biệt về công nghệ lấy dữ liệu và công nghệ render không gây ảnh hưởng đến ứng dụng. Điều này khá quan trọng trong việc tích hợp các ứng dụng.

Ngoài ra, cách làm này thực sự đảm bảo việc tách biệt vai trò của người thiết kế giao diện với vai trò của lập trình viên thiên về dữ liệu. Như vậy khi làm việc theo nhóm, PM có thể tổ chức nhóm phát triển thành các nhóm kĩ năng và phát triển ứng dụng song song với nhau

+ Page designer
+ Application flow developer
+ Business Process and Data developer

Tuy nhiên, View của thế giới hiện tại đã khác đi nhiều. Có một lớp lập trình viên thiên về tầng presentation đã ra đời. Do vậy, nhóm kĩ năng mới sẽ là

+ Page designer: những người hiểu template file và cách design nó sao cho đẹp
+ Presentation Developer: những người hiểu template, hiểu template cần dữ liệu từ nguồn nào và còn hiểu cả cách cấu trúc template cần thay đổi như thế nào với dữ liệu và sự kiện từ Action Controller gửi xuống. Họ còn quan tâm đến việc View đó được sử dụng và sử dụng lại như thế nào trong các application flows khác nhau (ví dụ bối cảnh ajax). Đặc biệt họ quan tâm đến các ứng dụng mashup.
+ Application flow developer: người hướng đến việc render toàn bộ trang, page navigation và để trách nhiệm render từng khối cụ thể của trang vào Presentation Developer
+ Business Process and Data developer: người chỉ quan tâm đến việc kiểm tra tính hợp lệ của user input, business workflows, tính tích hợp của quy trình và dữ liệu nhưng không quan tâm đến dữ liệu đó được sử dụng ở đâu và theo cách render nào.

Vai trò của View cần thay đổi đặc biệt là các ứng dụng web thiên về View phức tạp khi vai trò của Model đã trở nên ổn định.

—————————————————————-
FAT CONTROLLER, THIN CONTROLLER VÀ GOD CLASS
—————————————————————-

Trong cộng đồng CakePHP, Zend Framework và Rails từ 1 – 2 năm trở lại đây bắt đầu xuất hiện lời ca thán về fat controller hay thin controller. Theo họ, việc chuyển một số công việc của Controller vào Model là hợp lý hơn. Phải chăng đó là dấu hiệu của God Class? Trong lập trình hướng đối tượng với MVC, việc tạo là một God Class là điều nên tránh. Fat Controller có thể là hiện tượng rời rạc của cái gọi là God Class. Tuy nhiên nó có thể chỉ là cách quan sát và nhận thức về tính thủ tục và labour-extensive của Controller hay View và tính tự động hóa do framework-backed của Model. Nhu cầu điều phối lại chức năng của Model, Controller và View rõ ràng là dấu hiệu của Code Smells.

Ví dụ: việc thiết kế Model thiếu trách nhiệm khiến cho công việc lọc và xử lý dữ liệu trả lại trên Controller và View trở nên labour-extensive hơn: http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model

Theo quan điểm của tôi, nếu bạn thấy Controller của bạn hay View làm nhiều hơn logic của nó cần có trong khi Model lại hơi ít việc quá thì hay xem lại cách thức giao tiếp giữa 3 thành phần này. Giao diện giữa chúng càng compact thì mô hình của bạn càng hiệu quả.

—————————————————————-
KHI CONTROLLER TRỞ THÀNH GOD CLASS
—————————————————————-

Controller có lẽ thành phần bị phê phán nhiều nhất trong MVC. Như đã nói ở trên rất nhiều những người code Rails sau khi đã định nghĩa Model dựa trên ActiveRecords để làm các công việc như extract data, update data và validate dữ liệu thì họ bỏ hết trách nhiệm xử lý business logic sang cho Controller. Controller cũng đóng vai trò là thành phần quản lý việc cung cấp dữ liệu cho View nên View thực sự là Passive. Thậm chí Controller còn biết cả cấu trúc tên biến dữ liệu sẽ dùng trên View. Lúc đó View giảm tính Logic của nó xuống chỉ là còn là điều khiển việc render trên 1 cấu trúc có sẵn. Vì thế ta thấy có công thức View = ViewRenderer.

Các khái niệm như partial hay slot chỉ là một cách tăng cường tính sử dụng lại view template và các view helper cũng chỉ làm 1 số logic xử lý dữ liệu trên View trước khi nó được render trên 1 cấu trúc template. Dữ liệu hoàn toàn là do Controller cung cấp. Theo cách này DRY biến mất vì cùng một thao tác cung cấp dữ liệu cho View có thể được duplicate trên nhiều Controller. Trên thực tế việc sử dụng lại View chẳng qua chỉ là sử dụng lại cấu trúc HTML và cách render dữ liệu trên trang HTML đó. Muốn dùng lại dữ liệu, bạn có thể cần pass cả đối tượng Model xuống View. http://mentalized.net/journal/2006/12/08/simplifying_my_rails_views/ Tôi gọi đây là một bad practice.

Cái mà View template cần chính là một thứ DTO (VOs) hoặc BO không phải là cả model vì chúng ta cần tránh phô trương quá nhiều logic trên template. Chúng ta nên tập trung các thao tác trên dữ liệu trên 1 hay 1 số node chính (đặc biệt là các node điều phối) của mô hình MVC thay vì trên các nhánh phụ để tổ chức và quản lý tài nguyên và nghiệp vụ cho dễ hơn.

Nếu như Controller xử lý quá nhiều (ví dụ như edit hay add 1 multi-step form là trường hợp tôi thấy khó chịu nhất) thì đó thường là liên quan đến xử lý user input thành dạng được chuẩn hóa trước khi phân tích nó thành 1 lời gọi trên Model. Thường thì khi đến Model, user input đã phải khá là fine grained. Tuy nhiên đối với các form phức tạp với nhiều checkbox có tính phân nhánh thì việc để Controller thực hiện việc validate dữ liệu là rất nặng nề. Như thôi đã nói ở trên Controller chỉ thực hiện các basic validation (ví dụ diễn giải URL thành các module, controller class và action method). Nhưng đối mặt với các form phức tạp với 20 – 30 input có phân nhánh mà để xử lý Controller có thể phải phân tích đến 10+ trường hợp và mỗi trường hợp có thể có đến nhiều hơn 1 trường hợp phụ thì Controller đã có nguy cơ phình to. Khi đó nhu cầu phân tách Controller đã xuất hiện.

Thông thường Controller được tách thành 3 lớp chính
+ Dispatcher
+ Filter
+ Event Handler: lớp Action trên Struts hay Zend hoặc Controller trên CakePHP, chính là nơi sẽ làm việc với user input và các quyết định trên View

Khi Event Handler Controller bị phình thì rõ ràng có lý do để xét đến
+ User Input Validation
+ Multistep View Handler
+ Changes Committer
+ View Selection

Theo cách làm của tôi, User Input Validation Logic nên được chuyển về cho Model và xác nhận rõ việc đánh giá user input theo các rule của application là một vấn đề business logic thay vì application flow logic. Điều này có tính hợp lý riêng. Ví dụ: trên trang contact, input có tên là job position không cần phải validate nhưng trên trang sign up thì lại cần. Lý do của sự khác biệt: do yêu cầu của nghiệp vụ. Rõ ràng là cách quy định này có thể thay đổi từ app này sang app khác mà không phụ thuộc vào software architect.

Khi đó thay vì dùng if/else, lớp thực hiện User Input Validation sẽ cài đặt các rule có sẵn hoặc custom rule hoàn toàn cách khỏi lớp Event Handler. Controller phụ trách event handler sẽ gọi đến Model chuyên biệt về xử lý user input này và yêu cầu validate hoặc không. Trong các trường hợp đặc biệt một form validation như vậy có thể re-use lại ở nhiều Controller khác nhau và hỗ trợ tính kế thừa.

Ví dụ:
+ Controller A sử dụng PasswordChangeForm để validate dữ liệu với các input pass1, pass2 và email
+ Controller B sử dụng PasswordChangeForm2 extends từ PasswordChangeForm với thêm input là username

Như vậy việc tách user input ở mức form ra khỏi Controller sẽ giúp

+ Reuse code thông qua re-use cả class hoặc extend
+ Reuse rule checking thông qua việc định nghĩa rule thành các chuẩn riêng
+ Reuse tính gắn kết rule vào form và do đó đơn giản hóa việc apply quy trình nghiệp vụ trên form.

Bằng cách này bạn sẽ thấy Event Handler Controller đã đơn giản đi rất nhiều. Trong phần tiếp theo tôi sẽ trình bày cách thiết kế MVC Pull của tôi để tiếp tục giảm vai trò điều phối của Controller và tăng tính chủ động của View

(còn tiếp)

—————————————————————-
Phụ lục
—————————————————————-

Code demo về việc tách form ra khỏi Controller. Các đoạn code dưới đây là các code thực sự chạy vận hành trên PONE framework để cung cấp 1 số dịch vụ tại World’Vest Base Inc. Tuy nhiên, đây là các code ở mức application developer. Do vậy, code này chỉ có thể test nếu có PONE framework. PONE cung cấp mô hình Observer pattern để các lớp dưới đây vận hành được.

File Home_PagesController: file Controller. Xem method contactAction()

_layout->attachCss('home');

        // Attach file: javascript
        $this->_layout->attachScript('home');

        // Attach file: meta
        $this->_layout->attachMeta('home');
    }

    /**
     * When use comes to this page by a mistake, redirect them to the about-us page
     *
     * @link   http://www.wvbresearch.com/home/pages
     * @name   index
     * @access public
     */
    public function indexAction()
    {
        $this->forward('home', 'pages', 'about');
    }

    /**
     * Show the introduction page about the website and company behind it
     *
     * @link   http://www.wvbresearch.com/home/pages/about
     * @name   about
     * @access public
     */
    public function aboutAction()
    {
        // Attach placeholder: the name of ElementGroup
        $this->_layout->registerBody('homeAbout');

        // Set content for the response
        $this->_response->setContent($this->_layout->render());
    }

    /**
     * Show the contact page
     *
     * @link   http://www.wvbresearch.com/home/pages/contact
     * @name   contact
     * @access public
     */
    public function contactAction()
    {
        // Instantiate form object
        $this->initForm('contact');
        // Create action state object
        $this->initState();

        try
        {
            // Check if the form is submitted and/or is submitted correctly
            if (false === $this->_form->isPost() || false === $this->_form->validate())
            {
                throw new Exception();
            }

            // Initialize the model object wih an appropriate database access object
            $model    = new ContactModel($this->getDatabaseConnection('oracleweb', true));

            // Persist the form data into data storage medium (database, file system, messaging server, email server, file-backed memory server)
            if (false === $model->save($this->_form))
            {
                throw new Exception();
            }
        }
        catch (Exception $ex)
        {
            // the form input is not complete. User need to fill it (if the form is rendered at the first time)
            // or user need to correct the data and submit again
            $this->_state->setState(Pone_Action_State::FORM_INCOMPLETE);
        }

        // Render the body part that contains contact form
        $this->_layout->registerBody('homeContact');

        // Set content for the response
        $this->_response->setContent($this->_layout->render());
    }

    /**
     * Show the faqs page
     *
     * @link   http://www.wvbresearch.com/home/pages/faq
     * @name   faq
     * @access public
     */
    public function faqAction()
    {
        // Attach placeholder: the name of ElementGroup
        $this->_layout->registerBody('homeFaq');

        // Set content for the response
        $this->_response->setContent($this->_layout->render());
    }

    /**
     * Show the WVB global offices page
     *
     * @see    Group_HomeGlobalOffices
     * @see    index.tpl.php
     * @link   http://www.wvbresearch.com/home/pages/offices
     * @name   WVB Global Offices
     * @access public
     */
    public function officesAction()
    {
        // Attach placeholder: the name of ElementGroup
        $this->_layout->registerBody('homeGlobalOffices');

        // Set content for the response
        $this->_response->setContent($this->_layout->render());
    }

    /**
     * Show the sitemap page
     *
     * @link   http://www.wvbresearch.com/home/pages/sitemap
     * @name   faq
     * @access public
     */
    public function sitemapAction()
    {
        // Attach placeholder: the name of ElementGroup
        $this->_layout->registerBody('homeSitemap');

        // Set content for the response
        $this->_response->setContent($this->_layout->render());
    }

    /**
     * Show the terms and conditions page
     *
     * @link   http://www.wvbresearch.com/home/pages/termsandconditions
     * @name   termsandconditions
     * @access public
     */
    public function termsandconditionsAction()
    {
        // Attach placeholder: the name of ElementGroup
        $this->_layout->registerBody('homeTerms');

        // Set content for the response
        $this->_response->setContent($this->_layout->render());
    }

    /**
     * Show the terms and conditions page
     *
     * @link   http://www.wvbresearch.com/home/pages/termsandconditions
     * @name   termsandconditions
     * @access public
     */
    public function privacyAction()
    {
        // Attach placeholder: the name of ElementGroup
        $this->_layout->registerBody('homePrivacy');

        // Set content for the response
        $this->_response->setContent($this->_layout->render());
    }
}

?>

File Form_Contact đóng vai trò như là Form Model, một model đặc biệt phục vụ cho việc xử lý form validation dựa trên business rules dành cho form.

setFormKey('contact');

        $this->setFormFeedbackHeader(_t('common.error.form.false'));

        $titleRules = array(
        Pone_Form_Rule::NOT_EMPTY => array('feedback' => _t('common.error.title.empty'))
        );
        $this->setValidationRule('title', $titleRules);

        $nameRules = array(
        Pone_Form_Rule::NOT_EMPTY => array('feedback' => _t('common.error.name.empty'))
        );
        $this->setValidationRule('name', $nameRules);

        $emailRules = array(
        Pone_Form_Rule::EMAIL => array('feedback' => _t('common.error.email.notvalid'))
        );
        $this->setValidationRule('email', $emailRules);

        $contentRules = array(
        Pone_Form_Rule::NOT_EMPTY => array('feedback' => _t('common.error.content.empty'))
        );
        $this->setValidationRule('content', $contentRules);
    }
}
?>

File Group_HomeContact: Vùng View sẽ tương tác trực tiếp với Form trên

getFront()->getActionController();
		$this->form = $action->getDataForm();

		if (null    === $this->form)
		{
			throw new Pone_Exception('This element group is form-based so the action controller needs to initialize a form object for being re-used in this object');
		}

		$message    = $action->getActionState();

		if (Pone_Action_State::OK === $message->getState())
		{
			$this->_templateFile  = 'homeContactCompletion';
		}
	}
}
?>

One thought on “Mô hình MVC: Vai trò của các thành phần và các vấn đề thiết kế trong các Web Framework

Leave a Reply

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