フィードアグリゲータを簡単に作る

先週のアップデートsfZendPluginが更新しました。これを使って、symfonyでさくっとサイトのRSSフィードを一つにまとめて表示するアグリゲータを作成してみます。 今日は、もろもろの復習も兼ねてプロジェクトのセットアップから順序を追って説明します。

本アプリケーションは実用には不足しています。symfonyアプリケーション構築の参考にどうぞ。また、不具合等有りましたらコメント頂けると助かります。

プロジェクトのセットアップ

フィードアグリゲータのディレクトリのセットアップをします。

mkdir planet && cd planet

symfonyコマンドを利用して、planetプロジェクトを作成しfrontendアプリケーションの作成します。その後defaultモジュールを作成します。

symfony new planet
symfony app frontend
symfony module frontend homepage

ここからあまり一般的ではなくなるのですが、私製のプラグインsfSubversionsfConfiguresfLighttpdPluginをインストール`しています。必須では有りません

dspluginsroot="http://svn.tracfort.jp/svn/dino-symfony/plugins"
dsplugins="sfConfigurePlugin sfLighttpdPlugin"
 
cd plugins
svn co ${dspluginsroot}/sfSubversionPlugin
for i in $dsplugins;
do
    symfony svn-checkout-plugin ${dspluginsroot}/${i}
done
cd ..

symfonyの公式プラグインリポジトリからsfZendPluginプラグインをインストールします。

cd plungins
svn export http://svn.symfony-project.com/plugins/sfZendPlugin
cd ..

上のコードはsfSubversionPluginをインストールしていれば下のようにする事が可能です。

symfony svn-checkout-plugin http://svn.symfony-project.com/plugins/sfZendPlugin

上記でインストールしたプラグインを利用してプロジェクトのキックスタートコマンドを実行します。

symfony init-bootstrap
symfony init-configure
symfony init-lighttpd
./bootstrap
./configure --with-server-port=10191

config/databases.ymlのコメントを外します。

# fix-up
sed -e 's/^#//' < config/databases.yml.in >config/databases.yml.in.tmp
mv config/databases.yml.in.tmp config/databases.yml.in

フロントコントローラを開発環境用に作り直します。clear-controllerでは、prod環境のフロントコントローラは利用できないため注意して下さい。

rm web/index.php
symfony init-controller frontend dev index

さっきインストールした私製プラグインを利用してブラウザを開きます。

symfony lighttpd open

以上で下記のような画面が開きます。

ここまで30秒程です。プロジェクト生成時にいつもこのプロセスを利用するため、シェルスクリプトにしています。

routingの修正

apps/frontend/config/routing.ymlを修正して@homepageを変更します。また、その他のデフォルトのroutingは不要ですので削除します。

homepage:
  url:   /
  param: { module: homepage, action: index }

特に、:module/:actionのルーティングは便利な事も多いですが、意図しないリクエストが発生する危険性を鑑み削除する事にしています。

settingの修正

sfZendPluginを有効にするため下記の記述をapps/frontend/config/settings.yml行います。

all:
  .settings:
    escaping_strategy:      both
    escaping_method:   ESC_ENTITIES
    zend_lib_dir:   %SF_ROOT_DIR%/plugins/sfZendPlugin/lib
    autoloading_functions:
      - [sfZendFrameworkBridge, autoload]

以前のsfZendPluginとは使い方が変わっていますので注意して下さい。

Zend_Feedを試す

Zend_Feedを利用してフィードを取得します。apps/frontend/modules/homepage/actions/actions.class.phpに試しに以下のように記述します。

    public function executeIndex()
    {
        $this->feeds = Zend_Feed::findFeeds('http://blog.symfony.jp/');
    }

何も考えず、いきなりZend_Feedが利用できます。これってすごい事ですよね!

テンプレートapps/frontend/modules/homepage/templates/indexSuccess.phpは以下のようにします。

<?php foreach($feeds as $feed): ?>
<h2><?php echo $feed->title() ?></h2>
<ul>
<?php foreach($feed as $entry): ?>
<li>
<h3><?php echo $entry->title() ?></h3>
<div>
<?php
      echo nl2br($entry->summary());
?>
</div>
</li>
<?php endforeach ?>
</ul>
<?php endforeach ?>

これを表示すると次のような画面がでます。一応表示されましたがなんかおかしいですね。http://blog.symfony.jpではATOMとRSSの両方のフィードを提供しているため二つ表示されてしまいます。また、RSSの本文が表示されていません。これらを解決します。

単一サイトのフィードを表示する

フィードの一番最初の物を持ってくるように修正します。

  public function executeIndex()
  {
     $site_feeds = Zend_Feed::findFeeds("http://blog.symfony.jp/");
     if(count($site_feeds) >= 1){
          $feeds[] = $site_feeds[0];
      }
  }

フィードの最初で本当に良いのか疑問ですが…

次に、単に以下のようにしていた部分を

<?php
      echo nl2br($entry->summary());
?>

以下のように書き換えます。

<?php
if($entry instanceof Zend_Feed_Entry_Rss){
    echo nl2br($entry->description());
}else{
    echo nl2br($entry->summary());
}
?>

これで単一フィードについてはそれらしく表示する事ができるようになりました。

アグリゲートをして表示する

次に複数のサイトからフィードを取得するように修正します。

      $sites = array('http://blog.symfony.jp/','http://www.dino.co.jp');
      foreach($sites as $site){
          $site_feeds = Zend_Feed::findFeeds($site);
          if(count($site_feeds) >= 1){
              $feeds[] = $site_feeds[0];
          }
      }
 
      $this->feeds = $feeds;

このコードで複数のサイトからフィードを取得できました。

仕上げ

最後に、時系列にエントリーをソートします。これでexecuteIndex()は完成です。

  public function executeIndex()
  {
      $entries = array();
      $sites = array(
                     'http://www.symfony-project.com/',
                     'http://blog.symfony.jp/',
                     'http://www.dino.co.jp'
                     );
      foreach($sites as $site){
          $site_feeds = Zend_Feed::findFeeds($site);
          if(count($site_feeds) >= 1){
              foreach($site_feeds[0] as $entry){
                  $e["site"] =$site_feeds[0]->title();
                  $e["title"] =$entry->title();
                  $e["link"] =$entry->link();
                  if($entry instanceof Zend_Feed_Entry_Rss){
                      $e["body"] = strip_tags($entry->description()); 
                      $entries[strtotime($entry->pubDate())] = $e;
                  }else{
                      $e["body"] = strip_tags($entry->summary());
                      $entries[strtotime($entry->published())] = $e;
                  }
              }
          }
      }
      krsort($entries);
      $this->entries = $entries;
  }

indexSuccessの表示も最終的に以下のように変えました。

<ul>
<?php foreach($entries as $time => $entry): ?>
<li>
<h2><?php echo link_to($entry["title"]."-". $entry["site"],$entry["link"]) ?></h2>
<div>
<p>
<?php echo date("r",$time); ?>
</p>
<p>
<?php
    echo nl2br($entry["body"]);
?>
</p>
</div>
</li>
<?php endforeach ?>
</ul>

結果は、以下のようになりました。


とりあえず動きました、HTMLのタグを無条件に取ってしまっているあたりと、キャッシュを使っていないので毎回フィードの取得が実行されます。コーディング時間はここまでで30分程かかりました。 実用的にはまだまだですね。。。取りあえず今日はこんな所にしておきます。

Leave a Reply

You must be logged in to post a comment.