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で保存。