December 21, 2011

FuelPHPのTasks

FuelPHP Advent Calendar 2011 21日目です。
@madmamor です。

昨日は @kenji_s さんの
FuelPHP でのセキュリティ対策(1)
でした。

今日は、FuelPHPのTasksについて書きます。
ドキュメントは、以下になります。

公式:
http://docs.fuelphp.com/general/tasks.html
日本語:
http://press.nekoget.com/fuelphp_doc/general/tasks.html

早い話が、コマンドラインやcronで実行できるFuelPHPのクラスです。
ローカルでも実行できますが、今回は、せっかくなのでサーバでやってみました。


尚、以下の方法でインストールを試みましたが、エラーが出てしまい、
とりあえずwgetでDLして解凍しました。
http://docs.fuelphp.com/installation/instructions.html
http://press.nekoget.com/fuelphp_doc/installation/instructions.html

DL/解凍する場所は、どこでも良いです。
phpのバージョンが5.2系以前では動きませんので注意して下さい。FuelPHPは5.3.xで動作します。
以下、FuelPHPに標準で入っているTasksのRobotsクラスを扱って話を進めます。

--
%wget --no-check-certificate https://github.com/downloads/fuel/fuel/fuelphp-v1.1.zip
%unzip fuelphp-v1.1.zip
%cd fuelphp-v1.1
%php oil refine robots
--

 
                                        "KILL ALL HUMANS!"
                                  _____     /
                                 /_____\
                            ____[\*---*/]____
                           /\ #\ \_____/ /# /\
                          /  \# \_.---._/ #/  \
                         /   /|\  |   |  /|\   \
                        /___/ | | |   | | | \___\
                        |  |  | | |---| | |  |  |
                        |__|  \_| |_#_| |_/  |__|
                        //\\  <\ _//^\\_ />  //\\
                        \||/  |\//// \\\\/|  \||/
                              |   |   |   |
                              |---|   |---|
                              |---|   |---|
                              |   |   |   |
                              |___|   |___|
                              /   \   /   \
                             |_____| |_____|
                             |HHHHH| |HHHHH|

%php oil refine robots

%php oil r robots
としても良いです。
このコマンドにより、fuel/app/tasks/robots.phpのrunメソッドが実行されます。
1つの処理だけなら、runメソッドのみ実装すれば良さそうです。

%php oil r robots "Kill all Mice"
とすれば、runメソッドに対して"Kill all Mice"を引数として与えることがでできます。

また、fuel/app/tasks/robots.phpには、runメソッドとは別にprotectメソッドがあります。
%php oil r robots:protect
とすれば実行できます。
runメソッド以外を実装して実行する場合はこの形になるようです。

 
                                        "PROTECT ALL HUMANS"
                                  _____     /
                                 /_____\
                            ____[\*---*/]____
                           /\ #\ \_____/ /# /\
                          /  \# \_.---._/ #/  \
                         /   /|\  |   |  /|\   \
                        /___/ | | |   | | | \___\
                        |  |  | | |---| | |  |  |
                        |__|  \_| |_#_| |_/  |__|
                        //\\  <\ _//^\\_ />  //\\
                        \||/  |\//// \\\\/|  \||/
                              |   |   |   |
                              |---|   |---|
                              |---|   |---|
                              |   |   |   |
                              |___|   |___|
                              /   \   /   \
                             |_____| |_____|
                             |HHHHH| |HHHHH|

さらに、以下のように、フルパスでも実行可能です。結果は同じです。
php /home/[username]/xxx/yyy/fuelphp-v1.1/oil r robots

このような感じで、DLして即、実行できます。

fuel/app/tasks/robots.phpを参考に、必要な処理(クラスとメソッド)を実装。
これをcronで実行するようにしておけば、わりとあっさりいけそうです。

尚、サーバによってはphpコマンドで反応するphpのバージョンが古い可能性があります。
以下のようにしないといけないかもしれません。。。
/usr/local/php/5.3.8/bin/php /home/[username]/xxx/yyy/fuelphp-v1.1/oil r robots



P.S.
いろいろなライブラリを適当に寄せ集めてやっつけで作った
cron用の各種プログラムをFuelPHPのTasksでリプレイスしたい。。。



明日は @kenji_s さんの
FuelPHP でのセキュリティ対策(2)
の予定です。

December 20, 2011

gitのpost-receiveでメール送信メモ。

hooksにpost-receiveファイルを作成。

hooks/post-receive
--
xxx/yyy/git-1.7.8/contrib/hooks/post-receive-email
--

送信設定
--
git config hooks.mailinglist "xxx <[to]@gmail.com>,yyy <[to]@gmail.com>"
git config hooks.emailprefix "[git pushed]"
git config hooks.envelopesender "xxx <[from]@gmail.com>"
--

hooksにpost-receive.sampleが無いのは何故だろう。。。

December 18, 2011

githubのアカウント作りました。

今更な感じもしますが。

https://github.com/mp-android
になります。
このアカウントには、AndroidアプリのRepositoryのみを作っていく予定です。

これまで雑に使っていたgoogle code
http://code.google.com/p/madroom-project/
は空っぽにしました。

幾つかのDLファイルは、githubに移して有ります。

一人で使うので、中々しっかりバージョン管理出来るか心配ですが。

assetsに.gitkeep置くのとか、空のREADME置くのは面倒なので、していません。

Windows7でPortableGitでgithubにpushしようとするとPermission denied (publickey).

"github Permission denied (publickey)."とかでググると色々と情報が出てきますが、
僕の場合は、ちょっと原因が違いました。

~/.ssh/known_hosts をいくらいじっても何の反応もなく、おかしいなーと思っていたら
PortableGit/.ssh/known_hosts が有りました。。。

これを編集したら、無事解決。

December 16, 2011

FuelPHPのcoreクラスを拡張してみる。

FuelPHP Advent Calendar 2011 16日目です。
@madmamor です。

先日の15日目は @eifuku さんの
cent osでのphp5.3環境のセットアップ with "yum" でした。


FuelPHPのcoreクラスを拡張したくなるケースは、色々と出てくると思います。
以前、Logクラスを拡張したので、そのまとめ的な内容です。

先に、ドキュメントは以下になります。
公式:
http://docs.fuelphp.com/general/extending_core.html
日本語:
http://press.nekoget.com/fuelphp_doc/general/extending_core.html



私がLogクラスを拡張した理由は2つ有ります。

(1) メソッド名の簡略化
Log::info は Log::i で呼び出したい。
Log::debug は Log::d で呼び出したい。
Log::warning は Log::w で呼び出したい。
Log::error は Log::e で呼び出したい。

(2) 非スカラー型(配列等)の変数も直接渡してログ出力したい。

となります。

早速、拡張したソースを掲載しても良いのですが、その前に。
そもそも、coreクラスがどこに有るのか。知っておいた方が良いはずです。

coreクラスはその名の通り、core/classesの中に有ります。
直下にはディレクトリとphpファイルがずらっと並んでいます。


14日目の @kenji_s さんの記事( FuelPHP の URL とコントローラの関係 )にある通り、
"クラス名の中の「_」はフォルダ区切りを意味する"
という決まり事があります。

ですので、ControllerクラスとController_Templateクラスはそれぞれ、以下のような配置になっています。
core/classes/controller.php
core/classes/controller/template.php

これを知っているだけでも、coreのソースを追いやすくなると思います。


では、Logクラス(core/classes/log.php)の拡張例を紹介してみます。



【例1】
クラス名をそのままにする場合。("コアクラスを拡張し、置き換える"場合。)

app/classes/log.phpを作成。
<?php

class Log extends Fuel\Core\Log
{
    public static function i($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::info($msg, $method);
    }
    public static function d($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::debug($msg, $method);
    }
    public static function w($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::warning($msg, $method);
    }
    public static function e($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::error($msg, $method);
    }
}

次に、app/bootstrap.phpを編集します。
以下のような箇所が有るはずなので
Autoloader::add_classes(array(
    // Add classes you want to override here
    // Example: 'View' => APPPATH.'classes/view.php',
));

ここに、今回作成したLogクラスを追加します。
Autoloader::add_classes(array(
    // Add classes you want to override here
    // Example: 'View' => APPPATH.'classes/view.php',
    'Log' => APPPATH.'classes/log.php',
));
これをしないと、先に作成したapp/classes/log.phpのLogクラスは無視されます。


使い方は、debugの場合
Log::d($msg);
となります。
Log::dの中で
if(!is_scalar($msg)) $msg = print_r($msg,true);
しているので、$msgは配列でも構いません。



【例2】
クラス名をMylog等としたい場合。("コアクラスを置き換えずに拡張する"場合。)その他は例1と同様。

app/classes/mylog.phpを作成。
<?php

class Mylog extends Fuel\Core\Log
{
    public static function i($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::info($msg, $method);
    }
    public static function d($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::debug($msg, $method);
    }
    public static function w($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::warning($msg, $method);
    }
    public static function e($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::error($msg, $method);
    }
}

使い方は、debugの場合
Mylog::d($msg);
となります。
尚、この場合、app/bootstrap.phpの編集は不要です。



以上、coreクラスの拡張方法でした。



17日目は @mataga さんの
FuelPHP動作実験 - oil console & PHP Interactive改を使って マニュアルの例文コピペでいろんなメソッドを試してみよう☆彡
です。

December 11, 2011

FuelPHPでFacebookアプリを作ってみよう。実装編。

2011/12/11 追記
当初、ソース内にValidation::factory()と記述をしていましたが、近々廃止予定とのことです。
Validation::forge()を使うべきとご指摘を頂きました。有難うございます。
当記事の該当箇所について、修正してあります。


FuelPHP Advent Calendar 2011 11日目です。
@madmamor です。

前日の @kenji_s さんの
FuelPHP での Migration の使い方
でもご紹介頂いたとおり、今回は、Facebookアプリの実装編です。

尚、先にこちらをお読み下さるよう、お願いします。
FuelPHPでFacebookアプリを作ってみよう。準備編。

今回実装する機能は
* Facebookでのログイン/ログアウト機能
* Facebook情報のDB保存(バリデーション付き)
* Facebookのウォール投稿機能(バリデーション付き)
です。
各項にポイントを書いています。

では、一気にいきましょう。


【DBとテーブルを作成】
MySQLを使います。DB名はfuelfb。テーブル名はusers。とします。
尚、今回、Facebook情報はusersテーブルに保存するのみです。
検索して使用する。という処理は組み込みませんが、DB周りの機能紹介を兼ねて、記載します。
create database fuelfb;

create table fuelfb.users
(
    id int not null auto_increment,
    created_at int not null,
    updated_at int not null,
    facebook_id varchar(255) not null unique,
    facebook_name varchar(255) not null,
    facebook_link varchar(255) not null,
    primary key (id)
);
【ポイント】
idはFuelPHPが定める主キーカラム名です。

created_atはFuelPHPが定めるレコード作成時刻カラム名です。
updated_atはFuelPHPが定めるレコード更新時刻カラム名です。
created_atとupdated_atはデフォルトだとUNIXタイムスタンプとなります。
datetime型にすることも可能ですが、今回はint(UNIXタイムスタンプ)で。

その他のカラムは、Facebook認証時に取得するデータ用のカラムです。
ユーザ入力ではありませんが、外部データなので、制約は緩めにしてあります。


【ORMモデルを作成】
前述のusersテーブルに対するモデルです。

fuel/app/classes/model/user.phpを作成。
<?php

namespace Model;

class User extends \Orm\Model
{

    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array('events'=>array('before_insert')),
        'Orm\Observer_UpdatedAt' => array('events'=>array('before_save')),
        'Orm\Observer_Validation'=> array('events'=>array('before_save')),
    );

    protected static $_properties = array(
        'id',
        'created_at',
        'updated_at',
        'facebook_id' => array(
            'validation' => array(
                'trim',
                'required',
                'max_length' => array(255),
                'valid_string' => array('integer'),
            ),
        ),
        'facebook_name' => array(
            'validation' => array(
                'trim',
                'required',
                'max_length' => array(255),
            ),
        ),
        'facebook_link' => array(
            'validation' => array(
                'trim',
                'required',
                'max_length' => array(255),
            ),
        ),
    );
}
【ポイント】
今回は、Orm\Modelを継承したモデルとします。

$_observersで、様々な振る舞いを指定可能です。
今回は、created_at値、updated_at値の自動登録と、
insert or update前のバリデーション自動実行。を設定しています。

$_propertiesでカラム毎にバリデーションを定義可能です。

バリデーションは、"単一のStringを受け、Stringを返却するPHP関数"を指定可能です。
今回は、trimを設定しています。

$_propertiesには全てのカラムを記述して下さい。エラーとなります。


【ORMパッケージの有効化】
fuel/app/config/config.phpを修正
'packages'  => array(
    //'orm',
),

'packages'  => array(
    'orm',
),
【ポイント】
前述のORMモデルが使用可能となります。


【DB接続情報を設定】
fuel/app/config/development/db.phpを修正
<?php
/**
 * The development database settings.
 */

return array(
    'default' => array(
        'connection'  => array(
            'dsn'        => 'mysql:host=localhost;dbname=fuel_dev',
            'username'   => 'root',
            'password'   => 'root',
        ),
    ),
);

<?php
/**
 * The development database settings.
 */

return array(
    'default' => array(
        'type'            => 'mysql',
        'connection'    => array(
            'hostname'        => 'localhost',
            'port'            => '3306',
            'database'        => 'fuelfb',
            'username'        => 'root',
            'password'        => '',
        ),
    ),
);
【ポイント】
必要に応じて、接続情報は書き換えて下さい。


【ビューの修正】
fuel/app/views/index/index.phpを修正
<p>Index</p>

<?php if ($is_login): ?>
<a href="http://127.0.0.1/fuelfb/public/index/logout">Log out.</a>
<?php else: ?>
<a href="http://127.0.0.1/fuelfb/public/index/login">Login with Facebook.</a>
<?php endif; ?>

<?php if ($is_login): ?>
<?php echo(Form::open('index/index/'))?>
<?php echo(Form::textarea('message'))?>
<?php echo(Form::submit('submit','Post to Facebook'))?>
<?php echo(Form::close())?>
<a href="http://127.0.0.1/fuelfb/public/index/logout">Log out.</a>
<?php else: ?>
<a href="http://127.0.0.1/fuelfb/public/index/login">Login with Facebook.</a>
<?php endif; ?>
【ポイント】
ログインしていれば入力フォームとログアウトリンクを表示します。

ログインしていなければログインリンクを表示します。

判断基準の$is_loginは後述のコントローラでセットします。


【コントローラに機能を実装】
fuel/app/classes/controller/index.phpを修正
<?php
require_once APPPATH.'vendor/facebook-php-sdk/src/facebook.php';

class Controller_Index extends Controller_Template {

    private $fb;

    public function before()
    {
        parent::before();
        $this->fb = new Facebook(Config::get('facebook.init'));
    }

    public function action_index()
    {
        $this->template->title = 'Index » Index';

        $data = array(
            'is_login' => $this->fb->getUser()?true:false,
        );

        $this->template->content = View::forge('index/index',$data);
    }

    public function action_login()
    {
        exit('TODO : login');
    }

    public function action_callback()
    {
        exit('TODO : callback');
    }

    public function action_logout()
    {
        exit('TODO : logout');
    }

}

<?php
require_once APPPATH.'vendor/facebook-php-sdk/src/facebook.php';

use Model\User;

class Controller_Index extends Controller_Template {

    private $fb;

    public function before()
    {
        parent::before();
        $this->fb = new Facebook(Config::get('facebook.init'));
    }

    public function action_index()
    {
        $this->template->title = 'Index » Index';

        $is_login = $this->fb->getUser()?true:false;
        $data = array(
            'is_login' => $is_login,
        );

        if($is_login and Input::method() == 'POST')
        {
            $v = Validation::forge();
            $v->add('message', 'message')->add_rule('required');
            if(!$v->run())
            {
                Session::set_flash('notice', $v->errors('message')->get_message());
            }
            else
            {
                $message = $v->validated('message');
                try
                {
                    $res = $this->fb->api(array(
                        'method' => 'stream.publish',
                        'message' => $message,
                    ));
                    Session::set_flash('notice', 'complete!!');
                }
                catch (FacebookApiException $e)
                {
                    Session::set_flash('notice', $e->getMessage());
                }
                Response::redirect('index/index/');
            }
        }

        $this->template->content = View::forge('index/index',$data);
    }

    public function action_login()
    {
        $url = $this->fb->getLoginUrl(Config::get('facebook.login'));
        Response::redirect($url);
    }

    public function action_callback()
    {
        try
        {
            $me = $this->fb->api('/me');

            $user = User::find_by_facebook_id($me['id']);
            if(!$user) $user = new User;

            $user->facebook_id = $me['id'];
            $user->facebook_name = $me['name'];
            $user->facebook_link = $me['link'];

            $user->save();
            Response::redirect('/index/index/');
        }
        catch (Orm\ValidationFailed $e)
        {
            throw new Exception($e->getMessage());
        }
        catch (FacebookApiException $e)
        {
            throw new Exception($e->getMessage());
        }
    }

    public function action_logout()
    {
        $url = $this->fb->getLogoutUrl(Config::get('facebook.logout'));
        $this->fb->destroySession();
        Response::redirect($url);
    }

}
【ポイント】
以下の順を想定して、各メソッドのポイントを記載します。
(1) http://127.0.0.1/fuelfb/public/index/index/ にアクセス。
(2) "Login with Facebook."をクリック。
(3) Facebook認証をして http://127.0.0.1/fuelfb/public/index/index/ に戻ってくる。
(4) Facebookのウォールへ投稿。
(5) ログアウト。

■before()
Facebookオブジェクト($fb)の初期化。全アクションに対しての、事前処理みたいな感じです。

■action_login()
Facebook認証画面にリダイレクトします。
fuel/app/config/custom.phpのfacebook.loginには、
認証後のリダイレクト先とパーミッション設定が設定してあるはずです。

■action_callback()
Facebook認証後のリダイレクト先になります。

$me = $this->fb->api('/me');
自身のFacebook情報を取得しています。

$user = User::find_by_facebook_id($me['id']);
usersテーブルに対してFacebookのIDで検索を行なっています。
find_by_[カラム名]はFuelPHPのORMが標準に提供する検索機能です。

$user->save();
前述のfind_by_facebook_idでデータがヒットしなければinsert。
ヒットすればupdateを行います。

Response::redirect('/index/index/');
action_index()へリダイレクトします。

catch (Orm\ValidationFailed $e)
ORMモデルにバリデーションルールを書きましたが、それに反する場合、ここに入ります。
今回はユーザ入力データでは無いので、実質発生しないはずです。
なので、画面表示はしていません。

catch (FacebookApiException $e)
$this->fb->api('/me'); でエラーが発生した場合、ここに入ります。
その他、FacebookのAPIでエラーが発生した場合も、FacebookApiExceptionが発生します。

■action_logout()
Facebookのセッションを破棄してリダイレクトします。
リダイレクト先はfuel/app/config/custom.phpのfacebook.logout.nextに書いてあるはずです。

■action_index()
if($is_login and Input::method() == 'POST')
フォームに入力してsubmitボタンを押すと、この中に入ります。

$v = Validation::forge();
$v->add('message', 'message')->add_rule('required');
if(!$v->run())
{
    Session::set_flash('notice', $v->errors('message')->get_message());
}
必須チェックを行なっています。
入力が空だった場合、エラーメッセージを表示します。
具体的には、fuel/app/views/template.phpの
Session::get_flash('notice')
の箇所で表示されます。

$message = $v->validated('message');
try
{
    $res = $this->fb->api(array(
        'method' => 'stream.publish',
        'message' => $message,
    ));
    Session::set_flash('notice', 'complete!!');
}
catch (FacebookApiException $e)
{
    Session::set_flash('notice', $e->getMessage());
}
Response::redirect('index/index/');
バリデーション完了後のデータを取得して、Facebookのウォールに投稿(stream.publish)します。
FacebookApiExceptionについてはaction_callback()に記載した通りとなります。
最後にリダイレクトしているのは、リロード対策です。

■その他
再現性が乏しいのですが、Facebookの証明書エラーが発生するケースがありました。
その場合、ブラウザの関連するキャッシュをクリアして下さい。
また、XAMPPのApacheを再起動してみて下さい。
これで、私の場合、エラーが発生しなくなりました。


以下、画面キャプチャを掲載しておきます。

まずは、この画面が表示されるはずです。
Login with Facebook.をクリックして、Facebook側に飛びます。

Facebook側の認証画面です。
認証が済むと、今回作成したプログラム側に戻ります。

正しく実装されていると、この画面に戻るはずです。
textareaとpostボタンが表示されます。
postが正常に行われると、"complete!!"と表示されます。

この後、ログアウトをして、最初の画面に戻れば、一通りOKと思います。


以上、実装編でした。

fuel/app/classes/welcome.php等のゴミファイルが残っていたり
fuel/app/config/config.phpのbase_urlが空だったり
その他、色々とやり残したことはありますが、ミニマムな手順は、こんな感じと思います。

有難うございました。


明日の12日目は @mataga さんです。
FuelPHP動作実験 - 実験くんソースをModulesに閉じ込めてモジュール分割してみる。


P.S.
次回は、軽めのネタでいきます。。。

December 10, 2011

nyaosを起動するbatファイル

aptanaはgitを使うことでターミナルが使えるようになり、各種コマンドの入力に不便は無いのですが、
eclipseやmotodevだと少々、不便。

gitにしてもsvnにしても、ガツガツと作業をする時は、やはりコマンドを使いたい。
ただ、そのためだけにaptanaプラグインを入れるのは。。。
コマンドプロンプトは大嫌いだし。。。

というワケで、愛用のnyaosを起動するbatファイルを作ってみました。
3行ですが。。。
このbatファイルを実行した場所を開きます。そのままダブルクリックで起動できました。

--nyaos.bat--
%~d0
cd %~p0
C:\xxx\nyaos-3.2.0_0-win\nyaos.exe
--

nyaosのDLはこちらから。
http://www.nyaos.org/

Subclipseのsvn:ignoreについてメモ。

参考:
http://techracho.bpsinc.jp/baba/2009_12_08/727

ポイントは、ディレクトリを先にcommit。
その後、そのディレクトリの右クリから
"プロパティを設定" > "svn:ignore" > "*"

SubclipseでSSHメモ。

Windows > Preferences > Team > SVN > SVNインターフェス > SVNKit (Pure Java) SVNKit v[バージョンNO]
にする。

svn+ssh://xxxのリポジトリロケーションを追加するとダイアログが出るので、必要事項を入力。
鍵はOpenSSH形式であること。

参考:
http://symfony.jobweb.jp/?p=646

December 5, 2011

created_atやupdated_atの型をdatetimeやtimestampにする。

FuelPHPのORMはcreated_atやupdated_atというカラムに対して
* レコード作成時刻
* レコード更新時刻
をUNIXタイムスタンプで自動登録してくれる機能があります。

UNIXタイムスタンプとなると、カラムの型は必然的に整数値型になるわけですが、
datetime型やtimestamp型にしたい場合もあるはずです。

これは、以下の方法で、簡単に解決できました。
protected static $_observers = array(
    'Orm\Observer_CreatedAt' => array('events'=>array('before_insert'), 'mysql_timestamp' => true,),
    'Orm\Observer_UpdatedAt' => array('events'=>array('before_save'), 'mysql_timestamp' => true,),
);
詳細は
http://docs.fuelphp.com/packages/orm/observers/included.html
になります。

ドキュメントを見ると、同じ要領で、created_atやupdated_atというカラム名
を変えることも容易なようです。(未確認ですが。。。)

FuelPHPでFacebookアプリを作ってみよう。準備編。

FuelPHP Advent Calendar 2011 に参加させて頂きます。
@madmamor です。

5日目の本日は、FuelPHPでFacebookアプリのサンプル作成手順を紹介します。
FuelPHPのルールを意識しつつ、Facebookでの認証とウォールへの投稿を実装します。
要、Facebookアカウント。です。

とは言うものの、FuelPHPはまだまだ未知数な部分も多く、
もしかすると、よろしくない書き方が含まれているかもしれません。
その辺り含め、ディスカッションのネタになれば幸いです。

今回は、準備編です。敷居の低い環境を想定して話を進めます。
* windows + XAMPP(1.7.7)
* XAMPPはクリーンな状態。php.ini等、設定は一切触っていない。
* FuelPHPのバージョンは1.1-RC1

では、始めましょう。


【先に】
掲載するソースは、oilコマンドで自動生成されたファイルを基にしています。
但し、今回は、oilコマンド無しで作成出来る内容です。


【FuelPHPの配置】
(1)
XAMPPのドキュメントルート(htdocs下)に今回のアプリを配置するディレクトリを作成します。
"fuelfb"とします。


(2)
以下からFuelPHPをDL、解凍。fuelfb下に配置します。
http://fuelphp.com/

必要なものは
* fuelディレクトリ以下
* publicディレクトリ以下
* oilファイル(今回は不必要ですが、一応。)

xampp/
├ htdocs/
 ├ fuelfb/
  ├ fuel/
  ├ public/
  ├ oil


(3)
以下にアクセスできれば、ここまでの話はクリアです。
http://127.0.0.1/fuelfb/public/

【Facebookにアプリ登録】
(1)
Facebookにログインして、以下にアクセス。
https://developers.facebook.com/apps
* 初回アクセス時は開発者登録が必要なので、"許可する"をクリック。


(2)
* 右上の"+新しいアプリケーションを作成"をクリック
* "App Display Name:" にアプリ名を入力。(お好きな名前で。"face"とか"fb"とかは使えないようです。)
* "App Namespace:" は、とりあえず空で良いと思います。
* "I agree to the Facebook Platform Policies." にチェック。
* "続行"をクリック、セキュリティチェックを行い"送信"をクリック。
* "App ID:"と"App Secret:"の内容を控える。
* "アプリをFacebookに結合する方法を選択してください"で"ウェブサイト"をクリック。
* "サイトURL:"に、今回作成するWEBアプリのURLを入力。
... 今回は http://127.0.0.1/fuelfb/public/ になります。
* 一番下の"変更を保存"をクリック。


(4)
* 左上の"詳細設定"をクリック。
* "Sandbox Mode:"を"有効"にする。
... WEBに配置して公開する。となったら、"無効"にすることを忘れずに。
* 一番下の"変更を保存"をクリック。


Facebookのアプリ登録は、以上。


【Facebook PHP SDKのDLと配置】
認証やウォール投稿に必要となります。

(1)
Facebook PHP SDK
https://github.com/facebook/php-sdk
アーカイブは、以下からDL可能です。
https://github.com/facebook/php-sdk/downloads

必要なのはsrcディレクトリ以下です。
今回は、fuel/app/vendor下に"facebook-php-sdk"というディレクトリを作成。
その下に、DLしたsrcディレクトリを配置することにしましょう。
fuel/
├ app/
 ├ vendor/
  ├ facebook-php-sdk/[新規作成]
   ├ src/
    ├ base_facebook.php
    ├ facebook.php
    ├ fb_ca_chain_bundle.crt


【自分用のコンフィグファイルを作成】
色々と触りやすくする為、自分用のコンフィグファイルを作ります。
以下、次回用の設定を含みます。

(1)
fuel/app/config/custom.phpを作成します。
<?php
return array(
    'facebook' => array(
        'init' => array(
            'appId'  => 'YOUR_FACEBOOK_APP_ID',
            'secret' => 'YOUR_FACEBOOK_APP_SECRET',
        ),
        'login' => array(
            'redirect_uri' => 'http://127.0.0.1/fuelfb/public/index/callback/',
            'scope' => array('publish_stream',),
        ),
        'logout' => array(
            'next' => 'http://127.0.0.1/fuelfb/public/index/index/',
        ),
    ),
);

/* End of file custom.php */
'appId'と'secret'の値は、予め控えておいた、FacebookアプリのApp IDとApp Secretを書いて下さい。


(2)
fuel/app/config/config.phpを修正します。
下の方(214行目辺り)の
'config'  => array(),

'config'  => array(
    'custom' => null,
),
にして下さい。
先に作成した、fuel/app/config/custom.phpが自動で読み込まれるようになります。


【コントローラとビューの作成】
次に、ファイルを3つ、作成します。
* fuel/app/classes/controller/index.php
* fuel/app/views/template.php
* fuel/app/views/index/index.php (fuel/app/views/index/ディレクトリも併せて作成して下さい。)

ソース内容は、以下の通りです。


fuel/app/classes/controller/index.php
<?php
require_once APPPATH.'vendor/facebook-php-sdk/src/facebook.php';

class Controller_Index extends Controller_Template {

    private $fb;

    public function before()
    {
        parent::before();
        $this->fb = new Facebook(Config::get('facebook.init'));
    }

    public function action_index()
    {
        $this->template->title = 'Index » Index';

        $data = array(
            'is_login' => $this->fb->getUser()?true:false,
        );

        $this->template->content = View::forge('index/index',$data);
    }

    public function action_login()
    {
        exit('TODO : login');
    }

    public function action_callback()
    {
        exit('TODO : callback');
    }

    public function action_logout()
    {
        exit('TODO : logout');
    }

}
import('facebook-php-sdk/src/facebook', 'vendor');
としたかったのですが、どうも、fuel/core/vendor下に配置しないとエラーになるようです。
なので今回は、require_onceの形を取っています。

fuel/app/views/template.php
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title><?php echo $title; ?></title>
    <style type="text/css">
        * { margin: 0; padding: 0; }
        body { background-color: #EEE; font-family: sans-serif; font-size: 16px; line-height: 20px; margin: 40px; }
        #wrapper { padding: 30px; background: #fff; color: #333; margin: 0 auto; width: 600px; }
        a { color: #36428D; }
        h1 { color: #000; font-size: 55px; padding: 0 0 25px; line-height: 1em; }
        .intro { font-size: 22px; line-height: 30px; font-family: georgia, serif; color: #555; padding: 29px 0 20px; border-top: 1px solid #CCC; }
        .notice { border: 1px solid #CCC; padding: 10px; background-color: #EEE; }
        h2 { margin: 50px 0 15px; padding: 0 0 10px; font-size: 18px; border-bottom: 1px dashed #ccc; }
        h2.first { margin: 10px 0 15px; }
        p { margin: 0 0 15px; line-height: 22px;}
        a { color: #666; }
        pre { border-left: 1px solid #ddd; line-height:20px; margin:20px; padding-left:1em; font-size: 16px; }
        pre, code { color:#137F80; font-family: Courier, monospace; }
        ul { margin: 15px 30px; }
        li { line-height: 24px;}
        label { display: block; }
        .footer { color: #777; font-size: 12px; margin: 40px 0 0 0; }
    </style>
</head>
<body>
    <div id="wrapper">
        <h1><?php echo $title; ?></h1>
        <?php if (Session::get_flash('notice')): ?>
            <div class="notice"><p><?php echo implode('</p><p>', (array) Session::get_flash('notice')); ?></div></p>
        <?php endif; ?>

        <?php echo $content; ?>

        <p class="footer">
            <a href="http://fuelphp.com">Fuel</a> is released under the MIT license.<br />Page rendered in {exec_time}s using {mem_usage}mb of memory.
        </p>
    </div>
</body>
</html>
なんだか良くわからない。。。と思われた方も、今回は、気にしなくて良いです。
とりあえず、ベタ貼りで良いです。

fuel/app/views/index/index.php
<p>Index</p>

<?php if ($is_login): ?>
<a href="http://127.0.0.1/fuelfb/public/index/logout/">Logout.</a>
<?php else: ?>
<a href="http://127.0.0.1/fuelfb/public/index/login/">Login with Facebook.</a>
<?php endif; ?>

これら3ファイルは、oilコマンドで自動生成して、若干の修正を施した内容です。
(fuel/app/views/template.phpは、全くそのままです。)


【XAMPPのphp.ini修正】
xampp/php/php.iniの
;extension=php_curl.dll

extension=php_curl.dll
にします。
Facebook PHP SDKを使用する際に必要です。
ここで、XAMPPのApacheを再起動して下さい。


【確認】
http://127.0.0.1/fuelfb/public/index/index/
にアクセスして、以下が表示されれば、OKです。
fuel/app/classes/controller/index.phpのaction_index()が呼び出されました。
"index"の部分がfuel/app/views/index/index.phpです。
外枠にはfuel/app/views/template.phpが使用されています。


今回は、ここまでです。
とりあえず、ベタ貼りで動けばOKです。
後で改造して挙動を伺って頂ければと思います。
お疲れ様でした。

次回は、
DB(テーブル)作成、モデル作成、認証&ウォール投稿機能作成。
をバリデーション付きで、一気に行います。
ポイントとなりそうな箇所の解説も併記します。


■4日目
@fukata さん
[FuelPHP]Validationの使い方
次回予定している実装編の参考にさせて頂きます。

■6日目
@9ensan さん
FuelPHPで作るログイン管理
ログイン周りはまだ見ていなかったので、助かります。

P.S.
祝、10人突破!!


2011/12/11 追記
実装編の記事は、こちらになります。
FuelPHPでFacebookアプリを作ってみよう。実装編。

December 3, 2011

予約投稿のテストです。

2011/12/03 18:30に投稿されれば成功です。

Orm\Modelが提供する検索機能。

以下のテーブルとOrm\Modelがあるとします。

create table twitter_users
(
    id int not null auto_increment,
    created_at int not null,
    updated_at int not null,
    twitter_user_id varchar(255) not null unique,
    twitter_screen_name varchar(255) not null,
    twitter_oauth_token varchar(255) not null,
    twitter_oauth_token_secret varchar(255) not null,
    primary key (id)
);

namespace Model;
 
class Twitter_User extends \Orm\Model
{

    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array('events'=>array('before_insert')),
        'Orm\Observer_UpdatedAt' => array('events'=>array('before_save')),
    );  

}

これだけで
$twitter_user = Twitter_User::find_by_twitter_screen_name([screen_name]);
のような感じで、検索出来るようです。

Model_Crudだとどうなのだろう。個人的にはあまり使わなそうだけど。。。


2011/12/03 追記
Twitter_User::delete_by_twitter_screen_name([screen_name]);
は出来なかった。
この辺が実装されると、より強烈になる気がする。

Orm\Modelで$_observersを書く場合の注意

以前に書いた、以下2つの記事に関係します。

▼Orm\Modelでバリデーションを定義する。
http://madroom-project.blogspot.com/2011/11/ormmodel_28.html

▼Orm\Modelでリレーショナル型データベースを表現する。
http://madroom-project.blogspot.com/2011/11/ormmodel.html

サンプルとして、以下のようなコードを記載しました。
protected static $_observers = array(
    'Orm\Observer_CreatedAt' => array('before_insert'),
    'Orm\Observer_UpdatedAt' => array('before_save'),
    'Orm\Observer_Validation'=> array('before_save'),
);

どうも、以下の形を取るべきなようです。
protected static $_observers = array(
    'Orm\Observer_CreatedAt' => array('events'=>array('before_insert')),
    'Orm\Observer_UpdatedAt' => array('events'=>array('before_save')),
    'Orm\Observer_Validation'=> array('events'=>array('before_save')),
);

以下のWarningがログに吐かれていました。
--
Orm\Model::observers - Passing observer events as array is deprecated, they must be
inside another array under a key "events". Check the docs for more info.
--

慣れるまでは頻繁にログ見るようにしなくちゃな。。。

November 29, 2011

FuelPHP Advent Calendar 2011

中々こういった機会も無いと思いますので、参加させて頂きます。

▼FuelPHP Advent Calendar 2011
http://atnd.org/events/22380

▼FuelPHP Advent Calendar 2011 が始まります!
http://d.hatena.ne.jp/Kenji_s/20111129/1322565994


結果論になってしまいますが、フライングし過ぎた感がある。。。
先にイベントを知っていたら、ネタを溜め込みまくって全然書いていなかった事でしょう。

どうせだから、ココまでのまとめ的な記事を書こうかなー。

November 28, 2011

Orm\Modelでバリデーションを定義する。

2011/12/03 追記
こちらも併せて御覧ください。
http://madroom-project.blogspot.com/2011/12/ormmodelobservers.html



前回の記事に書いたモデルにバリデーションを追加してみます。
http://madroom-project.blogspot.com/2011/11/ormmodel.html

\app\classes\model\twitter\user.php
namespace Model;

class Twitter_User extends \Orm\Model
{
 
    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array('before_insert'),
        'Orm\Observer_UpdatedAt' => array('before_save'),
        'Orm\Observer_Validation' => array('before_save'),
    );

    protected static $_properties = array(
        'id', 
        'created_at',
        'updated_at',
        'user_id',
        'twitter_user_id' => array(
            'validation' => array(
                'trim',
                'required',
                'max_length' => array(255),
                'valid_string' => array('integer'),
            ),
        ),
        'twitter_screen_name' => array(
            'validation' => array(
                'trim',
                'required',
                'max_length' => array(255),
                'match_pattern' => array('/^([a-zA-Z0-9_])+$/'),
            ),
        ),
        'twitter_oauth_token' => array(
            'validation' => array(
                'trim',
                'required',
                'max_length' => array(255),
                'match_pattern' => array('/^([a-zA-Z0-9\-])+$/'),
            ),
        ),
        'twitter_oauth_token_secret' => array(
            'validation' => array(
                'trim',
                'required',
                'max_length' => array(255),
                'valid_string' => array('alpha_numeric'),
            ),
        ),
    );

    protected static $_belongs_to = array(
        'user' => array(
            'key_from' => 'user_id',
            'model_to' => 'Model\User',
            'key_to' => 'id',
            'cascade_save' => false, // trueにすると一切の変更がない場合のsave()でエラーとなる。
            'cascade_delete' => false, // trueにすると削除時に親も消してしまう。注意。
        )
    );
}
match_patternの内容からrequired要らないじゃん。とかは、無しでお願いします。。。
その他、ツイッタの認証情報に対する検証精度は保証出来ません。。。


ポイントは
* $_observersに'Orm\Observer_Validation' => array('before_save')を追加すること。
* $_propertiesにカラム毎の定義を書くこと。
の二点と思います。
尚、$_propertiesには、内部でしか使用しないidやcreated_atも書いておかないとエラーになりました。

バリデーションの細かな使い方は
\core\classes\validation.php
を見て、なんとなく確認しました。

で、気づいた方もいらっしゃるかと思いますが、各バリデーションに'trim'と書いています。
CodeIgniterでは、以下のような事がバリデーションで可能です。
http://codeigniter.jp/user_guide_ja/libraries/form_validation.html
> Note: また、 trim、htmlspecialchars、urldecode などの引数を1つだけとる
> PHP の組み込み関数をどれでも使用することができます。

FuelPHPでも見事、出来ました。
恐らく、書いた順に実行されるはずなので、md5する場合とかは、一番最後に書きましょう。


追記:
使い方を書いていなかったので、書いておきます。
$twitter_userは前述のTwitter_Userモデルです。
try
{
    $twitter_user->save();
    // TODO: 成功時の処理
}
catch (Orm\ValidationFailed $e)
{
    // TODO: エラー時の処理(エラーメッセージは $e->getMessage() で取得可。)
}

参考:
http://docs.fuelphp.com/packages/orm/creating_models.html
http://docs.fuelphp.com/packages/orm/observers/included.html

Orm\Modelでリレーショナル型データベースを表現する。

2011/12/03 追記
こちらも併せて御覧ください。
http://madroom-project.blogspot.com/2011/12/ormmodelobservers.html



まず、以下2つのテーブルがあることとします。
usersが親。twitter_usersが子。
users.id = twitter_users.user_idの関係です。
create table users
(
    id int not null auto_increment,
    created_at int not null,
    updated_at int not null,
    primary key (id)
);
create table twitter_users
(
    id int not null auto_increment,
    created_at int not null,
    updated_at int not null,
    user_id int not null unique,
    twitter_user_id varchar(255) not null unique,
    twitter_screen_name varchar(255) not null,
    twitter_oauth_token varchar(255) not null,
    twitter_oauth_token_secret varchar(255) not null,
    primary key (id)
);
一見、1:1なので、usersにまとめられそうですが、その辺りは大人の事情です。
これをOrm\Modelで表現するにはどうするか。が今回の話です。


まず、親となるusersテーブルに対するモデル。
app\classes\model\user.php
namespace Model;

class User extends \Orm\Model
{

    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array('before_insert'),
        'Orm\Observer_UpdatedAt' => array('before_save'),
    );

    protected static $_has_one = array(
        'twitter_user' => array(
            'key_from' => 'id',
            'model_to' => 'Model\Twitter_User',
            'key_to' => 'user_id',
            'cascade_save' => true, // true/falseどちらで有るべきか、要調査。とりあえずはドキュメント通り。
            'cascade_delete' => true, // falseにすると、削除時にtwitter_userが削除されなかった。
        )
    );
}

次に、子となるtwitter_usersテーブルに対するモデル。
\app\classes\model\twitter\user.php
namespace Model;

class Twitter_User extends \Orm\Model
{
 
    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array('before_insert'),
        'Orm\Observer_UpdatedAt' => array('before_save'),
    );

    protected static $_belongs_to = array(
        'user' => array(
            'key_from' => 'user_id',
            'model_to' => 'Model\User',
            'key_to' => 'id',
            'cascade_save' => false, // trueにすると一切の変更がない場合のsave()でエラーとなる。
            'cascade_delete' => false, // trueにすると削除時に親も消してしまう。注意。
        )
    );
}

* 親のモデルは、子のモデルを$_has_oneで定義する。
* 子のモデルは、親のモデルを$_belongs_toで定義する。
みたいなニュアンスでしょうか。(果たして認識は正しいだろうか。。。)
尚、'cascade_save'と'cascade_delete'については、実は良くわかっていません。。。
とりあえずドキュメントのままです。(今度調べよう。名前からなんとなく憶測はできるけど。。。)


これで、例えば検索は
$user = User::find_by_id(1);
すると
$screen_name = $user->twitter_user->twitter_screen_name;
とか出来ます。
$user->twitter_userは、存在しない場合、nullになるようです。


saveする場合も、例えば
$twitter_user = new Twitter_User;
$twitter_user->user = new User;
して、
$twitter_user->twitter_user_id = [Twitterのuser_id];
や、その他のカラム値をセットして
$twitter_user->save();
すると、親を含めて一発登録出来ました。

追記:(上記ソースには以下の対処を反映済。)
上記の方法でsaveした時、一切の変更がない場合に、以下のエラーが発生しました。
Orm\FrozenObject [ Error ]: No changes allowed.
子のモデル(\app\classes\model\twitter\user.php)の$_belongs_toの
'cascade_save'をfalseに変更したところ、発生はしなくなりましたが、
一切の変更がない場合でも、updateが走るようになりました。
(updated_atのみが更新されます。)
とりあえず問題は無いですが、この辺りはしっかりと把握しておきたいなぁ。


参考:
http://docs.fuelphp.com/packages/orm/relations/belongs_to.html
http://docs.fuelphp.com/packages/orm/relations/has_one.html


P.S.
それぞれのモデルで$_observersというのがありますが、
これは、今回の話にはあまり関係有りません。。。

FuelPHPでオリジナルconfigファイルの作成

FuelPHPの設定ファイルはcore/config/下に配置され、
設定を変更する場合はapp/config/下にコピー。編集。
が基本です。

また、app/config/[環境別ディレクトリ]/下にコピーすれば、それが最優先されるはずです。

以下の関係と認識しています。
app/config/[環境別ディレクトリ]/ > app/config/ > core/config/

最も基本となる設定ファイルはconfig.phpですが、
例えば先日書いた記事のTwitterのOauth関係の設定をconfig.phpに追記したくはありません。
http://madroom-project.blogspot.com/2011/11/fuelphp-zend-framework-twitteroauth.html

そこで、オリジナルの設定ファイルが欲しくなり、以下を参考に、やってみました。
http://docs.fuelphp.com/classes/config.html


(1)
まず
app/config/_custom.php
を作成。
ファイル名ソートの関係で先頭にアンダースコアを付与しています。
この辺りは、当然、お好きに。
return array(
    '_twitter' => array(
        'callbackUrl' => '[callbackUrl]',
        'siteUrl' => 'http://twitter.com/oauth',
        'consumerKey' => '[consumerKey]',
        'consumerSecret' => '[consumerSecret]',
        'authorizeUrl' => 'http://twitter.com/oauth/authenticate',
    ),
);

/* End of file _custom.php */

(2)
config/config.phpの'always_load'の'config'を以下にする。
'config'  => array(
    '_custom' => null,
),
valueにnullを指定してやらないと、どうも、ダメでした。
この点については、後日、調査しよう。。。

尚、手動でロードする場合は、コントローラのbeforeメソッドなどに、以下を記述。
Config::load('_custom');

これでConfig::getにて、app/config/_custom.phpの内容を取得できます。
(1)の設定の取得は、Config::get('_twitter')です。

--
2011/12/21 追記
Config::get('_twitter.callbackUrl')といった感じで、ドット繋ぎで取得することも可能です。
--


(3)
一応、app/config/[環境別ディレクトリ]/_custom.phpも作成して確認しましたが
期待通り、環境別の切り分けもOKでした。

FuelPHPのLogクラスを使いやすくする

2011/12/14 追記
拡張したLogクラスを改良して、掲載ソースを更新しました。


例えばdebugログの出力は
Log::debug('DEBUG');

となります。

これでも良いのですが、Logクラスは頻繁に使用するはずなので
Log::d('DEBUG');
のようにして使いたい。

という訳で、拡張します。

(1)
app/classes/log.phpを作成。
<?php

class Log extends Fuel\Core\Log
{
    public static function i($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::info($msg, $method);
    }
    public static function d($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::debug($msg, $method);
    }
    public static function w($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::warning($msg, $method);
    }
    public static function e($msg, $method = null)
    {
        if(!is_scalar($msg)) $msg = print_r($msg,true);
        return parent::error($msg, $method);
    }
} 

(2)
app/bootstrap.phpのAutoloader::add_classesにapp/classes/log.phpを追加。
Autoloader::add_classes(array(
    // Add classes you want to override here
    // Example: 'View' => APPPATH.'classes/view.php',
    'Log' => APPPATH.'classes/log.php',
));

(3)
これで、
Log::d('DEBUG');
とかで使用出来ます。

余談ですが、ログファイルへの出力ログレベルはapp/config/config.phpの'log_threshold'です。
環境別に切り替えるには、app/[環境別ディレクトリ]/config.phpを作成すればOKです。
return array(
    'log_threshold'    => Fuel::L_DEBUG,
);

/* End of file config.php */
今回は書いていませんが、'base_url'とかもここに入ってくるでしょう。



参考:
http://docs.fuelphp.com/general/extending_core.html

November 27, 2011

FuelPHP + Zend Framework でTwitterのOauthをする

中途半端な部分もありますが、コントローラのサンプルを書いておきます。

Zendは1.11.10を使いました。(2にしたいな。。。)

printしている箇所以降は、実際にはDB保存したり、
認証完了後のURLにリダイレクトしたり。となります。

バリデーションについては書いていませんが、実際には、当然、行いましょう。

$accessToken->user_idでツイッタAPIに問い合わせて、
再度ユーザ情報を取得し直せば、より安全と思います。

signinアクションにアクセスするとツイッタにリダイレクトされます。
ツイッタでの認証後、callbackに戻る流れです。

以下の3箇所は、適切な内容に書き換えて下さい。
* [This Controller URL]
* [consumerKey]
* [consumerSecret]

Session::set('twitter', array('accessToken' => serialize($accessToken)));
の箇所は、他所で
$twitter = Session::get('twitter');
$accessToken = unserialize($twitter['accessToken']);
のようにして使用される想定です。

class Controller_Twitter extends Controller_Template {

    public function before()
    {
        parent::before();
        require_once 'Zend/Loader/Autoloader.php';
        Zend_Loader_Autoloader::getInstance();
    }

    public function action_signin()
    {
        $twitter = array(
            'callbackUrl' => '[This Controller URL]/callback/',
            'siteUrl' => 'http://twitter.com/oauth',
            'consumerKey' => '[consumerKey]',
            'consumerSecret' => '[consumerSecret]',
            'authorizeUrl' => 'http://twitter.com/oauth/authenticate',
        );

        $zoc = new Zend_Oauth_Consumer($twitter);
        $requestToken = $zoc->getRequestToken();

        Session::set('zoc', $zoc);
        Session::set('twitter', array('requestToken' => serialize($requestToken)));
        Session::write();

        $zoc->redirect();
    }

    public function action_callback()
    {
        $sess = Session::get('twitter');
        $zoc = Session::get('zoc');

        if ($_GET && $sess['requestToken'])
        {
            $requestToken = unserialize($sess['requestToken']);
            $accessToken = $zoc->getAccessToken($_GET, $requestToken);
            Session::set('twitter', array('accessToken' => serialize($accessToken)));

            print(' [user_id] : '.$accessToken->user_id);
            print(' [screen_name] : '.$accessToken->screen_name);
            print(' [oauth_token] : '.$accessToken->oauth_token);
            print(' [oauth_token_secret] : '.$accessToken->oauth_token_secret);
            exit();
        }
        else
        {
            Request::show_404();
        }
    }
}

oil generate scaffoldでOrm\Modelを継承したModelを生成

先日、以下の記事を書きました。
http://madroom-project.blogspot.com/2011/11/windows-xamppfuelphp.html

この時、postsテーブルにはcreated_atというカラムと
updated_atというカラムが作成されました。
これらのカラムは、oil generate scaffoldで作成されたmigrationファイルに、
デフォルトで含まれています。

カラム名から察するに、insert時やupdate時に、自動でタイムスタンプがsetされるのだろう。
と思っていましたが、どうも、0という値が入り、タイムスタンプが入りません。

原因は、生成されたModelが、Model_Crudを継承していて、
Orm\Modelを継承していないからでした。

Orm\Modelを継承したModelを作成するには、
"--orm"を付与してoil generate scaffoldを実行する必要があります。
尚、"generate"は"g"と略せます。具体的には、以下のようなコマンドになります。
$ php oil g scaffold post title:string summary:varchar[250] body:text --orm

これで、Orm\Modelを継承したModelが作成されました。
namespace Model;

class Post extends \Orm\Model
{
 
    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array('before_insert'),
        'Orm\Observer_UpdatedAt' => array('before_save'),
    );
}

また、app/config.phpのalways_loadのpackagesで、ormを有効にする必要があります。
'always_load'  => array(

    /**
     * These packages are loaded on Fuel's startup.  You can specify them in
     * the following manner:
     *
     * array('auth'); // This will assume the packages are in PKGPATH
     *
     * // Use this format to specify the path to the package explicitly
     * array(
     *     array('auth'    => PKGPATH.'auth/')
     * );
     */
    'packages'  => array(
        'orm',
    ),

これでOKと思いきや、postsコントローラにアクセスすると、以下のエラーが発生。
--
Fuel\Core\Database_Exception [ 1054 ]: Unknown column 't0.' in 'where clause' [ SELECT `t0`.`id` AS `t0_c0`, `t0`.`title` AS `t0_c1`, `t0`.`summary` AS `t0_c2`, `t0`.`body` AS `t0_c3`, `t0`.`created_at` AS `t0_c4`, `t0`.`updated_at` AS `t0_c5` FROM `posts` AS `t0` WHERE `t0`.`id` = '0' OR ((`t0`.`` IS null)) LIMIT 1 ]
--

このエラーは、自動生成されたコントローラの
$data['posts'] = Post::find_all();

$data['posts'] = Post::find('all');
にすることで解決出来ました。

これでinsertやupdateをしてやると、無事、created_atやupdated_atに
UNIXタイムスタンプが入るようになりました。


参考:
http://docs.fuelphp.com/packages/oil/generate.html

November 26, 2011

windows + xampp にPHPUnitをインストールする。

手順のメモ。

* xamppはCドライブ直下にあることとする。
* 以下のコマンドはxampp/php下で実行。
* 管理者として実行。

(1) pearチャンネルのアップデート
pear update-channels

(2) PEAR更新
pear upgrade pear

(3) 古いPHPUnit削除
pear uninstall phpunit
pear uninstall phpunit2

(4) 一応、以下も手動で削除
xampp/php/PEAR/PHPUnit
xampp/php/PEAR/PHPUnit2

(5) チャンネル追加
pear channel-discover pear.phpunit.de
pear channel-discover components.ez.no
pear channel-discover pear.symfony-project.com

(6) php.iniを編集
extension=php_curl.dll
を有効に。

(7) PHPUnitをインストール
pear install phpunit/PHPUnit

(8) 確認
phpunit --version
でバージョンが表示されること。


■参考
PHPUnitをXAMPPにインストール
http://lazesoftware.com/blog/11/0213/

windows + xamppでFuelPHPのブログチュートリアル

oilコマンドの使用を前提にして書きます。
http://madroom-project.blogspot.com/2011/11/windows-xampp-fuelphpoil.html

以下を参考にさせて頂きました。というか、途中までは殆どそのままです。
FuelPHP のブログチュートリアル:
http://d.hatena.ne.jp/Kenji_s/20111109/1320827056


では、手順をメモ。


(1)
development環境なので、app/config/development/db.phpを修正。
return array(
    'default' => array(
        'type'   => 'mysql',
        'connection' => array(
            'hostname'  => 'localhost',
            'port'   => '3306',
            'database'  => 'fuel_sample_blog',
            'username'  => 'root',
            'password'  => '',
            'persistent' => false,
        ),
    ),
);
* DBのfuel_sample_blogは予め作成しておくこと。
(persistentって何だろう。。。)


(2)
$ php oil generate scaffold post title:string summary:varchar[250] body:text
を実行。以下が生成されました。
app/classes/model/post.php
app/migrations/001_create_posts.php
app/classes/controller/posts.php
app/views/posts/index.php
app/views/posts/view.php
app/views/posts/create.php
app/views/posts/edit.php
app/views/posts/_form.php
app/views/template.php


(3)
$ php oil refine migrate
を実行。
fuel_sample_blogにmigrationテーブルとpostsテーブルが作成されました。
app/config/migrations.phpも作成されました。(他にも何か作成される??)


(4)
Postsコントローラにアクセス。
"Add new Post"で登録。
"View"、"Edit"、"Delete"で表示、編集、削除。


(5)
$ php oil refine migrate:down
を実行すると、postsテーブルがDropされました。
その後、(3)を実行すると再作成されました。



ここからは実験的な内容です。



(6)
これだけだとあまり面白く無いので、試しに
app/migrations/002_create_posts.php
を作成。
(自動で作る方法もあるのかな??というか、たぶんこの方法は誤った方法なのだと思う。)
namespace Fuel\Migrations;

class Create_posts {

    public function up()
    {
        \DBUtil::add_fields('posts', array(
            'add_field' => array('type' => 'text'),
        ));
    }

    public function down()
    {
        \DBUtil::drop_table('posts');
    }
}
$ php oil refine migrate
を実行してみると、insertしたレコードはそのままに、add_fieldカラムが追加されました。
app/config/migrations.phpの内容も自動更新されるようです。


(7)
$ php oil refine migrate --version=1
を実行すると
Migrated app:default version: 1.
と表示されましたが、postsテーブルがDropされていました。

* app/migrations/001_create_posts.phpのdown()がコールされた??(version downのdown。か。)

この後、以下のコマンドがどれも上手くいきません。
$ php oil refine migrate
$ php oil refine migrate --version=1
$ php oil refine migrate --version=2
尚、migrationテーブルのversionは0になっていました。


(8)
app/migrations/002_create_posts.php
を削除して
$ php oil refine migrate
を実行すると、postsテーブルが再度作成されましたが、
当然、レコードは0件です。


(9)
備考。
app/migrations/002_create_posts.php
に"DBUtil::add_fields"と書きましたが、これは
fuel/core/classes/dbutil.phpを参考にしました。
他にも色々ありますね。

とりあえず、postsコントローラの内容を見ながら、
FuelPHPにおけるDB周りの作法の基本を学びたい。


参考:
http://docs.fuelphp.com/classes/database/introduction.html
http://press.nekoget.com/fuelphp_doc/general/migrations.html

windows + xampp でFuelPHPのoilコマンド。

windows + xamppでoilコマンド使えないかなーということで。

以下に書かれているコマンドに成功しました。
http://press.nekoget.com/fuelphp_doc/packages/oil/generate.html
具体的には "$ php oil g controller posts action1 action2 action3" です。

手順をメモします。
尚、以下のコマンドは全て"oil"ファイルがある場所で実行しています。
すなわち、fuelディレクトリやpublicディレクトリがある場所です。


(1)
まず、php.exeにパスを通します。
xampp/phpディレクトリになります。

パスを通すのには、以下が便利です。
■Redmond Path
http://www.forest.impress.co.jp/lib/sys/wincust/registry/redmondpath.html

また、コマンドプロンプトは個人的に使いにくく、NYAOSがおすすめです。
UNIXライクです。現在の最新は"NYAOS 3.x"です。
今回はwindowsの話なので"Windows binary"をDLすれば良いかと。

■NYAOS
http://www.nyaos.org/index.cgi


(2)
ココまでの話での備考。

実は今回はNYAOSではなく、Aptana + portablegitによる
Aptanaのターミナルから実行して確認しています。
尚、パスを通した後に再起動必要かも。


(3)
とりあえずhelpを実行してみます。
$ php oil help

以下が表示されれば、めでたし。
--
Usage:
php oil [cells|console|generate|help|test]

Runtime options:
-f, [--force] # Overwrite files that already exist
-s, [--skip] # Skip files that already exist
-q, [--quiet] # Supress status output
-t, [--speak] # Speak errors in a robot voice

Description:
The 'oil' command can be used in several ways to facilitate quick development, help with
testing your application and for running Tasks.

Documentation:
http://fuelphp.com/docs/packages/oil/intro.html
--


(4)
$ php oil g controller posts action1 action2 action3
も実行してみましょう。ごそごそとファイルが生成されればOKと思います。


(5)
上記、coreディレクトリやpackagesディレクトリを移動して無ければうまくいくはずですが、
移動している場合、もう少し、やることがあります。

私の場合、coreディレクトリやpackagesディレクトリは
http://madroom-project.blogspot.com/2011/11/windows-xampp-fuelphp.html
のように、共通的な場所(各種ライブラリフォルダ)に移動させているので、
前述のコマンドを実行するとエラーになりました。

なので、oilファイルを修正します。
とは言っても、coreディレクトリやpackagesディレクトリの移動に伴う
public/index.phpの修正と似たような感じです。

define('APPPATH', realpath(__DIR__.'/fuel/app/').DIRECTORY_SEPARATOR);
の下に
require_once(APPPATH.'config/config.localhost.php');
を記述。
oilはローカルで実行できれば良いので、ハードコーディング。
config/config.localhost.phpについては、前述のURLの記事を御覧ください。

PKGPATHとCOREPATHの宣言部を修正。
それぞれ、以下にする。
define('PKGPATH', realpath(ENV_PKGPATH).DIRECTORY_SEPARATOR);
define('COREPATH', realpath(ENV_COREPATH).DIRECTORY_SEPARATOR);

これで、コマンド実行時のエラーは発生しなくなりました。
ローカル用のconfigファイルも共通化出来たし、良かった。

November 25, 2011

FuelPHPでSmartyの設定とかのカスタマイズについて。

FuelPHPでSmartyを動かすまでの手順は、以下を参考にして下さい。
http://madroom-project.blogspot.com/2011/11/fuelphpsmarty.html

まず、Smartyの設定は
fuel/packages/parser/parser.php(以下、parser.php)
に書いてありました。

そして、この設定は
fuel/packages/parser/classes/view/smarty.php(以下、smarty.php)
で読み込まれるようです。

smarty.phpを見るとわかるように、例えば
$smarty->auto_literal
は有りません。

設定する必要があるならば、smarty.phpに以下を追記。
static::$_parser->auto_literal = \Config::get('parser.View_Smarty.auto_literal', false);
parser.phpの'View_Smarty'直下に'auto_literal'の設定を追加。
といった具合でしょうか。


尚、parser.phpには"app/config下にコピーして使ってください。"みたいなコメントがあります。
が、少しハマりました。。。
app/config/parser.phpを作成(オリジナルをコピー)して、
例えば'View_Smarty'の'delimiters'を
'delimiters'    => array('{%', '%}'),
にしても、全く変化が有りませんでした。

どうも、オリジナルのparser.phpでの設定が強いように見えます。
でも、'include'に関しては、オリジナルのparser.phpの'include'が無ければ、
コピーしたparser.phpの'include'を見に行く。という挙動を取るように見えます。
具体的には
packages/parser/classes/view.php の
// Include necessary files
foreach ((array) \Config::get('parser.'.$class.'.include', array()) as $include)
{
    if ( ! array_key_exists($include, static::$loaded_files))
    {
        require $include;
        static::$loaded_files[$include] = true;
    }
}
に刺さるのかなと。

うーん。出来ればオリジナルのファイルに手を加えたくない。
最もクリーンに拡張できる方法は何だろう。。。

そもそも、Smartyに大きな執着は有りませんが、調べ始めてしまったので、
とりあえずココまでは調査してみました。

FuelPHPでSmartyを使ってみる。

とりあえず、必要最小限の手順をメモします。

(1)
app/vendorにSmartyを配置。




(2)
app/config/config.phpの'always_load'の'packages'に'parser'を追加。
'always_load'  => array(

    /**
     * These packages are loaded on Fuel's startup.  You can specify them in
     * the following manner:
     *
     * array('auth'); // This will assume the packages are in PKGPATH
     *
     * // Use this format to specify the path to the package explicitly
     * array(
     *     array('auth'    => PKGPATH.'auth/')
     * );
     */
    'packages'  => array(
        //'orm',
        'parser',
    ),
(3)
アクセスするコントローラを修正。
今回は、DLしたFuelPHPに入っているdefaultのコントローラである、
app/classes/controller/welcome.phpを修正。
return Response::forge(View::forge('welcome/index'));

$data = array(
    'title' => 'TITLE',
    'body' => 'BODY',
);
return Response::forge(View_Smarty::forge('welcome/index', $data));
にする。

(4)
viewファイルの作成。
app/views/welcomeにindex.smartyを作成。
<!DOCTYPE html>
<html>
<head>
    <title>{$title}</title>
</head>
<body>
    <p>{$body}</p>
</body>
</html>


(5)
コントローラにアクセス。

November 24, 2011

windows + xamppにFuelPHPをインストールしてみる。

とりあえずローカルで触ってみよう。というわけで。
インストールと、自分的な最低限の調整をメモ。



■DL
http://fuelphp.com/

windowsのローカル環境にインストールするので、手動で。
http://docs.fuelphp.com/installation/instructions.html#/manual

DLして解凍して配置してpublic/index.phpにアクセスするだけでとりあえず動きました。



■環境別設定
(1)
まず、以下は、共通のライブラリフォルダに退避。
fuel/core
fuel/packages
(packagesは退避すべきか??後日確認。。。)


(2)
環境別の設定ファイルを作成。
とりあえずローカルで動かすので
fuel/app/config/config.localhost.php
を作成。以下を記述。

define('ENV_PKGPATH', 'xxx/fuel/packages/');
define('ENV_COREPATH','xxx/fuel/core/');
$_SERVER['FUEL_ENV'] = 'development'; // Fuel::DEVELOPMENT
//$_SERVER['FUEL_ENV'] = 'test'; // Fuel::TEST
//$_SERVER['FUEL_ENV'] = 'stage'; // Fuel::STAGE
//$_SERVER['FUEL_ENV'] = 'production'; // Fuel::PRODUCTION

環境が増えるに連れて
config.xxx.php
config.yyy.php
といった具合に環境別ファイルを用意。
尚、xxxやyyyの箇所は$_SERVER['SERVER_NAME']を指します。


(3)
public/index.phpを修正。

define('APPPATH', realpath(__DIR__.'/../fuel/app/').DIRECTORY_SEPARATOR);
の下に
require_once(APPPATH.'config/config.'.$_SERVER['SERVER_NAME'].'.php');
を追記。

PKGPATH定数の宣言箇所を以下に変更。
define('PKGPATH', realpath(ENV_PKGPATH).DIRECTORY_SEPARATOR);

COREPATH定数の宣言箇所を以下に変更。
define('COREPATH', realpath(ENV_COREPATH).DIRECTORY_SEPARATOR);

* 備考
$_SERVER['FUEL_ENV']の値を変更するだけでapp/config下のdevelopmentとかproductionを
自動で切り分けてくれると思われます。
まだ未確認ですが、構成的に間違い無いでしょう。

なので、環境別の設定は
* FuelPHP依存のconfigファイルは、app/configの環境別ディレクトリで対応。
* FuelPHP非依存のconfigファイルは、前述のconfig.*.phpで対応。
を前提としています。

November 20, 2011

Activityを開いた時、ソフトウェアキーボードが自動で表示されないようにする。

Activityを開いた時、EditTextにフォーカスが当たっていると、
ソフトウェアキーボードが勝手に開きます。

以下をonCreate等に記述すると勝手に開かなくなりました。
--
getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
--
LayoutParams は android.view.WindowManager.LayoutParams です。

参考:
http://y-anz-m.blogspot.com/2010/05/android_17.html

November 17, 2011

AndroidのWebViewでlocalhostにアクセスする場合のURL

例えばAndroid向けのWEBアプリをローカルのXAMPPで下ごしらえしている時とかに関係する話です。

WebViewがloadUrlするURLは

http://localhost/xxx
ではなく
http://10.0.2.2/xxx
にしましょう。との事です。

参考:
http://stackoverflow.com/questions/4336394/webview-and-localhost

Xperia arcを2.3.4にしたらバッテリの減りが激速になった件

タイトルの通りですが、僕の場合はどうやらK9Mailが悪さをしていたらしく
K9Mailをインスコしなおしたらアプデ前のような感じに戻りました。

ちなみにどれくらいの速さで減ったかというと

朝一(8:00くらい。)100%。
通勤中の電車でツイッタみたり音楽聴いたり。
会社に着いて(9:45くらい。)放置。
で、14時頃には尽きる感じでした。

もちろん、タスクキルアプリは入れているし
アプデ前とアプデ後とで、K9Mailの設定は一切変えていません。

細かな原因はわからないですが、良かった。

November 14, 2011

ReverbNationのAPI

まだ公開されていませんが、ページ下部に"API"というリンクがあります。
クリックするとemailの入力を促されます。
http://www.reverbnation.com/

非常に好きなサイトなので、email登録しておきました。
APIが公開された時にメールが届くことでしょう。

ReverbNationのAPIが公開されれば、WEBと音楽に関する開発が楽しくなりそうです。
本来は、Myspaceがこういったアプローチをすべきだったのだとも思う。

API公開と同時に公式アプリがリリースされて、Android/iOSの標準的なUIで
各アーティストの曲を試聴できるようになったりしないかな。

Androidだと管理者用のアプリは既に公式にリリースされているけど、
やはりリスナー用のが欲しいですよね。

と思ったら、そのアーティスト専用のアプリを生成してくれる機能があるっぽい。
http://www.reverbnation.com/main/overview_artist?feature=yourownapp
要、アーティスト側の有料登録。ですかね。

AndroidのタスクキルのIgnore Listメモ

【TasKillerのIgnore List】
■Battery Widget
更新されなくなるから。

■GooCal Widget
更新されなくなるから。

■POBox Touch
しぶとい為、ウィジェットが白くならないから。

■Sony Ericssonホーム
しぶとい為、ウィジェットが白くならないから。

■TasKiller
しぶとい為、ウィジェットが白くならないから。

■カレンダーの保存
しぶとい為、ウィジェットが白くならないから。

■ミュージック
再生が止まってしまうから。

■Lock screen notifications
しぶとい為、ウィジェットが白くならないから。

■k9 mail
メール受信しなくなるから。

【Task ManagerのIgnore List】
※理由は同上。
■GooCal Widget
■TasKiller
■k9 mail

ERROR: Unknown command 'crunch'の解決方法

Android SDK ManagerでUpdate availableのものを一通り更新。

参考:
http://nek-blog.blogspot.com/2011/11/android-error-unknown-command-crunch.html

November 12, 2011

Xperia arcの再生情報を取得する

DroidNPでXperia arcの再生情報を扱うためのプラグインをリリースしました。
https://market.android.com/details?id=net.madroom.dnp4sem

ソースは↓で公開しています。
https://github.com/mp-android/DroidNP4SEM


ソース自体簡単ですが、一応、解説。

receiverで以下を受け取るようにすれば反応します。
(1) com.sonyericsson.music.playbackcontrol.ACTION_TRACK_STARTED
(2) com.sonyericsson.music.playbackcontrol.ACTION_PAUSED
(3) com.sonyericsson.music.TRACK_COMPLETED

尚、DroidNPのプラグインでは(1)しか使っていません。他は必要なさそうだったので。
というか、実は(2)と(3)の動作確認はしてません。悪しからず。
どんなタイミングで発信されるか(そもそも、発信自体されるか)は、各自でご確認下さい。

アーティスト名とかは、以下で取れます。
intent.getExtras().getString("ARTIST_NAME")
intent.getExtras().getString("ALBUM_NAME")
intent.getExtras().getString("TRACK_NAME")

この話、docomoのXperia arcでのみ確認しています。
auのとかacroとかでもたぶん取れると思いますが、未確認。
尚、X10では取得できませんでした。


Androidの標準プレイヤーや、それから派生した各種プレイヤーのネーミングルールから
大きく逸脱していてグダグダな感じが凄くしましたが、取れて良かった。

とゆーかarcはAndroid標準プレイヤーを載せてないのだから、
arc標準プレイヤーはAndroid標準プレイヤーの仕様に沿っていて欲しい。。。

November 7, 2011

GAE/J + Velocity メモ。

とりあえずミニマムで。

■velocity/velocity-toolsのDL
http://velocity.apache.org/download.cgi

■必要なjar
* commons-beanutils-1.7.0.jar
* commons-collections-3.2.1.jar
* commons-digester-1.8.jar
* commons-lang-2.4.jar
* commons-logging-1.1.jar
* velocity-1.7.jar
* velocity-tools-view-2.0.jar

■WEB-INF/velocity.propertiesを作成
velocity-1.7/src/java/org/apache/velocity/runtime/defaults/velocity.properties
をコピーして以下を変更。
* input.encoding=UTF-8
* output.encoding=UTF-8
* file.resource.loader.path = WEB-INF/vm/page

■web.xmlに以下を追記

    notFoundFilter
    gaeVelocityTest.servlet.NotFoundFilter


    notFoundFilter
    *.vm
    REQUEST


    velocityServlet
    gaeVelocityTest.servlet.MyVelocityServlet


    velocityServlet
    /velocity


    velocity
    org.apache.velocity.tools.view.servlet.VelocityViewServlet
    
        org.apache.velocity.properties/WEB-INF/velocity.properties


    velocity
    *.vm


■appengine-web.xmlに以下を追記
true

■NotFoundFilter(vmファイルへの直アクセスを防ぐ)
package gaeVelocityTest.servlet;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class NotFoundFilter implements Filter {
    @Override
    public void destroy() {
    }

    @Override
    public void doFilter (
             ServletRequest req, ServletResponse res, FilterChain chain)
             throws IOException, ServletException {
         
        ((HttpServletResponse)res).sendError(404, ((HttpServletRequest)req).getRequestURI());
   }

   @Override
   public void init(FilterConfig conf) throws ServletException {
   }
}

■MyVelocityServlet
package gaeVelocityTest.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.velocity.tools.view.VelocityViewServlet;

public class MyVelocityServlet extends VelocityViewServlet {

    private static final long serialVersionUID = -2215018323215813266L;

    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        try {
            req.setAttribute("val", "テスト");
            req.getRequestDispatcher("/test.vm").forward(req, res);
        } catch (ServletException e) {
            e.printStackTrace();
        }
    }

}

■WEB-INF/vm/page/test.vm
--
$val
--

■確認
http://127.0.0.1:8888/velocity にアクセス。
デプロイして同様にアクセス。

■参考
GAE/JでVelocity
http://blog.suz-lab.com/2009/12/gaejvelocity.html

GAE/Javaでウェブアプリ開発: Velocityテンプレートエンジン(2)
http://blog.livedoor.jp/ykohat/archives/50788389.html

vmファイルへの直アクセスを防ぐ
http://d.hatena.ne.jp/paulownia/20090604/1244085243

GAE/JでUnsupportedClassVersionError

以下が原因でした。
http://code.google.com/intl/ja/appengine/docs/java/gettingstarted/installing.html

>> Google App Engine では、Java 5 と Java 6 がサポートされています。
>> App Engine 上で実行される Java アプリケーションは、Java 6 仮想マシン(JVM)と
>> 標準ライブラリを使用して動作します。
>> ローカル サーバーを App Engine と同じように動作させるため、
>> できる限り Java 6 でアプリケーションをコンパイルしてテストすることをおすすめします。

November 3, 2011

FindBugsの文字化けを直す

参考:
http://d.hatena.ne.jp/takahashikzn/20100127/1264537683

インストールディレクトリの
plugins/edu.umd.cs.findbugs.plugin.eclipse_1.3.9.20090821/findbugs-plugin.jar
の中にある
messages_ja.xml
を修正する。

(1)
<?xml version="1.0" encoding="Shift_JIS"?>

<?xml version="1.0" encoding="UTF-8"?>

(2)
全体をUTF-8で保存。

October 29, 2011

PhoneGapで録音する場合の注意点

PhoneGapで録音する場合、Mediaを使用します。
http://docs.phonegap.com/en/1.1.0/phonegap_media_media.md.html#Media

以下、Androidでの話です。

var media = new Media("xxx.mp3");
とすると、録音ファイル名はxxx.mp3になります。

ただ、厳密にはSDカード直下にテンポラリファイルとして録音。
録音完了後に正式なファイル名(上の例ですとxxx.mp3)にリネーム。
としているようです。

録音停止直後にJavascriptからAndroid側のメソッドを呼び出してxxx.mp3にアクセスする処理を
書いていたのですが、まだリネーム中なのか、xxx.mp3が存在しないケースが有りました。
(存在するケースも有りました。)

PhoneGap側を以下のように書きなおすと、この問題は収まりました。
media = new Media("xxx.mp3", onSuccess);

onSuccessは録音成功時に呼び出されるJavascriptの関数名になります。
onSuccessの中で、Android側のメソッドを呼び出すように修正しました。

PhoneGapでOptions Menuを表示させる

PhoneGap(1.1.0)でOptions Menuが表示されなかったので調べて見ました。
どうやら、DroidGapを継承したActivityに以下の記述が必要らしいです。
@Override
public boolean onKeyDown(int code,KeyEvent event){
    // Note: http://madroom-project.blogspot.com/2011/10/phonegapoptions-menu.html
    if(code==KeyEvent.KEYCODE_BACK) {
        return super.onKeyDown(code, event);
    }
    return false;
}
尚、return falseするのみだと、バックキーが反応しなくなりました。
なので、この形にしています。

参考:
http://groups.google.com/group/phonegap/browse_thread/thread/91e06087e7779344

PhoneGap(JavaScript)からAndroidのメソッドを実行する

参考:
http://blog.livedoor.jp/hiroki0907/archives/51728780.html

--Java(Android)--
package com.phonegap.example;

import android.os.Bundle;
import android.widget.Toast;

import com.phonegap.DroidGap;

public class XxxActivity extends DroidGap {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.init();
        appView.addJavascriptInterface(new JavaScriptCallback(), "android");
        super.loadUrl("file:///android_asset/www/index.html");
    }

    public class JavaScriptCallback {
        public void callbackTest(String str) {
            Toast.makeText(getApplicationContext(), str, Toast.LENGTH_SHORT).show();
        }
    }
}

--JavaScript--
function callback_test() {
    android.callbackTest("yyy");
}

October 21, 2011

Aptana Studioの環境メモ 2011/10/21

Aptanaは主に、PHPでのweb開発用で使用しています。
以下、構成のメモです。
随時、ここに書き足します。



■DL
http://www.aptana.com/

■設定メモ
▼FTPでファイル/ディレクトリUP時の標準permission
Window > Preferences > Aptana Studio > Remote > FTP

▼新規ファイルの文字コード/改行コード
Window > Preferences > General > Workspace > New text file line delimiter

▼フォントの変更
Window > Preferences > General > Appearance > Colors and Fonts > Basic > Text Font > Edit...

▼行番号表示
Window > Preferences > General > Editors > Text Editors > Show line numbers

▼エディタ配色の変更
Window > Preferences > Aptana Studio > Themes
ExportファイルがImport出来ない場合、Exportファイルを開きUTF-8で再保存してImport

▼.htaccess等の隠しファイルを表示
Project Explorerの▼ > Customize View... > Filters
".* files"と".* resources"のチェックを外す?
何かファイルを開いている状態で行わないと反応しない?

▼差分エディタ(?)の色
Window > Preferences > General > Appearance > Colors and Fonts > Text Compare > Outgoing change color



■plugin
▼archive
* JStyle 3.6.2(macでは正しく動作せず。)
http://mergedoc.sourceforge.jp/index.html#jstyle.html

* DBViewer 1.2.3
http://www.ne.jp/asahi/zigen/home/plugin/dbviewer/about_jp.html
Window > Preferences > DBViewer Plugin > SQL エディター
予約語 ... 白
関数 ... 白
文字列 ... 水色
コメント ... 黄色
デフォルト ... 白
背景色 ... 黒

▼update site
* ERMaster
http://ermaster.sourceforge.net/update-site/
Help > Install New Software > Available Software Sites > Eclipse Helios Update Site
に一時的にチェックを入れる必要有。

* RSS View
http://www.junginger.biz/eclipse/

* EGit
http://download.eclipse.org/egit/updates

* BzrEclipse
http://verterok.com.ar/bzr-eclipse/update-site/
Window > Preferences > Team > Bazaar > Console
でBazaar関係のコンソール出力設定

* Subclipse
http://subclipse.tigris.org/update_1.6.x

* openextern
http://openextern.googlecode.com/svn/trunk/openextern_update/
Windowsはそのままで良さそう。
Macは以下のように設定。
open -a finder .
open -a terminal .

■備考
Subclipseで最低限必要なものは
* Subclipse
* Subversion JavaHL Native Library Adapter
* SSHで接続するには SVNKit Client Adapter



■その他
▼XAMPP
http://www.apachefriends.org/jp/xampp-windows.html

▼PortableGit
http://code.google.com/p/msysgit/downloads/list
インストーラでインストールしたmsysgitのgit.exeを指すと以下のエラーが出る。
"This path is not a valid git v1.6.0 or higher binary"(1.7.4なのだけど。。。)
なのでmsysgitはアンインストールしてPortableGitにした。

October 15, 2011

MOTODEVとeclipseの環境メモ 2011/10/15

MOTODEVとeclipseは以下で使い分けています。
* MOTODEV ... 本開発用
* eclipse ... プラグイン等の確認用

以下、構成のメモです。(androidアプリ開発に特化した構成です。)
随時、ここに書き足します。



【共通】
■Android SDK
http://developer.android.com/sdk/index.html

■plugin
▼update site
* openextern
http://openextern.googlecode.com/svn/trunk/openextern_update/
Windowsはそのままで良さそう。
Macは以下のように設定。
open {path}
open -a terminal {path}
* FindBugs
http://findbugs.cs.umd.edu/eclipse
* Subclipse
http://subclipse.tigris.org/update_1.6.x

▼archive
* JStyle(macでは正しく動作せず。)
http://mergedoc.sourceforge.jp/index.html#jstyle.html



【MOTODEV Studio for Android】
http://developer.motorola.com/docstools/motodevstudio/



【eclipse】
http://www.eclipse.org

■plugin
▼update site
* Android SDK用プラグイン
https://dl-ssl.google.com/android/eclipse/



【備考】
Subclipseで最低限必要なものは
* Subclipse
* Subversion JavaHL Native Library Adapter
* SSHで接続するには SVNKit Client Adapter



【設定メモ】
エンコード
Window > Preferences > General > Workspace > Text file encoding

Java
Window > Preferences > Java > Compiler > Compiler compliance level
* 1.6であることを確認

行番号表示
Window > Preferences > General > Editors > Text Editors

Javaファイルのインデントをスペースにする
Window > Preferences > Java > Code Style > Formatter

XMLファイルのインデントをスペースにする
Window > Preferences > XML > XML Files > Editor

半角スペース可視化
Window > Preferences > General > JStyle

bzr diffで"--using=xxx"を省略して外部ソフトを起動する

bazaar.confを編集してコマンドのエイリアスを作成する。

■bazaar.confの所在
* XPの場合
C:\Document settings\(ユーザー名)\Application Data\bazaar\2.0
* Vsitaの場合
C:\Users\(ユーザー名)\AppData\Roaming\bazaar\2.0

* 参考
http://tobysoft.net/wiki/index.php?Bazaar%2F%A5%A4%A5%F3%A5%B9%A5%C8%A1%BC%A5%EB

■bazaar.confに追記
--bazaar.conf--
[ALIASES]
diff=diff --using=[使用するソフト名]
--

* 参考
http://doc.bazaar.canonical.com/latest/ja/user-guide/using_aliases.html