‘CakePHP’ タグのついている投稿

PHP + SQLite で正規表現(PDO_SQLITE編) in CakePHP

CakePHP 1.3.6 での話です。

これ、やったのはずいぶんと前なんですが書いてなかったですね。
私はすごくうれしかったのですが、周りの反応は薄かった覚えがあります。

CakePHP 1.3 でSQLite3ってのはかなりイレギュラーで、確か、CakePHPのTracから探してきたドライバ(dbo_sqlite3.php)を使用しています。
確認しようと思ったのですがTracに繋がらないぞ?ってな状況。

そんなこんなでとてもマイナーな話なんですよね。 反応が薄いのもむべなるかな。

ですが、PDOでSQLiteにアクセスしている場合なら応用が効くかと思います。
dbo_sqlite3.php 内部でPDOが使用されておりますので。

app_model.php でやっているので関係するところをまとめて引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class AppModel extends Model {
    function beforeFind($queryData) {
        parent::beforeFind($queryData);
        //正規表現関数有効化
        $this->regxEnable();
    }
 
    /**
     * 正規表現関数有効化
     */
    public function regxEnable(){
        $db =& $this->getDataSource();
        $db->connection->sqliteCreateFunction('RGX', array('AppModel', 'sqliteRegexMatch'), 2);
    }
 
    /**
     * 正規表現関数
     */
    public function sqliteRegexMatch($regex, $str) {
        if(empty($regex)){
            return true;
        }else{
            return preg_match($regex, $str);
        }
    }
}

$db->connection がPDOのインスタンスです。
sqliteRegexMatch で $regex が empty の場合に true を返しているのは実装しているアプリケーションの都合ですのでご注意を。

実装の際、下記サイトを参考にさせていただきました。

SQLiteで正規表現 « karak

違いはPDOであること、preg_matchを使用していること、SQL関数を処理するコールバック関数としてメソッドを使っていることです。

CakePHP的ポイント

  • DBOからPDOのインスタンスを取得
  • beforeFindで検索時に正規表現関数を有効化

PDO_SQLITE的ポイント

  • sqliteCreateFunctionでSQL関数登録
  • sqliteCreateFunctionのコールバック関数にメソッドを登録する際の書式

使い方ですが、参考サイトと違ってpreg_matchでの正規表現であることに注意してくださいね。

私の場合、CakePHPでの使用ですので

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    /**
     * モデルのconditonsに正規表現を使う場合の配列を返す。
     *
     * @param <type> $pattern 正規表現
     * @param <type> $cols 判定対象のカラムの配列
     * @return <bool>
     */
    function whereByRgx($pattern, $cols) {
        $conditons = array();
        if(!empty($pattern)){
            foreach ($cols as $colname) {
                $conditons[] = "RGX('" . $pattern . "', " . $colname . ")";
            }
        }
        return $conditons;
    }

てなモデルでのfindなどで使用するconditions用の配列を返すメソッドを作って使っております。

Popularity: 6% [?]

CakePHPのRouter::normalizeにヤラれる

あるいは認証後に存在しないsコントローラーに飛ばされてエラーになる件。

1.3.6での話なのですが、例えば

http://lancard.com/hoge/

というCakePHPで構築したサイトがあってAuthで認証を行っているとします。
そこにhogesコントローラーがあったとしましょう。そのindexアクション

http://lancard.com/hoge/hoges/

で認証切れすると戻り先のUrlとしてAuth.redirectセッションに/hogesが
保持されるのですが、この状態で認証して再ログインすると

http://lancard.com/hoge/s

に飛ばされる不思議現象。 犯人はRouter::normalize。

リダイレクト処理時にRouter::normalizeで前処理されるのですがその中に

1
2
3
if (!empty($paths['base']) && stristr($url, $paths['base'])) {
    $url = preg_replace('/^' . preg_quote($paths['base'], '/') . '/', '', $url, 1);
}

というコードがあって、結果、Router::normalize(‘/hoges’)は/sを返しやがります。

1
2
3
if (!empty($paths['base']) && stristr($url, $paths['base'] . '/')) {
    $url = preg_replace('/^' . preg_quote($paths['base'], '/') . '/', '', $url, 1);
}

と改造して$paths['base']の後ろに/がある時だけ置換するようにして対処しました。
悪影響がないことを祈りつつ。

なるべくならフレームワークに手を入れるのは避けたいところ。いい手があったら教えてください。

Popularity: 5% [?]

CakePHP で debug.log にクエリを出力する(手抜き版)

シェル書いてる時とか Web でも Ajax のときとか

「画面にクエリだされても見えないし、困る〜」

ってときありますよね?

「つーか、なんで画面にだすんだ?ログファイルに出してくれればいいのに 」

とか思いません?

で、調べたらみなさんDBのドライバを拡張して対応されてるようです。
ですが、常にクエリをログに書いて欲しいわけでもないし、ドライバ拡張とか面倒。

クエリを画面にだしてるとこどうなってるのかなぁ、と思って調べたら

cake/libs/view/elements/sql_dump.ctp

にゴリゴリに書いてありました。

これマネしてapp_model.phpにでもメソッド作ればいいんじゃないかなーってことで、作りました。

1
2
3
4
5
6
7
8
9
    /**
     * 通常画面に表示されるログを取得してdebug.logに出力する。
     */
    function sqlToLog(){
        $db =& $this->getDataSource();
        if ($db->isInterfaceSupported('getLog')){
            $this->log($db->getLog(), LOG_DEBUG);
        }
    }

元のログ表示と違って明示的に呼び出すものなのでgetLogインターフェースの確認とか要らない気がしますが、一応。

このメソッドを実行すると debug.log にそれまでに実行されたクエリやら付帯情報(画面に表示されてますよね)が書き出されます。注意点として、画面に出すデータを横取りしているのでログに書き出されたものは画面には表示されません。

配列を print_r で出力した形式なので見やすくはないですが、まぁ、十分かな。

Popularity: 8% [?]

CakePHP でシェルを書くときに最初にやったこと(親クラス作り)

CakePHP にはデバッグで便利な pr() って関数があります。
出力の前後に pre タグ を付けてくれる print_r() のラッパーです。当然Web用。

シェルにも欲しいです。てなわけで作りました。

1
2
3
4
    function pr($obj){
        $pr = print_r($obj, true);
        $this->out($pr);
    }

簡単。こんな感じで使います。

1
2
    $popopopooon = array('こんにちわ' => 'こんにちワン', 'ありがとう' => 'ありがとウサギ');
    $this->pr($popopopooon);

このようにコンソールに出力されます。

1
2
3
4
5
 Array
(
    [こんにちわ] => こんにちワン
    [ありがとう] => ありがとウサギ
)

print_r() は第2引数に true を渡すと出力せずに結果を返してくれるのでオブジェクト構造をログに出したりするときにも便利です。
 
でもコレ、全部のシェルに書きたくない。

どこに集約したらいいのか悩んだのですが、実際に実行するシェルの親クラスを作成して継承することにしました。継承の段数が増えるのはあまりすきじゃないのですが、Shellクラス をそのまま継承するよりも1段増やしといたほうがいいかな?って気もしたので。

appbase.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class AppbaseShell extends Shell {
 
    function initialize() {
        parent::initialize();
    }
 
    function startup() {
        //ウェルカムメッセージを消すためにオーバーライド
    }
 
    /**
     * デバッグ用
     */
    protected function pr($obj){
        $pr = print_r($obj, true);
        $this->out($pr);
    }
}

ついでに startup() をオーバライドしてウェルカムメッセージを消しました。
こういう事やるには1段あいだに入ってるほうがいいですね。

実際に起動するシェルは AppbaseShellクラス を継承します。

aisatu.php

1
2
3
4
5
6
7
8
9
10
11
App::import('Shell', 'Appbase');
class AisatuShell extends AppbaseShell {
    function initialize() {
        parent::initialize();
    }
 
    function main() {
        $popopopooon = array('こんにちわ' => 'こんにちワン', 'ありがとう' => 'ありがとウサギ');
        $this->pr($popopopooon);
    }
}

サンプルコードは今テキトーに書いたのでちょっとあやしいかも。

最後にオチを。

このprメソッド、実際に作ったんですが普通に print_r() すればコンソールに出力されるんですよねー。「そりゃそうだ」と後で気づいたわけで。

CakePHP のシェルでは $this->out() でコンソールへの出力をすることになってますが、開発中にしか使わないし print_r() をそのまま使えばいいかなと。親クラスは機能の集約に役立ってます。

Popularity: 6% [?]

UTF-8なCakePHP+EUC-JPなMySQL4.0

CakePHPは非常に良くできたPHPフレームワークです。
DBの種類等も設定ファイルに少し書いてやるだけで動いてくれます。

文字コードもそうで下記設定ファイルに書くだけです。
MySQLでEUC-JPを扱う場合も下記のようにするだけです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/app/config/database.php
<?php
class DATABASE_CONFIG {
 
    var $default = array(
        'driver' => 'mysql',
        'persistent' => false,
        'host' => 'localhost',
        'port'=>3306,
        'database' => 'dbname',
        'login' => 'dbuser',
        'password' => 'dbpassword',
        'prefix' => '',
        'encoding' => 'ujis',
    );
 
}
?>

MySQLの場合はここで設定したencodingを使って、「SET NAMES ujis;」を自動的に発行してくれています。ここで問題が発生したのですが、MySQL4.0系にはSET NAMES構文がないので文字化けしてしまいます。CakePHP側もEUC-JPにしてしまえばいいのですが、今回はUTF-8である必要があり、DBとの入出力の際に、UTF-8⇔EUC-JPと変換することにしました。CakePHPはデータ操作の際に呼ばれるコールバック関数がありますので、そこで変換してやればOKです。

基本的に全ての生成されたModelはAppModelクラスを継承していますので、このクラスで変換してやります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/app/config/app_model.php
<?php
class AppModel extends Model {
 
    //データ取得後に呼ばれるコールバック関数
    function afterFind($results) {
        //$results配列をUTF-8に変換する処理
 
        return $results;
    }
 
    //データ保存前に呼ばれるコールバック関数    
    function beforeSave() {
        //$this->data配列をEUC-JPに変換する処理
 
        return true;
    }
}
?>

もっと根っこのところで変換かけても良いのでしょうが、今回はCakePHPのライブラリには手を加えたくなかったので、こうしました。子クラスで上記関数をオーバーライドしている場合は親の関数を呼ぶのをお忘れなく。こうしたほうが良いというのがあればツッコミお待ちしてます。

Popularity: 11% [?]