[OpenPNE3]管理画面付きプラグイン(opFreepagePlugin)#4 PC/モバイルの動作画面の作成

Written by uechoco 2月 27

前回まででopFreepagePluginの管理画面側は完成しています。今回はいよいよPCとモバイルの動作画面を作ります。

まずはPC版から。symfonyコマンドでpc_frontendのappとmoduleを作ります。moduleはfreepageという名前にします。

BASH:
  1. ./symfony opGenerate:app opFreepagePlugin pc_frontend
  2. ./symfony opGenerate:module opFreepagePlugin pc_frontend freepage

新しく作られたapps/pc_frontend/modules/freepage/actions/actions.class.phpのexecuteIndexメソッドを変更します。

php:
  1. public function executeIndex($request)
  2.   {
  3.     // forward 404 page unless freepage does exist
  4.     $this->forward404Unless($this->freepage = FreepagePeer::retrieveByPk($request->getParameter('id')), sprintf('Object freepage does not exist (%s).', $request->getParameter('id')));
  5.  
  6.     if ($this->freepage->getAuth()) {
  7.       if (($member = $this->getUser()->getMember()) && $member->getIsActive()) {
  8.         // ok
  9.       } else {
  10.         $this->forward404('This page is for sns members.');
  11.       }
  12.     }
  13.  
  14.     // forward 404 page unless app_type is 'pc';
  15.     $this->forward404Unless($this->freepage->getAppType() == 'pc', sprintf('This page is for %s application.', $this->freepage->getAppType()));
  16.  
  17.     $this->getResponse()->setTitle($this->freepage->getTitle());
  18.   }

このIndexアクションでは、

  • idがパラメータとして存在するか
  • パラメータidがfreepageテーブルに存在するか
  • 該当フリーページが認証付きページの場合、認証済みのユーザであるかどうか
  • 該当フリーページがPC用かどうか

をチェックしています。認証済みユーザの認証部分の処理はもっとスマートな方法がありそうですね。近いうちに調べてみます。続いてさきほど自動生成されたapps/pc_frontend/modules/freepage/templates/indexSuccess.phpにも変更を加えます。

php:
  1. <?php echo $sf_data->getRaw('freepage')->getBody(); ?>

今回は本質的ではないのでデザインを考慮していません。大事なポイントは、HTMLエンティティがエスケープされていない生のデータを取得して、表示しているところです。こうしないと管理画面で苦労して書いたタグがそのまま出てしまうことになるので、生データを出力します。

これでPC版は完成です。(http://example.com/freepage/id/1)
03_opfreepage_pc

続いてモバイル版も作りましょう。ほとんど同じ手順で作ることができます。まずはmobile_frontend appを作り、freepage moduleを追加します。

BASH:
  1. ./symfony opGenerate:app opFreepagePlugin mobile_frontend
  2. ./symfony opGenerate:module opFreepagePlugin mobile_frontend freepage

自動生成されたapps/mobile_frontend/modules/freepage/actions/actions.class.phpを書き換えます。PC版との違いはPC/mobile判定部分だけです。

php:
  1. public function executeIndex($request)
  2.   {
  3.     // forward 404 page unless freepage does exist
  4.     $this->forward404Unless($this->freepage = FreepagePeer::retrieveByPk($request->getParameter('id')), sprintf('Object freepage does not exist (%s).', $request->getParameter('id')));
  5.  
  6.     if ($this->freepage->getAuth()) {
  7.       if (($member = $this->getUser()->getMember()) && $member->getIsActive()) {
  8.         // ok
  9.       } else {
  10.         $this->forward404('This page is for sns members.');
  11.       }
  12.     }
  13.  
  14.     // forward 404 page unless app_type is 'mobile';
  15.     $this->forward404Unless($this->freepage->getAppType() == 'mobile', sprintf('This page is for %s application.', $this->freepage->getAppType()));
  16.  
  17.     $this->getResponse()->setTitle($this->freepage->getTitle());
  18.   }
  19. [/phpocde]
  20.  
  21. そしてapps/mobile_frontend/modules/freepage/templates/indexSuccess.phpを変更します。こちらはデザインが入っていないのでPC版の同ファイルと同じ内容です。
  22. [phpcode]
  23. <?php echo $sf_data->getRaw('freepage')->getBody(); ?>

モバイル版も完成です。(http://example.com/mobile_frontend.php/freepage/id/1)
04_opfreepage_mobile

次回はルーティングの調整をします。

[php]configure: warning: lemon versions supported for regeneration of libsqlite parsers: 1.0 (found: none)

Written by uechoco 2月 20

CentOS 5.2でPHP 5.2.8のconfigure中に表題のエラーが発生。

などを参考に

BASH:
  1. <pre>wget http://www.sqlite.org/cvstrac/getfile/sqlite/tool/lemon.c
  2. gcc -o lemon lemon.c
  3. sudo mv lemon /usr/local/bin</pre>

こうすることで、warningが消えた。

[雑記]個人ブログを分離・新設しました。

Written by uechoco 2月 19

少し前からですが、個人ブログを開設しています。

ランニングと本とグルメとキイロイトリをブログ

主に各内容はブログのタイトルに書きならべました。なんてひどいタイトルww。

  • ランニングは、ホノルルマラソンや東京マラソン、日々の練習について書きます。
  • 本は、技術系以外の本を読んだ時にレビューを書いてみようと思っています。ただ、技術系以外の本を読む習慣がありません。(ぉぃ
  • グルメは、浅草・高田馬場・新宿などを中心に、勝手気ままにお店や食べ物を紹介していきます。たまに写真を撮り忘れて書けないこともしばしば。
  • キイロイトリは、キイロイトリです。カオルさんのペットです。かわいいです。かわいいです。かわいいです。部屋にたくさん居ます。

開設する前までに溜めこんだ記事は、内容に合わせて古い日付を付けています。今後各記事はそのままの日付です。

個人的なブログなので、気ままにやっていこうと思います。

[OpenPNE3]管理画面付きプラグイン(opFreepagePlugin)#3 管理画面編集フォーム作成

Written by uechoco 2月 19

 前回はopFreepagePluginの管理画面の一覧表示部分を作ってみました。今回はそこにデータを追加するための新規作成・編集削除の2画面を一気に作ります。symfonyを使ったことある人ならば、「propel:generate-crud的な感じ」と言えばソースコードの構成が伝わるかもしれません。

まずはactions.class.phpに以下の6つのメソッドを追加します。

php:
  1. public function executeNew(sfWebRequest $request)
  2.   {
  3.     $this->form = new FreepageForm();
  4.   }
  5.  
  6.   public function executeCreate(sfWebRequest $request)
  7.   {
  8.     $this->forward404Unless($request->isMethod('post'));
  9.  
  10.     $this->form = new FreepageForm();
  11.  
  12.     $this->processForm($request, $this->form);
  13.  
  14.     $this->setTemplate('new');
  15.   }
  16.  
  17.   public function executeEdit(sfWebRequest $request)
  18.   {
  19.     $this->forward404Unless($freepage = FreepagePeer::retrieveByPk($request->getParameter('id')), sprintf('Object freepage does not exist (%s).', $request->getParameter('id')));
  20.     $this->form = new FreepageForm($freepage);
  21.   }
  22.  
  23.   public function executeUpdate(sfWebRequest $request)
  24.   {
  25.     $this->forward404Unless($request->isMethod('post') || $request->isMethod('put'));
  26.     $this->forward404Unless($freepage = FreepagePeer::retrieveByPk($request->getParameter('id')), sprintf('Object freepage does not exist (%s).', $request->getParameter('id')));
  27.     $this->form = new FreepageForm($freepage);
  28.  
  29.     $this->processForm($request, $this->form);
  30.  
  31.     $this->setTemplate('edit');
  32.   }
  33.  
  34.   public function executeDelete(sfWebRequest $request)
  35.   {
  36.     $request->checkCSRFProtection();
  37.  
  38.     $this->forward404Unless($freepage = FreepagePeer::retrieveByPk($request->getParameter('id')), sprintf('Object freepage does not exist (%s).', $request->getParameter('id')));
  39.     $freepage->delete();
  40.  
  41.     $this->redirect('opFreepagePlugin/list');
  42.   }
  43.  
  44.   protected function processForm(sfWebRequest $request, sfFormPropel $form)
  45.   {
  46.     $form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));
  47.     if ($form->isValid())
  48.     {
  49.       $freepage = $form->save();
  50.       $this->redirect('opFreepagePlugin/list');
  51.     }
  52.   }

これでもうアクションの追加は終わりです。あとは対応するビューの作成とフォームの調整です。

templates/ディレクトリにnewSuccess.phpを作成し、以下のコードを入力します。

php:
  1. <?php slot('submenu') ?>
  2. <?php include_partial('submenu') ?>
  3. <?php end_slot() ?>
  4.  
  5. <h2><?php echo __('フリーページの新規作成') ?></h2>
  6.  
  7. <?php include_partial('form', array('form' => $form)) ?>

続いて、editSuccess.phpを作成し、以下のコードを入力します。

php:
  1. <?php slot('submenu') ?>
  2. <?php include_partial('submenu') ?>
  3. <?php end_slot() ?>
  4.  
  5. <h2><?php echo __('フリーページの編集') ?></h2>
  6.  
  7. <?php include_partial('form', array('form' => $form)) ?>

この2つのビューは新規作成と編集画面に対応するものですが、非常に似ていますし、短いですね。というのも、肝心のフォームの描画は部分テンプレートとして分離しています。templares/ディレクトリに_form.phpを作成して、以下のコードを追加します。

php:
  1. <?php include_stylesheets_for_form($form) ?>
  2. <?php include_javascripts_for_form($form) ?>
  3.  
  4. <form action="<?php echo url_for('opFreepagePlugin/'.($form->getObject()->isNew() ? 'create' : 'update').(!$form->getObject()->isNew() ? '?id='.$form->getObject()->getId() : '')) ?>" method="post" <?php $form->isMultipart() and print 'enctype="multipart/form-data" ' ?>>
  5. <?php if (!$form->getObject()->isNew()): ?>
  6. <input type="hidden" name="sf_method" value="put" />
  7. <?php endif; ?>
  8.   <table>
  9.     <tfoot>
  10.       <tr>
  11.         <td colspan="2">
  12.           <?php echo $form->renderHiddenFields() ?>
  13.           &nbsp;<a href="<?php echo url_for('opFreepagePlugin/index') ?>">キャンセル</a>
  14.           <?php if (!$form->getObject()->isNew()): ?>
  15.             &nbsp;<?php echo link_to('削除', 'opFreepagePlugin/delete?id='.$form->getObject()->getId(), array('method' => 'delete', 'confirm' => '本当に削除しますか?')) ?>
  16.           <?php endif; ?>
  17.           <input type="submit" value="<?php echo $form->getObject()->isNew() ? '作成' : '更新' ?>" />
  18.         </td>
  19.       </tr>
  20.     </tfoot>
  21.     <tbody>
  22.       <?php echo $form->renderGlobalErrors() ?>
  23.       <tr>
  24.         <th><?php echo $form['title']->renderLabel() ?></th>
  25.         <td>
  26.           <?php echo $form['title']->renderError() ?>
  27.           <?php echo $form['title'] ?>
  28.         </td>
  29.       </tr>
  30.       <tr>
  31.         <th><?php echo $form['body']->renderLabel() ?></th>
  32.         <td>
  33.           <?php echo $form['body']->renderError() ?>
  34.           <?php echo $form['body'] ?>
  35.         </td>
  36.       </tr>
  37.       <tr>
  38.         <th><?php echo $form['app_type']->renderLabel() ?></th>
  39.         <td>
  40.           <?php echo $form['app_type']->renderError() ?>
  41.           <?php echo $form['app_type'] ?>
  42.         </td>
  43.       </tr>
  44.       <tr>
  45.         <th><?php echo $form['auth']->renderLabel() ?></th>
  46.         <td>
  47.           <?php echo $form['auth']->renderError() ?>
  48.           <?php echo $form['auth'] ?>
  49.         </td>
  50.       </tr>
  51.     </tbody>
  52.   </table>
  53. </form>

この_form部分テンプレートは、新規作成と編集の両方に対応しているテンプレートですので、ところどころで三項演算子の分岐で新規作成時と編集時の描画内容を分けています。このようにフォームの描画部分を1つのファイルにまとめることで、効率の良いコード保守ができるわけです。

 一応これでフォームは表示できるのですが、symfonyが自動で生成したフォームは余計なフィールドが混ざっているので、たいていはカスタマイズが必要です。今回もcreated_atやupdated_atを表示させないようにしたり、app_typeやauthの表示方法を変更するために、フォームファイルをカスタマイズします。/plugins/opFreepagePlugin/lib/form/FreepageForm.class.phpを開いて、クラスの中身を以下のように書き換えます。

php:
  1. protected static $auths = array('0' => 'なし', '1' => 'あり');
  2.   protected static $app_types = array('pc' => 'pc', 'mobile' => 'mobile');
  3.  
  4.   public function configure()
  5.   {
  6.     unset($this['created_at'], $this['updated_at']);
  7.  
  8.     $this->widgetSchema['body'] = new sfWidgetFormTextarea(array(), array('cols' => 50, 'rows' => 20));
  9.  
  10.     $this->widgetSchema['app_type'] = new sfWidgetFormSelect(array('choices' => self::$app_types));
  11.     $this->validatorSchema['app_type'] = new sfValidatorChoice(array('choices' => array_keys(self::$app_types)));
  12.  
  13.     $this->widgetSchema['auth'] = new sfWidgetFormChoice(array('choices' => self::$auths, 'multiple' => false, 'expanded' => true));
  14.     $this->validatorSchema['auth'] = new sfValidatorChoice(array('choices' => array_keys(self::$auths)));
  15.  
  16.     $this->widgetSchema->setLabels(array(
  17.       'app_type' => 'アプリケーションタイプ',
  18.       'auth'     => '認証が必要',
  19.     ));
  20.   }

上から順番に処理の内容を言うと、bodyフィールドのcols・rowsの設定、app_typeフィールドをセレクトボックスとし選択肢を限定、authフィールドをオプションボタンとし選択肢を限定、いくつかのフィールドのラベルを変更、となっています。実は最初authフィールドをチェックボックスとして実装したのですが、0と1で判断するauthフィールドとはなぜか相性が良くないらしく、編集時にauth=0のはずがチェックがされた状態で描画されてしまいました。とりあえずオプションボタンにすることで回避できました。

以上で管理画面側は全て出来上がりました。実際に編集画面を見てみるとこんな感じになります。
02_opfreepage_edit

次回はPC/mobileの実際の表示画面の作成をやります。次回の内容でプラグインの機能部分は終わって、それ以降はプラグインの微調整と、プラグインのリリースなどが待っています。あともう少しでプラグインの完成です!

[symfony]Unable to execute INSERT statement. [wrapped: SQLSTATE[23000]: Integrity constraint violation: 1048 Column ‘id’ cannot be null]

Written by uechoco 2月 18

参照整合性のエラーとか、聞いたことがない。symfonyでモデルクラスを使ってテーブルにsave()すると表題のようなエラーで動いてくれない。ソースコードを疑ったが、どこにも悪いところがない。あとはもうDBだけかとおもってDBDesignerを見直してみると罠があった。該当のテーブルのidカラムのAUTO_INCREMENTのチェックが外れていた。

当然このDBDesignerの設計図から吐き出されるSQLも意図していないものなので、リバースエンジニアリングしてschema.ymlを作ったymlも知っていたはず。ところが、自分でschema.ymlを書き換えて、各テーブルのid属性の値を消してしまったので、symfonyが普通のidカラムだと思って、モデルを作ってしまったようだ。

たぶん、ご操作でたまたまAUTO_INCREMENTのチェックが外れてしまったんだと思うが、このチェックが外れていたおかげで実に2時間を浪費してしまった。なんたる失態。