/jobeet/en/14.markdown
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
- Day 14: Feeds
- =============
- Yesterday, you started developing your first very own symfony application. Don't
- stop now. As you learn more on symfony, try to add new features to your
- application, host it somewhere, and share it with the community.
- Let's move on to something completely different. If you are looking for a job,
- you will probably want to be informed as soon as a new job is posted. Because it
- is not very convenient to check the website every other hour, we will add
- several job feeds here to keep our Jobeet users up-to-date.
- Formats
- -------
- The symfony framework has native support for ~formats|Formats~ and
- ~mime-types|Mime Types~. This means that the same Model and Controller can have
- different ~templates|Templates~ based on the requested format. The default
- format is HTML but symfony supports ~several other formats|Built-in Formats~ out
- of the box like `txt`, `js`, `css`, `json`, `xml`, `rdf`, or `atom`.
- The format can be set by using the `setRequestFormat()` method of the
- ~request|HTTP Request~ object:
- [php]
- $request->setRequestFormat('xml');
- But most of the time, the format is embedded in the URL. In this case, symfony
- will set it for you if the special ~`sf_format`~ variable is used in the
- corresponding route. For the job list, the list URL is:
- http://www.jobeet.com.localhost/frontend_dev.php/job
- This URL is equivalent to:
- http://www.jobeet.com.localhost/frontend_dev.php/job.html
- Both URLs are equivalent because the routes generated by the
- `sfPropelRouteCollection` class have the `sf_format` as the extension and
- because `html` is the default format. You can check it for yourself by running
- the `app:routes` task:
- ![Cli](http://www.symfony-project.org/images/jobeet/1_4/15/cli.png)
- Feeds
- -----
- ### Latest Jobs Feed
- Supporting different formats is as easy as creating different templates. To
- create an [Atom feed](http://en.wikipedia.org/wiki/Atom_(standard)) for the
- latest jobs, create an `indexSuccess.atom.php` template:
- [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**
- >Template Names
- >
- >As `html` is the most common format used for web applications, it can be
- >omitted from the template name. Both `indexSuccess.php` and
- >`indexSuccess.html.php` templates are equivalent and symfony uses the first one
- >it finds.
- >
- >Why are default templates suffixed with `Success`? An action can return a value
- >to indicate which template to render. If the action returns nothing, it is
- >equivalent to the following code:
- >
- > [php]
- > return sfView::SUCCESS; // == 'Success'
- >
- >If you want to change the suffix, just return something else:
- >
- > [php]
- > return sfView::ERROR; // == 'Error'
- >
- > return 'Foo';
- >
- >As seen yesterday, the name of the template can also be changed by using the
- >`setTemplate()` method:
- >
- > [php]
- > $this->setTemplate('foo');
- By default, symfony will change the response ~`Content-Type`~ according to the
- format, and for all non-HTML formats, the layout is disabled. For an Atom feed,
- symfony changes the `Content-Type` to `application/atom+xml;charset=utf-8`.
- In the Jobeet footer, update the link to the feed:
- [php]
- <!-- apps/frontend/templates/layout.php -->
- <li class="feed">
- <a href="<?php echo url_for('job', array('sf_format' => 'atom')) ?>">Full feed</a>
- </li>
- The ~internal URI|Internal URI~ is the same as for the `job` list with the
- `sf_format` added as a variable.
- Add a `<link>` tag in the head section of the layout to allow automatic discover
- by the browser of our feed:
- [php]
- <!-- apps/frontend/templates/layout.php -->
- <link rel="alternate" type="application/atom+xml" title="Latest Jobs"
- href="<?php echo url_for('job', array('sf_format' => 'atom'), true) ?>" />
- For the link `href` attribute, an ~URL (Absolute)~ is used thanks to the second
- argument of the `url_for()` helper.
- Replace the Atom template header with the following code:
- [php]
- <!-- apps/frontend/modules/job/templates/indexSuccess.atom.php -->
- <title>Jobeet</title>
- <subtitle>Latest Jobs</subtitle>
- <link href="<?php echo url_for('job', array('sf_format' => 'atom'), true) ?>" rel="self"/>
- <link href="<?php echo url_for('@homepage', true) ?>"/>
- <propel>
- <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', JobeetJobPeer::getLatestPost()->getCreatedAt('U')) ?></updated>
- </propel>
- <doctrine>
- <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', Doctrine_Core::getTable('JobeetJob')->getLatestPost()->getDateTimeObject('created_at')->format('U')) ?></updated>
- </doctrine>
- <author>
- <name>Jobeet</name>
- </author>
- <id><?php echo sha1(url_for('job', array('sf_format' => 'atom'), true)) ?></id>
- <propel>
- Notice the usage of `U` as an argument to `getCreatedAt()` to get the date as a
- timestamp. To get the date of the latest post, create the `getLatestPost()`
- method:
- </propel>
- <doctrine>
- Notice the usage of the `U` as an argument to `format()` to get the date as a
- timestamp. To get the date of the latest post, create the `getLatestPost()`
- method:
- </doctrine>
- <propel>
- [php]
- // lib/model/JobeetJobPeer.php
- class JobeetJobPeer extends BaseJobeetJobPeer
- {
- static public function getLatestPost()
- {
- $criteria = new Criteria();
- self::addActiveJobsCriteria($criteria);
- return JobeetJobPeer::doSelectOne($criteria);
- }
- // ...
- }
- </propel>
- <doctrine>
- [php]
- // 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();
- }
- // ...
- }
- </doctrine>
- The feed entries can be generated with the following code:
- [php]
- <!-- 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>
- <propel>
- <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $job->getCreatedAt('U')) ?></updated>
- </propel>
- <doctrine>
- <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $job->getDateTimeObject('created_at')->format('U')) ?></updated>
- </doctrine>
- <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 ?>
- The `getHost()` method of the request object (`$sf_request`) returns the current
- host, which comes in handy for creating an absolute link for the company logo.
- ![Feed](http://www.symfony-project.org/images/jobeet/1_4/15/feed.png)
- >**TIP**
- >When creating a feed, ~debugging|Debug~ is easier if you use command line tools like
- >[`curl`](http://curl.haxx.se/) or
- >[`wget`](http://www.gnu.org/software/wget/), as you see the actual content of
- >the feed.
- ### Latest Jobs in a Category Feed
- One of the goals of Jobeet is to help people find more targeted jobs. So, we
- need to provide a ~feed|Feeds~ for each category.
- First, let's update the `category` route to add support for different formats:
- [yml]
- // apps/frontend/config/routing.yml
- category:
- url: /category/:slug.:sf_format
- class: sfPropelRoute
- param: { module: category, action: show, sf_format: html }
- options: { model: JobeetCategory, type: object }
- requirements:
- sf_format: (?:html|atom)
- Now, the `category` route will understand both the `html` and `atom` formats.
- Update the links to category feeds in the ~templates|Templates~:
- [php]
- <!-- 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>
- <!-- 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>
- The last step is to create the `showSuccess.atom.php` template. But as this feed
- will also list jobs, we can ~refactor|Refactoring~ the code that generates the
- feed entries by creating a `_list.atom.php` partial. As for the `html` format,
- ~partials|Partial Templates~ are format specific:
- [php]
- <!-- apps/frontend/modules/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>
- <propel>
- <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $job->getCreatedAt('U')) ?></updated>
- </propel>
- <doctrine>
- <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $job->getDateTimeObject('created_at')->format('U')) ?></updated>
- </doctrine>
- <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 ?>
- You can use the `_list.atom.php` partial to simplify the job feed template:
- [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="<?php echo url_for('job', array('sf_format' => 'atom'), true) ?>" rel="self"/>
- <link href="<?php echo url_for('@homepage', true) ?>"/>
- <propel>
- <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', JobeetJobPeer::getLatestPost()->getCreatedAt('U')) ?></updated>
- </propel>
- <doctrine>
- <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', Doctrine_Core::getTable('JobeetJob')->getLatestPost()->getDateTimeObject('created_at')->format('U')) ?></updated>
- </doctrine>
- <author>
- <name>Jobeet</name>
- </author>
- <id><?php echo sha1(url_for('job', array('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>
- Eventually, create the `showSuccess.atom.php` template:
- [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) ?>" />
- <propel>
- <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $category->getLatestPost()->getCreatedAt('U')) ?></updated>
- </propel>
- <doctrine>
- <updated><?php echo gmstrftime('%Y-%m-%dT%H:%M:%SZ', $category->getLatestPost()->getDateTimeObject('created_at')->format('U')) ?></updated>
- </doctrine>
- <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>
- As for the main job feed, we need the date of the latest job for a category:
- [php]
- <propel>
- // lib/model/JobeetCategory.php
- </propel>
- <doctrine>
- // lib/model/doctrine/JobeetCategory.class.php
- </doctrine>
- class JobeetCategory extends BaseJobeetCategory
- {
- public function getLatestPost()
- {
- return $this->getActiveJobs(1)->getFirst();
- }
- // ...
- }
- ![Category Feed](http://www.symfony-project.org/images/jobeet/1_4/15/category_feed.png)
- Final Thoughts
- --------------
- As with many symfony features, the native format support allows you to add feeds
- to your websites without effort. Today, we have enhanced the job seeker
- experience. Tomorrow, we will see how to provide greater exposure to the job
- posters by providing a Web Service.
- __ORM__