ぷぎがぽぎ このページをアンテナに追加 RSSフィード

2011-02-27

[][] symfony1.4でZnedFrameworkのOAuthを使う

もうすぐSymfony2が出ますね!楽しみですね!

でも、まだsymfony1.4もバリバリ現役ですよシリーズです。


twitterのOAuthをやりたくて...

symfony1.4のアプリケーションでtwitter連携をしようと思い色々とライブラリを探してみた結果、手堅くということでZendFrameworkのOAuthライブラリを使うことにしました。。が、symfony1.4以降でのLoaderの設定を含めたやりかたが公式サイトにちょこっと掲載されているだけなので自分のためにメモしておきます。

プラグインで拡張する

複数のアプリケーションで使うこともあるでしょうし、将来違うプロジェクトでも使うことがあるかもということでごく普通にプラグインとして以下のような構造で用意しました。プラグインの中にOAuthを動かすために必要な最小限のZendのライブラリをlib/vendor/Zendディレクトリにコピーしてあります。

f:id:brtRiver:20110227142436p:image

Znedのautoloadを設定する

no titleで紹介されているやりかたと基本同じです。違うのはライブラリのパスはプラグインの中に含めているので、プラグインのXXXPluginConfigurationクラスにプラグインのパスで指定することです。

<?php
class apTwitterPluginConfiguration extends sfPluginConfiguration
{
  /**
   * @see sfPluginConfiguration
   */
  public function initialize()
  {
      self::registerZend();
  }
  static protected $zendLoaded = false;
  static public function registerZend()
  {
    if (self::$zendLoaded)
    {
      return;
    }
    set_include_path(dirname(dirname(__FILE__)).'/lib/vendor'.PATH_SEPARATOR.get_include_path());
    require_once dirname(dirname(__FILE__)).'/lib/vendor/Zend/Loader/AutoLoader.php';
    Zend_Loader_Autoloader::getInstance();
    self::$zendLoaded = true;
  }
}

ググってみるとautoload.ymlに書けばOKという解説の記事もありますけど、パスが通るだけでZendライブラリ内でrequireされたファイルへのパスが通らないのでZendFrameworkと連携するのであればZend_Loader_Autoloader::getInstace()を使うことになると思います。また、この書き方もZendFrameworkのバージョンによって違うので古いバージョンの場合は(多分)動かないはずです。

routing.ymlを設定

twitterの認証画面に進むためのルーティングと、callbackのルーティングを定義しておきます。

twitter_login:
  url:   /twitter_login
  param: { module: apTwitter, action: login}
twitter_callback:
  url:   /twitter_callback
  param: { module: apTwitter, action: callback}

定義せずにコード中にハードコーディングしても良いですが、routing.ymlをつかって置けば、callback先の開発環境、本番環境のホスト名の違いなどを意識しなくてよいので便利ですよ。

actionの例

あとは見慣れたアクションクラスを書くだけです。routing.ymlでルーティングを設定したのでUrlヘルパーを読み込み、callback先のURLを取得できるようにしている点ぐらいです。

<?php
class apTwitterActions extends sfActions
{
  public function executeLogin($request)
  {
      $this->getContext()->getConfiguration()->loadHelpers(array('Url'));
      $config = array(
          'requestScheme' => Zend_Oauth::REQUEST_SCHEME_HEADER,
          'signatureMethod' => 'HMAC-SHA1',
          'callbackUrl' => url_for('@twitter_callback', true),
          'requestTokenUrl' => 'http://twitter.com/oauth/request_token',
          'authorizeUrl' => 'http://twitter.com/oauth/authorize',
          'accessTokenUrl' => 'http://twitter.com/oauth/access_token',
          'consumerKey' => 'コンシューマーキー',
          'consumerSecret' => 'コンシューマーシークレット'
      );
      $consumer = new Zend_Oauth_Consumer($config);
      // fetch a request token
      $token = $consumer->getRequestToken();
      $consumer->redirect();
      return sfView::NONE;
  }
  public function executeCallback($request)
  {
     // callback時にセッションやDBにごにょごにょ...
  }
}

使い方はTwitterクライアントのOAuth認証(Zend_Oauth) – シンプルなTwitterクライアントの作成 – | エンジニア ブログを参考にさせていただきました。

[][] symfony1.4でZnedFrameworkのOAuthを使う

もうすぐSymfony2が出ますね!楽しみですね!

でも、まだsymfony1.4もバリバリ現役ですよシリーズです。


twitterのOAuthをやりたくて...

symfony1.4のアプリケーションでtwitter連携をしようと思い色々とライブラリを探してみた結果、手堅くということでZendFrameworkのOAuthライブラリを使うことにしました。。が、symfony1.4以降でのLoaderの設定を含めたやりかたが公式サイトにちょこっと掲載されているだけなので自分のためにメモしておきます。

プラグインで拡張する

複数のアプリケーションで使うこともあるでしょうし、将来違うプロジェクトでも使うことがあるかもということでごく普通にプラグインとして以下のような構造で用意しました。プラグインの中にOAuthを動かすために必要な最小限のZendのライブラリをlib/vendor/Zendディレクトリにコピーしてあります。

f:id:brtRiver:20110227142436p:image

Znedのautoloadを設定する

no titleで紹介されているやりかたと基本同じです。違うのはライブラリのパスはプラグインの中に含めているので、プラグインのXXXPluginConfigurationクラスにプラグインのパスで指定することです。

<?php
class apTwitterPluginConfiguration extends sfPluginConfiguration
{
  /**
   * @see sfPluginConfiguration
   */
  public function initialize()
  {
      self::registerZend();
  }
  static protected $zendLoaded = false;
  static public function registerZend()
  {
    if (self::$zendLoaded)
    {
      return;
    }
    set_include_path(dirname(dirname(__FILE__)).'/lib/vendor'.PATH_SEPARATOR.get_include_path());
    require_once dirname(dirname(__FILE__)).'/lib/vendor/Zend/Loader/AutoLoader.php';
    Zend_Loader_Autoloader::getInstance();
    self::$zendLoaded = true;
  }
}

ググってみるとautoload.ymlに書けばOKという解説の記事もありますけど、パスが通るだけでZendライブラリ内でrequireされたファイルへのパスが通らないのでZendFrameworkと連携するのであればZend_Loader_Autoloader::getInstace()を使うことになると思います。また、この書き方もZendFrameworkのバージョンによって違うので古いバージョンの場合は(多分)動かないはずです。

routing.ymlを設定

twitterの認証画面に進むためのルーティングと、callbackのルーティングを定義しておきます。

twitter_login:
  url:   /twitter_login
  param: { module: apTwitter, action: login}
twitter_callback:
  url:   /twitter_callback
  param: { module: apTwitter, action: callback}

定義せずにコード中にハードコーディングしても良いですが、routing.ymlをつかって置けば、callback先の開発環境、本番環境のホスト名の違いなどを意識しなくてよいので便利ですよ。

actionの例

あとは見慣れたアクションクラスを書くだけです。routing.ymlでルーティングを設定したのでUrlヘルパーを読み込み、callback先のURLを取得できるようにしている点ぐらいです。

<?php
class apTwitterActions extends sfActions
{
  public function executeLogin($request)
  {
      $this->getContext()->getConfiguration()->loadHelpers(array('Url'));
      $config = array(
          'requestScheme' => Zend_Oauth::REQUEST_SCHEME_HEADER,
          'signatureMethod' => 'HMAC-SHA1',
          'callbackUrl' => url_for('@twitter_callback', true),
          'requestTokenUrl' => 'http://twitter.com/oauth/request_token',
          'authorizeUrl' => 'http://twitter.com/oauth/authorize',
          'accessTokenUrl' => 'http://twitter.com/oauth/access_token',
          'consumerKey' => 'コンシューマーキー',
          'consumerSecret' => 'コンシューマーパスフレーズ'
      );
      $consumer = new Zend_Oauth_Consumer($config);
      // fetch a request token
      $token = $consumer->getRequestToken();
      $consumer->redirect();
      return sfView::NONE;
  }
  public function executeCallback($request)
  {
     // callback時にセッションやDBにごにょごにょ...
  }
}

使い方はTwitterクライアントのOAuth認証(Zend_Oauth) – シンプルなTwitterクライアントの作成 – | エンジニア ブログを参考にさせていただきました。

2011-02-06

[][]ECサイトリニューアルオープン

f:id:brtRiver:20110205124104p:image

symfonyの開発実例って大規模なものが多いとか言われたりしますが、小さなECサイトの開発をsymfonyをベースで作ったのでちょこっと紹介。

愛犬のための犬グッズ専門サイト アットペット

アットペット

元々個人的に関わっているサイトなのですが、7年前に古い自前PHPフレームワークで動いていたものをそろそろどうにかするかということで慣れたsymfonyで機能を追加しつつ作り直しました。

オンラインショッピングはもちろんのこと、ポイントでお買い物したり、買った商品に愛犬の写真と共にレビューを投稿することもできます。ポイント還元率が高いので大容量のドッグフードをお探しの方など是非利用してみてください。


なぜsymfonyを選択したか?

PHPでECサイトをさくっと作るとなると、EC-CUBEで構築するという選択肢もありましたが次の理由でEC-CUBEを使わないことにしました。


カスタマイズする部分が結構多いのでEC-CUBEだと余計に工数が掛かりそう

買い物のフローがEC-CUBEと異なっていたり、会員機能にもペット情報が紐づいていたりと、改修するのにどのファイルを触ればよいか調べたり、その修正が他の機能に影響を与えないかテストを書いたりすることを考えると今回はEC-CUBEを使わないほうがベターと判断


旧データの移行をEC-CUBEに合わせるのが面倒

受注データやポイントの履歴などもあったので、EC-CUBEを熟知していない状態でそれらを全て移すのもこれまた大変と。


今後がっつり機能追加していきたいので拡張性重視

会員機能やコミュニティー機能を追加していくのに、ベースが奇麗なほうが良いと判断。無理に組み込んで拡張したEC-CUBEを更に拡張は大変だろうなぁと。


正直EC-CUBEをカスタマイズしたこともありますが、かなり昔のことですし精通しているわけではないので現在はこれらの理由は当てはまらないかもしれません。

で、PHPといえばフレームワーク天国ですがsymfonyを選んだ理由は次の理由


自分がsymfonyに慣れ親しんでいるから

symfony1が出る前から触ってるぐらいですから、愛着もあります。なんでもそうですが慣れれば便利です。そして結構重要な要素です。


アドミンジェネレーターを使いたかったから

一番の理由はこれです。

symfonyには管理画面のようなCRUD画面を設定(yaml)ファイルの記述だけで作れてしまうアドミンジェネレーターという超便利な機能があります。

もちろん、yamlだけで制御できないところはカスタマイズできますし、テーブルにカラムが追加されてもソースを修正する必要もありませんし、あったとしても最小限で済みます。

とにかく工数を抑えたかったので管理画面についてはアドミンジェネレータを駆使して作りました。あれをゼロから作ったとしていたら工数は倍以上は掛かっていたと思います。(もちろん見た目など妥協すべき点もありますが)。


キャッシュ機能が豊富

symfonyではページ全体をキャッシュしたり、ページの一部(パーシャルやコンポーネント)だけをキャッシュしたりできます。symfonyそのものがヘビーなのでこういう部分には結構力を入れている印象です。サイトのアクセス数が少ないとはいえキャッシュ機能が充実しているのは重要です。



小規模なECサイトですが、開発するときに注意したことや感想をまとめておきます。


CMSとしてWordpressを利用しsymfonyと連携

オーナーがページを増やしたり、記事を書いたりするのはすべてWordpressにお任せしました。そして、Wordpressで表示する画面のヘッダーのphpでsymfonyのインスタンスを作成し「ログイン」状態でログインボタンの表示を着替えたりできるように連動させました。つまり、Wordpressのテンプレートからsymfonyのlink_toヘルパー関数などを呼んだり、Userインスタンスを利用できりるようにしたということですね。これが簡単にできるのは便利でした。

via:404 Not Found


プラグインとことん活用

あるものは利用させていただく方針。個人的にPEARで管理はあまりしたくない派なので公式サイトやOpenpearSubversionGithubからgit cloneでプロジェクトに持ってきてます。

そして自分で作ったプラグインも含め以下のプラグインを利用

  • sfDoctrineGuardPlugin
    • 言わずと知れたDoctrineで会員管理するためのプラグイン。これをベースに会員登録、ログイン、パスワード再発行などの実装を行いました。また、追加の会員属性(ふりがななど)を保持する必要があるので、そういう情報を含め全て別テーブルにデータを持つようにし、sfDoctrineGuardPluginでは基本情報のみ持つようにしました。スッキリ。
    • via:no title
  • sfFormExtraPlugin
  • jpFormExtraPlugin
    • sfFormExtraPluginとはsfFormの標準Widgetにさらに便利なwidgetを追加するプラグインです。日付をカレンダーで洗濯したりするアレなどが含まれます。入れておいて損はないです。そして、このwidget類にさらに日本文化特有のwidget(日本語カレンダー、郵便番号から住所検索)を追加したプラグインを用意し自前のwidgetはこのプラグインで管理するようにしました。今度別の案件があってもそのまま利用できますしね。
    • via:no title
  • sfSimplePagePlugin
    • ちょっとしたページを追加したいときにアクションを用意しなくてもページを作成できるプラグインです。サイトマップの各ページで利用し、アクションクラスを用意せずに作りました。
    • via:no title
  • sfThumbnailPlugin
    • 画像のサイズを変更したりするライブラリですね。投稿写真のリサイズ処理に利用しています。
    • via:no title

他にもライブラリをプラグイン化したりしました。ある意味プラグインだらけです。


Google AnalyticsAPIを利用したアクセスランキング表示

Google AnalyticsのAPIを利用すればアクセス数ランキングのためのデータを集計したりすることも簡単です。本当はタスクとしと書きたかったのですが、とりあえずコンポーネントとして切り出して1日に1回だけAPIにアクセスし以後はその結果をキャッシュしたものを表示するようにしました。モジュールのconfig/cache.ymlに以下のように書けば特定のコンポーネントだけキャッシュできるsymfonyの標準の機能そのままです。

via:no title

_accessRanking:
  enabled: true

ジェネレータは自前のひな形を用意

symfonyのデフォルトのgenerate:moduleタスクではコンポーネントクラスを作ってくれなかったりするので、自前のスケルトンを用意しそれをひな形にするようにしました。また、そのひな形にはアクションクラスでよく使うメソッドや使い方をactions.class.sample.phpとして書き出すようにしておいたのでオンラインでググる必要も最小限に。

via: 404 Not Found

actions.class.sample.php
<?php
class sampleActions extends sfActions
{
  public function preExecute()
  {
    // code here
  }
 /**
  * Executes index action
  *
  * @param sfRequest $request A request object
  */
  public function executeIndex(sfWebRequest $request)
  {
    // default
    return sfView::SUCCESS;
    // not return template but render text
    return $this->renderText('message here');
  }
  private function __sampleCode()
  {
    // for debug
    $this->logMessage($message, 'debug'); // emerg, alert, crit, err, warning, notice, info, and debug

    // get Rouging object
    $object = $this->getRoute()->getObject();
    
    // with form
    $this->form = new HogeForm($object);
    $this->form->bind($request->getParameter('hoge'));
    if ($this->form->isValid()) {
      // code here
      $name = $this->form->getValue('name');
    }
    $widget = $this->form['name']->getWidget();
    
    // use Request Parameter
    $name = $request->getParameter('name', 'default_value');
    $all_parameters = $request->getParameterHolder()->getAll();
    $condition = $request->isMethod('post'); // if request method is 'post', return true.
    
    // set template
    $this->setTemplate('new'); // render 'newSuccess.php' instead of 'indexSuccess.php'
    
    // use session
    $this->getUser()->setAttribute('name', 'value');
    $condition = $this->getUser()->hasAttriute('error');
    $name = $this->getUser()->getAttribute('name', 'default_value');
    $condition = $this->getUser()->isAuthenticated();
    
    // use flash
    $this->getUser()->setFlash('error', 'error is occured!');
    $condition = $this->getUser()->hasFlash('error');  
    $name = $this->getUser()->getFlash('error');   
    
    // forward
    $this->forward('module', 'action');
    $this->forwardIf($condition, 'module', 'action'); // if $condition is true, forwarding to 'module/action'
    $this->forward404If($condition, 'message'); // if $condition is true, forwarding to the 404 page
    
    // redirect
    $this->redirect('@routing_name');
    $this->redirect('module/action');
    $this->redirectIf($condition, '@routing_name');
    $this->redirect404If($condition, '@routing_name');
    
    // call Doctrine
    $result = Doctrine_Core::getTable('ModelName')->find('id');
    $q = Doctrine_Query::create()->from('ModelName');
    
    // access to Global Parameters
    $request->getMethod() 	      // $_SERVER['REQUEST_METHOD']
    $request->getUri() 	          // $_SERVER['REQUEST_URI']
    $request->getReferer() 	      // $_SERVER['HTTP_REFERER']
    $request->getHost() 	        // $_SERVER['HTTP_HOST']
    $request->getLanguages() 	    // $_SERVER['HTTP_ACCEPT_LANGUAGE']
    $request->getCharsets() 	    // $_SERVER['HTTP_ACCEPT_CHARSET']
    $request->isXmlHttpRequest() 	// $_SERVER['X_REQUESTED_WITH'] == 'XMLHttpRequest'
    $request->getHttpHeader() 	  // $_SERVER
    $request->getCookie() 	      // $_COOKIE
    $request->isSecure() 	        // $_SERVER['HTTPS']
    $request->getFiles() 	        // $_FILES
    $request->getGetParameter() 	// $_GET
    $request->getPostParameter() 	// $_POST
    $request->getUrlParameter() 	// $_SERVER['PATH_INFO']
    $request->getRemoteAddress() 	// $_SERVER['REMOTE_ADDR']
    
    // add a new parameter to RequestParameter
    $request->addRequestParameters(array('new_key' => 'new_value'));
  }
}

気軽なデプロイ

小規模なプロジェクトなのでデプロイはsymfony標準コマンドの project:deploy をそのまま利用。このコマンドはrsyncでファイルを転送してくれます。ある意味PHP関係ないですがこんなコマンドが用意されているところがsymfonyっぽい。

sfFormはきつい

symfonyを触っているというよりsfFormに苦しんだ開発でした。sfFormとはsymfonyに用意されているフォーム処理用のサブフレームワークのようなものです。ある程度場数をこなせばその流用で早く開発ができるのですが、そこにたどり着くまでがかなり大変です。今回もこの部分で苦しみました。

doctrine:build-formsでフォームクラスは生成されますが、このフォームクラスは直接使わずにextendsしたものを必ず用意するようにしました。同じ会員テーブルでも、会員登録時と管理画面の編集時でフォームが異なることなんてよくありますから。


UIJQuery

JavaScriptはJQueryを使いました。これも使い慣れているのが一番の理由。カレンダーとかそういうウィジット関連はsymfonyのプラグインが用意されているのでそれを利用。でも、表示の日本語化とかそのあたりは自前で拡張。


ソース管理はGitとDropbox

実質一人で作業してるので個人で使っているgitリポジトリを利用してます。そしてgitを使えない非開発者とのドキュメント系の共有はDropboxで行いました。十分です。


動く状態のものができるまで1.5ヶ月

本業とは別で作業してました。家に帰ってちょこちょことちまちまと。。で管理画面も含めて実作業期間は1.5ヶ月ぐらい掛かったと思います。そして機能追加や修正やデザイン部分も含めて3ヶ月程度でしょうか。実際デザインも自分でやったのでPHPでごりごり書いている時間よりも慣れていないHTMLCSSを調整してる時間のほうが確実に長かったと思います。



機会があれば勉強会とかでもう少し詳しく話すかなー。聞きたい人がいれば。。(´・ω・`)


さて、今月はSymfony2 + MongoDBでアプリ1つと、LithiumでSukonvを作ってしまわなければ!!!

2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |