A Django site.
9月 21, 2008
» symfony1.1でswiftを使ってみた。

最近の開発は、ずっとsymfonyなので心中するしかないかなー、なんて思っていたりします。しかし、実際に使っていると、フレームワークではカバーしきれないコードを、ゴリゴリと、かっこ悪い方法で実装してしまったりして、嫌悪感いっぱいになってしまうことがあります。

その一つがメール周りです。symfony1.1からsfMailを使わない方向になりました(まぁ、使おうと思えば使えるけど)。そこで、代替とされているのが、Swift Mailerですね。直接SMTPが叩くので、速いだとか負荷が減るだとか、ほげほげだということで、swiftが採用されているようですが、個人的な感想を言えば、私はsfMailerで十分間に合っていたと思います。。。

とも言っていられないので、ちょっとswiftを使ってみました。でも、
How to send emails in symfony 1.1The symfony Cookbook How to send an emailを読んでもなんかしっくり来ないんですよね。理由は、これらのハウツーには、メールの本文をコントローラに書く方法しか提供していないからだと思います。確か前のsfMailerではViewテンプレートにメールの本文が書けたのですが、それってswiftでどうやったらいいのかなー、なんて思って調べてやってみました。まぁ、Jonathan Wageさんのアイデアなんだけどね。

というわけで、彼のアイデアをまとめると次のような感じか。
メールを使うアクションクラスをすべてsfSwiftMailerActionsの子クラスとする。
実際に呼び出す際には、親クラスの実装メソッドsendMailにモジュール名とアクション名を渡して、そのテンプレートに本文を書く。

  1. <?php
  2. class sfSwiftMailerActions extends sfActions
  3. {
  4.   public function preExecute()
  5.   {
  6.     $mailVars = sfSwiftMailerVars::getInstance()->getAll();
  7.  
  8.     foreach ($mailVars as $key => $value)
  9.     {
  10.       $this->$key = $value;
  11.     }
  12.   }
  13.  
  14.   public function sendEmail($module, $action)
  15.   {
  16.     sfConfig::set('symfony.view.' . $module . '_' . $action . '_layout', false);
  17.     $body = sfContext::getInstance()->getController()->getPresentationFor($module, $action);
  18.  
  19.     $mailVars = sfSwiftMailerVars::getInstance();
  20.  
  21.     $message = $mailVars->has('message')
  22.       ? $mailVars->get('message')
  23.       : new Swift_Message(null, null, 'text/html');
  24.     $swift = $mailVars->has('swift')
  25.       ? $mailVars->get('swift')
  26.       : new Swift(new Swift_Connection_Sendmail(Swift_Connection_Sendmail::AUTO_DETECT));
  27.  
  28.     $message->setSubject($mailVars->get('subject'));
  29.     $message->setBody($body);
  30.  
  31.     $swift->send($message, $mailVars->get('recipients'), $mailVars->get('from'));
  32.     $swift->disconnect();
  33.  
  34.     $mailVars->clear();
  35.   }
  36.  
  37.   public function __set($key, $value)
  38.   {
  39.     sfSwiftMailerVars::getInstance()->set($key, $value);
  40.  
  41.     return parent::__set($key, $value);
  42.   }
  43.  
  44. }
  45.  
  46. class sfSwiftMailerVars extends sfParameterHolder
  47. {
  48.   static $instance = null;
  49.  
  50.   public static function getInstance()
  51.   {
  52.     if (!self::$instance)
  53.     {
  54.       self::$instance = new sfSwiftMailerVars();
  55.     }
  56.  
  57.     return self::$instance;
  58.   }
  59.  
  60. }

ふむ。なるほど。テンプレートの内容を取るだけななら

  1. $body = sfContext::getInstance()->getController()->getPresentationFor($module, $action);

でできてしまうのですね。

私が手元で実装したものでは、もう少し劣化させて、上記とは違って、SwiftクラスやSwift_Messageクラスは直書きでやってしまっています。まぁ、デフォルトの挙動なんていらないと思うので。というわけで、このソースはテストはしていません。ほぼ同じものを手元で書いて、その動作は確認しているので、だいたいのロジックはこれでいいようです。

あとは、実際のアクションクラスにこんな感じで書けばいいのですね。

  1. class hogeActions extends sfSwiftMailerActions
  2. {
  3.   public function executeEdit($request)
  4.   {    $swift->send($message, $mailVars->get('recipients'), $mailVars->get('from'));
  5.     $this->recipients = 'example@ganchiku.com';
  6.     $this->from = 'example@ganchiku.com';
  7.     $this->subject = 'hello world';
  8.     $this->sendEmail('hoge', 'confirm');
  9.   }
  10.  
  11.   public function executeConfirm($request)
  12.   {
  13.   }
  14. }

で、あとは、confirmSuccess.phpに本文を適当に書けばいいのですね。ふーむ。確かに、すっきりはしますが、結構面倒ですね。。。もっといい方法があったら教えてください。

9月 2, 2008
» 日系二世はドラマチックな人が多い。

ハワイに来ている目的は、日系人たちの昔の生活や文化などをなんかしらの方法で、デジタル化して残していこうということなんですね。すでに移民の一世は全滅しているのですが、二世はかろうじて生きています。だいたい80歳オーバーなので、おそらくあと10年経ったら、一気に減るだろうなー。

リリウオカラニ公園今、私がいるハワイ島のヒロというところは、この日系移民が多かった場所で、今でも多くの日系人が住んでいる。右の写真は、ヒロにあるリリウオカラニ公園の鳥居とマウナケアが写っているもの(ちなみ私はよくこの公園の周りをジョギングしている)。実際は、若い日系人は、すでに本土に移住してしまったりしていることが多いのだけども、おじいちゃんやおばあちゃんは日系人がいっぱいいる。そして、太平洋戦争のときに関係があったのが、二世もしくは、一世なんですね。一世は、日本人なので、そのまま強制就労所に入れられるわけだが、二世はすごく複雑な状況になったようだ。実際は、アメリカ側に付いて、最初はヨーロッパにアメリカ軍として参加することになるのだが、終戦間際には、日本にも行っている。ただ、この時期に日本に帰っている移民の家族もいたわけだ。そして、彼らは日本軍に付くことになる。また、戦後にマッカッサーと昭和天皇の間に入ってやりとりしていたのも、日系二世だったりするわけだ。日系二世は、日本語学校で教育を受けていたので、教育勅語を習っているわけ。そんな人たちなので、日本がなんとか残る方向で努力をしたようだ。

しかしながら、私たちの歴史教育では誰もこんなことを学ばないよね。誰も話したがらないし、私も知らなかった。ということで、戦争のみならずだけど、日系二世の文化や生活を残すという活動をしているわけ。
私とルース
右の写真に写っているのも私と日系二世のおばあちゃん。なんでも、ハワイでコンサートをしていた美空ひばりの伴奏をしていたとか。

私は歴史学者でもなんでもないのだけど、今生きている二世の話を聞く機会があるので、とても勉強になっている。

さて、件名に書いたように日系二世にはドラマチックな人生を歩んだ人が多い。もちろん、それは太平洋戦争が関係がある。最近、映画の主人公になれるんじゃないか、と思われる人生を歩んだ二人会ったので書いてみることにする。

一人は、日系二世として、ハワイで生まれたが、戦争前に帰国しており、その後、広島で育って、妹は原爆で死亡し、自分は神風特攻隊になったが、終戦したため、実際に行くことはなかった人。そして、彼は最後の日系移民として、またハワイに戻ることになった。日本軍になっていたため、アメリカの市民権は取り上げられていたし、今でもアメリカで語り継がれる神風の一員だったので、そんな人がアメリカに来て、いろいろ最初は大変だったに違いない。神風の一員で、その後、アメリカに移住したなんて彼くらいしかいないんじゃないかな。しかも、まだまだ元気。実は、アメリカに帰ってきてからもドラマがあるらしいのだけど、詳しくは聞いていない。その彼は自伝のような書籍を出すようだ。私の手元にもドラフトがある。

もう一人は、彼女も日系二世として、ハワイに生まれたが、同じく戦争前に帰国しており、英語と候文ができるため、戦争中は、英語の情報を候文に翻訳していた人。読む人は、東条英機だったと。東条英機は、候文しか書いたり、読んだりしなかったらしい。戦後は、逆にマッカーサーへの文書の翻訳をしていたそうだ。マジっすか。なんだか歴史の授業のようなんだけど。。。彼女の場合は、軍隊に入っていたわけではないので、その後、アメリカに戻ることができたようだ。彼女の話では、戦争中は牛乳はタダで手に入ったらしい。戦争中は、東京に住んでいたそうなのだが、大使館に牛乳を売る店があったようなのだけど、日本人は牛乳を飲まないので、店に行ってタダでもらっていたそうだ。今、聞くとマジっすか!といった内容がいろいろある。

まぁ、こんな感じですごく不思議なアメリカにいます。私の今まで知っていたアメリカとは大違いだ。朝のマクドナルドでは、日系二世のおじいちゃんやおばあちゃんがいっぱいコーヒーを飲みにくるらしい。シニアプライスで60セントだからね。なんか不思議な感じ。私も日系二世のおばあちゃんにマクドナルドおごってもらいました。マックチキンとサイドサラダとコーヒーとサンデーで$4を切るという不思議な値段。コーヒーはシニアプライスだから。

何人かは私にアメリカに移住すればいい、と言ってくれているけど、したくてもアメリカは受け入れてくれないですから!

しかし、ハワイに来て一度も泳いでいない。ダイビングも一度だけ。。。日本にいたときは、毎日泳いでいたのに。。。あと一ヶ月ハワイにいます。免許も取ろうかしら。ハワイは免許を持っていれば、ローカルプライスでいろいろ安くなるので。

8月 9, 2008
» よき人たれ

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

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

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

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

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

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

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

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

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

8月 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という二つのテーブルに保存することができるようになります。

ふー。疲れました。

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

7月 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まで書けるかな。疲れてきた。次の方がややこいし。

7月 20, 2008
» i love 開発会

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

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

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

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

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

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

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

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

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

7月 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時間半をスポーツジムに割り当てているのですね。風呂の時間を込みで。ということは、相当短い時間なのです。

さらに、コストパフォーマンスを考えて、私