July 28, 2008

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

ならべて.comは、symfonyで開発された仕組みですが、1.0を使用しています。先日、ブログに貼り付けることができるウィジェットをリリースしており、もちろん開発は続けているのですが、現在その他に、別の仕組みを開発しており、そこでは、1.1を採用することにしました。symfony自体に関しては、もちろん根本にある使い方は変わっていないのですが、それなりに苦労しましたので、久しぶりにPHPネタでブログを書いてみます。

コードをできるだけ書かないことは、よりシンプルな開発となり、また、それがバグを減らすことなり重要ですよね。そこで、今回の開発では、アドミンジェネレータやプラグインを採用しようと決めました。プラグインでは、symfonyの開発者でもあるFabienさんのsfGuardPluginを使用しました。インストールできない人は、マニュアルを読んでください。

しかし、実際にsymfonyを使用して開発している方に話を聞いたのですが、実際のアプリとして作り込むには、sfGuardPluginは使いにくいので、アプリの作り方として勉強するならいいという話でした。今から書こうとする内容としては、いきなりこんなことを言うなんて凹んでしまいますが、そこは、よりコードを書かないようにゴリ押しで進めてみました。

さて、sfGuardPluginでは、ユーザの持つ情報をsf_guard_userテーブルに保存し、sfGuardUserというモデルで管理しています。このモデルのyamlスキーマは以下のようになっています。

  1. sf_guard_user:
  2.     _attributes:    { phpName: sfGuardUser }
  3.     id:             ~
  4.     username:       { type: varchar, size: 128, required: true, index: unique }
  5.     algorithm:      { type: varchar, size: 128, required: true, default: sha1 }
  6.     salt:           { type: varchar, size: 128, required: true }
  7.     password:       { type: varchar, size: 128, required: true }
  8.     created_at:     ~
  9.     last_login:     { type: timestamp }
  10.     is_active:      { type: boolean, required: true, default: 1 }
  11.     is_super_admin: { type: boolean, required: true, default: 0 }

sfGuardPluginに付いてくるsfGuardUserモジュールによって、sf_guard_userだけのCRUDは可能です。しかし、ユーザの属性には、もっといろいろな情報を持たせて、同時に登録したり、修正したいですよね?例えば、メールアドレスだったり、住所だったり、生年月日だったり、と。こういうときにあるのが、sf_guard_user_profileテーブルです。自分のスキーマファイルにsf_guard_user_profileを好きなカラムで指定することができます。そうすると、アクションクラスの中で$this->user->getProfile()と、持ってくることができます。つまり、テーブルが二つになって、1対1のリレーションで構成を作ってくれます。個人的にはこの1対1のリレーションが嫌いなのですが、プラグインの中を変更することは嫌ですので、このsf_guard_user_profileを使用しようと思います。しかし、やはり1対1のためか、アドミンジェネレータや、1.1から変更のあったフォーム周りを使用しようとすると結構大変でした。というわけで、ゴリ押しです。

さて、今開発しているものをそのまま持ってきてしまうと、説明がややこしくなったり、権利関係で問題になりそうですので、簡略化したモデルを使用しましょう。例えば、次のようなものです。

  1. sf_guard_user_profile:
  2.     _attributes: { phpName: sfGuardUserProfile }
  3.     id:
  4.     user_id:
  5.       type: integer
  6.       required: true
  7.       foreignTable: sf_guard_user
  8.       foreignReference: id
  9.       onDelete: cascade
  10.       onUpdate: cascade
  11.     name:
  12.       type: varchar(64)
  13.       required: true
  14.     birthday:
  15.       type: date
  16.     created_at:
  17.     updated_at:

準備は整いました。というわけで、ようやく本題。今回のsfGuardPluginネタは、二つのポストに分けて書こうと思います。前半のこのポストでは、symfony1.1のsfGuardPluginを、アドミンジェネレータを使用する方法について書きます。後半の次のポスト(予定)では、同様の環境をアドミンジェネレータではなく、symfony1.1から採用された新しいフォームクラスを使用する方法について書きます。

symfonyのアドミンジェネレータは非常に優れていますね。私は初めて使用したときは、単なるCRUDをやってくれるだけなのかな、とバカにしていたのですが、設定ファイルであるgenerator.ymlを編集したり、パーシャルを使用したりすることによって、とても柔軟に仕組みを作ることができるようになっています。しかも、コードをあまり書くことなくに、です。いやぁ、素晴らすぃ。

さて、今回の仕組みの目的は、sf_guard_userでは、格納できる情報が少ないので、sf_guard_user_profileを使用して格納できる情報を増やして、かつ、アドミンジェネレータで一つのモジュールで管理することです。

アドミンジェネレータでは、一つのモデルに対して一つのモジュールを管理する際には、とても有効に使うことができるのですが、複数のモデルを一つのモジュールで編集するには、苦労します。というか、そもそも、複数のモデルを一つのページで編集をさせるという設計がイマイチな感じがしますが、このsf_guard_user_profileを使用する以上は、しょうがないです。ということで、ゴリ押しです。

さて、プロジェクトやアプリケーションを作ったりするのは、ここでは説明しません。それらで躓いている人は、マニュアルを読んでください。もしくは20万円くらいで私が教えます。backendというアプリケーションがすでにあるということで、話を進めていきます。

さきほどのsf_guard_user_profileがある状態で propel:build-allをすると、lib/model/以下にsfGuardUserProfile(Peer|).php等のモデルクラスのファイルができていると思います。それを確認して、アドミンジェネレータを使用してみましょう。確認ですが、実際にアドミンジェネレーターで使用するモデルは、sfGuardUserProfileで、sfGuardUserではありません。

  1. $ ls
  2. apps/   config/  doc/  log/      symfony*  web/
  3. cache/  data/    lib/  plugins/  test/
  4.  
  5. $ ./symfony propel:init-admin backend user sfGuardUserProfile
  6. >> dir+      /home/shin/project/test/apps/backend/modules/user/config
  7. >> file+     /home/shin/project/test/apps/ba...dules/user/config/generator.yml
  8. >> dir+      /home/shin/project/test/apps/backend/modules/user/actions
  9. >> file+     /home/shin/project/test/apps/ba.../user/actions/actions.class.php
  10. >> tokens    /home/shin/project/test/apps/ba...dules/user/config/generator.yml
  11. >> tokens    /home/shin/project/test/apps/ba.../user/actions/actions.class.php

とすると、userモジュールが生成されて、デフォルトのCRUDができるようになりますね。画面のキャプチャを取ろうと思いましたが、面倒なので、想像で補ってください。生成されたgenerator.ymlは、次のようになっていますね。

  1. generator:
  2.   class:              sfPropelAdminGenerator
  3.   param:
  4.     model_class:      sfGuardUserProfile
  5.     theme:            default

そして、このgenerator.ymlをどんどん編集していきましょう。編集の仕方は、この辺を参照してください。

さて、アドミンジェネレータで生成された編集画面ですが、Userやら、Nameやら、Birthdayやら、Created atやら、Updated atの項目がありますが、このままでは、使えないです。というわけで、次のことをするとしましょう。

  1. ラベルを日本語化する
  2. created_at, updated_at, user_idとかがいらないので消す
  3. その代わり、sf_guard_userのusername, passowrd, is_activeを編集できるようする

ラベルを日本語化する

  1. generator:
  2.   class:              sfPropelAdminGenerator
  3.   param:
  4.     model_class:      sfGuardUserProfile
  5.     theme:            default
  6.  
  7.     fields:
  8.       name: { name: 名前 }
  9.       birthday: { name: 生年月日 }

fieldsの項目が増えただけです。編集可能なフィールドは、nameとbirthdayだけにしましょう。というわけで、その二つだけラベルをセットしました。

created_at, updated_at, user_idとかいらないので消す

  1. generator:
  2.   class:              sfPropelAdminGenerator
  3.   param:
  4.     model_class:      sfGuardUserProfile
  5.     theme:            default
  6.  
  7.     fields:
  8.       name: { name: 名前 }
  9.       birthday: { name: 生年月日 }
  10.  
  11.     list:
  12.       title: ユーザ一覧
  13.       display:
  14.         [ id, name ]
  15.       object_actions:
  16.         _edit: -
  17.         _delete: -
  18.  
  19.     edit:
  20.       title: ユーザ編集
  21.       display:
  22.         "基本情報": [ name ]
  23.         "詳細情報": [ birthday ]

とりあえず、sfGuardUserProfileで編集可能なフィールドは、nameとbirthdayだけですので、シンプルですね。

sf_guard_userのusername, passowrd, is_activeを編集できるようする

ユーザの情報では、usernameとpasswordを編集したいですよね?ついでに、is_activeも編集できるようにしましょう。つまり、有効ユーザか否かという項目です。さらにusernameは、メールアドレスの格納場所としましょう。本当はメールアドレスはemailとかmail_address等のフィールドとしたいところですが、しょうがないので、usernameをメールアドレスとして扱います。

まず、generator.ymlを修正してみます。

  1. generator:
  2.   class:              sfPropelAdminGenerator
  3.   param:
  4.     model_class:      sfGuardUserProfile
  5.     theme:            default
  6.  
  7.     fields:
  8.       email_address: { name: メールアドレス }
  9.       password: { name: パスワード }
  10.       name: { name: 名前 }
  11.       birthday: { name: 生年月日 }
  12.       is_active: { name: 有効ユーザ }
  13.  
  14.     list:
  15.       title: ユーザ一覧
  16.       display:
  17.         [ id, email_address, name, is_active ]
  18.       object_actions:
  19.         _edit: -
  20.         _delete: -
  21.  
  22.     edit:
  23.       title: ユーザ編集
  24.       display:
  25.         "基本情報": [ email_address, password, name ]
  26.         "詳細情報": [ birthday, is_active ]

fieldsのemail_address, password, is_activeにラベルを足しました。listの表示にemail_address, is_activeを足しました。editの編集表示にemail_address, is_activeを足しました。

さて、このままではエラーが出てしまいます。つまり、email_address, password, is_activeを持ってこれないのですね。私もここで少しはまってしまったのですが、アドミンジェネレータの生成するキャッシュファイルを見て、sfGuardUserProfileモデルのクラスに、usernameやpassword、is_activeのアクセサを作成すればいいことがわかりました。
中で、object_input_tagなどを使用しており、そこでゲッターメソッドを呼んでいましたので、実装します。

さらに、sfGuardUserProfileのインスタンスを保存する際に、ついでに、それに紐付けられたsfGuardUserのインスタンスも変更されたusername, password, is_activeを持って編集すればいいのだと理解しました。そして、それをsfGuardUserProfileモデルのクラスに実装します。

usernameは、メールアドレスとして使用するため、ちょっとだけかぶせてあります。

  1. class sfGuardUserProfile extends BasesfGuardUserProfile
  2. {
  3.     private $guardUser = null;
  4.  
  5.     public function getEmailAddress()
  6.     {
  7.         return $this->getUsername();
  8.     }
  9.  
  10.     public function setEmailAddress($value)
  11.     {
  12.         $this->setUsername($value);
  13.     }
  14.  
  15.     public function getIsActive()
  16.     {
  17.         return $this->getGuardUser()->getIsActive();
  18.     }
  19.  
  20.     public function setIsActive($value)
  21.     {
  22.         $this->getGuardUser()->setIsActive($value);
  23.     }
  24.  
  25.     public function getPassword()
  26.     {
  27.         return '';
  28.     }
  29.  
  30.     public function setPassword($value)
  31.     {
  32.         $this->getGuardUser()->setPassword($value);
  33.     }
  34.  
  35.     public function save($con = null)
  36.     {
  37.         if (is_null($con)) {
  38.             $con = Propel::getConnection();
  39.         }
  40.         try {
  41.             $con->begin();
  42.             $this->getGuardUser()->save($con);
  43.             $this->setUserId($this->getGuardUser()->getId());
  44.             parent::save($con);
  45.             $con->commit();
  46.         } catch (Exception $e) {
  47.             $con->rollback();
  48.             throw $e;
  49.         }
  50.     }
  51.  
  52.     private function getUsername()
  53.     {
  54.         return $this->getGuardUser()->getUsername();
  55.     }
  56.  
  57.     private function setUsername($value)
  58.     {
  59.         $this->getGuardUser()->setUsername($value);
  60.     }
  61.  
  62.     private function getGuardUser()
  63.     {
  64.         if ($this->guardUser) {
  65.             return $this->guardUser;
  66.         }
  67.         $this->guardUser = $this->getSfGuardUser();
  68.  
  69.         if (is_null($this->guardUser)) {
  70.             $this->guardUser = new sfGuardUser();
  71.         }
  72.  
  73.         return $this->guardUser;
  74.     }
  75. }

getPasswordが空文字列を返していますが、それはsfGuardPluginを使用するとデフォルトでは、復元できない形になってしまってしまうからです。管理者がパスワードが見れないという点ではいいのですが、どうするかは悩ましいところですね。平文で入れる方法もあるのですが、少々トリッキーなので、また別の機会に取り上げます。かもしれません。

はい。少々長いコードですね。最初の目的のコードを書かないようにするという目的からは少し反していますが、自分で組んだらもっと書かないといけないので、許してください。

これで、エラーがなく、編集が可能になりました。しかし、今度はメールアドレス、パスワード、有効ユーザの項目がdisabledになってしまっています。symfonyの中を見ると、sfCrudGenerator.class.phpで、CreoleTypesで見ているようです。そこで、disabledにされてしまっているので、簡単にはできなさそうでした。ということで、パーシャルの使用でゴリ押しします。userモジュールのactions, configなどのディレクトリのある階層にtemplatesディレクトリを作成して、_email_address.php, _password.php, _is_active.phpを作成します。
そして、それぞれのファイルに次のように書きます。
_email_address.php

  1. <?php
  2. $value = object_input_tag($sf_guard_user_profile, 'getEmailAddress', array (
  3.     'size' => 64,
  4.     'control_name' => 'sf_guard_user_profile[email_address]',
  5. ));
  6. echo $value ? $value : '&nbsp;';
  7. ?>

_password.php

  1. <?php
  2. $value = object_input_tag($sf_guard_user_profile, 'getPassword', array (
  3.     'size' => 64,
  4.     'control_name' => 'sf_guard_user_profile[password]',
  5. ));
  6. echo $value ? $value : '&nbsp;';
  7. ?>

_is_active.php

  1. $value = select_tag('sf_guard_user_profile[is_active]',
  2.     options_for_select(array(
  3.     '1' => '有効ユーザ',
  4.     '0' => '無効ユーザ'
  5.     ), (int)$sf_guard_user_profile->getIsActive())
  6. );
  7. echo $value ? $value : '&nbsp;';
  8. ?>

そして、generator.ymlでのeditの項目を、今作成したパーシャルで置き換えます。最終的に generator.ymlは以下のようになります。

  1. generator:
  2.   class:              sfPropelAdminGenerator
  3.   param:
  4.     model_class:      sfGuardUserProfile
  5.     theme:            default
  6.  
  7.     fields:
  8.       email_address: { name: メールアドレス }
  9.       password: { name: パスワード }
  10.       name: { name: 名前 }
  11.       birthday: { name: 生年月日 }
  12.       is_active: { name: 有効ユーザ }
  13.  
  14.     list:
  15.       title: ユーザ一覧
  16.       display:
  17.         [ id, email_address, name, is_active ]
  18.       object_actions:
  19.         _edit: -
  20.         _delete: -
  21.  
  22.     edit:
  23.       title: ユーザ編集
  24.       display:
  25.         "基本情報": [ _email_address, _password, name ]
  26.         "詳細情報": [ birthday, _is_active ]

これで、とりあえず完了です。sfGuardUserProfileを保存すると、sfGuardUserもそのトランザクション中で保存されます。もう一ひねりしたいところは、パスワードの編集なのですが、これは、作成する仕組みが管理者がパスワードが見えるべきか否か、といった議論になってしまいますので、それぞれのアプリで応用してみてください。

また、sfGuardPluginの方に付いてくるモジュールでは、permissionやgroup roleに関しても登録できるようになっていますが、力尽きましたので、いつか書くかもしれません。ここで書いたように基本は、同じです。かぶせて保存です。

さて、全然関係ないですが、symfonyのコーディングスタンダードには、ちょっと好きになれません。生成されるソースコードがスペース2つだったり、if文やwhile文にも中括弧が次の行にあったりすると、嫌で嫌でしょうがないです。クラスやメソッドの始まりなら次の行から中括弧があっていいんですけどね。

しかし、パート2まで書けるかな。疲れてきた。次の方がややこいし。

July 20, 2008

i love 開発会

18日から東京に来ています。今回は、21日にPHP Conferenceがあるとのことで、また、18日に東京で用事があったついで19日, 20日と開発会に参加しました。開催してくれたakkyさんとakiyanさんに感謝。

しかし、18日, 19日ってオープンソース関西が京都であったんですね。普段、京都にいるくせに、今回はそちらには不参加で、東京にいました。まぁ、縁がないものは縁がないんでね。

さて、開発会ですが、去年の12月から何度か参加させてもらっています。自分で主催したことはありませんが、akkyさんのAACamp、phaさんのもくもく会、そして今日このエントリを書いている場所を提供してくれたakiyanさんのヨセミテ開発会に参加しました。

最近は、ほぼ一人で開発をしているのですが、結構ダレてきてしまって、すぐ寝ころんでしまったり、ネットサーフィン(最近この言葉使わないね)をしてしまったりするんですね。しかし、こういう会があると、他の集中している人と一緒に緊張感を共有し集中することができて、重宝しています。

以前は図書館で開発をしていたときもあったのですが、開発会の方がいいと思うんですよね。というのも、図書館では、確かに周りに集中している人がいるのですが、開発をしているという雰囲気を共有できないからです。つまり、詰まってしまったときとか、誰にも話しかけることもできないので、結局一人となってしまいます。

しかし、開発会では同じような目的、つまり開発に来ているので、詰まったときにちょっと小話をしたりするなど、インフォーマルな会話ができるのです。このインフォーマルな会話が続いたら集中はできないと思うのですが、開発会に参加する人は、おしゃべりだけをしにきているわけではなく、開発に来ているので、それもありません。つまり、適度な量のインフォーマルな会話があるわけです。これが程よく気持ち良いです。

また、泊まり込みではなく、お酒で打ち上げるわけでもない緩い場というのもすごく気に入っています。お酒無しで解散というのが結構好きです。ときどきはお酒もあってもいいと思いますが、毎回だと疲れます。これは、旅行しているときもそうなのですが、私は、お酒無しで一緒にご飯を食べるような人間関係が好きなので、この緩い関係が激しく良いです。

というわけで、ここ1ヶ月で一番集中できているのではないか、というくらい進んで、ブログにその喜びを書いておきます。

ちょっと小ネタで、開発会で使っていたsfGuardPlugin周りのことでも書こうかしら。

July 6, 2008

スポーツジムで節約する

京都に住んで3ヶ月が経ちましたが、未だガス会社と契約しておりません。幸いなことにコンロは電気ですので、お湯を沸かして茶は飲むことができますので助かっていますが、シャワーは水です。暖かい地域に長期旅行をする際には、水シャワーしかない宿泊先が多くありますので、体がそれに馴染んでしまっているのかもしれません。ちなみに、電話も固定電話を引かずに、イー・モバイルを使用しているので、電話とガスの基本使用料はない状態です。携帯電話もほとんどかかってこないし、出ることも少ないので、解約してもいいと思っています。

また、昔から私はサウナと水風呂のコンビネーションが好きで、毎日に銭湯に通っていた時期もあります。しかし、銭湯代って結構バカにならないんですよね。京都の銭湯は、おそらく380円くらいだと思います。私が通っていたころは、330円でしたので、だいぶ高くなりましたね。毎日通おうとすると、約10000円かかるわけです。貧乏人には、これが辛いですね。風呂無しの安い場所に住んでも10000円銭湯代に払わなければいけないとなると、かなりの痛手になると思います。そのため私は、学部時代に、銭湯代がタダということで、2年間ほど銭湯に住んだこともあります。

さて、kotorikoさんに、「すでにムキムキなので、僕の方法で筋肉で遊ぶことができないね。」って言われるくらい、筋トレが好きな私は、4月から定住している京都で、ほぼ毎日スポーツジムに通っています。スポーツジムというと、お金がかかるとか言う人がいますが、うまいこと活用すればかなり安く通うことができると思っています。

だいたいスポーツジムに入ると、レギュラー会員やら、ナイト会員やら、デイタイム会員といった、使える時間を選べるオプションがあります。だいたい値段もこんな感じかな。また、もう少し細かにオプションがあるだろうし、値段もスポーツジムによって変わってきますが、一応、スポーツジムに4つくらい通った経験から、まぁこんなもんか、と言った感じです。

  • レギュラー会員:10000円(営業時間フルに使える)
  • ナイト会員:6000円(17:00 - 23:00に使える、週末に使えないときもある。)
  • デイタイム会員:7500円(10:00 - 17:00に使える、週末に使えないときもある。)

ちなみに日本のスポーツジムには、だいたいサウナが付いています。ちょっとラッキーだとジャグジーがあったり、湯船もちゃんとあって、水風呂もあります。アンラッキーですと、シャワーしかない場所があったり、老化していてカビ臭い場所もあります。風呂に入るためだけにナイト会員になっても、銭湯よりも安いですね。まぁ、スポーツジムは一週間に一度休みがあったり、ナイト会員は、週末使えなかったりする場合があるので、一概にそうとは言えませんが、毎日銭湯に行くぐらいなら、スポーツジムの方が安いでしょう。

私は、よくコストパフォーマンスを考えていると自分では考えています。お気に入りのレストランがあると通います。最近も野菜を中心に食べさせてくれるレストランが住まいの近くにあるので、毎日昼はそこで800円ランチ(本当は、1200円だが、特権割引で800円となっている。)を食べています。800円ランチが高いかどうかなのですが、単純に考えると高いです。毎日通うのもどうだろうというくらい高いです。しかし、野菜と惣菜とパンのビュッフェがあり、おいしいメインディッシュがあって、食後にコーヒーまで付くとなると、ありえないくらい安いと思います。こんなに安くていいの?っていうくらい安いです。コンビニでもドリンク買って、弁当買って、小腹が空くのを満たすためのお菓子を買ったりするとすぐに800円を越えてしまいます。しかも、なかなか野菜が採れないですよね。野菜好きな私は、この800円のランチのコストパフォーマンスは抜群だと思っています。

さて、スポーツジムの話に戻ります。私は、レギュラー会員になっていますので、そのスポーツジムの営業日であれば、営業時間内の好きな時間に行くことができます。旅行が長かったので、体も鈍っていましたが、だいぶ筋肉が戻ってきました。というのも、ほぼ毎日通っているんですね。人によっては、3時間とかいる人がいますが、私は、長くて1時間。短くて30分しか筋トレをしません。また、スタジオ等で全身の筋トレをするプログラムがあるのですが、私は、全身の筋トレを一日でやることは、否定派です。全身の筋トレをすると、次の日に筋トレする部位がなくなってしまうんですよね。カーディオエクセサイズであれば毎日でもいいですが、筋トレはダメだと思っています。というわけで、毎日通うためには、部位ごとに鍛えます。今日は、胸の日。明日は、背中の日。明後日は、足の日。のように分けています。基本的には、これを週に二回繰り替えして、一日は、調整日とします。腕などの小さな部位は面倒なのでやりません。本当はやった方がいいのでしょうが、胸を鍛えると三頭筋にも効きますし、背中を鍛えると二頭筋にも効きます。私くらいであれば、それで充分です。また、鍛えるのは好きですが、ボディビルダーになろうとかは考えていないので、そこまで神経質になる必要はないと思います。

そして、筋トレとは別に、一日の終わりがけに泳ぐために、もう一度スポーツジムに行きます。そして、サウナや水風呂に入って、体を洗って、帰ってきます。幸いなことに歩いていける距離にスポーツジムがあるため、こういう生活ができるんですね。というわけて、調子に乗ると一日2回スポーツジムに行くことがあるんですね。一日2回ジムに行けて、サウナも使えて、銭湯代わりになるとなれば、スポーツジムってすごく安いと思うんですよね。今の住まいでは風呂に入ることができないという縛りを設けているために、毎日スポーツジムに通わなければいけないというので、程よくモチベーションを保つことができます。

ところで、ここで、「一日に2回もスポーツジム行くなんて、コイツ馬鹿か?」と思われるかもしれませんが、先ほども書いたように、私の場合は、一回のトレーニングの時間が少ないため、実際には合計で一日1時間半をスポーツジムに割り当てているのですね。風呂の時間を込みで。ということは、相当短い時間なのです。

さらに、コストパフォーマンスを考えて、私はタオル会員というものになっています。月1000円で、バスタオルとボディタオルを貸してもらえます。また、これが高いか安いか、なのですが、一日2回行くことのある私にとっては、ありえないくらいくらい安いです。シューズやウェアも貸してくれるみたいですが、私はそれほど筋トレでは汗をかきませんので、風呂に入るためのタオルよりもは必要ではないと思っていますので、その契約はしていません。それに、タオルはいいけど、服やシューズは、ちょっと他人が使ったものを使いたくないですから。

こんなにスポーツジムを有効活用している私ですが、今月いっぱいでとりあえず退会することにしました。8月から2ヶ月間ほど、また海外に出る予定ですので、その間、会費を払うのがもったいないのです。本当は、新規で入ると新規加入費などを払わないといけないときがあるのですが、結構多くのスポーツジムがキャンペーンなどで新規加入費無料とかやっているんですね。それを使うことができれば、節約できますね。私も最初は、それで入りました。また、キャンペーン以外でも紹介で入ったら、新規加入費が無料となったり、紹介者と本人に商品券などをプレゼントするとかいった特典がよくありますよね。次に京都に戻ってきたときには、紹介で入る予定です。3ヶ月もスポーツジムに毎日通っていると、何人か常連さんと仲良くなりますので、こういうときに活躍してもらいます。

このスポーツジムでの節約術を使うと、メリットとしては、次のものが挙げられます。

  • ガス代が節約できる。
  • 銭湯代が節約できる。
  • シャンプー、ボディソープ代が節約できる。
  • 体を洗わないといけないので、スポーツジムに行く動機ができる。
  • 健康になれる。代謝も上がる。脂肪が減る。
  • ムキムキになれる。実際になるのは、かなりの努力と正しい方法が必要。

しかし、メリットだけではありません。デメリットとしては、次のものが挙げられます。

  • 誰かが泊まりにきたときに、風呂が使えない。
  • 忙しかったりして、スポーツジムに行けないと、体が臭くなる。
  • 好きな時間にシャワーを浴びたり、風呂に入ることができない。
  • いざとなったら水シャワー(夏はいいけど。。。男一人はいいけど。。。)

というわけで、銭湯好きで、水泳好きで、筋トレ好きで、生活が規則正しくて、一人暮らしであれば、スポーツジムは、すごくコストパフォーマンスがいいと思います。

京都の二条周辺で興味のある方がいれば、私が紹介してあげます。退会前、もしくは、復帰後であれば。

July 2, 2008

CoverFlowってイカしてるね。

この4月から私もマカーになったので、CoverFlowのインタフェースにも慣れてきた(と、言いつつほとんど仮想マシンのubuntu使っているけど)。また、iPodもCoverFlowなインタフェースを持っているので、ちょっと作ってみるのがいいんじゃない?ということで、簡単にFlexで作ってみた。

ネタは、写真をCoverFlowで表示するというもの。Flex CoverFlowで遊んでみた - l4lを参考にして、作ってみた。なんかsearchResultのループで回すところがなんか納得できなかったので、そこだけは自分で簡単にやってみた。

しかし、元ネタのサンプルFlex CoverFlow performance improvement, Flex Carousel Component, and Vertical CoverFlowを見ると、Carouselの方がイカしているじゃない?というわけでこちらを採用。Carouselという言葉の訳では、回転木馬らしいのだけども、「回転木馬」って言われてもしっくりこないので、とりあえずCoverFlowの亜種と言ったらいいのかしらん。

で、こんな感じ。例によって、写真は、私のホンジュラスのダイビングの成果。

サーバサイドのソースは、こう。単純にディレクトリの中のファイルを適当なXMLで出力しているだけ。

  1. <?php
  2. $dir = 'diving';
  3. $doc = new DomDocument('1.0', 'UTF-8');
  4. $doc->formatOutput = true;
  5.  
  6. $path = 'http://' . $_SERVER['HTTP_HOST'] . '/playgrounds/coverflow';
  7. if (is_dir($dir)) {
  8.     if ($dh = opendir($dir)) {
  9.         $root = $doc->createElement('images');
  10.         $root = $doc->appendChild($root);
  11.         while (($file = readdir($dh)) !== false) {
  12.             if ($file === '..' or $file === '.') {
  13.                 continue;
  14.             }
  15.             $image = $doc->createElement('image', "$path/$dir/$file");
  16.             $root->appendChild($image);
  17.         }
  18.         closedir($dh);
  19.     }
  20. }
  21. echo $doc->saveXML();

そして、Flex側のコードは、こんな感じ。Doug McCuneさんのswcファイルはインクルードしておく。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  3.     xmlns:coverflow="com.dougmccune.coverflow.*"
  4.     layout="vertical" horizontalAlign="center" verticalAlign="middle" width="600" height="400"
  5.     xmlns:containers="com.dougmccune.containers.*"
  6.       initialize="searchService.send()">
  7.     <mx:Script>
  8.         <![CDATA[
  9.             import mx.controls.Alert;
  10.             import mx.collections.XMLListCollection;
  11.             [SWF(frameRate="24")]
  12.             import mx.core.Container;
  13.             import mx.events.FlexEvent;
  14.             import mx.controls.Image;
  15.             import mx.rpc.events.ResultEvent
  16.             [Bindable]
  17.             private var serverURL:String = "images.php";
  18.             [Bindable]
  19.             private var xml:XMLListCollection;
  20.             private function getResult(event:ResultEvent):void
  21.             {
  22.                 xml = new XMLListCollection(XML(event.result)..image);
  23.                 coverflow.selectedIndex=int(xml.length/2);
  24.             }
  25.             private function test(event:Event):void
  26.             {
  27.                 mx.controls.Alert.show(event.target.parent.source);
  28.             }
  29.         ]]>
  30.     </mx:Script>
  31.     <mx:HTTPService id="searchService" url="{serverURL}" resultFormat="e4x" result="getResult(event)" />
  32.     <mx:VBox id="box" verticalGap="0" height="100%" width="100%" maxWidth="600" maxHeight="400">
  33.         <containers:CarouselContainer id="coverflow" width="100%" height="100%"
  34.             horizontalGap="10" borderStyle="solid" backgroundColor="0x000000"
  35.             reflectionEnabled="true" autoUpdateFlexMaterials="true">
  36.             <mx:Repeater id="rp" dataProvider="{xml}">
  37.                     <mx:Box name="p{rp.currentItem}" width="300" height="225" horizontalScrollPolicy="off" verticalScrollPolicy="off" paddingTop="20">
  38.                     <mx:Image horizontalAlign="center" name="img{rp.currentItem}"
  39.                          width="250" height="225" useHandCursor="true" buttonMode="true"
  40.                           source="{rp.currentItem}"  click="test(event)" />
  41.                     </mx:Box>
  42.             </mx:Repeater>
  43.         </containers:CarouselContainer>
  44.         </mx:VBox>
  45. </mx:Application