Nhắc lại về ViewModel

ViewModel như là một đối tượng chứa các biến mà action của controller trả về, ViewModel sẽ chuyển các biến đó cho View (PhpRender truy cập và dựng HTML). Tuy nhiên ViewModel cũng đối tượng mà MVC của ZF biết sẽ dùng template (.phtml) nào để dựng HTML.

Action của controller thiết lập biến cho ViewModel ngay tại phương thức khởi tạo của ViewModel hoặc dử dùng phương thức setVariable, setVariable, nếu muốn thay đổi template mà View dùng tới với ViewModel này thì dùng phương thức của ViewModel: setTemplate('templatename')

//Truyền biến ngay từ khởi tạo
$view = new ViewModel([
    'tenbien1' => $giatri1,
    'tenbien2' => $giatri2,
    ...
    ]);
//Truyền biến ngay từ khởi tạo
$view = new ViewModel();

$view->setVariables([
    'tenbien1' => $giatri1,
    'tenbien2' => $giatri2,
    ...
    ]);
//Hoặc
$view->setVariable('tenbien3', $giatri3)

Lúc này ở các View .phtml có thể truy cập và lấy giá trị của biến bằng cách gọi tên biến

<? //Trong file .phtml
    $var1 = $this->tenbien1;
    $var2 = $tenbien2;
    //....

ViewModel chứa ViewModel khác

Một ViewModel có thể chứa các ViewModel con, điều này giúp bạn dễ dàng hơn trong trình bày cấu trúc trang web. Khi PhpRender làm việc, nó sẽ kiểm tra xem ViewModel có chứa các ViewModel con không, nếu có thì các ViewModel con sẽ được dựng (render) HTML trước, kết quả HTML trả về sẽ đặt vào vị trí thích hợp của ViewModel theo chỉ số placeholder

Một placeholder là tên biến lưu trữ trong ViewModel

Các hàm bổ xung ViewModel

Phương thức Chi tiết
addChild addChild($child, $placeholder, $append): Thêm vào một ViewModel con $child, kết quả dựng HTML của $child lưu vào biến $placeholder của cha, $append là true thì nối thêm kết quả vào, false thì ghi đè kết quả vào $placeholder
getChildren() Lấy danh sách các ViewModel con
hasChildren() Kiểm tra có ViewModel con không
clearChildren() Xóa toàn bộ ViewModel con
count() Trả về số lượng ViewModel con
getIterator() Trả về đối tượng iterator các ViewModel con (duyệt qua được bằng next)
setTerminal() Thiết lập cờ termial: nếu true kế thúc việc Render ở Model này
terminate() Kiểm tra có cờ terminal.
setCaptureTo($placeholder) Thiết lập kết quả sẽ gán vào $placeholder
setAppend() Thiết lập kết quả ghi đề hay nối thêm vào $placeholder
isAppend() Kiểm tra xem là ghi đề hay nối thêm vào $placeholder

Áp dụng ViewModel con

Trở lại ví dụ về sản phẩm ỏ phần trước, bạn đã xây dựng được Controller có tên Product (xem lại Tạo controller Product)

Trong đó bạn có Route để truy cập đến action detail với URL: http://localhost/zf3/product/dien-thoai/dien-thoai-iphonex.html

Giờ bạn sửa lại action detail như sau:

module/Application/src/Controller/ProductController.php

//...
public function detailAction() {

    //Đọc biến dữ liệu từ URL đã phân tích bởi Route
    $categoryname = $this->params()->fromRoute('categoryname');
    $productname = $this->params()->fromRoute('productname');

    //Phát sinh một sản phẩm demo
    $product = [
        'cate' => $categoryname,
        'name' => $productname,
        'price'=> 10000,
        'description' => 'iPhone 7 256 GB mang trên mình thiết kế quen thuộc từ thời iPhone 6,
                            máy được trang bị bộ nhớ lưu trữ lớn, khả năng chống nước cao cấp,
                            dàn loa stereo cho âm thanh hay và cấu hình cực mạnh.',
        'img' => 'https://cdn.tgdd.vn/Products/Images/42/87838/iphone-7-256gb-5-400x460.png'
    ];


    $view = new ViewModel(
        ['name' => $productname]
    );
    return $view;

}
//...

Tiếp theo sửa detail.phtml

module/Application/view/application/product/detail.phtml

<?=$this->adstop?>

<h1><small>Tên sản phẩm: </small><?=$this->name?></h1>

<?=$this->ads?>
<div class="row">
    <div class="col-md-8">
        <?=$this->main?>
    </div>

    <div class="col-md-4">
        <?=$this->sidebar?>
    </div>
</div>

Chạy thử với URL: http://localhost/zf3/product/dien-thoai/dien-thoai-iphonex.html

Từ detail.phtml ta thấy nó có xuất các dữ liệu adstop, name, sidebar, main. Kết quả ở trên ta chỉ thấy có biến name xuất ra vì ở action có thiết lập biến này cho ViewModel. Các biến còn lại chưa thiết lập nên giá trị xuất là NULL (không có gì).

Giờ ta sẽ dùng adstop, sidebar, main như là placeholder để các ViewModel con chèn dữ liệu vào.

Tạo ViewModel con $main

Cập nhật lại action detail như sau:

module/Application/src/Controller/ProductController.php

//...
public function detailAction() {

    //Đọc biến dữ liệu từ URL đã phân tích bởi Route
    $categoryname = $this->params()->fromRoute('categoryname');
    $productname = $this->params()->fromRoute('productname');

    //Phát sinh một sản phẩm demo
    $product = [
        'cate' => $categoryname,
        'name' => $productname,
        'price'=> 10000,
        'description' => 'iPhone 7 256 GB mang trên mình thiết kế quen thuộc từ thời iPhone 6,
                            máy được trang bị bộ nhớ lưu trữ lớn, khả năng chống nước cao cấp,
                            dàn loa stereo cho âm thanh hay và cấu hình cực mạnh.',
        'img' => 'https://cdn.tgdd.vn/Products/Images/42/87838/iphone-7-256gb-5-400x460.png'
    ];

    
    //Tạo viewmodel con $main, và thiết lập capture kết quả
    //vào placeholder có tên main của ViewModel cha

    $main = new ViewModel(['product' => $product]);
    $main->setTemplate('application/partial/main');
    $view->addChild($main, 'main');


    $view = new ViewModel(
        ['name' => $productname]
    );
    return $view;

}
//...

Ở trên ViewModel $main có sử dụng template là application/partial/main và truyền vào nó biến $product, ta tiến hành tạo template này

module/Application/view/application/partial/main.phtml

<img src="<?=($this->product)['img']?>" />
<div>
    <?=($this->product)['description']?>
</div>

Chạy thử với URL: http://localhost/zf3/product/dien-thoai/dien-thoai-iphonex.html

Tiếp theo tạo một ViewModel con $tintuc và thiết lập nó capture vào keyholder sidebar

Cập nhật lại action detail như sau:

module/Application/src/Controller/ProductController.php

//...
public function detailAction() {

    //Đọc biến dữ liệu từ URL đã phân tích bởi Route
    $categoryname = $this->params()->fromRoute('categoryname');
    $productname = $this->params()->fromRoute('productname');

    //Phát sinh một sản phẩm demo
    $product = [
        'cate' => $categoryname,
        'name' => $productname,
        'price'=> 10000,
        'description' => 'iPhone 7 256 GB mang trên mình thiết kế quen thuộc từ thời iPhone 6,
                            máy được trang bị bộ nhớ lưu trữ lớn, khả năng chống nước cao cấp,
                            dàn loa stereo cho âm thanh hay và cấu hình cực mạnh.',
        'img' => 'https://cdn.tgdd.vn/Products/Images/42/87838/iphone-7-256gb-5-400x460.png'
    ];

    $main = new ViewModel(['product' => $product]);
    $main->setTemplate('application/partial/main');
    $view->addChild($main, 'main');

    
    //Tạo viewmodel con $tintuc, và thiết lập capture kết quả
    //vào placeholder có tên sidebar của ViewModel cha
    $tintuc = new ViewModel();
    $tintuc->setTemplate('application/partial/tintuc');
    $view->addChild($tintuc, 'sidebar', true);


    $view = new ViewModel(
        ['name' => $productname]
    );
    return $view;

}
//...

Tạo template application/partial/tintuc

module/Application/view/application/partial/tintuc.phtml

<h2>Tin tức</h2>
<div class="list-group">
    <a href="#" class="list-group-item list-group-item-action flex-column align-items-start">

        <div class="d-flex w-100 justify-content-between">
            <h5 class="mb-1">iPhone 8 Plus bán tốt hơn iPhone 8</h5>
            <small>3 ngày trước</small>
        </div>
        <p class="mb-1">
            <img src="https://cdn2.tgdd.vn/Files/2017/11/10/1040182/iphone8vs8plus_800x450-300x200.jpg" class="w-25">
            Q3/2017: iPhone 8 Plus bán tốt hơn iPhone 8, nhưng vẫn thua iPhone 7</p>
        <small>Mời xem chi tiết.</small>
    </a>
    <a href="#" class="list-group-item list-group-item-action flex-column align-items-start">
        <div class="d-flex w-100 justify-content-between">
            <h5 class="mb-1">7 game iPhone đặc sắc dành cho ngày cuối tuần (26/11)</h5>
            <small>3 ngày trước</small>
        </div>
        <p class="mb-1">
            <img src="https://cdn2.tgdd.vn/Files/2017/11/10/1040182/iphone8vs8plus_800x450-300x200.jpg" class="w-25">

            Donec id elit non mi porta gravida at eget metus. Maecenas sed diam eget risus varius blandit.</p>
        <small class="text-muted">Donec id elit non mi porta.</small>
    </a>

</div>

Chạy thử với URL: http://localhost/zf3/product/dien-thoai/dien-thoai-iphonex.html

Bạn thấy ở Sidebar đã có mục tin tức, giờ bạn muốn thêm vào đúng vị trí này một ViewModel nữa, ví dụ hiện thị sản phẩm khác.

Cập nhật lại action detail như sau:

module/Application/src/Controller/ProductController.php

//...
public function detailAction() {

    //Đọc biến dữ liệu từ URL đã phân tích bởi Route
    $categoryname = $this->params()->fromRoute('categoryname');
    $productname = $this->params()->fromRoute('productname');

    //Phát sinh một sản phẩm demo
    $product = [
        'cate' => $categoryname,
        'name' => $productname,
        'price'=> 10000,
        'description' => 'iPhone 7 256 GB mang trên mình thiết kế quen thuộc từ thời iPhone 6,
                            máy được trang bị bộ nhớ lưu trữ lớn, khả năng chống nước cao cấp,
                            dàn loa stereo cho âm thanh hay và cấu hình cực mạnh.',
        'img' => 'https://cdn.tgdd.vn/Products/Images/42/87838/iphone-7-256gb-5-400x460.png'
    ];

    $main = new ViewModel(['product' => $product]);
    $main->setTemplate('application/partial/main');
    $view->addChild($main, 'main');


    $tintuc = new ViewModel();
    $tintuc->setTemplate('application/partial/tintuc');
    $view->addChild($tintuc, 'sidebar', true);

    
    //Tạo viewmodel con $tintuc, và thiết lập capture kết quả
    //vào placeholder có tên sidebar của ViewModel cha
    $sanphamkhac = new ViewModel();
    $sanphamkhac->setTemplate('application/partial/sanphamkhac');
    //Chú ý giá trị true
    $view->addChild($sanphamkhac, 'sidebar', true);
    

    $view = new ViewModel(
        ['name' => $productname]
    );
    return $view;

}
//...

Tạo template application/partial/sanphamkhac

module/Application/view/application/partial/sanphamkhac.phtml

<h2 class="mt-4">Sản phẩm khác</h2>
<div class="card mt-4">
    <img class="card-img-top" src="https://cdn.tgdd.vn/Products/Images/42/114113/iphone-8-64gb-1-400x460.png" alt="Card image cap">
    <div class="card-body">
        <h4 class="card-title">Điện thoại iPhone 8 64GB</h4>
        <p class="card-text">iPhone 8 64GB nổi bật với điểm nhấn mặt lưng kính kết
            hợp cạnh viền và mặt trước giữ nguyên thiết kế như iPhone 7,
            cùng với đó là hàng loạt công nghệ đáng mong đợi như sạc nhanh, không dây hay hỗ trợ thực tế ảo AR.</p>
        <a href="#" class="btn btn-primary">Mua ngay</a>
    </div>
</div>

Chạy thử với URL: http://localhost/zf3/product/dien-thoai/dien-thoai-iphonex.html

Với cách làm tương tự bạn hãy tạo ViewModel con, template để hiện thị nội dung quảng cáo và capture vào vị trí adstop