CakeMatsuriTokyo2009に行ってきた

10/30、31に渋谷のシダックスホールで開催されたCakeMatsuriTokyo2009に行ってきました。
今年は1日目がワークショップ、2日目がカンファレンスという日程でした。
自分はカンファレンスのみの参加でしたが充実した内容でとても良いカンファレンスだったと思います。

以下、俺俺まとめメモ。

基調講演 CakePHP道(Yusuke Andoさん)

日本OSS奨励賞受賞おめでとうございます!

  • 障壁が低くいからユーザーが多い→ユーザーが多い事自体がCakePHPの価値になっている
  • Give back much as you take.
  • コードを公開することで還元できる
  • set::combine
  • 公開されているソースコードから学ぶ(remora,cookbook,croogo,candycane)
  • 技を使ってクリーンで小さいコードを目指す
  • CakePHPで世界が身近になる
事例紹介1 CakePHPで作る地域SNS(あつさん)
事例紹介2 スズキ自販サイトでの CakePHP 活用事例(syuhariさん)
  • PostgreSQL,MySQLのビューをmodelにした
  • PHP5.3を使用
  • Cakeをカスタマイズすると引き継ぎ難になる
  • syuhariは守破離
事例紹介3 nanarpiのレシピ(和田修一さん)
CakePHP: The framework strikes back(Graham Weldonさん)
  • cakephp1.3
    • jsのoutputをHtmlhelperが担当
    • jqueryがデフォルトに(prototype/mootoolsも使用可)
    • バッファリング可能
    • Bakeでテスト、テンプレート、プラグインの使用が可能
    • Routingでadmin以外のprefixも簡単に追加できる
    • DatasourceプラグインによってDatasourceへのアクセス、共有、管理が容易に
    • 下位バージョンからのMigration guideも用意
  • CakePHP2.0
    • PHP4はサポートしない(PHP5.2以降のサポート)
    • PHP6のfutureもサポートしていく
    • ActiveRecored見直し
    • モデルからオブジェクトが返される
    • 後方互換のため配列も返せる
    • モデルは必要な時のみロードされる
    • ClassRegistryは削除
    • 速度向上、メモリ使用量の削減がポイント
    • テストケースのカバレッジを100%にする
    • 日本のコミュニティの規模はアメリカに次いで2位
    • CakePHP2.0への要望を聞きたい
事例紹介4 物件紹介ポータル Expression Engineと連携でShoppingCart(bennyleeさん)
  • 俺俺フレームワークが横行→共通フレームワークが欲しい
  • 条件
    • PHP4+5対応
    • 習得コストが低い
    • 日本のコミュニティ、ドキュメントが充実
  • 権限管理は自作components
  • 無限画像アップ
  • 更新が少ないDBの検索結果はキャッシュ→外部ライブラリで実現(coreのcacheを使えば楽)
研究分野でのCakePHPの活用事例とその周辺の話(飯塚康至さん)
  • みんなの研究生活P-LabTexを使ってweb上で論文作成
  • ext.jsを使ってexcelライクなUI
  • SQLを書かなくて済むのが良い→SQLを書かない設計をする
  • pr()、pagenaitonが便利
  • bbsコントローラのモデル名はbbになってますw
  • エディタはeclipsepleiades
  • dropboxはバージョン管理もできる
  • 準委任契約→CakePHPの生産性が高くて稼げないw
プラグイン活用法(slywalkerさん)
Key-Value Stores & Non-Relational Databases(Joel Perrasさん)
LT1 ウチのCakePHP(新原雅司さん)

usesでmodelを指定せずにClassRegister::initでモデルのインスタンスを生成、例外処理を行う

  • insertとupdateを明示的に分ける実装
  • 独自behavior
  • CakePHPは懐が深いので気に入らないところは拡張しましょう
LT2 Lerning Cakephp(藤原敬弘さん)
  • とにかく作る→ブログチュートリアル
  • 好きなものを作る→マニュアル参考
  • 高度なものを作る→マニュアル参考
  • そしてハックする!
LT3 CakePHP in iPhone application(松浦晃洋さん)
  • スコア登録、ゲームランキング機能
  • 警告画面にブラウザを組み込んでランキング表示
  • iPhoneアプリ作りましょう!
  • iPhoneアプリの本書いてます!(来年発売?)
LT4 CakePHP弊社事例(渡辺翔太さん)
  • twiiterの管理、宣伝ASP
  • APIからのデータ取得独自Datasourceを作る
  • 取得したデータをmodelからまとめて取得
LT5 CakePHP + Fusic(小山健一郎さん)
LT6 CakePHP is good(こんのようすけさん)
  • バターケーキじゃなくてホイップが食べたいんだ!
  • 開発に必要なのはルール
LT7 remember me(パーハックキースさん)
  • 日本語勉強サイトを作成
  • remember meが動かない→このコードはテストしてませんw
LT8 bind,unbindはもう古い!Containable Behaviorでrecursive=3だってこわくない!(秋田真宏さん
  • containable behavior
  • unbined→いらないものを書く
  • containable→必要なものをかく
  • 必要なものを書くほうが直感的、アソシエーション増えてもOK
LT9 Hituji×Cake Story(北川大祐さん)
  • 俺俺フレームワークからの移行
  • CakePHPは開発が活発、バージョンアップ時のテストがキツイ
  • クリティカルな処理はストアドへ
  • Cakeにストアド実行関数を実装
  • cssファイルをcontroller単位で分割
LT10 基本のお菓子―とりあえずこのプラグインさえ使えれば(高橋征義さん

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の勉強も兼ねてtwitterbotを作りました。
題材はユーロ空軍のエース、式波・アスカ・ラングレー大尉です。
http://twitter.com/asuka_jp

機能概要

1.基本的に何を言われてもバカにする

2.質問されても答えない

3.謝ったら怒る

4.ちょっとデレる

5.たまに関連情報をつぶやく

6.自動でフォローする

技術的なところ

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に変更。

CakePHPでSQLエラーメッセージを取得する

SQLエラーで判定をしたかったんだけどModelにメソッドがなくてハマった。
調べたのでメモ。

$db =& ConnectionManager::getDataSource($this->useDbConfig); 
$message = $db->lastError();

APIのページはすごく使える。

参考にしたページ

CakePHPのE_STRICTを消す

CakePHPはPHP4にも対応しているためE_STRICTがオンな環境だとエラーがでます。
これをCakePHP使用時だけオフにする方法を試したのでメモ。

APP/webrootのindex.phpの最初と最後に以下のコードを追加。

//最初に追加
$E = error_reporting(); 
if(($E & E_STRICT) == E_STRICT) error_reporting($E ^ E_STRICT); 

//最後に追加
error_reporting($E); 

error_reportingで切り替えてるだけです。
dispatcher.phpでやったほうがいいのかも?