English spoken conference

Symfony 5: The Fast Track

A new book to learn about developing modern Symfony 5 applications.

Support this project

Caution: You are browsing the legacy symfony 1.x part of this website.

Ngày 15: Feed

Tóm tắt

Hôm qua, bạn đã phát triển ứng dụng đầu tiên của mình với symfony. Đừng dừng lại. Khi bạn học được nhiều hơn về symfony, hãy thêm những tính năng mới vào ứng dụng, đưa nó lên đâu đó, và chia sẻ với cộng đồng.

Hôm nay, chúng ta sẽ chuyển sang một vấn đề hoàn toàn mới.

Nếu bạn đang tìm kiếm một công việc, có lẽ bạn sẽ muốn được biết ngay khi công việc được đưa lên. Và thật bất tiện khi phải thường xuyên truy cập vào website để kiểm tra. Để người dùng có thể cập nhật các công việc mới ở Jobeet, hôm nay chúng ta sẽ thêm vài job feed.

Format

Symfony framework hỗ trợ sẵn vấn đề formats và mime-types. Có nghĩa là cùng một Model và Controller có thể có các template khác nhau tùy vào format được yêu cầu. Format mặc định là HTML nhưng symfony cũng hỗ trợ những format khác như txt, js, css, json, xml, rdf, và atom.

Format có thể được set nhờ phương thức setRequestFormat() của đối tượng request:

$request->setRequestFormat('xml');

Nhưng format thường được nhúng trong URL. Trong trường hợp này, symfony sẽ thiết lập cho bạn dựa vào biến sf_format dùng trong route. Với job list, URL là:

http://jobeet.localhost/frontend_dev.php/job

URL này tương đương với:

http://jobeet.localhost/frontend_dev.php/job.html

2 URL là như nhau vì route tạo bởi lớp sfDoctrineRouteCollection có chứa sf_format. Bạn có thể kiểm tra điều này bằng cách chạy lệnh app:routes:

Cli

Feed

Feed các công việc mới nhất

Hỗ trợ các format khác nhau đơn giản là tạo các template khác nhau. Để tạo một Atom feed cho các công việc mới nhất, ta tạo template indexSuccess.atom.php:

<!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Jobeet</title>
  <subtitle>Latest Jobs</subtitle>
  <link href="" rel="self"/>
  <link href=""/>
  <updated></updated>
  <author><name>Jobeet</name></author>
  <id>Unique Id</id>
 
  <entry>
    <title>Job title</title>
    <link href="" />
    <id>Unique id</id>
    <updated></updated>
    <summary>Job description</summary>
    <author><name>Company</name></author>
  </entry>
</feed>

sidebar

Tên Template

Do html là format được dùng phổ biến nhất đối với ứng dụng web, nên bạn có thể bỏ qua trong tên của template. Cả 2 template indexSuccess.phpindexSuccess.html.php là như nhau và symfony sẽ sử dụng cái đầu tiên nó tìm thấy.

Tại sao các template mặc định đều có cụm Success? Việc xác định template nào được render dựa vào giá trị trả về của action. Nếu action không trả về gì cả, nó tương đương với:

return sfView::SUCCESS; // == 'Success'

Nếu bạn muốn thay đổi cụm sau, hãy trả về một giá trị khác:

return sfView::ERROR; // == 'Error'
 
return 'Foo';

Bạn cũng có thể đổi tên của template bằng cách sử dụng phương thức setTemplate():

$this->setTemplate('foo');

Mặc định, symfony sẽ thay đổi response Content-Type tùy theo format, và với tất cả format không phải HTML, layout sẽ không được sử dụng. Với Atom feed, symfony thay đổi Content-Type thành application/atom+xml; charset=utf-8

Ở Jobeet footer, sửa lại link của feed:

<!-- apps/frontend/templates/layout.php -->
<li class="feed">
  <a href="<?php echo url_for('@job?sf_format=atom') ?>">Full feed</a>
</li>

Internal URI tương tự như job list với một biến được thêm vào sf_format.

Thêm tag <link> vào head ở layout:

<!-- apps/frontend/templates/layout.php -->
<link rel="alternate" type="application/atom+xml" title="Latest Jobs"
  href="<?php echo url_for('@job?sf_format=atom', true) ?>" />

Với href attribute của link, ta sử dụng đường dẫn tuyệt đối nhờ tham số thứ 2 của helper url_for().

Sửa lại Atom template header:

<!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
<title>Jobeet</title>
<subtitle>Latest Jobs</subtitle>
<link href="<?php echo url_for('@job?sf_format=atom', true) ?>" rel="self"/>
<link href="<?php echo url_for('@homepage', true) ?>"/>
<updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', strtotime(Doctrine::getTable('JobeetJob')->getLatestPost()->getCreatedAt())) ?></updated>
<author>
  <name>Jobeet</name>
</author>
<id><?php echo sha1(url_for('@job?sf_format=atom', true)) ?></id>

Ta đã dùng function strtotime() để chuyển ngày created_at thành timestamp. Để lấy ngày của công việc mới nhất, ta cần tạo phương thức getLatestPost():

// lib/model/doctrine/JobeetJobTable.class.php
class JobeetJobTable extends Doctrine_Table
{
  public function getLatestPost()
  {
    $q = Doctrine_Query::create()
      ->from('JobeetJob j');
    $this->addActiveJobsQuery($q);
 
    return $q->fetchOne();
  }
 
  // ...
}

Các feed entry được tạo bằng đoạn code sau:

<!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
<?php use_helper('Text') ?>
<?php foreach ($categories as $category): ?>
  <?php foreach ($category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')) as $job): ?>
    <entry>
      <title>
        <?php echo $job->getPosition() ?> (<?php echo $job->getLocation() ?>)
      </title>
      <link href="<?php echo url_for('job_show_user', $job, true) ?>" />
      <id><?php echo sha1($job->getId()) ?></id>
      <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', strtotime($job->getCreatedAt())) ?></updated>
      <summary type="xhtml">
       <div xmlns="http://www.w3.org/1999/xhtml">
         <?php if ($job->getLogo()): ?>
           <div>
             <a href="<?php echo $job->getUrl() ?>">
               <img src="http://<?php echo $sf_request->getHost().'/uploads/jobs/'.$job->getLogo() ?>"
                 alt="<?php echo $job->getCompany() ?> logo" />
             </a>
           </div>
         <?php endif; ?>
 
         <div>
           <?php echo simple_format_text($job->getDescription()) ?>
         </div>
 
         <h4>How to apply?</h4>
 
         <p><?php echo $job->getHowToApply() ?></p>
       </div>
      </summary>
      <author>
        <name><?php echo $job->getCompany() ?></name>
      </author>
    </entry>
  <?php endforeach; ?>
<?php endforeach; ?>

Phương thức getHost() của đối tượng request ($sf_request) trả về host hiện tại, cần cho việc tạo đường dẫn tuyệt đối tới company logo.

Feed

tip

Khi tạo một feed, bạn có thể debug dễ dàng thông qua các lệnh như curl hay wget, bạn sẽ thấy được nội dung thực sự của các feed.

nếu bạn gặp lỗi Parse error: syntax error, unexpected T_STRING, có thể do bạn để "short_open_tags = On" trong php.ini, hãy chuyển nó thành Off (người dịch)

Feed các công việc mới nhất của một Category

Một trong những mục đích của Jobeet là giúp mọi người tìm được công việc phù hợp. Vì thế, chúng ta cần cung cấp feed cho mỗi category.

Đầu tiên, sửa lại route category để hỗ trợ các format khác:

// apps/frontend/config/routing.yml
category:
  url:     /category/:slug.:sf_format
  class:   sfDoctrineRoute
  param:   { module: category, action: show, sf_format: html }
  options: { model: JobeetCategory, type: object }
  requirements:
    sf_format: (?:html|atom)

Bây giờ, route category đã hiểu cả format htmlatom. Sửa lại link tới category feed trong templates:

<!-- apps/frontend/modules/job/templates/indexSuccess.php -->
<div class="feed">
  <a href="<?php echo url_for('category', array('sf_subject' => $category, 'sf_format' => 'atom')) ?>">Feed</a>
</div>
 
[php]
<!-- apps/frontend/modules/category/templates/showSuccess.php -->
<div class="feed">
  <a href="<?php echo url_for('category', array('sf_subject' => $category, 'sf_format' => 'atom')) ?>">Feed</a>
</div>

Cuối cùng, tạo template showSuccess.atom.php. Nhưng feed này cũng liệt kê các công việc, nên chúng ta có thể refactor mã nguồn bằng cách cho các feed entry vào trong partial _list.atom.php. Partial bây giờ có format atom:

<!-- apps/frontend/job/templates/_list.atom.php -->
<?php use_helper('Text') ?>
 
<?php foreach ($jobs as $job): ?>
  <entry>
    <title><?php echo $job->getPosition() ?> (<?php echo $job->getLocation() ?>)</title>
    <link href="<?php echo url_for('job_show_user', $job, true) ?>" />
    <id><?php echo sha1($job->getId()) ?></id>
      <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', strtotime($job->getCreatedAt())) ?></updated>
    <summary type="xhtml">
     <div xmlns="http://www.w3.org/1999/xhtml">
       <?php if ($job->getLogo()): ?>
         <div>
           <a href="<?php echo $job->getUrl() ?>">
             <img src="http://<?php echo $sf_request->getHost().$job->getLogo() ?>" alt="<?php echo $job->getCompany() ?> logo" />
           </a>
         </div>
       <?php endif; ?>
 
       <div>
         <?php echo simple_format_text($job->getDescription()) ?>
       </div>
 
       <h4>How to apply?</h4>
 
       <p><?php echo $job->getHowToApply() ?></p>
     </div>
    </summary>
    <author>
      <name><?php echo $job->getCompany() ?></name>
    </author>
  </entry>
<?php endforeach; ?>

Bạn có thể sử dụng partial _list.atom.php trong template job feed:

<!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Jobeet</title>
  <subtitle>Latest Jobs</subtitle>
  <link href="<?php echo url_for('@job?sf_format=atom', true) ?>" rel="self"/>
  <link href="<?php echo url_for('@homepage', true) ?>"/>
  <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', strtotime(Doctrine::getTable('JobeetJob')->getLatestPost()->getCreatedAt())) ?></updated>
  <author>
    <name>Jobeet</name>
  </author>
  <id><?php echo sha1(url_for('@job?sf_format=atom', true)) ?></id>
 
<?php foreach ($categories as $category): ?>
  <?php include_partial('job/list', array('jobs' => $category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')))) ?>
<?php endforeach; ?>
</feed>

Cuối cùng, tạo template showSuccess.atom.php:

<!-- apps/frontend/modules/category/templates/showSuccess.atom.php -->
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Jobeet (<?php echo $category ?>)</title>
  <subtitle>Latest Jobs</subtitle>
  <link href="<?php echo url_for('category', array('sf_subject' => $category, 'sf_format' => 'atom'), true) ?>" rel="self" />
  <link href="<?php echo url_for('category', array('sf_subject' => $category), true) ?>" />
  <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', strtotime($category->getLatestPost()->getCreatedAt())) ?></updated>
  <author>
    <name>Jobeet</name>
  </author>
  <id><?php echo sha1(url_for('category', array('sf_subject' => $category), true)) ?></id>
 
  <?php include_partial('job/list', array('jobs' => $pager->getResults())) ?>
</feed>

Như job feed, chúng ta cần ngày của công việc mới nhất trong một category:

// lib/model/doctrine/JobeetCategory.class.php
class JobeetCategory extends BaseJobeetCategory
{
  public function getLatestPost()
  {
    $jobs = $this->getActiveJobs(1);
 
    return $jobs[0];
  }
 
  // ...
}

Category Feed

Hẹn gặp lại ngày mai

Như nhiều tính năng khác của symfony, việc hỗ trợ sẵn các định dạng cho phép bạn thêm các feed cho website của mình mà không tốn nhiều công sức.

Hôm nay, chúng ta đã bổ sung thêm tính năng giúp người dùng dễ dàng cập nhật các công việc. Ngày mai, chúng ta sẽ nó về Web Service để cung cấp công cụ cho các job poster.