ひとりハッカソンでCakePHP+TwitterAPIアプリ

posted 2010-10-07 | written by mon_sat

@mon_sat です。
PHP Matsuri でのハッカソンネタ「愛の告白 Power of Love」ができるまでをチュートリアル形式で。
(チュートリアルとしては詳細が書かれていないのですが・・・)

まずは、何を作るか考えた

さて。ハッカソン。
何を作ったら良いかというところを考える。
過去作ったやつをCakePHPで修正したい気持ちは満々なのですが、せっかくなので1から作りたいですよね。

そこで、作りたいものリストの中からハッカソン期間中に作れそうなものをピックアップし、最終的に「告白」をするアプリに決めました。
これは、単に(このアプリ上から)「つぶやくと、一定期間次のつぶやきができなくなる」というTwitterアプリです。
アプリ名を「愛の告白」とすれば、送られた側は、そのアプリ名を認知することで、ああ真面目な告白なんだと分かるというもの。

ま、基本的にはネタですよね。

ですが、一応以下の選考基準に則って選びました。

  • ハッカソンの期間中に「完成」すること
  • 分かりやすいこと
  • 仕様がある程度はっきりしていること

今回はあくまで完成にこだわりました。
中途半端なものをつくっても、その後完成させるのは、私の性格では無理なので、ハッカソンのみで完成させることを第一に考えました。
多少機能は少なくても良いと割り切るしかありません。

次に「何をするアプリで」「何が嬉しいか(楽しいか)」が伝わりやすいものにしました。
どういう形式で発表するかは分かりませんでしたが、最悪140文字で紹介できるような単純なものを作ろうと思いました。

最後に、難易度の高いものは控え、過去の経験と現在ある知識で製作可能なものにしました。
そのため、サービス内容が固まっていて、仕様を固めやすいものにしました。
結果的に残ったのは一つでしたが。
 

仕様を決める

サービスの仕様を固めなくてはなりません。
あまり大風呂敷を広げすぎるとたためなくなるのはいつものこと。今回ばかりは多少時間が余る程度のものを検討します。

まず、サービスの核として

  • TwitterのOAuthを利用してログイン
  • TwitterAPIを利用して投稿
  • 投稿時に「次につぶやくまでの時間」を設定しDBへ保存
  • 上記の期間内に投稿されたらバリデーションエラー

ここまでを決めてました。

その後、あったら嬉しいor面白いものおよびやりたいことを列挙します
あったら嬉しいor面白いもの

  • つぶやきは吹き出し風に表示したい
  • フォローしている人を選択できるようにしたい
  • バリデーションエラーを凝ってみたい

やりたいこと

  • CSS3使って(意味も無く)アニメーションさせたい
  • Ktai Library 使って(意味も無く)ケータイ対応させたい
  • 国際化してコアデベロッパーにも使ってもらいたい

だいたいこんなところでしょうか。
この時点では、モデルの数もビューの数もぼんやりとしかありません。
その辺は作りながらで平気なボリュームなので、今は決めないのが、オレ流。(良い子はまねしないほうが良いです)

クイズです。さて、次にしたことは何でしょう?

正解は、プレゼンの内容を考える、です。(誰も当たらないな、この俺俺クイズw)
えっと、今回の場合、ハッカソンのためのアプリという面があり、さらに、当面のゴールは発表です。
発表の方法はおそらくプレゼンでしょう。
というわけで、真面目にネタアプリを作るためには、ゴールであるプレゼンを意識しようと思いました。
(ちなみにこの時点では10分以上はあるだろうという能天気なことを考えていましたw)

前回のイメージがあるので、コアデベロッパーも聞いてくれているという認識であれこれ想像してみました。
実はこれを考えているときが一番楽しく、3日くらい帰宅時にはこればっかり考えていました。
で、大体こんな感じ

  • コアデベロッパーいるから資料は英語で作ろう。もちろん話すのは無理。
  • デモも英語版を使おう。
  • デモはiPhone Simulator でやるのはどうか?
  • KeyNoteとかいうおしゃれなやつは持ってないから、最近使ってるNerineでいこう。
  • 最初にポストするのは何にしよう?嫁の名前をつぶやいて、有名人の名前でバリデーションエラーを起こそうか。
    • その場合(インパクトがあるのは)誰だろう?AKB48とか?広瀬香美とか?マツコデラックスとか?
    • 会場にいる女性からというのも良いかな?でも仲の良い子がいないなあ。
    • I Love PHP っていいかもね。みんなPHPerな訳だし。
      • その場合は I Love Perl かな?@dankogai宛につぶやくとか(笑)
  • ハッカソンなのに「ガラケー対応しています。誰得だけど」とか良いかも。
  • バリデーションエラー時は「愛が足りません」みたいな感じが良いかも。

あれ?後から考えると別に面白くないね。
でもまあ、つまり、できる限り小ネタを入れていこうと。

まあこの辺はあまり仕様に影響しませんが、ただ、開発の優先順位は変わりますよね。
最後何か対応できないってなったときに、何から削っていくのか。そのとき、プレゼンで影響の薄いところやサービスの核ではないところは切る覚悟ができます。

そして開発へ

いろいろ考えて開発は事前に済まそうと考えました。
理由は

  • セッションを聞きたい
  • コアデベロッパーとたっぷり交流したい
  • 人がいても開発できるような集中力は無い(いつもひとり)
  • 酒飲んだら寝る。確実に
  • スタッフ業務手伝えてないから、当日ぐらいできることしたい

というわけで、9/29(水)の10~18時で作ることに。
1日で終われば良いが、8時間弱はタイトなので、最悪10/1(金)の10~18時を使おうということで。
合計16時間なので、まあぎりぎりハッカソン中につくるのと一緒ですから(笑)

その模様はTwitterでハッシュタグつけてつぶやいていました。
まとめはこちら

というわけでスタートです。

まずは、Twitterでアプリの登録をします。

そして華麗にConsumer Key とConsumer Secret をゲット。

まずはtwitterでConsumer key & secret をget #phpmatsuri
http://twitter.com/mon_sat/status/25839176701

Twitter API か OAuth用のプラグイン等を探します。教えてグーグル先生!(今からかい!と自分で突っ込み)
過去Google AppEngine / Python にて、簡単なTwitterアプリは作ったことがあります。
そのときの、OAuthとTwitter API の知識があるとはいえ、結構楽観的です。(笑)

というわけで、でてきたのがこちら。OAuth Consumer Component

(ただ、PHP Matsuri で@nojimage さんがTwitterプラグイン作ってたことを知りました。Twitterで聞いたらそれを使えたかも)

事例はあるので、さくっと共用のvendorsディレクトリへ放り込みます。シンボリックリンクでapp/vendors内にリンクを作成します。
(cake/vendorsやapp/vendors内に直接DLで普通は何の問題もありません。ただ私の場合こうすることでシンボリックリンクごとgitで管理できるのでこうしています。ローカルもサーバーも同じディレクトリ構成にして)

で、アクセストークンを取得・・・取得・・・取得、できない。

AccessToken が取得できん・・・ #phpmatsuri
http://twitter.com/mon_sat/status/25845347460

最初はリクエストトークンが返ってこなかった。修正後もいろいろ試行錯誤したが、どうやってもアクセストークンが取得できない。
リクエストトークンは取得されている。コールバックまで返ってきている。
何だ?
というわけでソース見て、返ってきたjsonを表示させつつ、試行錯誤。

結局、駄目だったのはURL。
サンプルコードからtwitter APIのURLが変更されている主に、httpsのところとapi.twitter.comのところ。
この時点で、URLはbootstrap.phpにすべて書こうということで、変更した。

# config/bootstrap.php
Configure::write('tw' ,array(
  'request_token' => "https://api.twitter.com/oauth/request_token",
  'authorize' => "https://api.twitter.com/oauth/authorize?oauth_token=",
  'access_token' => "https://api.twitter.com/oauth/access_token",
  'post' => "http://api.twitter.com/1/statuses/update.json",
  'verify_credentials' => "http://api.twitter.com/1/account/verify_credentials.json",
));

開始から2時間後、無事アクセストークンをゲット。
しかし、ポストできない。

AccessToken 取得できた。が、OAuthConsumer Component で post しても Incorrect Signature となる。あれ? #phpmatsuri
http://twitter.com/mon_sat/status/25848412301

その後、試行錯誤してポストできた。
原因はやっぱりURL(だったと思う。記憶が...)

@mon_sat ‚ł«‚½IURLˆá‚Ă½Borz #phpmatsuri
http://twitter.com/mon_sat/status/25849669884

えっと、ポストできたけど(笑)

というわけで、原因を究明する。
この時点でひとりのフォロワーからBOFではないかというありがたい助言が入る。
しかし、BOFが何のことだか分からない。BOMやEOFではないらしい。が、いずれにせよ文字コード周りと当たりを付けて調査するも不発。
そして、TwitterAPIのエラーの内容が、送信する内容によって、Incorrect Signatureだったり、別のエラーだったりするという摩訶不思議。

結果的にはファイルの文字コードを見たらANSIとか書いてあった。なぜ?
日本語なんてほとんど使ってなかったが、とりあえず使ってるファイルをUTF-8で片っ端から保存し直して無事ポスト成功。

@directsearchjp あいうえお
http://twitter.com/mon_sat/status/25853121710

開始から早3時間20分経過。まずい。

そして昼飯後再開。

再開 #phpmatsuri
http://twitter.com/mon_sat/status/25854634041

Bake all

いよいよbakeするかということでまずはDBを作成。
その後に作ったschema.phpより抜粋

# config/schema/schema.php
var $posts = array(
  'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
  'user_id' => array('type' => 'integer', 'null' => false, 'default' => NULL),
  'post' => array('type' => 'text', 'null' => false, 'default' => NULL, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'date' => array('type' => 'datetime', 'null' => false, 'default' => NULL),
  'power' => array('type' => 'integer', 'null' => false, 'default' => NULL),
  'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL),
  'modified' => array('type' => 'datetime', 'null' => false, 'default' => NULL),
  'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)),
  'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
);
var $users = array(
  'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
  'twid' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 128, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'), 'twname' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 128, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'image' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 1024, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
  'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL),
  'modified' => array('type' => 'datetime', 'null' => false, 'default' => NULL),
  'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)),
  'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
);

公開サーバー用にスキーマファイルを生成

$ ./cake schema generate

そしていよいよbake x 2。

$ ./cake bake all
Welcome to CakePHP v1.3.4 Console
---------------------------------------------------------------
App : app
Path: /home/example/allapps/love.tklabo.net/local/app
---------------------------------------------------------------
---------------------------------------------------------------
Bake All
---------------------------------------------------------------
Possible Models based on your current database:
1. Post
2. User

この辺はさくっといけますね。

そして不要なアクションを削ります。
使うのは、Postのindex,view,addのみ。(adminおよびtwitterコールバック等をのぞく)
UsersControllerは、admin用ならいるかもしれませんが、ユーザー用には不要ということで、すべてをPostsControllerで作成することにしました。

この辺り、モデルも含めて、というかモデルですが、いろいろ試行錯誤しています。
後からフィールド名失敗したなあとか思うこともあるけど、まあ適当に。リファクタリング等も含めて今はやらない。

今回は、addアクションから作り始めました。
まずは骨格ということで、通常のバリデーションとして、postの文字数チェックと、powerが数値かどうかのみをチェックします。
この段階で、国際化の準備をする必要があることを思い出しました。
すべての書き出しは __() 関数を使っているのですが、バリデーションメッセージはこの書き方ができません。
というわけで、CakePlusの出番です。

このプラグインは複数の機能が乗っていますが、この中から、マルチバイトで文字数をチェックしてくれるバリデーション機能を追加しているAddValidationRuleBehaviorと、バリデーションメッセージを国際化可能なValidationErrorI18nBehaviorを使います。

わたしの共用のプラグインディレクトリにはすでにCakePlusがあるので、あとはシンボリックリンクをapp/pluginsに貼るだけです。

# model/post.php
var $actsAs = array(
  'Cakeplus.AddValidationRule',
  'Cakeplus.ValidationErrorI18n' => array( 'withFieldName' => false ),
);
var $validate = array(
  'post' => array(
    'maxLengthJP140' => array(
      'rule' => array('maxLengthJP', 140),
      'required' => true,
      'allowEmpty' => false,
    ),
//(中略)
function beforeValidate() {
  $messages = array(
    'maxLengthJP140' => __("Love words are always short. Please enter strings within 100 characters", true),
    'numericSelected' => __("Please select one", true),
  );
  $this->setErrorMessageI18n($messages, false);
  $this->replaceValidationErrorMessagesI18n();
  return parent::beforeValidate();
}

無事バリデーションができたので、動作を確認します。
(ただしこの段階ではDBに書き込まないし、当然Twitterにも投稿されません)
その後Twitterへの投稿とDB保存のロジックを加えました。
この時点のaddアクションはこんな感じ(だったはず)です。

# controllers/posts_controller.php
function add() {
  if (!empty($this->data)) {
    // set
    $this->Post->setPost($this->data);
    if ($this->Post->validates() && $result = $this->Love->post()) {
      $this->Post->data["Post"]["date"] = date("Y-m-d H:i:s", strtotime($result->created_at));
      $this->Post->save();
      $this->Session->setFlash(__('Your loving tweet has been sent', true));
      $this->redirect(array('action' => 'view', $this->Session->read("User.twid")));
    } else {
      $this->Session->setFlash(__('Your tweet could not be sent. Check your love. Please, try again.', true));
    }
  } else {
    $this->data["Post"]["status"] = $this->Love->readSessionIfExist();
  }
  $powers = $this->Post->powers();
  $this->set(compact('powers'));
  $this->set('title_for_layout', " | Tweet");
}
# models/post.php
function setPost($data) {
  // format
  $post =& $data["Post"]["post"];
  $post = $this->stringBeforePost . $post . $this->stringAfterPost($data["Post"]["power"]);
  parent::set($data);
}

setの前に、つぶやきの前後に必要な文字列を加えています。
これによりそれらを加えた文字数でバリデーションがかかるので便利ですね。
(最終的にはこの段階で、Bit.lyのAPIより短縮URLを取得し加えていますが)

$this->Love->Post()は、LoveComponent内でTwitterの送信をしていて、エラーの場合はfalseを正常に送れている場合はjsonの戻り値を返しています。

次の行で、TwitterAPIの返した投稿日時をDBに保存するべくモデル内のデータに書き入れています。
(ここには書かれていませんが、Userモデルの処理も後で追加されます)

メソッドがPOSTではないとき(つまりaddアクションをはじめて読み出すとき)の処理ですが。
これは、投稿した結果、TwitterAPIより認証エラーが返ってきた場合に、再ログインさせてからもう一度addアクションに帰ってくるために入れています。
再ログインする前にセッションに投稿した内容を書き入れて、addアクションに戻ってきたときに書き出しています。
※今考えたら、powerフィールドの値は保存していませんね。これ

初日にやったのはあとは画面を作ったくらいです。
画面は過去に作った別件のお遊びのやつを流用しました。
(後で修正した分も上記に追加されていますので、本当はもうちょっと細部は手を入れる必要がありました)

@directsearchjp test3 #ainokokuhaku [Power of Love: 2,592,000p]
http://twitter.com/mon_sat/status/25863635878

ひとりハッカソン終了。進捗40%・・・続きは金曜日 #phpmatsuri
http://twitter.com/mon_sat/status/25866576200

開発2日目

今日も一昨日のひとりハッカソンの続き。18時迄。合計16時間で何が作れるかワクワクすっぞ。 #phpmatsuri
http://twitter.com/mon_sat/status/26032504445

bit.ly で短縮URLを取得してツイートに混ぜる工程から。
例によって共用vendorsディレクトリにコードを突っ込み、シンボリックリンクを貼ります。
http://planetcakephp.org/aggregator/items/2649-new-bitly-class-to-use-with-cakephp-or-by-it-self

# controllers/components/love.php
function shortenUrlFromPostView() {
  $this->verifyCredentials();
  $twid = $this->Session->read("User.twid");
  if (!$this->Session->read("User.short_url")) {
    $long_url = Router::url(array('controller'=>"posts", 'action'=>"view", $twid), true);
    App::import('Vendor','Bitly', array("file" => "bitly/bitly.class.php"));
    $bitly = new Bitly(BITLY_USER, BITLY_API_KEY);
    $short_url = $bitly->shorten($long_url);
    $this->Session->write('User.short_url', strval($short_url[0]));
  }
  return $this->Session->read("User.short_url");
}

セッションに書き込まれていなかったらBit.lyより取得し、書き込まれていたらそれを読み出すということにしました。
今回は、ユーザーページのみなので、作成が楽です。
addアクションでPOST後の先頭で上記のメソッドを呼び出して終了です。
1時間弱で実装完了しました。

bit.ly API で短縮URLゲット #phpmatsuri
http://twitter.com/mon_sat/status/26036306558

@directsearchjp test #ainokokuhaku [Power of Love: 60p] bit.ly/bdTNGr
http://twitter.com/mon_sat/status/26037339525

Routingを設定。

# config/routes.php
Router::connect('/', array('controller' => 'posts', 'action' => 'index'));
Router::connect('/logout', array('controller' => 'posts', 'action' => 'logout'));
Router::connect('/add', array('controller' => 'posts', 'action' => 'add'));
Router::connect('/oauth/callback', array('controller' => 'posts', 'action' => 'callback'));
Router::connect('/a/*', array('controller' => 'posts', 'action' => 'view'));

もちろんテストも設定。
(でも今回書いたテストはRoutingだけだなあ。反省)

Userモデルに手を入れます。

流れだけを書くと。
ログインした時点で、Userデータを保存します。
Updateになる場合は、画像URLの変更有無だけチェックし、変更されている場合はupdateします。
(その方がログインするまで古いアイコンが表示され続けます)
そして、つぶやきの投稿時にそれらのデータを読み出し、$this->Post->data['Post']['user_id']等に格納します。

重複エラーも実装しなくてはいけません。

重複エラーは、独自バリデーションで実装します。

# models/post.php
function isDuplicate() {
  $conditions = array('user_id'=>$this->data["User"]["id"]);
  $recursive = -1;
  $recent = $this->find("first", compact("conditions", "recursive"));
  return time() > strtotime($recent["Post"]["date"]) + $recent["Post"]["power"];
}

あとは、基本的にバグ修正でした。
ログイン後のリダイレクトがループして上手く行かなかったり、addアクションにリダイレクトさせると、もう一回ログイン処理が走ったりと、ロジックの見直しを繰り返しました。

あとは、時間までデザインを修正します。
CSS3のグラデーションやつぶやきを吹き出し風にするのに苦労しました。
後者は結局できませんでしたが。

あと、バリデーションメッセージも変更しました。
バリデーションエラー時に「愛を確認してください」「愛が重複しています」という表現を思いつき、それを実装しました。

というわけで2日目終了。

アプリとしては16時間で完成しました。
イベント当日は、手が空いたら、デザインの修正等を行うということで、この日は作業終了です。

実装できなかった機能

  • フォローしている人を選択できるようにしたい
  • Ktai Library 使って(意味も無く)ケータイ対応させたい

うーん、いつもながら工数を見積もるのが下手だなあ。

当日

この日の開発時点と完成物との差異は、主に国際化とデザインのところです。

国際化

CakePHPは簡単に国際化ができますね。
注意点としては、国際化ファイル(.po)の作成を単純化するのであれば、ある程度サイトが固まってから(書き出すテキストがおおよそ決まってから)の方が良いということですね。
まずは、default.potの作成です。各言語ファイルの元になります。
その後、日本語用のディレクトリを作成し、ファイルをコピーします。

$ ./cake i18n
$ mkdir -p app/locale/jpn/LC_MESSAGES
$ cd app/locale/jpn/LC_MESSAGES
$ cp -p ../../default.pot ./default.po
$ vi default.po

poファイルを編集後、サイトにアクセスすると日本語になっているはずです。
このままでは、チェック等が大変なので、英語と日本語を交互に切替可能にしましょう。
まず、以下のアクションを作成し、フッターにそれぞれのリンクをつけました。

# posts_controller.php
function japanese() {
  $this->Session->write('love.language', "jpn");
  $this->redirect($this->Session->read('love.redirect_url'));
}
function language() {
  $this->Session->delete('love.language');
  $this->redirect($this->Session->read('love.redirect_url'));
}
function english() {
  $this->Session->write('love.language', "eng");
  $this->redirect($this->Session->read('love.redirect_url'));
}
# app_controller.php
function beforeFilter() {
  if ($lng = $this->Session->read('love.language')) {
    Configure::write('Config.language',$lng);
  }
}

今回は/posts/languageへのリンクは貼りませんでした。

デザイン

自分自身への課題としてHTML5のバリデーションとCSS3 animationを使ってみようというテーマを持っていました。

まずは、HTML5から。
これはまず、required属性で、つぶやき入力が入力されていなかったら、投稿できないようにしました。
そして、placeholder属性で、入力初期値を規定しました。
それぞれ、コードは次の通りです。

# views/themed/pc/posts/add.ctp
echo $this->Form->input('post', aa('placeholder', __("ex) I Love @mon_sat", true),'required',"required"));

Textareaにplaceholderが使えるかどうかは分かりませんでしたが、Google Chrome ではあっさり使えました。
(他のブラウザは未確認です)

また、投稿後、TwitterAPIが結果を返すまでに数秒かかるので、何らかの方法でSubmitボタンを押されないようにしたいと考えました。
オーバーレイするjQueryプラグインを過去に使ったことがあるので試みましたが、そのプラグインは使えませんでした。
時間もないので、とりあえずdisabledすることで対応。
こういうのも、HTML5で対応して欲しいところですねー。

他は例の無駄にふわふわしているやつですが、それはプレゼン資料に書いてありますのでご参考に。
というわけで完成です。

テスト

自分自身では何度もテストしたのですが、できれば自分以外の投稿が載っていた方が見栄えが良いかなと思い、夜、Grahamを捕まえてテストしてもらいました。

I love #phpmatsuri #ainokokuhaku [Power of Love: 300p] http://bit.ly/bVOVmQ
http://twitter.com/predominant/status/26169095412

というわけで完成です。
Ktai Library 対応とか他にも対応したかったアイディアはあるのですが、そんなことよりも、交流をしようと。
そのために事前に作ったのです。
おかげさまで楽しい夜が過ごせました。

プレゼン

プレゼンの模様は別の記事で書きました。
なので、別の観点より。

わたしは、作ったものを上手にプレゼンすることが苦手です。
そんなわたしが出会ったのが、踊る大捜査線シリーズの監督として有名な本広克行監督のこのつぶやき

> トミ子さんへ、一生懸命に創ったんだから一生懸命に売らないとね‼ (後略)
http://twitter.com/kmotohiro/status/22140801650

わたしはこの言葉にしびれました。
作って終わりではないのですね。作って、それを周知させて(ビジネスなら売り上げを立てて)はじめて完成なのです。
とかく魂込めて作っていると、完成した段階で、魂抜けます(笑)
でも、そこまでで半分なのです。

というわけで今回は。
満足感溢れる自分にむち打って、どうプレゼンするかを考えました。
どう頑張ってもしゃべりは上手くないので、内容を練ります。ひとり作戦会議です。
プレゼン内容は概ね固まっているので、いかにインパクトを与えるかを考えてみました。

その結果。

  • デモ中心。説明は極力省く。
  • 時間が余ったら解説。
  • ということで行こうと。

というのも、技術的に大したことをしていることは無いので、そこはアピールポイントではありません。
ネタで勝負というほど、爆笑をかっさらうネタでもありません。

なので「完成したアプリ」であることを最大限に利用したいと思いました。

その後時間が4分しかないことが分かり、結局デモするだけのプレゼンに(笑)

終わってから考えたこと。

ちゃんと、出る前に、URLをつぶやいたにもかかわらず、その旨を言わなかったのがまず失敗。
そこにアクセスしてくれれば、完成したアプリであるということは自ずと分かってくれたのに。

そして、「最後にこれを言おう」を決めていなかったのが失敗。

それだけ言えれば満足というような何かを言うべきだった。
気づいたらあと20秒弱という状況でしたが、事前に準備していれば話ができたはず。

もうひとつ。
デモでつぶやくときに、ハッシュタグをつけてつぶやかなかったのは最低。
投稿後「つぶやかれているのが確認できる」という言葉も無かった。
とっさに出た「この機会にフォローしてw」で軽く笑いをとれ救われたものの、痛恨のエラーでした。

他の人のプレゼンは参考になるものばかりだったのですが、何よりのけぞったのは@takamunetaniiさんのやつ。
なんと通信が失敗したときに自動的に笑いが取れるようになっていた。
うーん、これは準備の賜物ですねえ。
例えばTwitterAPIが想定外のエラーを返したときとかのバリデーションメッセージは準備しておくべきだったかも。くじらねたの何かとか。

さて、そんなこんなで、愛の告白アプリのできるまでは終わりです。
何かの参考になれば幸いです。
ならなくても半年後の私が参考にするでしょう。
それでは。
See you the next hack with beer !

http://love.tklabo.net/
完成品はこちらです。

PHP Matsuri 2010終了。お祭りは夜通し行われた!

posted 2010-10-05 | written by mon_sat

こんにちは。@mon_sat です。
先週末の土曜・日曜の2日間。都内の晴海グランドホテルにて、PHP Matsuri 2010 が開催されました。

先日も記事にしましたが、このイベントは、昨年行われたCake Matsuri を大幅にスケールアップし、CakePHP以外にもSymfonyやLithium等、PHPのフレームワークを利用しているユーザーを対象とした、カンファレンス & ハッカソンです。

海外からCakePHPのコアデベロッパーGraham氏(@predominant)。今年はLithiumのコアデベロッパーとして参加するjoel氏(@jperras)、同じくLithiumのコアデベロッパーであるNate氏(@nateabele)、最後は急遽来日が決定したSymfonyのコアデベロッパーKris氏(@kriswallsmith)の4名がいらっしゃいました。
豪華すぎますよね!

今年は会場が自宅から近いということもあり、スタッフとして関わることにしました。
何かしらお手伝いできることもあるかと思い。

しかし、当日までほぼ全く手伝えることはなく、自身の未熟さを反省しつつも、当日は受付を担当。
まあこんなものです。私にできるのは。

というわけで当日8時に集合。 10時開始(9時開場)なので、おおよそ1時間程度で準備を。 詳細は省略しますが、他のスタッフのてきぱきとした動きに戸惑いつつ、まずはスタッフTシャツに着替えて気合いを入れました(笑)
何とか受付できるようにしたところで、続々と参加者の皆さんが到着。

お祭りスタート!

そんなこんなで、はじまりましたよ、お祭りが。
午前中こそ受付エリアにいたものであまり参加できませんでしたが、手のすき出した午後からは、開発しつつ、交流しつつと、徐々にペースがつかめてきました。

開発したWebアプリについては、別途開発の模様を記事にします。触りはこちらのまとめをご覧ください

会場後方から見ていると、セッションを聞いている方もいればワークショップに参加している方もいて、黙々と開発する方もいれば、他の人との交流を楽しむ方もいるという、それぞれがそれぞれのスタイルで楽しんでいるのが分かります。

契約の関係があり残念ながらUstreamによる中継はできませんでしたが、Twitterのハッシュタグ #phpmatsuri を見ているだけでも、その内容の濃さが分かると思います。(まとめてくれている方に感謝しつつ→ http://togetter.com/li/56219

夜は長いのだよ。夜は

そして夜。

突然ホテル脇に止まるこの車
会場に入ってきたのはこの二人。
Red Bull Girls
テンションの上がる参加者達(笑)

というわけで本格的な開発作業に入る夜へ向けて、栄養が補給されたのでした。

さらに、恒例のケーキ。

しかも今回は3つ。
CakePHP Lithium Symfony

SymfonyとLithiumのロゴをかたどったものも用意されました。
今度はテンションの上がるコアデベロッパー達。

※ケーキは、スタッフが・・・いや参加者が美味しくいただきました(笑)

JIREI NIGHT - 事例ナイト

その後 JIREI NIGHT と称して @slywalker さんがスターウォーズ的な何かになり、軽快なトークとともに、事例発表会がスタート。

CakePHPとSymfonyの選りすぐりの事例が、長時間にわたって紹介されました。
さすがに両雄とも言えるFW。どちらも豪華な発表です。

夜は続くよ。いつまでも

さてさて。 開発者が大好きな深夜(笑)を迎えます。

事前にアプリを作っていった私は、ここぞと、コアデベロッパー達との交流にチャレンジ。
ビールを片手に Joel や Graham を始め、Nate , Kris とお話ししました。

途中、Graham に声をかけて、作ったアプリのテストをしてもらいました。
はい。コアデベロッパーがテスターという、贅沢なアプリです(笑)
「面白いね。いつ作ったの?」「昨日です」「デザインも好みだ」と言ってくれて、いと嬉し。

気づいたら1時(業界用語で25時w)。
でもちっとも眠くありません。普段は12時回ることも少ないのに。

@cakephper さんに Joel がプレゼントしたカナダビールを飲みつつ語らいました。
「精のつく」ビールだそうです(笑)
あるの?そんなの。
でもラベルを見たら妖艶な女性がこっち見てる(笑)
寒い地方だからでしょうか、どれもアルコールが10%近くあります。 味もユニークでちょっと醤油っぽい。でも美味しいという(笑)

寝て、起きる。

2時を迎えてまだ30人以上いましたが、さすがに私はダウン。
部屋で速攻で横になったのですが、なぜか5時に目が覚めた。ぱっちりと。
Twitterを確認すると、みんな普通にやってるで。おい。

というわけで、5時半に再参戦。

すでにテンション低く皆黙々とやるなか、Grahamの横にはハイテンションガールが(笑)
Graham が必死にコミュニケートしようとしています。

Twitterで返事をしようとしているところのようですが、ハイテンションガールの説明では何に困っているのか分からない(笑) お酒をたくさん嗜まれているようです(笑)

結局「日本語で返信したいんだけど上手く行かない。Google Translate の訳を一文字編集したい(Grahamは日本語勉強しています)けど、『ば』を出せない。ソフトウェアキーボードの環境が今無くて」ということらしい。
じゃあAjaxIMEがあるじゃんと、サイトへ行ってみるも、使い方が分からん(笑)
最終的に「コピペしよう」と「ば」を探すも見つからず(笑)
手詰まりorz

この時間になると、黙々と開発を続ける猛者と睡眠欲を貪る方々がほとんどでした。
さすがに深夜、いや明け方ではねー。

ハッカソン2日目

そんなこんなで7時になり。朝食。
今回は計4回食堂での食事があり、そこで普段交流無い方ともお話しできた。
意外と会場での食事よりも(そういう意味で)良かったかも。

午前中は、プレゼン資料を書く時間にあてました。
「真面目に作ったネタアプリ」としては、ここで手を抜くわけには行きません(笑)

当然、英語。
せっかくコアデベロッパーにも伝わるようにアプリは国際化したのです。
文法めちゃくちゃでも、伝わればいいかと呑気に考えて、作ります。

発表の時間が4分と決まって、あら、困った。
4分じゃ収まりません。 (資料はWebで。要HTML5対応ブラウザ by Nerine which is one of excellent apps for me. thanks)

というわけで「ひとり作戦会議」をしました。
結果、「デモ中心」の発表をし、伝える内容を以下の3点にしぼりました。

  • 16時間+αで「アプリを完成させること」をテーマにしたこと
  • このアプリの特徴は「愛をつぶやく」アプリで「一定期間空けないと次がつぶやけない」ということ(機能概要)
  • 細部まで手を抜かずにつくったこと(国際化したり、バリデーションメッセージに凝ったり、HTML5+CSS3使っていたり)

おそらく普通は「What is this」を話してからデモだと思うのですが、これはネタアプリなので最初に落ちを持ってきては意味がありません。
というわけで「愛が重複したらつぶやけない」というところまでやってみせようと。
(時間がもったいないのでOAuth認証も事前に済ませることに)

そして最後のやつについては「なるべく語らずに」気づいてもらえるようにしました。
とくにHTML5+CSS3については単なる趣味なので、そこをアピールすることはさけました。
多分どこに使っているか分からなかったと思いますが(笑)

HTML5は、つぶやき内容のtextareaに使っています。
required属性をつけて入力が無かったら送信できないようにしているのと、placeholder属性をつけて例文を入れました。 (後者はtextareaにも使えることを知りました)
CSS3はもしかしたら気づいた方もいると思いますが。 ヘッダー等で「無駄に」ふわふわしているアレです。
他にもバリデーションエラーがふわっと表示されたりするのにも使っています。

以前ブックマークしたサイトを参考に修正しつついろいろ試すことができました。面白いですねCSS3 animation。
(もちろんほかにも角丸やグラデーションで使ってます)

JavaScriptは1箇所しか使っていません。
もしかしたらplaceholder等をJavaScriptで実装していると思った方がいるかもしれませんが、そこではなく、投稿後TwitterAPIが結果を返すまで画面が遷移しないので、submitボタンを無効にするために使っています。
本当はoverlayで画面をかぶせたかったのですがそれは失敗しました。
他にもつぶやきの結果は吹き出し風にしたかったのですが、CSSの技術が備わっていませんでした(笑)

発表!

そんなこんなで発表です。
順番はアルファベットの昇順ということで、ちょうど中間点くらいのベストな位置になりました。
なんせネタアプリです。初っぱなになっては滑ること請け合いです(笑)
肩肘張らずに聞いてもらいたいものです。

他の発表者が「みんな真面目」な中、奇しくも私と次の@mochizさんはネタ系で続きます。(I like MoreGoro!)
@nojimageさんの男前な発表のあと、いよいよ私。

>会場にいなかった方へ
Ustreamでは分かりづらいですが、オーディエンスの反応は大絶賛でした。
もうあちこちでスタンディングオベーション。拍手や笑い声で私の声がかき消されるくらいの。

>会場にいた方へ
正直、反応なんて確認する余裕ありませんでしたから(言い訳)
あと記憶違いってあるよね。過去を美化することもよくあること。

>結論
Ustreamは正直。

LT全体としてはすごいというひとこと。

Joelのこのつぶやきにも現れてますが、発表ベースで30人以上、おそらく発表しなかった方も含めると相当数の方々から、新たな何かが生まれたわけです。
2日間で。
日本のコミュニティのパワーは、すごい。

発表者の皆さんも聴講者の皆さんもお疲れさまでした。(トータル3時間の連続LT。途中休憩はあり)

発表

最後に各賞の発表があり、私のWebアプリ「愛の告白」は、まさか、まさかのCakePHP賞の次点入賞

景品は、CakePHP辞典Nifty Cloudの無料チケット!(MASA-PさんNiftyさん、ありがとうございます)
いやー有り難いです。
発表すること自体が自分を成長させるものと思って発表したら、もったいない評価を頂戴してしまいました。

よっぽどのことが無い限り削除しませんので、気が向いたら使ってください。
彼女の誕生日に1年分の愛をつぶやくというのもいいですよー。

正直「誰得?」ですが、本来の使い方をすれば、「愛をつぶやかれた方、得」です。
@hampomさんは使うといいですね!

他にも書きたいことはいろいろあり、半年後の自分へのメッセージとしてまとめたいところですので、いろいろと続きます。
とりあえず以上で。

得点入れていただいた方、ありがとうございましたー。

お祭りレポートの残りおよび周辺記事はまた明日。

<< previous
|
next >>

プロフィール

@mon_sat

CakePHPをよく利用しています。

理解の浅かった半年前と、何も知らなかった一年前の自分への教科書として書いています。
当たり前のことも平易に。

RSS2.0

カテゴリ別エントリ一覧

タグ別エントリ一覧

アーカイブ