PageRenderTime 55ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/jobeet/en/14.markdown

https://github.com/marydn/symfony1-docs
Markdown | 407 lines | 339 code | 68 blank | 0 comment | 0 complexity | af77e40a3692f07f5fe0d4d696b613d6 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, CC-BY-SA-4.0
  1. Day 14: Feeds
  2. =============
  3. Yesterday, you started developing your first very own symfony application. Don't
  4. stop now. As you learn more on symfony, try to add new features to your
  5. application, host it somewhere, and share it with the community.
  6. Let's move on to something completely different. If you are looking for a job,
  7. you will probably want to be informed as soon as a new job is posted. Because it
  8. is not very convenient to check the website every other hour, we will add
  9. several job feeds here to keep our Jobeet users up-to-date.
  10. Formats
  11. -------
  12. The symfony framework has native support for ~formats|Formats~ and
  13. ~mime-types|Mime Types~. This means that the same Model and Controller can have
  14. different ~templates|Templates~ based on the requested format. The default
  15. format is HTML but symfony supports ~several other formats|Built-in Formats~ out
  16. of the box like `txt`, `js`, `css`, `json`, `xml`, `rdf`, or `atom`.
  17. The format can be set by using the `setRequestFormat()` method of the
  18. ~request|HTTP Request~ object:
  19. [php]
  20. $request->setRequestFormat('xml');
  21. But most of the time, the format is embedded in the URL. In this case, symfony
  22. will set it for you if the special ~`sf_format`~ variable is used in the
  23. corresponding route. For the job list, the list URL is:
  24. http://www.jobeet.com.localhost/frontend_dev.php/job
  25. This URL is equivalent to:
  26. http://www.jobeet.com.localhost/frontend_dev.php/job.html
  27. Both URLs are equivalent because the routes generated by the
  28. `sfPropelRouteCollection` class have the `sf_format` as the extension and
  29. because `html` is the default format. You can check it for yourself by running
  30. the `app:routes` task:
  31. ![Cli](http://www.symfony-project.org/images/jobeet/1_4/15/cli.png)
  32. Feeds
  33. -----
  34. ### Latest Jobs Feed
  35. Supporting different formats is as easy as creating different templates. To
  36. create an [Atom feed](http://en.wikipedia.org/wiki/Atom_(standard)) for the
  37. latest jobs, create an `indexSuccess.atom.php` template:
  38. [php]
  39. <!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
  40. <?xml version="1.0" encoding="utf-8"?>
  41. <feed xmlns="http://www.w3.org/2005/Atom">
  42. <title>Jobeet</title>
  43. <subtitle>Latest Jobs</subtitle>
  44. <link href="" rel="self"/>
  45. <link href=""/>
  46. <updated></updated>
  47. <author><name>Jobeet</name></author>
  48. <id>Unique Id</id>
  49. <entry>
  50. <title>Job title</title>
  51. <link href="" />
  52. <id>Unique id</id>
  53. <updated></updated>
  54. <summary>Job description</summary>
  55. <author><name>Company</name></author>
  56. </entry>
  57. </feed>
  58. >**SIDEBAR**
  59. >Template Names
  60. >
  61. >As `html` is the most common format used for web applications, it can be
  62. >omitted from the template name. Both `indexSuccess.php` and
  63. >`indexSuccess.html.php` templates are equivalent and symfony uses the first one
  64. >it finds.
  65. >
  66. >Why are default templates suffixed with `Success`? An action can return a value
  67. >to indicate which template to render. If the action returns nothing, it is
  68. >equivalent to the following code:
  69. >
  70. > [php]
  71. > return sfView::SUCCESS; // == 'Success'
  72. >
  73. >If you want to change the suffix, just return something else:
  74. >
  75. > [php]
  76. > return sfView::ERROR; // == 'Error'
  77. >
  78. > return 'Foo';
  79. >
  80. >As seen yesterday, the name of the template can also be changed by using the
  81. >`setTemplate()` method:
  82. >
  83. > [php]
  84. > $this->setTemplate('foo');
  85. By default, symfony will change the response ~`Content-Type`~ according to the
  86. format, and for all non-HTML formats, the layout is disabled. For an Atom feed,
  87. symfony changes the `Content-Type` to `application/atom+xml;charset=utf-8`.
  88. In the Jobeet footer, update the link to the feed:
  89. [php]
  90. <!-- apps/frontend/templates/layout.php -->
  91. <li class="feed">
  92. <a href="<?php echo url_for('job', array('sf_format' => 'atom')) ?>">Full feed</a>
  93. </li>
  94. The ~internal URI|Internal URI~ is the same as for the `job` list with the
  95. `sf_format` added as a variable.
  96. Add a `<link>` tag in the head section of the layout to allow automatic discover
  97. by the browser of our feed:
  98. [php]
  99. <!-- apps/frontend/templates/layout.php -->
  100. <link rel="alternate" type="application/atom+xml" title="Latest Jobs"
  101. href="<?php echo url_for('job', array('sf_format' => 'atom'), true) ?>" />
  102. For the link `href` attribute, an ~URL (Absolute)~ is used thanks to the second
  103. argument of the `url_for()` helper.
  104. Replace the Atom template header with the following code:
  105. [php]
  106. <!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
  107. <title>Jobeet</title>
  108. <subtitle>Latest Jobs</subtitle>
  109. <link href="<?php echo url_for('job', array('sf_format' => 'atom'), true) ?>" rel="self"/>
  110. <link href="<?php echo url_for('@homepage', true) ?>"/>
  111. <propel>
  112. <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', JobeetJobPeer::getLatestPost()->getCreatedAt('U')) ?></updated>
  113. </propel>
  114. <doctrine>
  115. <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', Doctrine_Core::getTable('JobeetJob')->getLatestPost()->getDateTimeObject('created_at')->format('U')) ?></updated>
  116. </doctrine>
  117. <author>
  118. <name>Jobeet</name>
  119. </author>
  120. <id><?php echo sha1(url_for('job', array('sf_format' => 'atom'), true)) ?></id>
  121. <propel>
  122. Notice the usage of `U` as an argument to `getCreatedAt()` to get the date as a
  123. timestamp. To get the date of the latest post, create the `getLatestPost()`
  124. method:
  125. </propel>
  126. <doctrine>
  127. Notice the usage of the `U` as an argument to `format()` to get the date as a
  128. timestamp. To get the date of the latest post, create the `getLatestPost()`
  129. method:
  130. </doctrine>
  131. <propel>
  132. [php]
  133. // lib/model/JobeetJobPeer.php
  134. class JobeetJobPeer extends BaseJobeetJobPeer
  135. {
  136. static public function getLatestPost()
  137. {
  138. $criteria = new Criteria();
  139. self::addActiveJobsCriteria($criteria);
  140. return JobeetJobPeer::doSelectOne($criteria);
  141. }
  142. // ...
  143. }
  144. </propel>
  145. <doctrine>
  146. [php]
  147. // lib/model/doctrine/JobeetJobTable.class.php
  148. class JobeetJobTable extends Doctrine_Table
  149. {
  150. public function getLatestPost()
  151. {
  152. $q = Doctrine_Query::create()->from('JobeetJob j');
  153. $this->addActiveJobsQuery($q);
  154. return $q->fetchOne();
  155. }
  156. // ...
  157. }
  158. </doctrine>
  159. The feed entries can be generated with the following code:
  160. [php]
  161. <!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
  162. <?php use_helper('Text') ?>
  163. <?php foreach ($categories as $category): ?>
  164. <?php foreach ($category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')) as $job): ?>
  165. <entry>
  166. <title>
  167. <?php echo $job->getPosition() ?> (<?php echo $job->getLocation() ?>)
  168. </title>
  169. <link href="<?php echo url_for('job_show_user', $job, true) ?>" />
  170. <id><?php echo sha1($job->getId()) ?></id>
  171. <propel>
  172. <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $job->getCreatedAt('U')) ?></updated>
  173. </propel>
  174. <doctrine>
  175. <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $job->getDateTimeObject('created_at')->format('U')) ?></updated>
  176. </doctrine>
  177. <summary type="xhtml">
  178. <div xmlns="http://www.w3.org/1999/xhtml">
  179. <?php if ($job->getLogo()): ?>
  180. <div>
  181. <a href="<?php echo $job->getUrl() ?>">
  182. <img src="http://<?php echo $sf_request->getHost().'/uploads/jobs/'.$job->getLogo() ?>"
  183. alt="<?php echo $job->getCompany() ?> logo" />
  184. </a>
  185. </div>
  186. <?php endif ?>
  187. <div>
  188. <?php echo simple_format_text($job->getDescription()) ?>
  189. </div>
  190. <h4>How to apply?</h4>
  191. <p><?php echo $job->getHowToApply() ?></p>
  192. </div>
  193. </summary>
  194. <author>
  195. <name><?php echo $job->getCompany() ?></name>
  196. </author>
  197. </entry>
  198. <?php endforeach ?>
  199. <?php endforeach ?>
  200. The `getHost()` method of the request object (`$sf_request`) returns the current
  201. host, which comes in handy for creating an absolute link for the company logo.
  202. ![Feed](http://www.symfony-project.org/images/jobeet/1_4/15/feed.png)
  203. >**TIP**
  204. >When creating a feed, ~debugging|Debug~ is easier if you use command line tools like
  205. >[`curl`](http://curl.haxx.se/) or
  206. >[`wget`](http://www.gnu.org/software/wget/), as you see the actual content of
  207. >the feed.
  208. ### Latest Jobs in a Category Feed
  209. One of the goals of Jobeet is to help people find more targeted jobs. So, we
  210. need to provide a ~feed|Feeds~ for each category.
  211. First, let's update the `category` route to add support for different formats:
  212. [yml]
  213. // apps/frontend/config/routing.yml
  214. category:
  215. url: /category/:slug.:sf_format
  216. class: sfPropelRoute
  217. param: { module: category, action: show, sf_format: html }
  218. options: { model: JobeetCategory, type: object }
  219. requirements:
  220. sf_format: (?:html|atom)
  221. Now, the `category` route will understand both the `html` and `atom` formats.
  222. Update the links to category feeds in the ~templates|Templates~:
  223. [php]
  224. <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
  225. <div class="feed">
  226. <a href="<?php echo url_for('category', array('sf_subject' => $category, 'sf_format' => 'atom')) ?>">Feed</a>
  227. </div>
  228. <!-- apps/frontend/modules/category/templates/showSuccess.php -->
  229. <div class="feed">
  230. <a href="<?php echo url_for('category', array('sf_subject' => $category, 'sf_format' => 'atom')) ?>">Feed</a>
  231. </div>
  232. The last step is to create the `showSuccess.atom.php` template. But as this feed
  233. will also list jobs, we can ~refactor|Refactoring~ the code that generates the
  234. feed entries by creating a `_list.atom.php` partial. As for the `html` format,
  235. ~partials|Partial Templates~ are format specific:
  236. [php]
  237. <!-- apps/frontend/modules/job/templates/_list.atom.php -->
  238. <?php use_helper('Text') ?>
  239. <?php foreach ($jobs as $job): ?>
  240. <entry>
  241. <title><?php echo $job->getPosition() ?> (<?php echo $job->getLocation() ?>)</title>
  242. <link href="<?php echo url_for('job_show_user', $job, true) ?>" />
  243. <id><?php echo sha1($job->getId()) ?></id>
  244. <propel>
  245. <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $job->getCreatedAt('U')) ?></updated>
  246. </propel>
  247. <doctrine>
  248. <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $job->getDateTimeObject('created_at')->format('U')) ?></updated>
  249. </doctrine>
  250. <summary type="xhtml">
  251. <div xmlns="http://www.w3.org/1999/xhtml">
  252. <?php if ($job->getLogo()): ?>
  253. <div>
  254. <a href="<?php echo $job->getUrl() ?>">
  255. <img src="http://<?php echo $sf_request->getHost().'/uploads/jobs/'.$job->getLogo() ?>"
  256. alt="<?php echo $job->getCompany() ?> logo" />
  257. </a>
  258. </div>
  259. <?php endif ?>
  260. <div>
  261. <?php echo simple_format_text($job->getDescription()) ?>
  262. </div>
  263. <h4>How to apply?</h4>
  264. <p><?php echo $job->getHowToApply() ?></p>
  265. </div>
  266. </summary>
  267. <author>
  268. <name><?php echo $job->getCompany() ?></name>
  269. </author>
  270. </entry>
  271. <?php endforeach ?>
  272. You can use the `_list.atom.php` partial to simplify the job feed template:
  273. [php]
  274. <!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
  275. <?xml version="1.0" encoding="utf-8"?>
  276. <feed xmlns="http://www.w3.org/2005/Atom">
  277. <title>Jobeet</title>
  278. <subtitle>Latest Jobs</subtitle>
  279. <link href="<?php echo url_for('job', array('sf_format' => 'atom'), true) ?>" rel="self"/>
  280. <link href="<?php echo url_for('@homepage', true) ?>"/>
  281. <propel>
  282. <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', JobeetJobPeer::getLatestPost()->getCreatedAt('U')) ?></updated>
  283. </propel>
  284. <doctrine>
  285. <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', Doctrine_Core::getTable('JobeetJob')->getLatestPost()->getDateTimeObject('created_at')->format('U')) ?></updated>
  286. </doctrine>
  287. <author>
  288. <name>Jobeet</name>
  289. </author>
  290. <id><?php echo sha1(url_for('job', array('sf_format' => 'atom'), true)) ?></id>
  291. <?php foreach ($categories as $category): ?>
  292. <?php include_partial('job/list', array('jobs' => $category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')))) ?>
  293. <?php endforeach ?>
  294. </feed>
  295. Eventually, create the `showSuccess.atom.php` template:
  296. [php]
  297. <!-- apps/frontend/modules/category/templates/showSuccess.atom.php -->
  298. <?xml version="1.0" encoding="utf-8"?>
  299. <feed xmlns="http://www.w3.org/2005/Atom">
  300. <title>Jobeet (<?php echo $category ?>)</title>
  301. <subtitle>Latest Jobs</subtitle>
  302. <link href="<?php echo url_for('category', array('sf_subject' => $category, 'sf_format' => 'atom'), true) ?>" rel="self" />
  303. <link href="<?php echo url_for('category', array('sf_subject' => $category), true) ?>" />
  304. <propel>
  305. <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $category->getLatestPost()->getCreatedAt('U')) ?></updated>
  306. </propel>
  307. <doctrine>
  308. <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $category->getLatestPost()->getDateTimeObject('created_at')->format('U')) ?></updated>
  309. </doctrine>
  310. <author>
  311. <name>Jobeet</name>
  312. </author>
  313. <id><?php echo sha1(url_for('category', array('sf_subject' => $category), true)) ?></id>
  314. <?php include_partial('job/list', array('jobs' => $pager->getResults())) ?>
  315. </feed>
  316. As for the main job feed, we need the date of the latest job for a category:
  317. [php]
  318. <propel>
  319. // lib/model/JobeetCategory.php
  320. </propel>
  321. <doctrine>
  322. // lib/model/doctrine/JobeetCategory.class.php
  323. </doctrine>
  324. class JobeetCategory extends BaseJobeetCategory
  325. {
  326. public function getLatestPost()
  327. {
  328. return $this->getActiveJobs(1)->getFirst();
  329. }
  330. // ...
  331. }
  332. ![Category Feed](http://www.symfony-project.org/images/jobeet/1_4/15/category_feed.png)
  333. Final Thoughts
  334. --------------
  335. As with many symfony features, the native format support allows you to add feeds
  336. to your websites without effort. Today, we have enhanced the job seeker
  337. experience. Tomorrow, we will see how to provide greater exposure to the job
  338. posters by providing a Web Service.
  339. __ORM__