CakeMatsuriTokyo2009に行ってきた
10/30、31に渋谷のシダックスホールで開催されたCakeMatsuriTokyo2009に行ってきました。
今年は1日目がワークショップ、2日目がカンファレンスという日程でした。
自分はカンファレンスのみの参加でしたが充実した内容でとても良いカンファレンスだったと思います。
以下、俺俺まとめメモ。
基調講演 CakePHP道(Yusuke Andoさん)
日本OSS奨励賞受賞おめでとうございます!
事例紹介2 スズキ自販サイトでの CakePHP 活用事例(syuhariさん)
- PostgreSQL,MySQLのビューをmodelにした
- PHP5.3を使用
- Cakeをカスタマイズすると引き継ぎ難になる
- syuhariは守破離
事例紹介3 nanarpiのレシピ(和田修一さん)
- CakePHPは重い→複雑な処理はCake、速度重視の表示系はClearSilver
- Tokyo Tyrantのデータはバッチ処理でMySQLに保存
- lsyncdでディレクトリを監視、テンプレートを別サーバに転送
- 優先度の高い表示は静的なHTML、優先度の低い部分は非同期
CakePHP: The framework strikes back(Graham Weldonさん)
- cakephp1.3
- CakePHP2.0
- PHP4はサポートしない(PHP5.2以降のサポート)
- PHP6のfutureもサポートしていく
- ActiveRecored見直し
- モデルからオブジェクトが返される
- 後方互換のため配列も返せる
- モデルは必要な時のみロードされる
- ClassRegistryは削除
- 速度向上、メモリ使用量の削減がポイント
- テストケースのカバレッジを100%にする
- 日本のコミュニティの規模はアメリカに次いで2位
- CakePHP2.0への要望を聞きたい
事例紹介4 物件紹介ポータル Expression Engineと連携でShoppingCart(bennyleeさん)
- 俺俺フレームワークが横行→共通フレームワークが欲しい
- 条件
- PHP4+5対応
- 習得コストが低い
- 日本のコミュニティ、ドキュメントが充実
- 権限管理は自作components
- 無限画像アップ
- 更新が少ないDBの検索結果はキャッシュ→外部ライブラリで実現(coreのcacheを使えば楽)
プラグイン活用法(slywalkerさん)
- プラグインの基本
- エンタープライズrailsがオススメ
- bootstrapでプラグインPATHを設定→複数のアプリケーションで共通のプラグイン使える
- AccountManagerPlugin
- テーマ機能を使ってviewを分ける
- pluginのCSSを読ませるためにplugin内にカスタムDispatcherを設置、bootstrapでAPP::importで読み込む
Key-Value Stores & Non-Relational Databases(Joel Perrasさん)
LT1 ウチのCakePHP(新原雅司さん)
usesでmodelを指定せずにClassRegister::initでモデルのインスタンスを生成、例外処理を行う
- insertとupdateを明示的に分ける実装
- 独自behavior
- CakePHPは懐が深いので気に入らないところは拡張しましょう
LT5 CakePHP + Fusic(小山健一郎さん)
- DB Schema/Model Info Plugin
- DB Schemaを表示
- Model relationshipを表示
LT7 remember me(パーハックキースさん)
- 日本語勉強サイトを作成
- remember meが動かない→このコードはテストしてませんw
LT8 bind,unbindはもう古い!Containable Behaviorでrecursive=3だってこわくない!(秋田真宏さん)
- containable behavior
- unbined→いらないものを書く
- containable→必要なものをかく
- 必要なものを書くほうが直感的、アソシエーション増えてもOK
LT9 Hituji×Cake Story(北川大祐さん)
CakePHPのShellでtwitterのログを保存する
ふと思い立って書いてみたので適当にメモしておきます。
CakePHPコアライブラリの使い方の勉強を兼ねてやりました。
table
適当にテーブルを作ります。
+------------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+---------------------+------+-----+---------+----------------+ | id | int(10) unsigned | NO | PRI | NULL | auto_increment | | status_id | bigint(20) unsigned | NO | | NULL | | | text | text | NO | | NULL | | | created_at | datetime | NO | | NULL | | +------------+---------------------+------+-----+---------+----------------+
model
テーブルから最新のstatus_idを取得するメソッドとAPIからのレスポンス配列を整形してsaveAllするメソッドを定義しています。
<?php class TwitterLog extends AppModel { var $name = 'TwitterLog'; function getSinceId() { $this->order = 'TwitterLog.id DESC'; $latestStatus = $this->find(); if ($latestStatus) { return $latestStatus['TwitterLog']['status_id']; } return false; } function saveStatus($statuses) { foreach ($statuses as $status) { $dateTime = date('Y-m-d H:i:s', strtotime($status['created_at'])); $data[] = array( 'status_id' => $status['id'] , 'text' => $status['text'], 'created_at' => $dateTime ); } return $this->saveAll($data); } }
shell
XmlとHttpSocketを使用します。最初にinit()を実行することでこれまでのつぶやきを全て(APIの制限内で)保存します。
<?php App::import('Core', array('Xml', 'HttpSocket')); class TwitterLogShell extends Shell { var $uses = array('TwitterLog'); const API_URL = 'http://twitter.com/statuses/user_timeline.xml'; const USER = 'username'; const PASS = 'password'; function startup() { $this->sock =& new HttpSocket(); $this->request = array( 'auth' => array( 'user' => self::USER, 'pass' => self::PASS ) ); } function main() { while (true) { $query = array( 'since_id' => $this->TwitterLog->getSinceId(), 'count' => 200 ); $ret = $this->_getStatus($query); if (empty($ret['Statuses']['Status'])) break; if (empty($ret['Statuses']['Status'][0])) { $statuses[] = $ret['Statuses']['Status']; } else { $statuses = array_reverse($ret['Statuses']['Status']); } $this->TwitterLog->saveStatus($statuses); } } function init() { $query = array('count' => 1); $ret = $this->_getStatus($query); $statusCount = $ret['Statuses']['Status']['User']['statuses_count']; $page = ceil($statusCount/20); while ($page > 0) { $query = array('page' => $page); $ret = $this->_getStatus($query); $statuses = array_reverse($ret['Statuses']['Status']); $this->TwitterLog->saveStatus($statuses); $page--; } } function _getStatus($query) { $res = $this->sock->get(self::API_URL, $query, $this->request); $xml = new Xml($res); $array = $xml->toArray(); $xml->__killParent(); $xml->__destruct(); $xml = null; unset($xml); return $array; } }
あとはcrontabで下記の様なコマンドを適当なタイミングで実行すれば自分のつぶやきログがDBに保存されていきます。
CAKE_DIR/console/cake twitter_log -app APP_DIR
式波botを作る
普段はほとんどPHPしか使わないのでperlの勉強も兼ねてtwitterのbotを作りました。
題材はユーロ空軍のエース、式波・アスカ・ラングレー大尉です。
http://twitter.com/asuka_jp
技術的なところ
1の機能については自分宛の発言を拾って、その発言のMD5をとってゴニョゴニョしてます。
発言パターンはいくつか用意しています。
2〜4の機能については正規表現で判定してセリフを出し分けてるだけです。
このあたりのコードは発言内容がバレるので公開しません、その程度のものです。。。
5の機能は公式のRSSを取得しています。
劇場版公開後なのであまり更新されませんが。
コードは以下のようになっています。
Net::Twitterを使っているので発言部分は他の機能と同じです。
#!/usr/bin/perl use strict; use Net::Twitter; use LWP::Simple; use XML::RSS; use Encode; use utf8; use File::stat; use HTTP::Date; use Digest::MD5; use Data::Dumper; use constant BASE_DIR => 'PATH'; my $twit = Net::Twitter->new(username => 'USERNAME', password => 'PASSWORD' ); my @items = &get_info(); if($#items+1 > 0) { foreach my $item (@items) { my $post = 'エヴァ関連情報: '; $post .= $item->{title} . ' ' . $item->{link}; my $res = $twit->update($post); } } sub get_info { my $url = 'http://extr.b-ch.com/eva_news/rss/index.rdf'; my $cache = BASE_DIR . '/cache/' . Digest::MD5::md5_hex($url); my $lastmod = -e $cache ? stat($cache)->mtime : 0; my $status = LWP::Simple::mirror($url, $cache); my @items; if(is_error($status)) { die ("rss not found...\n"); }elsif(is_success($status) && $status != RC_NOT_MODIFIED) { my $rss = XML::RSS->new; $rss->parsefile($cache); foreach my $item (@{$rss->{items}}) { my $time = HTTP::Date::str2time($item->{dc}->{date}); if($time > $lastmod) { push(@items, $item); }else { next; } } } return @items; }
6の機能は以下のようにして実装しました。
friendsに存在せずfollowersに存在するidをフォローしています。
sub auto_follow { my $friends = $twit->friends; my $followers = $twit->followers; if(defined($friends) && defined($followers)) { my @friend_list; my @follower_list; foreach my $friend (@{$friends}) { push(@friend_list, $friend->{id}); } foreach my $follower (@{$followers}) { push(@follower_list, $follower->{id}); } my $lc = List::Compare->new(\@friend_list, \@follower_list); foreach my $id ($lc->get_Ronly) { $twit->create_friend($id); } } }
細かいところは後でまとめたいと思います。
(追記) たまにundefが返ってくるようなのでautofollowを修正
(追記) 関連情報の要素数カウントが間違っていたので修正
ajaxヘルパーの使い方
今更ながら初めてajaxヘルパーを使ったので基本的なとこだけメモ。
事前準備としてprototype.jsをAPP/webroot/jsに置いておきましょう。
view1
<?php echo $javascript->link('prototype'); //update:ajaxで更新されるエレメントのid //loading:読み込み中に実行されるコード //complete:読み込み完了後に実行されるコード $options = array('update' => 'update_div_id', 'loading' => 'Element.show('loading_img_id');', 'complete' => 'Element.hide('loading_img_id');' ); //フォームの作成(formヘルパーでいうcreateメソッド) echo $ajax->form('example/ajax', 'post', $options); //以下いつもと同様にフォームの中身を記述 //読み込み中に表示する画像 echo $ajax->div('loading_img_id'); echo $html->link($html->image('loading.gif', array('style' => 'display:none');); echo $ajax->divEnd('loading_img_id'); //ajaxで更新されるエレメント echo $ajax->div('update_div_id'); echo $ajax->divEnd('update_div_id'); ?>
view2
<?php echo $message; ?>
controller
<?php class ExampleController extends AppController { var $name = 'Example'; var $helpers = array('Javascript', 'Ajax'); function ajax() { //何らかの処理を記述 $this->set('message', '更新しました'); $this->render('view2', 'ajax'); //ajaxレイアウトでview2をレンダリング } } ?>
この(適当な)例の場合、フォームを送信すると$ajax->divメソッドによって書き出された、
<div id="update_div_id"></div>
の間に$messageの内容が表示されます。
vimperatorプラグイン基礎の基礎
vimperatorプラグイン(ここでは何らかの動作をコマンドとして登録)の作り方を調べたのでメモ。
hostsの変更を反映してくれるアドオン、DNS Flusherと同様の機能をコマンドとして登録してみました。
function flush() { //ioサービス生成 var io = Components.classes['@mozilla.org/network/io-service;1'].createInstance(Components.interfaces.nsIIOService); //オンラインモードなら if(!io.offline) { //キャッシュサービス生成 var cache = Components.classes["@mozilla.org/network/cache-service;1"].getService(Components.interfaces.nsICacheService); //オフラインモードに切り替え io.offline = true; //キャッシュクリア cache.evictEntries(Components.interfaces.nsICache.STORE_ANYWHERE); //オンラインモードに切り替え io.offline = false; }else { //コマンドラインバッファにメッセージ出力 liberator.echo('Please release offline mode!'); //ビープ liberator.beep(); } } //addUserCommandでコマンドを登録 //第1引数にコマンド、第2引数に簡易説明、第3引数に実行するfunctionを渡す commands.addUserCommand( ['flush'], 'Flush hosts settings', function() { flush(); } );
上記スクリプトをjavascriptファイルとしてVimperator/pluginに保存するか、javascriptとして.vimperatorrcに追記するとDNS Flusherで行っている、
オフラインモード→キャッシュクリア→オンラインモード
という動作をflushというコマンドで実行できるようになります。
(IPの表示は実装していないので確認できないですが。。。)
追記:2.0との互換性を維持するためliberator.commands.addUserCommandからcommands.addUserCommandに変更。