symfony

18
Tóm tắt Hôm qua, chúng ta đã khám phá cách quản lý database của symfony: trừu tượng sự khác nhau giữa các database engines, chuyển đổi cơ sở dữ liệu thành các lớp hướng đối tượng. Chúng ta cũng đã sử dụng Doctrine để tạo database schema, tạo các bảng, và tạo sẵn một vài dữ liệu mẫu. Hôm nay, chúng ta sẽ bắt đầu chỉnh sửa module job đã tạo hôm qua. Module job đã có tất cả các mã nguồn cần thiết: Một trang list các công việc Một trang tạo công việc mới Một trang cập nhật công việc đã có Một trang xóa công việc Mặc dù mã nguồn đã có thể sử dụng, nhưng chúng ta cần sửa lại templates cho phù hợp. Kiến trúc MVC Nếu bạn đã từng phát triển một website bằng PHP mà không dùng framework, thường với mỗi trang HTML bạn sẽ dùng một file PHP. File PHP này sẽ chứa nhiều kiểu cấu trúc: các cấu hình khởi tạo và toàn cục, business logic liên quan đến yêu cầu của trang, lấy các dữ liệu từ database, và cuối cùng tạo mã HTML để hiển thị. Bạn có thể sử dụng một templating engine để tách phần logic và HTML. Tất nhiên, bạn cũng có thể sử dụng một database abstraction layer để tách phần thao tác với model ra khỏi business logic. Nhưng thường bạn sẽ tạo ra rất nhiều code mà việc maintain trở thành cơn ác mộng. Có thể bạn sẽ xây dựng ứng dụng rất nhanh, nhưng thật khó để thay đổi, nâng cấp, đặc biệt khi không có ai ngoại trừ bạn hiểu được cách nó làm việc. Có một giải pháp tuyệt vời để giải quyết những vấn đề trên. Đối với việc phát triển web , giải pháp thường dùng là tổ

Transcript of symfony

Page 1: symfony

Tóm tắt

Hôm qua, chúng ta đã khám phá cách quản lý database của symfony: trừu tượng sự khác nhau giữa các database engines, chuyển đổi cơ sở dữ liệu thành các lớp hướng đối tượng. Chúng ta cũng đã sử dụng Doctrine để tạo database schema, tạo các bảng, và tạo sẵn một vài dữ liệu mẫu.

Hôm nay, chúng ta sẽ bắt đầu chỉnh sửa module job đã tạo hôm qua. Module job đã có tất cả các mã nguồn cần thiết:

Một trang list các công việc Một trang tạo công việc mới Một trang cập nhật công việc đã có Một trang xóa công việc

Mặc dù mã nguồn đã có thể sử dụng, nhưng chúng ta cần sửa lại templates cho phù hợp.

Kiến trúc MVC

Nếu bạn đã từng phát triển một website bằng PHP mà không dùng framework, thường với mỗi trang HTML bạn sẽ dùng một file PHP. File PHP này sẽ chứa nhiều kiểu cấu trúc: các cấu hình khởi tạo và toàn cục, business logic liên quan đến yêu cầu của trang, lấy các dữ liệu từ database, và cuối cùng tạo mã HTML để hiển thị.

Bạn có thể sử dụng một templating engine để tách phần logic và HTML. Tất nhiên, bạn cũng có thể sử dụng một database abstraction layer để tách phần thao tác với model ra khỏi business logic. Nhưng thường bạn sẽ tạo ra rất nhiều code mà việc maintain trở thành cơn ác mộng. Có thể bạn sẽ xây dựng ứng dụng rất nhanh, nhưng thật khó để thay đổi, nâng cấp, đặc biệt khi không có ai ngoại trừ bạn hiểu được cách nó làm việc.

Có một giải pháp tuyệt vời để giải quyết những vấn đề trên. Đối với việc phát triển web , giải pháp thường dùng là tổ chức code theo MVC design pattern. Pattern này chia code thành ba tầng:

Model bao gồm business logic (database nằm ở tầng này). Bạn đã thấy rằng symfony chứa tất cả các class và file liên quan đến Model trong thư mục lib/model.

View là những gì tương tác với người dùng (template engine là một phần của tầng này). Trong symfony, tầng View được tạo bởi PHP templates. Các file này nằm trong các thư mục templates khác nhau mà chúng ta sẽ thấy ở các phần sau trong ngày hôm nay.

Controller thực hiện việc lấy dữ liệu từ Model và chuyển cho View để hiển thị ở client. Khi chúng ta cài symfony trong ngày đầu tiên, chúng ta đã thấy rằng mọi yêu cầu được điều khiển bởi file front controllers (index.php và

Page 2: symfony

frontend_dev.php). Những file front controllers này sẽ tìm actions tương ứng để thực hiện yêu cầu đó. Như chúng ta thấy hôm qua, các action được nhóm lại trong module.

Hôm nay, chúng ta sẽ dựa vào những nội dung trong ngày 2 để chỉnh sửa lại mã nguồn đã có sẵn của trang chủ và trang chi tiết công việc. Đồng thời, chúng ta cũng chỉnh sửa rất nhiều file liên quan để làm rõ cấu trúc thư mục của symfony và cách phân chia code giữa các tầng.

Page 3: symfony

Layout

Nếu để ý, bạn sẽ thấy rằng các trang có nhiều phần giống nhau.Bạn cũng hiểu rằng việc lặp lại code thật tệ, bất kể đó là code HTML hay PHP, do đó chúng ta cần tìm cách để giảm sự lặp lại này.

Một cách giải quyết là tách các header và footer thành các file riêng và include chúng vào mỗi template:

Nhưng ở đây, file header và footer không chứa valid HTML. Cần có cách tốt hơn. Thay vì reinventing the wheel, chúng ta dùng một design pattern khác để giải quyết vấn đề này: decorator design pattern. Decorator design pattern giải quyết vấn đề theo cách: sau khi nội dung chính được tạo, ta sẽ dùng một global template để thêm các phần còn lại, global template trong symfony gọi là một layout:

Layout mặc định của một application là file layout.php nằm trong thư mục apps/frontend/templates/. Thư mục này chứa tất cả các global templates cho một application.

Thay layout mặc định của symfony bằng đoạn code sau:

<!-- apps/frontend/templates/layout.php --><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Jobeet - Your best job board</title> <link rel="shortcut icon" href="/favicon.ico" /> <?php include_javascripts() ?>

Page 4: symfony

<?php include_stylesheets() ?> </head> <body> <div id="container"> <div id="header"> <div class="content"> <h1><a href="<?php echo url_for('job/index') ?>"> <img src="/images/logo.jpg" alt="Jobeet Job Board" /> </a></h1>  <div id="sub_header"> <div class="post"> <h2>Ask for people</h2> <div> <a href="<?php echo url_for('job/index') ?>">Post a Job</a> </div> </div>  <div class="search"> <h2>Ask for a job</h2> <form action="" method="get"> <input type="text" name="keywords" id="search_keywords" /> <input type="submit" value="search" /> <div class="help"> Enter some keywords (city, country, position, ...) </div> </form> </div> </div> </div> </div>  <div id="content"> <?php if ($sf_user->hasFlash('notice')): ?> <div class="flash_notice"> <?php echo $sf_user->getFlash('notice') ?> </div> <?php endif ?>  <?php if ($sf_user->hasFlash('error')): ?> <div class="flash_error"> <?php echo $sf_user->getFlash('error') ?> </div> <?php endif ?>  <div class="content"> <?php echo $sf_content ?> </div> </div>  <div id="footer"> <div class="content"> <span class="symfony"> <img src="/images/jobeet-mini.png" /> powered by <a href="http://www.symfony-project.org/">

Page 5: symfony

<img src="/images/symfony.gif" alt="symfony framework" /> </a> </span> <ul> <li><a href="">About Jobeet</a></li> <li class="feed"><a href="">Full feed</a></li> <li><a href="">Jobeet API</a></li> <li class="last"><a href="">Affiliates</a></li> </ul> </div> </div> </div> </body></html>

Một template trong symfony là một file PHP. Trong layout template, bạn sẽ thấy các PHP functions được gọi và tham chiếu đến các biến PHP. $sf_content là một biến thú vị: nó được tạo bởi framework và chứa code HTML tạo bởi một action.

Nếu bạn truy cập module job (http://jobeet.localhost/frontend_dev.php/job), bạn sẽ thấy các actions bây giờ đều có layout.

Page 6: symfony

Stylesheets, Images và JavaScripts

Chúng ta sẽ tổ chức chọn "best design" vào ngày thứ 21, trong khi chờ đợi chúng ta sẽ dùng tạm một design đơn giản: download các file ảnh và giải nén vào thư mục web/images/; download các file stylesheet và giải nén vào thư mục web/css/.

Lệnh generate:project tạo 3 thư mục mặc định: web/images/ để chứa ảnh, web/css/ để chứa các file css, và web/js/ chứa các file JavaScripts. Tất nhiên, bạn cũng có thể để ở các thư mục khác trong thư mục web/

Bạn đọc tinh ý có thể sẽ thấy rằng, mặc dù file main.css không được nhắc đến trong layout, nhưng nó vẫn được gọi khi tạo HTML. Sao lại có thể như vậy?

File stylesheet được include bởi hàm include_stylesheets() ở <head> trong layout. Hàm include_stylesheets() chính là một helper. Một helper là một function, tạo bởi symfony, nhận tham số và trả về mã HTML. Các helper giúp giảm thời gian code, chúng

Page 7: symfony

đóng gói các đoạn mã thường dùng trong template. Helper include_stylesheets() tạo thẻ <link> cho stylesheets.

Nhưng làm thế nào để helper biết cần include file stylesheets nào?

Tầng View có thể cấu hình bằng cách chỉnh sửa file view.yml của application. Đây là nội dung mặc định được tạo ra sau khi dùng lệnh generate:app:

# apps/frontend/config/view.ymldefault: http_metas: content-type: text/html  metas: #title: symfony project #description: symfony project #keywords: symfony, project #language: en #robots: index, follow  stylesheets: [main.css]  javascripts: []  has_layout: on layout: layout

File view.yml chứa cấu hình chung cho tất cả các templates của application. Ví dụ, phần stylesheets được xác định bởi một mảng các file stylesheet được include trong mọi trang của application (việc include được thực hiện bởi helper include_stylesheets() trong layout).

Trong file view.yml, ta viết main.css, chứ không dùng /css/main.css. Symfony sẽ tự động tìm file trong thư mục /css/.

Nếu có nhiều file, symfony sẽ include chúng theo thứ tự như viết trong cấu hình:

stylesheets: [main.css, jobs.css, job.css]

Bạn cũng có thể thay đổi attribute media và bỏ qua đuôi .css:

stylesheets: [main.css, jobs.css, job.css, print: { media: print }]

Cấu hình này sẽ được render thành:

<link rel="stylesheet" type="text/css" media="screen" href="/css/main.css" /><link rel="stylesheet" type="text/css" media="screen" href="/css/jobs.css" /><link rel="stylesheet" type="text/css" media="screen" href="/css/job.css" /><link rel="stylesheet" type="text/css" media="print" href="/css/print.css"/>

Page 8: symfony

File view.yml cũng xác định layout sử dụng cho application. Mặc định, tên của nó là layout, tức là file layout.php. Bạn cũng có thể không dùng layout bằng cách chuyển giá trị của has_layout thành false.

Ta thấy rằng file jobs.css chỉ cần ở trang chủ và file job.css chỉ cần ở trang xem chi tiết công việc. Cấu hình của file view.yml có thể thay đổi lại trong từng module cụ thể. Do đó, file view.yml của application chỉ chứa file main.css:

# apps/frontend/config/view.ymlstylesheets: [main.css]

Để cấu hình riêng cho module job, tạo file view.yml trong thư mục apps/frontend/modules/job/config/:

# apps/frontend/modules/job/config/view.ymlindexSuccess: stylesheets: [jobs.css] showSuccess: stylesheets: [job.css]

Dưới mục indexSuccess và showSuccess (chúng là các template ứng với action index và show , sẽ được đề cập đến ở phần sau), bạn có thể chỉnh sửa lại các mục đã có trong phần default ở file view.yml của application.Các cấu hình này sẽ thay thế các cấu hình ở application có cùng nội dung. Bạn cũng có thể tạo một vài cấu hình cho toàn bộ action của module dưới mục all.

Nguyên tắc cấu hình trong symfonyCó nhiều file cấu hình, các cấu hình giống nhau sẽ được xác định bởi các level khác nhau:

Cấu hình mặc định có giá trị trong toàn bộ framework Cấu hình toàn cục cho project (trong thư mục config/) Cấu hình cục bộ cho một application (trong thư mục apps/APP/config/) Cấu hình cục bộ cho một module (trong thư mục

apps/APP/modules/MODULE/config/)

Khi chạy, hệ thống sẽ xác định cấu hình từ tất cả các file này và cache lại để đảm bảo hiệu năng.

Page 9: symfony

Khi một thứ có thể cấu hình dựa trên file cấu hình, nó cũng có thể cấu hình bằng code PHP. Thay vì tạo file view.yml cho module job, bạn có thể sử dụng helper use_stylesheet() để include file stylesheet trong một template:

<?php use_stylesheet('main.css') ?>

Bạn cũng có thể sử dụng helper này trong layout để include một stylesheet chung cho application.

Chọn cách làm nào là tùy sở thích của mỗi người. File view.yml cung cấp cách cấu hình cố định cho các template. Còn khi sử dụng helper use_stylesheet() mọi thứ trở nên mềm dẻo hơn. Với Jobeet, chúng tôi sẽ sử dụng helper use_stylesheet(), vì thế bạn có thể xóa file view.yml và thêm lời gọi use_stylesheet() vào trong template job.

Tương tự, cấu hình JavaScript nằm trong mục javascripts của file view.yml và có thể dùng helper use_javascript() để gọi file JavaScript trong template.

Trang chủ

Trang chủ chính là action index của module job. Action index là phần Controller của trang và liên kết với template, indexSuccess.php, là phần View:

apps/ frontend/ modules/ job/ actions/ actions.class.php templates/ indexSuccess.php

Action

Mỗi action được tạo bởi một phương thức của một lớp. Với trang chủ, đó là lớp jobActions (tên module + Actions) và phương thức executeIndex() (execute + tên action). Nó thực hiện việc lấy tất cả các job từ database:

// apps/frontend/modules/job/actions/actions.class.phpclass jobActions extends sfActions{ public function executeIndex(sfWebRequest $request) { $this->jobeet_job_list = Doctrine::getTable('JobeetJob') ->createQuery('a') ->execute(); } // ...}

Page 10: symfony

Xem xét mã nguồn ta thấy: phương thức executeIndex() (Controller) gọi Table JobeetJob để tạo câu truy vấn nhận tất cả các job. Nó trả về một Doctrine_Collection của đối tượng JobeetJob và được gán cho đối tượng jobeet_job_list.

Tất cả các đối tượng này được tự động chuyển cho template (View). Để chuyển dữ liệu từ Controller cho View, hãy sử dụng $this-> :

public function executeIndex(sfWebRequest $request){ $this->foo = 'bar'; $this->bar = array('bar', 'baz');}

Bây giờ, trong template ta có thể sử dụng các biến $foo và $bar.

TemplateMặc định, template được đặt tên trùng với tên action kèm cụm Success, nhờ đó symfony có thể xác định action tương ứng.Template indexSuccess.php được sinh tự động gồm mã HTML theo cấu trúc table:<!-- apps/frontend/modules/job/templates/indexSuccess.php --><?php use_stylesheet('jobs.css') ?> <h1>Job List</h1><table> <thead> <tr> <th>Id</th> <th>Category</th> <th>Type</th><!-- more columns here --> <th>Created at</th> <th>Updated at</th> </tr> </thead> <tbody> <?php foreach ($jobeet_jobs as $jobeet_job): ?> <tr> <td> <a href="<?php echo url_for('job/show?id='.$jobeet_job->getId()) ?>"> <?php echo $jobeet_job->getId() ?> </a> </td> <td><?php echo $jobeet_job->getCategoryId() ?></td> <td><?php echo $jobeet_job->getType() ?></td><!-- more columns here --> <td><?php echo $jobeet_job->getCreatedAt() ?></td> <td><?php echo $jobeet_job->getUpdatedAt() ?></td> </tr> <?php endforeach ?> </tbody></table><a href="<?php echo url_for('job/new') ?>">New</a>

Page 11: symfony

Trong template, foreach duyệt qua danh sách các Job objects ($jobeet_job_list), và với mỗi job, giá trị của từng cột được hiển thị. Ở đây, việc truy cập các giá trị của cột đơn giản là gọi một phương thức accessor có tên bắt đầu bằng get kèm theo tên cột viết hoa chữ cái đầu (ví dụ phương thức getCreatedAt() với cột created_at). (đơn giản hơn có thể dùng: $jobeet_job->id, $jobeet_job->type, ... -)

Ta chỉ cần hiển thị một vài cột:

<!-- apps/frontend/modules/job/templates/indexSuccess.php --><?php use_stylesheet('jobs.css') ?><div id="jobs"> <table class="jobs"> <?php foreach ($jobeet_jobs as $i => $job): ?> <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>"> <td class="location"><?php echo $job->getLocation() ?></td> <td class="position"> <a href="<?php echo url_for('job/show?id='.$job->getId()) ?>"> <?php echo $job->getPosition() ?> </a> </td> <td class="company"><?php echo $job->getCompany() ?></td> </tr> <?php endforeach ?> </table></div>

Hàm url_for() là một symfony helper sẽ được đề cập vào ngày mai.

Page 12: symfony

Job Page Template

Bây giờ hãy chỉnh sửa giao diện của trang chi tiết công việc. Mở file showSuccess.php và thay toàn bộ nội dung bằng đoạn code sau:

<!-- apps/frontend/modules/job/templates/showSuccess.php --><?php use_stylesheet('job.css') ?><?php use_helper('Text') ?> <div id="job"> <h1><?php echo $job->getCompany() ?></h1> <h2><?php echo $job->getLocation() ?></h2> <h3> <?php echo $job->getPosition() ?> <small> - <?php echo $job->getType() ?></small> </h3>  <?php if ($job->getLogo()): ?> <div class="logo"> <a href="<?php echo $job->getUrl() ?>"> <img src="/uploads/jobs/<?php echo $job->getLogo() ?>" alt="<?php echo $job->getCompany() ?> logo" /> </a> </div> <?php endif ?>  <div class="description"> <?php echo simple_format_text($job->getDescription()) ?> </div>  <h4>How to apply?</h4>  <p class="how_to_apply"><?php echo $job->getHowToApply() ?></p>  <div class="meta"> <small>posted on <?php echo $job->getDateTimeObject('created_at')->format('m/d/Y') ?></small> </div>  <div style="padding: 20px 0"> <a href="<?php echo url_for('job/edit?id='.$job->getId()) ?>"> Edit </a> </div></div>

Template sử dụng biến $job lấy từ action để hiển thị thông tin công việc. Do đó, chúng ta cần đổi tên biến từ $jobeet_job thành $job trong action show (có 2 chỗ cần sửa):

Page 13: symfony

// apps/frontend/modules/job/actions/actions.class.phppublic function executeShow(sfWebRequest $request){ $this->job = Doctrine::getTable('JobeetJob')-> find($request->getParameter('id')); $this->forward404Unless($this->job);}

Chú ý rằng cột “date” có thể được chuyển đổi sang trường đối tượng PHP DateTime(PHP DateTime object instance ).Như chúng ta đã định nghĩa cột “create_at” để đánh dấu thời gian ,bạn có thể chuyển đổi các giá trị của cột này cho một đối tượng DateTime bằng cách sử dụng phương thức “getDateTimeObject()” và sau đó gọi các phương thức định dạng ()

$job->getDateTimeObject('created_at')->format('m/d/Y');

Phần mô tả công việc sửa dụng helper simple_format_text() để format nội dung, bằng cách thay thế kí tự xuống dòng thành mã html <br />. Helper này nằm trong nhóm helper Text, mặc định không được tự động load, do đó chúng ta cần dùng helper use_helper() để load.

Page 14: symfony

Slots

Hiện tại, tiêu đề của tất cả các trang được xác định trong thẻ <title> ở layout:

<title>Jobeet - Your best job board</title>

: Nhưng với trang chi tiết công việc, chúng ta cần cung cấp nhiều thông tin hữu ích hơn, như là tên công ty và vị trí tuyển dụng.

Trong symfony, khi một vùng của layout phụ thuộc vào template, ta sử dụng slot:

Thêm một slot vào layout cho phép title có thể tự động thay đổi:

// apps/frontend/templates/layout.php<title><?php include_slot('title') ?></title>

Mỗi slot được xác định bởi một tên (title) và được hiển thị qua helper include_slot(). Bây giờ, ở đầu template showSuccess.php, dùng helper slot() để xác định nội dung của slot:

// apps/frontend/modules/job/templates/showSuccess.php<?php slot('title') ?> <?php echo sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition()) ?><?php end_slot() ?>

Nếu tiêu đề phức tạp, ta có thể đặt trong block: