PageRenderTime 42ms CodeModel.GetById 2ms app.highlight 36ms RepoModel.GetById 1ms app.codeStats 0ms

/jobeet/en/14.markdown

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