August 9, 2008

よき人たれ

今年の夏は、エジプトでダイブマスターを取って、いつでもダイビングインストラクターで働けるようにしようと思っていたのですが、いろいろあって、ハワイのハワイ島に来ています。

多くの人と話していると、「いいなー、ハワイ。リラックスできて。」と言われますが、まったく持ってリラックスなんてできないです。なんつーか、ブルーワーカー状態です。そして、ボランティアに積極的に関わっています。そのために、来たわけではないので、本業もしっかりしないといけない。。。さて、実際に、ボランティアをしなかったら、引き篭もりになって、あまり人と話さないんだろうけど、おかげさまで、いろんな人に会う機会をいただいています。しかし、リラックスだけなら、京都にいたときの方がリラックスしていたと思います。

最近はいろいろ頼まれごとが多く、肉体労働も兼ねたお手伝いをしています。こうやってボランティアで社会に、コミュニティに、いいことをしていると、なぜかいろいろな人に助けてもらうことができることに気づきました。

昔は私もおせっかいで、いろいろ人を助けるのが好きだったのですが(美化しすぎ)、ここ8年ほどフリーライダーに近い状態になり、「自分さえ良ければ、いいや」と思うようになっていました。必死に長時間労働することが、「スマートではないし、自分は無理だなぁ」、なんて思っていたりしました。今でも、「雇われて働くことに関してのみ」、その思いは変わりません。雇用関係にあるのならば、仕事は仕事として、より効率良く、ビジネスライクに付き合うのがいいと思っています。基本的に、啓蒙本は生理的に受け付けなくて嫌いなのですが、最近の仕事啓蒙本なんかは、どんどんドラスティックに仕事をしていこう、という流れがあるように思えます。いや、普通の仕事に関しては、それで私も同意ですし、納得しています。私も、ホワイトカラーな仕事で自己実現なんて、ほとんどの人にはできない、と考えています。(昔、どこかの人事部長を引退をした人の講義を取って、その講義の中で、彼が仕事と自己実現を結びつけることを延々と語ることに、激しく嫌悪感を持ったことを思い出します。)

しかし、雇用関係ではなく、好意として働くこと(ボランティアとか)に関しては、自分の道徳に従い、「義理、人情、難波節」で働くことが、よい人生につながるように感じるようになりました。そして、よきことをしていると、いつの間にか、いろんな人が助けてくれる、ということをハワイで実感しています。

最近の風潮では、いろいろと他の人でもできることは、どんどんアウトーソーシングするのが、いいと言われているなか、このようなボランティアでは、どんどんDIYで解決していきます。そして、彼らが住んでいる社会にとって、コミュニティにとって、役に立つことがあれば、どんどんと助けにいきます。まぁ、何人かの人は、すでに引退した人だから時間を持て余している、とも思うのですが、この行動力はずば抜けています。

そして、彼らは、よき人のネットワークを持っています。よき人には、よき人を紹介して、さらに気持ちのいい活動をすることが出きるような社会を無自覚に作り上げています。アメリカでは、人のネットワークがとても大事だと言われています。会社に仕事を頼んでも、イマイチなものしか返ってこなかったり、全然反応がなかったりなんて日常茶飯事です。個人の裁量が大きいのですね。その代わり、素晴らしい人を知っていれば、それだけで取引コストと機会コストを最低まで落とすことができます。会社よりも、人なんですね。

最初は、表面的なお手伝いをしようと思っていた私も、こちらで、よきことをしている人を手伝うようになっています。そうすると、自分の気持ちも幸せになるばかりか、いろいろ私のために助けてくれるようにしてくれる人がおり、結果的にプラスの方向へ動いているような気がします。そこで、「よき人たれ」というのは、すごく重要なんだな、と気づきました。もちろん、「よきこと」とは、自分の中にある道徳に関してであって、100%皆が同じものを「よきこと」と思っているわけではないと思います。適当だけど、80%くらいかなぁ。

先日、ボランティアのつながりで、ヒロにあるライオンズクラブの人たちの晩ご飯と会合にお呼ばれしたのですが、なかなか興味深かったです。若者はいなくて、半分以上が引退した人たちばかりなのですが、善意で集まった昔の学級組織のような感じでした。いい年こいたオッサンらが、いい関係を築いていている学級組織です。変な表現ですが、懐かしくて、うれしかったです。というか、半分以上が日系人で、名字は、馴染み深いものばかりでした。三世までなると、日本語を話せる人があまりいないですね。

August 4, 2008

symfony1.1でsfGuardPluginを使う。パート2

ここ二日通っているベトナム料理のレストランの眼鏡っ子ウェイトレスがかわいくて、通おうかなーと思ってる私は、いろんな人に助けれながらハワイ島のヒロに滞在しています。ぜんぜん関係ないですが、今日は相撲の土俵の屋根の解体作業を手伝いました。きつい肉体労働で疲れました。。。

さて、パート1では、sfGuardPluginのsfGuardUserProfileとsfGuardUserを1対1のテーブル関係でリレーションがあった際に、アドミンジェネレータで一つのモデルをCRUDしているかのように扱う方法を説明しました。

しかし、実はこのアドミンジェネレータの方法は、symfony1.1的には、フォームの使い方が古いんですね。symfony1.1からは、sfFormクラスを使ってフォームを作るように、方法が変わったのです。なので、form_tagとかのヘルパー関数を使うのではなく、sfFormクラスの拡張クラスを使うことになります。また、Propelを使用している際には、うまく同期をとってくれるsfFormPropelの拡張クラスを使用することになりますね。例によって、ジェネレータで、ベースクラスと実際の処理を書く空クラスが作られ、それを上書きしていくという方法で実装するようになります。

さて、前提条件をここでおさらいするのは面倒なので、前回のパート1のまま、進めてみます。つまり、schema.ymlにsf_guard_user_profileを用意して、build-allをした状態とします。パート1では、backendとしましたが、今回は、frontendとしてみます。

今回のフォーム作成においては、CRUDのCreationのフォームとその値のデータベースへの登録についてを説明します。

パート1と同じように1対1の関連を持つ二つのテーブルを同時に登録する際の使い方について書きます。モジュール名はなんでもいいのですが、ベタにuserとしておきます。

  1. $ ./symfony project:init-module frontend user
  2. PHP Warning:  Xdebug MUST be loaded as a Zend extension in Unknown on line 0
  3. >> dir+      /home/shin/project/test/apps/frontend/modules/user/templates
  4. >> file+     /home/shin/project/test/apps/fr...user/templates/indexSuccess.php
  5. >> dir+      /home/shin/project/test/apps/frontend/modules/user/actions
  6. >> file+     /home/shin/project/test/apps/fr.../user/actions/actions.class.php
  7. >> file+     /home/shin/project/test/test/fu...al/frontend/userActionsTest.php
  8. >> tokens    /home/shin/project/test/test/fu...al/frontend/userActionsTest.php
  9. >> tokens    /home/shin/project/test/apps/fr...user/templates/indexSuccess.php
  10. >> tokens    /home/shin/project/test/apps/fr.../user/actions/actions.class.php

さて、環境が整いました。ここでフォームの入力可能なフィールドを何とするか決めます。パート1との続きということで、メールアドレス、パスワード、名前、生年月日とします。また、パート1と同じくsf_guard_userのusernameは、メールアドレスを入れる項目とします。パート1では、propel:build-allなどで、モデルを作成した際に、ついでにフォームクラスの雛形も生成されます。lib/form/sfGuardUserProfileForm.class.phpがすでにあると思います。そのクラスsfGuardUserProfileFomの空のメソッドconfigureに、フォームで使用するフィールドを選択したり、そのフィールドのラベルを変更したり、バリデターをつけたりしましょう。

つまり、大きく分けて、次の3つを行います。

  1. フォームで使用するフィールドを選択する
  2. ラベルを日本語化する
  3. バリデーションを追加する

フォームで使用するフィールドを選択する

さきほど、どのフィールドを入力可能とするか、決めました。メールアドレス、パスワード、名前、生年月日ですね。では、さっそく、それをセットしましょう。

  1. public function configure()
  2.     {
  3.         $years = range(date('Y') - 60, date('Y') - 17);
  4.         $this->setWidgets(array(
  5.             'username' => new sfWidgetFormInput(),
  6.             'name' => new sfWidgetFormInput(),
  7.             'password' => new sfWidgetFormInputPassword(),
  8.             'birthday' => new sfWidgetFormDate(array('format' => '%year%年%month%月%day%日', 'years' => array_combine($years, $years)))
  9.         ));
  10.     }

誕生日の年の項目は、17歳から60歳の人を対象としてみます。それより若い人、老いた人を対象にしたい場合は、適当に修正してください。

そして、作成したモジュールuserのactions.class.phpにexecuteRegisterメソッドを追加して、Viewファイル、registerSuccess.phpもuserモジュールのtemplates以下に追加しましょう。
actions.class.php

  1. public function executeRegister($request)
  2.     {
  3.         $this->form = new sfGuardUserProfileForm();
  4.     }

registerSuccess.php

  1. <?php $user = $form->getObject() ?>
  2. <form action="<?php echo url_for('user/register') ?>" method="post">
  3.   <table>
  4.     <tfoot>
  5.       <tr>
  6.         <td colspan="2">
  7.           <input type="submit" value="登録する" />
  8.         </td>
  9.       </tr>
  10.     </tfoot>
  11.     <tbody>
  12.       <?php echo $form ?>
  13.     </tbody>
  14.   </table>
  15. </form>

これで、簡単な登録フォームができました。デザイナーの方とフォームのデザインなどを協調的に作業する際には、この$formをこうやって単にechoするのではなく、詳細に書いていくことができるようですが、面倒ですので、ここではしません。

ラベルを日本語化する

さて、パート1のアドミンジェネレータのときと同じく、ラベルがまだ英語になっていますので、日本語に書き換えましょう。sfGuardUserProfileFormクラスのconfigureメソッドの先ほど書いた後あたりに、に次の行を加えて、ラベルをセットします。

  1. $this->widgetSchema->setLabels(array(
  2.             'username' => 'メールアドレス',
  3.             'password' => 'パスワード',
  4.             'name' => '名前',
  5.             'birthday' => '生年月日'
  6.          ));

バリデーションを追加する

これで、フォームができました。次は、バリデーションです。メールアドレスの項目は、必須で、メールアドレスのフォーマットチェック、ユニークチェックをしましょう。パスワードは、必須項目とするだけにします。nameも必須項目とするだけにします。実際に使用する際には、文字数チェックなども、そのアプリの仕様に基づいて変更してください。そして、ラベルをセットした後あたりに、次の行を加えて、バリデーションをセットします。

  1. $this->setValidators(array(
  2.             'username' => new sfValidatorEmail(
  3.                 array(),
  4.                 array('required' => 'メールアドレスの項目は必須です。',
  5.                       'invalid' => 'メールアドレスのフォーマットが間違っています
  6. 。もう一度ご確認ください。')),
  7.             'nickname' => new sfValidatorString(
  8.                 array(),
  9.                 array('required' => 'ニックネームの項目は必須です。')),
  10.             'password' => new sfValidatorString(
  11.                 array(),
  12.                 array('required' => 'パスワードの項目は必須です。')),
  13.             'birthday' => new sfValidatorDate(array('required' => false)),
  14.         ));
  15.         $this->validatorSchema->setPostValidator(new sfValidatorPropelUnique(
  16.             array('model' => 'sfGuardUser', 'column' => array('username')),
  17.             array('invalid' => '指定のメールアドレスは既に登録されています。')
  18.         ));
  19.  
  20.         $this->widgetSchema->setNameFormat('user[%s]');

最後のsetNameFormatは、パラメータのネームスペースのようなものです。フォームの値がuserというキーの連想配列に入るようにしています。つまり、user[password]とか、user[birthday][year]とかになって、サーバにパラメータが送られてくるようになります。

さて、あとは、actionクラスを修正して、ちゃんとバリデーションが通った際には、保存できるようにしましょう。さきほどは、formという変数をレンダーしているだけでしたので、ロジックを書きます。

  1. public function executeRegister($request)
  2.     {
  3.         $this->form = new sfGuardUserProfileForm();
  4.         if ($request->isMethod('post')) {
  5.             $this->form->bind($request->getParameter('user'));
  6.             if ($this->form->isValid()) {
  7.                 $this->form->setIsActive(false);
  8.                 $user = $this->form->save();
  9.                 $email = $user->getEmailaddress();
  10.                 // ここで確認メールを送って、is_activeをtrueにする操作をしたり。
  11.             }
  12.             // validation failed
  13.         }
  14.     }

ところで、

  1. $this-form->setActive(false);

とあるのですが、それは、まだ実装していませんね。なぜこんなことをするかというと、デフォルトのsfGuardUserのis_activeの値は、trueなのです。なので、セットせずに、保存してしまうとここで登録したユーザがそのままログインができるようになってしまうのですね。確認メールを送信するなどして、ちゃんと有効なメールアドレスを登録しているユーザだけを有効にしたい場合が多くあると思いますので、sfGuardUserProfileFormクラスにsetIsActiveメソッドを追加しておきましょう。確認メールの送り方などは、力尽きましたので、ここでは書きません。

というわけで、sfGuardUserProfileFormクラスは最終的に以下のようになりました。

  1. class sfGuardUserProfileForm extends BasesfGuardUserProfileForm
  2. {
  3.     public function configure()
  4.     {
  5.         $years = range(date('Y') - 60, date('Y') - 17);
  6.         $this->setWidgets(array(
  7.             'username' => new sfWidgetFormInput(),
  8.             'name' => new sfWidgetFormInput(),
  9.             'password' => new sfWidgetFormInputPassword(),
  10.             'birthday' => new sfWidgetFormDate(array('format' => '%year%年%month%月%day%日', 'years' => array_combine($years, $years)))
  11.         ));
  12.  
  13.         $this->widgetSchema->setLabels(array(
  14.             'username' => 'メールアドレス',
  15.             'password' => 'パスワード',
  16.             'name' => '名前',
  17.             'birthday' => '生年月日'
  18.         ));
  19.  
  20.         $this->setValidators(array(
  21.             'username' => new sfValidatorEmail(
  22.                 array(),
  23.                 array('required' => 'メールアドレスの項目は必須です。',
  24.                       'invalid' => 'メールアドレスのフォーマットが間違っています。もう一度ご確認ください。')),
  25.             'name' => new sfValidatorString(
  26.                 array(),
  27.                 array('required' => '名前の項目は必須です。')),
  28.             'password' => new sfValidatorString(
  29.                 array(),
  30.                 array('required' => 'パスワードの項目は必須です。')),
  31.             'birthday' => new sfValidatorDate(array('required' => false)),
  32.         ));
  33.  
  34.         $this->validatorSchema->setPostValidator(new sfValidatorPropelUnique(
  35.             array('model' => 'sfGuardUser', 'column' => array('username')),
  36.             array('invalid' => '指定のメールアドレスは既に登録されています。')
  37.         ));
  38.  
  39.         $this->widgetSchema->setNameFormat('user[%s]');
  40.     }
  41.  
  42.     public function setIsActive($value = false)
  43.     {
  44.         $this->object->setIsActive($value);
  45.     }
  46. }

もう一息です。toArrayとfromArrayを上書きする必要があるのを忘れていました。
sfGuardUserProfile.phpにtoArrayとfromArrayを上書きしてみます。

  1. public function fromArray($arr, $keyType = BasePeer::TYPE_PHPNAME)
  2.     {
  3.         parent::fromArray($arr, $keyType);
  4.         $this->setUsername($arr['username']);
  5.         $this->setPassword($arr['password']);
  6.     }
  7.  
  8.     public function toArray($keyType = BasePeer::TYPE_PHPNAME)
  9.     {
  10.         $result = parent::toArray($keyType);
  11.         $result['username'] = $this->getUsername();
  12.         $result['password'] = $this->getPassword();
  13.         return $result;
  14.     }


これで、とりあえず完了です。パート1で使用したアクセサやsaveメソッドは、そのまま使用することで、sf_guard_userとsf_guard_user_profileという二つのテーブルに保存することができるようになります。

ふー。疲れました。

間違いがありましたら、いろいろ教えてください。

Bloglines feedburner