header image

Ryuzeeについて

mixi Twitter Twitter

携帯対応

QRコード

RING

人気ブログランキング

新着記事

2010/05/31 14:53:49 PHP none Comments

久々のどうでもよい話。
別にKVSなんて今に始まった話でもないんだけど、ちと仕事で使うかもしれないので試してみた。

MongoDBって何よ?

以下の特徴をもつ所謂NoSQLデータベースの1つ。(特徴の日本語訳はこのへん)

  • ドキュメント指向のストレージ
  • フルインデックスをサポート
  • レプリケーションと高い可用性
  • 自動シェアーディグ
  • 高機能なドキュメントベースクエリ
  • Fast In-Placeな更新
  • Map/Reduce
  • GridFSによるファイル保存
  • 商用サポートもある

MongoDBの入手

MongoDBこの辺からダウンロード
Mac用もWindows用もその他のOS用もビルドされたモジュールが用意されている。
試験環境はWindows XP SP3(32bit)なので、それに該当するものをダウンロード

MongoDBのインストール

ダウンロードしたファイルは単なるzipファイルなので、解凍して適当な場所に配置する。
以上で準備完了。

起動するには、binディレクトリに移動して、コマンドプロンプトで

mongod.exe --dbpath=../data

とすれば良い。

これだとMongoDBを開始・停止するのが面倒なのでWindowsのサービスに登録する。こんなかんじでコマンド打つとサービスとしてインストールされる。
なお、--dbpathの指定をしないとうまくインストールできないようなので注意
(Linuxの人は/etc/rc.d/init.d/あたりに起動スクリプト用意すればよし)

mongod.exe --install --dbpath=C:\tools\mongodb-win32-i386-1.4.3\data

MongoDBへPHPで接続する準備

ちゃんとPHP用の拡張が用意されている。
Linux系の環境なら

pecl install mongo

として、php.iniに

extension=mongo.so

を追加すればOK。

Windowsの場合は、peclコマンドでソース持ってきたところでコンパイルできないので、バイナリを導入する。
詳細はPHPのマニュアルに書いてある

テスト環境はPHP5.2.8なので、こちらのTSモジュールをダウンロード・解凍して、php_mongo.dllを[XAMPPのインストールフォルダ]/php/extにコピーする。
(XAMPPの場合は、Thread Safe版のモジュールでないと、php5.dllが無いぞ~、というエラーでPHPが起動しないはず)

その上で、php.iniに以下を追記(XAMPPの場合、apache/bin/php.iniとphp/php.iniの2つの設定ファイルが利用される。前者はapache経由の場合、後者はCLIの場合)

extension=php_mongo.dll

その後(一応)XAMPPのapacheを再起動しておく。

MongoDBをPHPから使ってみるよ

このチュートリアルが良くまとまっている(英語)ので、それを見ればおおよそ分かる。

MongoDBへの接続

try
{
    $link = new Mongo();
}
catch(MongoConnectionException $e)
{
   die('つながらないんです');
}

コンストラクタの引数に、下記のように接続先のMongoDBの情報を設定できる。

$link = new Mongo("example.com:65432");

DBの指定

$db   = $link->testdb;

もしこのtestdbが存在しなければ自動で作られる。

コレクションの作成

$col  = $db->user;

コレクション(テーブルみたいなもん)も存在しない場合は自動で作成される。

データ追加

$doc = array(
    'name' => 'ryuzee',
    'age' => 24,
    'country' => 'Japan',
    'url' => array('http://www.ryuzee.com/','http://twitter.com/ryuzee')
);
$col->insert($doc);

連想配列にセットして、insertすればOK。第二引数はoption配列で、safeをtrueに指定すると追加に成功したかどうかの戻り値を応答する。

$option["safe"] = true;
$ret = $col->insert($doc, $option);

検索

$res = $col->find();

テーブルのデータを全件取得する。

$res = $col->findone(array('_id' => new MongoId('4c0347097bb3f8c013000000')));

ID指定で検索できる。

$res = $col->find()->skip(1)->limit(1);

開始位置や件数を指定できる。

更新・削除

$col->update(
    array('_id' => new MongoId('4c0347097bb3f8c013000000')),
    array('$set' =>
        array('name' => 'ryuzee2')
    )
);

IDを指定して、$setの配列に更新フィールドをセットすることでデータの更新を行う。

$col->remove(array('_id' => new MongoId('4c0347097bb3f8c013000000')), array('safe' => true));

IDをキーにしてデータを削除する。safeオプションの意味は追加時と同じ。

コレクションの削除

$col->drop();

その他

CakePHPでMongoDBに接続するには以下を参照。
http://d.hatena.ne.jp/cakephper/20100122/1264140610

2010/05/28 15:15:12 PHP none Comments

もうタイトルのまんまですが、データの更新時に自動でデータの差分を取得して履歴テーブルに突っ込むbehaviorを作りました。
自分で作っているアプリケーションで、データ更新時の変更履歴を表示させたいと思ったのだが、対象テーブルが沢山あっていちいち似たような実装をあちこちに作るのは気が狂いそうなので汎用化しちゃえ、というのが作った動機

概要

AutoLoggerBehavior

http://github.com/ryuzee/auto_logger_behavior から入手可能

ライセンスはMITライセンス

使い方

1. 入手したauto_logger.phpをapp/models/behaviors/に配置
2. 履歴テーブルの作成

CREATE TABLE `change_logs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `mode` varchar(6) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `old_value` text,
  `new_value` text,
  `created` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

3. 履歴テーブル用のモデルを作成。上記のテーブルの場合はapp/models/以下にchange_log.phpという名でChangeLogモデルを作成

4. 履歴を取得したいモデルを開き、以下を追加。savetoには履歴を保存したいモデル名を指定する。省略時はChangeLogモデルとみなす。

var $actsAs = array(
    'AutoLogger' => array('saveto' => 'ChangeLog'),
);

5. 以上で完了。あとは対象モデルの新規作成、編集、削除時に自動で指定のテーブルに履歴が保存されます。

その他

自分が使うために作ったので、あんまり細かくテストしていないです。ご利用は自己責任で。

2010/05/25 07:00:09 PHP 2 Comments

別にUNIONに限らず、生のSQLを投げて取得したデータは、何でも同じようにページングできる。

・ページングのために利用する新しいモデルを作成する
・そのモデルでは$usetable=falseに設定し、既存のテーブルとは関連付けしない。
・そのモデルにおいて、paginate関数とpaginateCount関数をoverrideする。
・コントローラー側では、既存のpaginateと同じ利用の仕方をする。但し引数として設定しても無視する項目がある。

サンプルコード

下記のサンプルコードはPHPMyScrumでのタスクとストーリーの横断検索機能の実装。

app/models/search.php

class Search extends AppModel {
    var $useTable = false;
    var $base_sql = " SELECT ? as itemtype, resolution_id, id, name, description FROM stories where disabled ='f' and ( name like ? or description like ?) UNION SELECT ? as itemtype, resolution_id, id, name, description FROM tasks where disabled = 'f' and ( name like ? or description like ?) ";
    var $keyword   = "";
    var $bind_param = array(ITEMTYPE_STORY, "","",ITEMTYPE_TASK, "","");

    function setKeyword($value)
    {
        $this->keyword = "%" . $value . "%";
        $this->bind_param = array(
            ITEMTYPE_STORY,
            $this->keyword,
            $this->keyword,
            ITEMTYPE_TASK,
            $this->keyword,
            $this->keyword,
        );
    }

    /** * Overridden paginate method */
    function paginate($conditions, $fields, $order, $limit, $page = 1, $recursive = null, $extra = array())
    {
        if($page == 0){ $page = 1; }
        $recursive = -1;
        $sql = $this->base_sql . ' LIMIT '.(($page-1)*$limit) . ',' . $limit;
        return $this->query($sql, $this->bind_param);
    }

    /** * Overridden paginateCount method */
    function paginateCount($conditions = null, $recursive = 0, $extra = array())
    {
        $this->recursive = $recursive;
        $results = $this->query($this->base_sql, $this->bind_param);
        return count($results);
    }
}

app/controllers/search_controller.php

class SearchController extends AppController {
    var $name = "Search";

    function index()
    {
        $result = array();

        if(isset($this->passedArgs['Search.query']))
        {
            $keyword = $this->passedArgs['Search.query'];

            $this->Search->setKeyword($keyword);
        }
        $this->paginate = array(
            'limit' => 20,
        );
        $result = $this->paginate();
        $this->set('result', $result);
    }

    function search()
    {
        $url['action'] = 'index';

        foreach ($this->data as $k=>$v){
                foreach ($v as $kk=>$vv){
                        $url[$k.'.'.$kk]=$vv;
                }
        }
        $this->redirect($url, null, true);
    }
}

2010/05/19 06:32:39 PHP none Comments

Webserviceコンポーネントを使うと、既存のコントローラーにほとんど手を入れることなく、簡単に応答をXMLやjsonに切り替えることが出来る。
Webserviceコンポーネントは、Jose Diaz-Gonzalez氏が作成し、MITライセンスで公開されている。

入手およびインストール

git://github.com/ryuzee/webservice_plugin.git

からgit cloneで入手する。
※日本語対応やいくつかの問題を筆者がforkして修正した。

入手したファイルのうち
controller/components/webservice.phpを自身のアプリケーションのcontroller/components/にコピーする。
views/webservice.phpを自身のアプリケーションのviewsにコピーする

使い方

app/config/route.phpを開き、末尾に以下を追加する

Router::parseExtensions();

XMLやjson形式で応答したいコントローラーにおいて

var $components = array('Webservice');

を追加する。
以上で作業は完了。

XMLやjson形式で応答したい場合は以下のようにリクエストを行う。
/controller名/action名.xml
/controller名/action名.json

現在のコンポーネントだと、有効にしたコントローラーの全アクションでXMLとjsonの応答が有効になるけど、このあたりは簡単にカスタマイズできるだろう。

2010/05/12 03:41:36 PHP none Comments

CakePHP標準だとRailsのような差分情報を含めたスキーマの管理ができず、不特定多数に配布するアプリケーションでの更新が困難だったり、開発現場でも人によってスキーマが異なってしまったり、といった問題が起こりやすかった。
このような問題を解決するのがCakePHP Migrations Pluginだ。

CakePHP Migrations Pluginは、CakeDCがMITライセンスで配布するオープンソースのCakePHPのプラグインで、これを利用するとRailsのMigrationと同じことが出来る!

詳細については
http://cakedc.com/downloads/view/cakephp_migrations_plugin

入手は最新版をgithubから。
http://github.com/CakeDC/Migrations

なお、動作検証はCakePHP1.3で行った。

大まかな流れ

  1. 現在のテーブルのschema.phpを作成

    cake\console\cake schema generate

    でスキーマを生成する。

  2. テーブルの変更
    alter table hogehoge add column hugahuga integer not null default 0;
    (複数変更可)

  3. migrationコマンドで差分を生成
    cake migration generate

という順番で作業する

差分ファイルは以下のような形式で出力される(一部抜粋)。

public $migration = array(
    'up' => array(
        'create_field' => array(
            'users' => array(
                'display_name' => array('type' => 'string', 'null' => false, 'length' => 100),
            ),
        ),
        'alter_field' => array(
            'users' => array(
                'email' => array('name' => 'email_address', 'type' => 'string', 'null' => false, 'length' => 200, 'default' => NULL),
            ),
        ),
    ),
    'down' => array(
        'drop_field' => array(
            'users' => array('display_name',),
        ),
        'alter_field' => array(
            'users' => array(
                'email_address' => array('name' => 'email', 'type' => 'string', 'null' => false, 'length' => 200),
            ),
        ),
    ),
);

Migration作成時の注意

Migrationファイルは最新のschema.phpをもとに差分情報を生成しているので、schema.phpが古い状態で複数のMigrationファイルを作成した場合、同時に適用すると同じ変更を複数回実行しようとしてエラーになる。
この場合は作成されたmigrationファイルのうちconflictするものを自分で削除し、map.phpを編集する必要がある。
なお、このmigration機能を利用するためには、modelが作成されていることが(ほぼ)前提となる。
また、僕の環境だと、debugモードが0だとモデルがキャッシュされており、schemaファイルやmigrationファイルに変更が反映されなかったので、debugモードは0以外にして作業を行うことが必要そうだ。

Migrationの実行

マイグレーションを実行するとき

cake\console\cake migration run all

とする。
問題ないようであれば以下のように出力される。

Welcome to CakePHP v1.3.0 Console
---------------------------------------------------------------
App : app
Path: C:\cygwin\home\ryuzee\CakeTest\app
---------------------------------------------------------------
Cake Migration Shell
---------------------------------------------------------------
Running migrations:
  [001] 001
     > Creating table information.
     > Creating table priorities.
     > Creating table projects.
     > Creating table remaining_times.
     > Creating table resolutions.
     > Creating table sprints.
     > Creating table stories.
     > Creating table story_comments.
     > Creating table tasks.
     > Creating table teammembers.
     > Creating table teams.
     > Creating table users.
     > Creating table wiki.

  [002] 002
     > Adding field display_name to users.
     > Changing field email from users.

All migrations have completed.

なお、詳細なマニュアルは
http://cakedc.com/eng/downloads/view/cakephp_migrations_plugin
を参照のこと。

2010/05/10 18:15:52 PHP none Comments

ハマったのでメモ。

$ cake schema generate
Welcome to CakePHP v1.3.0 Console
---------------------------------------------------------------
App : app
Path: /home/ryuzee/PHPMyScrum/app
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------
Generating Schema...
Error: Missing database table 'app_models' for model 'AppModel'

となって、スキーマの生成で落ちてしまう。
modelディレクトリ以下のクラスにおいて、

$useTable = false

でないモデルは全てテーブルが存在することを期待してしまっているのが原因。
なので、app_model.phpはappディレクトリの直下に置くようにする。

常識らしいが、無知というのは怖い。

2010/04/15 04:28:48 PHP none Comments

かなり今更な自分用メモ。プラグインで解決できる。

  • http://opencakefile.sourceforge.net/ にアクセスして、org.xicabin.cakephp.opencakefile_1.0.0.jarを入手
  • %ECLIPSE_HOME%/pluginsに入手したjarを配置して、eclipseを-cleanオプション付で起動
  • これでメニューバー上にCakePHPのアイコンが表示されていて、これをクリックするとMCの相互切り替えは出来る。V→Mも可能
  • Ctrl + Shift + ;でMVCを切り替えられる
  • コントローラー名/アクション名の文字列を選択して、Ctrl + Shift + V をクリックすると、そのViewに切り替わる
  • Ctrl + Shift + (C|V|M)のショートカットがある、とのことだが、他のプラグインや操作とバッティングするので使わない
2010/04/07 23:06:13 PHP none Comments

QdsmtpはオープンソースSMTP用ライブラリで、こちらから入手できる
僕はCakePHPでQdmailとQdsmtpの組み合わせでメールを送信することが多いんだけど、今回真面目にエラー処理をする必要があったので調べてみた。
で、以下が僕の実装なんだけど、たぶんQdmailでの定石だと思うのでメモしておく。

大事なのは上2行。errorDisplayをfalseにしないと、Qdmailでのエラーが、レスポンスで表示されてしまう。
またQdmailではQdsmtp側のエラーは抑止してくれないので、2行目の項目を入れないと、Qdmail側のエラーは抑止できても、SMTPサーバへの接続エラーみたいなPHPレベルでのエラーが画面に出力されてしまうことになる。

$this->Qdmail->errorDisplay(false);
$this->Qdmail->smtpObject()->error_display = false;
$this->Qdmail->smtpLoglevelLink(true);
$this->Qdmail->logPath(LOGS);
$this->Qdmail->logFilename("mail.log");
$this->Qdmail->errorlogPath(LOGS);
$this->Qdmail->errorlogFilename("error_mail.log");

errorDisplayをfalseに設定しない場合、ListenしていないSMTPサーバにつなげようとすると、以下のようなエラーが画面に出る。

Qdmail error: Qdmail Version 1.2.6b ,PHP Version 5.2.8
Qdmail error: OS WINNT ; PHP Version 5.2.8 ; Qdmail version 1.2.6b
php.ini status: mb_language = Japanese ; mb_internal_encoding = UTF-8 ; mb_detect_order = ASCII,UTF-8
Qdmail Status debug: 0, log: 3, errorlog: 3
Qdmail error: No send . Because SMTP mail method replied error line -> 2190
Qdmail error: Send Error line -> 2115

一方で、errorDisplayをfalseにしつつ、$this->Qdmail->smtpObject()->error_display = false;の設定をしていない場合は以下のように画面表示される。

QdSmtp error: Connection error HOST: localhost PORT: 25 line -> 628
QdSmtp error: Error Resouce or stop connect line -> 522
QdSmtp error: Error From setting line -> 488

両方設定していた場合は、接続先SMTPサーバが存在しなかったり、応答してくれない場合も、画面上にエラー文字列は出ない。

こういうところを適当にしておくと、環境情報が何かのきっかけで表に出てしまったりしてよろしくないので注意する必要がある。

2010/04/07 22:49:16 PHP 2 Comments

2010/04/08にタイトル変更しました。すまんです。実は1.2.6を使っていたつもりで、1.3RCを使っていたというオチでした。
ちなみに以下の話は1.2系から1.3系に移行する場合はそのまま適用できます。

最近オープンソースのScrum支援ツールを作っていて、CakePHPを使っているのだが、最新の1.3RCで細かい挙動がかなり変わっていて、ちょっとハマったのでメモ。

elementの呼び出しで、renderElement()が使えなくなった。

$this->renderElement("project_info");

は以下のようになる。

$this->element("project_info");

これに伴って、メール送信ライブラリのqdmailに手を入れる必要がある。qdmail.phpの3823行目付近

$content = $view->renderElement( $this->view_dir . DS . $type . DS . $this->template , array('content' => $content ) , true );

は以下のように変える必要がある。

$content = $view->element( $this->view_dir . DS . $type . DS . $this->template , array('content' => $content ) , true );

ちなみに、沢山の箇所にrenderElementがあるような場合はapp_view.phpにrenderElementを定義すれば良いんじゃないかな?

Htmlヘルパーのエスケープ処理

画像リンクを作るときに、昔の定石では以下のようにしていた。(第5引数がリンク対象をエスケープするかどうか、だった)

echo $this->Html->link($html->image("test.gif"), '/',null,null,false);

でも、1.2.6では以下のようにする。

echo $this->Html->link($html->image('test.gif'), '/', array('escape' => false));

他にも気づいたら書く。

2009/10/18 08:41:10 PHP 2 Comments

cakephpではSchema機能を使ってテーブルを作成することが出来る。
で、ついでにマスター系データもまとめて登録する方法が【CakePHP】お手軽便利なCakeSchemaに載っている。
ただ載っている方法には若干問題がある。

  • そもそもcakephpでは、テーブルを使わないモデルでは$useTable=falseに設定しないといけなくて、それ以外の場合は、モデルへのアクセス時にテーブルが存在しないとエラーが発生する。
  • 従って、初期データを保存するためのモデルがアソシエーションが設定されているモデルだと、関連モデルのテーブルがまだ作成されていない場合にアクセスすると、その時点でエラーになってしまう。作成する順番だけを変えれば良いケースもあるだろうけど、相互参照しているようなモデルでは無理。
  • つまり、アソシエーションがある場合は先にテーブルを生成しなければならず、schema.phpのafterメソッドで、都度データを入れることはできないような気がする。(unBindModelとかしてみたけど、そもそもモデルのロードで先にエラーになるので、そもそもダメ)

ということで僕なりにアレンジしたのが以下の方法。
schema.phpのafterメソッド

function after($event = array()) {

    $model_names = array();
    $prop = get_class_vars(get_class($this));
    foreach($prop as $key => $value)
    {
        $s = Inflector::classify($key);
        if (!($s == "Name" || $s == "File" || $s == "Path" || $s == "Log" || $s == "Connection" || $s == "Table"))
        {
            $model_names[] = $s;
        }
    }

    if(!empty($event['create'])){
        if(!isset($this->InitialValues)){
            require_once($this->path.DS.'initial_values.php');
            $this->InitialValues = new InitialValues();
        }
        $modelname = Inflector::classify($event['create']);

        if($modelname == $model_names[count($model_names)-1])
        {
            foreach($model_names as $target_modelname)
            {
                $this->InitialValues->set($target_modelname);
            }
        }
    }
}

肝は、全部のデータを最後のテーブルの作成完了後に作る、というだけ。
NameとかFileとか除外しているところはin_arrayで除外した方がいいけど、まぁいいか。

 

日記 PHP オープンソース インストールマニアックス IIS Trac MySQL Perl Linux Agile・生産性向上 wordpress フリーソフト 自宅サーバ 書評 ブックマーク phpMyFaq TraM Plugin 早起き Delphi apache CakePHP Firefox Ruby eclipse セキュリティ プラグイン アジャイル mojavi Subversion Ajax/Web2.0 SQLServer Zope サーバ フレームワーク phpBB 仮想化 PostgreSQL OpenVZ scuttle CMS 文字化け 自宅 翻訳・日本語化 ApacheDS LDAP Excel 生産性向上 CodeIgniter XAMPP hacks taskfreak 修正 言語ファイル Ajax SBM ダウンロード HTML::FillInForm mod_security 情報共有


ads

読まなきゃモグリ