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.
--

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