CakePHP 周辺知識

目次

assert() 関数には文字列を渡す

第3回 CakePHP 勉強会で時間の都合から言えなかったことがありました。 assert() 関数には式を渡すのではなく文字列を渡して使うのが正解です。

assert($arg != 0);   // 誤り!
assert('$arg != 0'); // OK

どちらの記述も一見すると正しく動いているように見えるのですが、式が成立しないときのエラーメッセージが変わります。前者は失敗したことしかわかりませんが、後者はどの式に失敗したのかがわかります。

ながらく C/C++ のプリプロセッサという超法規的措置の assert を使っていた人は要注意です。

IE で Ajax リクエストをキャッシュさせないようにする

IE で Ajax リクエストをキャッシュさせないようにするためには、次のいずれかの手段をとります。

  • post 通信にする
  • リクエスト URL に一意なクエリストリングを付加する
  • If-Modified-Since ヘッダを送出する

個人的には2番目の手段が好きです。 new Date() で済みますので。

メルセンヌツイスタを使った疑似乱数 - mt_rand()

今日知ったのですが PHP の疑似乱数生成にはメルセンヌツイスタが使えるそうです。

その名も mt_rand() 。

マニュアルによると古いバージョンの PHP でも使えるようですが、本当でしょうか。というか、このあたりの心配を払拭するためにも PHP5 へ移行したい気持ちでいっぱいです。

ちなみにメルセンヌツイスタは普通の rand() と違って、再現する乱数にはなりません。

壊れた UTF-8 ストリームを検証する

頭の痛い壊れた UTF-8 の検証問題ですが、ネットでこんなコードを見つけました。

<?php
function check($check)
{
  $sjis = mb_convert_encoding($check, "SJIS-win", "UTF-8");
  $utf8 = mb_convert_encoding($sjis , "UTF-8"   , "SJIS-win");

  return ($check === $utf8);
}
?>

なんてわかりやすいんでしょうか。しかし、正しく機能するのか謎です。ちょっと試してみた限りではかなりの精度で誤りを検出できるようです。

実は、現在開発中のシステムがいろんな言語を扱う予定があるため UNICODE を使いたいのです。発想が非日本人っぽくて自己嫌悪してしまいそうですが、引き続き書籍などからヒントを得てみようと思います。

何か良い方法がわかり次第レポートいたします。

JavaScript でイベントが発生した要素を取得する

Ajax ヘルパーを使うとビューテンプレートからイベントハンドラを設定可能となり、ある程度 CakePHP が JavaScript の記述量を吸収してくれるのですが、コールバック関数は自分で書く必要があります。

このとき、イベントの情報がコールバック関数の引数として渡ってくるのですが、この参照が指すオブジェクトの仕様がブラウザによって違うので振り分けが非常に面倒です。

これを解決するためには prototype.js の Event.element() メソッドを使います。

function callbackFunction(e)
{
  var element = Event.element(e);
}

というか、ほぼ IE 向けの対策なのですが、 IE の仕様をベースに策定したイベントモデルがなぜ IE でのみ違う振る舞いをするのか謎が深まります。

バッチスクリプトやファイルアップロート時の php.ini ディレクティブ

バッチスクリプトやファイルアップロードといった重い処理を実行する際に設定する php.ini ディレクティブをまとめてみました。

ディレクティブ 意味 初期値 設定場所
upload_max_filesize アップロードするファイルサイズの最大値 “2M” PHP_INI_PERDIR
PHP_INI_ALL(PHP <= 4.2.3)
post_max_size POST データサイズの最大値 “8M” PHP_INI_PERDIR
PHP_INI_SYSTEM ( PHP <= 4.2.3 )
memory_limit スクリプトが確保できるメモリサイズの最大値 “128M” PHP_INI_ALL
max_execution_time スクリプトが強制終了させられるまでの秒数 “30″ PHP_INI_ALL

なお、これらの設定値は次の関係が成り立つように設定する必要があります。

upload_max_filesize <post_max_size <memory_limit

CakePHP 1.2 から Oracle を利用する際の情報

CakePHP 1.2 から Oracle を利用する際の情報がいくつかあるようです。

CakePHP_Oracle.ppt :: handsOut.jp
フィブログ CakePHPからOracleに接続する方法
Using Oracle with CakePHP: 15 Minute Blog Tutorial & William Graham’s blog

私が感じた注意点は次のような部分です。

  • Oracle には MySQL のような AUTO_INCREMENT がないので id はシーケンスとトリガーで自動採番する
  • database.php の driver には oracle を指定する
  • database.php の connect には oci_connect を指定する

冒頭のサイトの情報によると CakePHP 1.2 + Oracle では DELETE がうまく動作しないらしいのですが、少し古い MySQL でも DELETE は動作しません。おそらく DELETE 文にエイリアスを書いていることが原因かと思われるのですが、とりあえず Model::beforeDelete() 中で Model::query() 等を使って直に DELETE を実行すれば対応できます。

Oracle のインストール方法に関してはこちらを参照ください。

CentOS へ Oracle10gXE をインストールする手順

CentOS へ Oracle10gXE をインストールするためには、次の手順を踏みます。

Oracle 10g XE のダウンロード

次のサイトから Oracle10gXE の RPM パッケージをダウンロードします。ダウンロードの際には oracle.com のアカウントが必要です。

ORACLE Technology NETWORK

libaio のインストール

Oracle10gXE をインストールするために必要な libaio を追加インストールします。

# yum install libaio

Oracle 10g XE のインストール

ダウンロードした RPM パッケージから Oracle10gXE をインストールします。

# rpm -ivh oracle-xe-univ-10.2.0.1-1.0.i386.rpm
Preparing...                ########################################### [100%]
   1:oracle-xe-univ         ########################################### [100%]
Executing Post-install steps...

You must run '/etc/init.d/oracle-xe configure' as the root user to 
configure the database.

上記メッセージに従って、 /etc/init.d/oracle-xe configure を実行します。

# /etc/init.d/oracle-xe configure

Oracle Database 10g Express Edition Configuration
-------------------------------------------------
This will configure on-boot properties of Oracle Database 10g Express 
Edition.  The following questions will determine whether the database should 
be starting upon system boot, the ports it will use, and the passwords that 
will be used for database accounts.  Press <Enter> to accept the defaults. 
Ctrl-C will abort.

Specify the HTTP port that will be used for Oracle Application Express [8080]:

Specify a port that will be used for the database listener [1521]:

Specify a password to be used for database accounts.  Note that the same
password will be used for SYS and SYSTEM.  Oracle recommends the use of 
different passwords for each database account.  This can be done after 
initial configuration:
Confirm the password:

Do you want Oracle Database 10g Express Edition to be started on boot (y/n) [y]:

HTTP ポート、リスナーポート、パスワード、 boot 時にoracle XE 起動するかどうかを対話式に設定していきます。

Specify the HTTP port that will be used for Oracle Application Express [8080]:
.

Oracle Application Express へのアクセスポートを設定します。デフォルトの 8080 で問題ない場合はそのまま Enter を押します。

Specify a port that will be used for the database listener [1521]:
.

データベースのリスナーポートを設定します。デフォルトの 1521 で問題ない場合はそのまま Enter を押します。

initial configuration:
Confirm the password:

システム管理者である SYS と SYSTEM のパスワードを設定します。確認のため、2回、同じパスワードを入力します。

Do you want Oracle Database 10g Express Edition to be started on boot (y/n) [y]:
.

システムのブート時に Oracle を開始するかを設定します。システムのブートとともに開始して問題ない場合は Enter を押します。

インストール時に追加された oracle ユーザに bash 関連ファイル付与

インストール時に追加された oracle ユーザにシェル環境を与えるため、スケルトンの設定ファイルをコピーします。

# su - oracle
$ cp /etc/skel/.bash* /usr/lib/oracle/xe/

SQL*Plus をフルパスで呼び出しさないで済む設定を各ユーザアカウントに追加

該当アカウントホームディレクトリに移動後…

$ vi .bash_profile
$ vi /usr/lib/oracle/xe/.bash_profile

.bash_profile ファイルに次の行を追記する。

# ファイルの末尾
. /usr/lib/oracle/xe/app/oracle/product/10.2.0/server/bin/oracle_env.sh

以上です。

CakePHP 1.2 から Oracle を使う場合はこちらも参照ください。

CakePHP で構築したそのサイト、正しく表示されますか?

過去の IE での動作検証に役立ちそうなソフトウェアを見つけました。

IE5.5, IE6, IE7, IE8の確認が同時にできる -IETester

この記事によると IETester をインストールすることによって、任意のバージョンのレンダリングエンジンを選択して Web サイトを表示することができるとのことです。スタンドアロンで動作する上に、既存の IE の環境を壊したりすることはないようです。

IE8 もターゲットになっている点に驚きます。

それにしても、こういうソフトが作れると言うことは、 IE のレンダリングエンジンは QueryInterface() で過去のバージョンが取ってこれるということなのでしょうか。

日本語によるメール送信

フレームワークを使っていると足下がおろそかになってしまいそうな心配がぬぐえません(本当はフレームワークのせいではなくスキルの未熟さから来ているのでしょうが)。

今回はメール送信を通じて日本語を扱う際の設定を見直してみました。

PHP で日本語を扱う際には mbstring 関連のディレクティブを設定することになりますが。大抵は次のような設定になると思います。

mbstring.language = Japanese
mbstring.internal_encoding = EUC-JP

名前から察するに mbstring.language によって mbstring 関連の関数が日本語に初期化され、 mbstring.internal_encoding によって mbstring 関連の関数が内部で利用する文字コードが決定されると思ってしまいがちですが、これが大きな誤解で、 mbstring.language は mb_send_mail() が送信の際に参考にする言語で、 mbstring.internal_encoding は mbstring 系の関数のデフォルトのエンコーディング程度の意味しかないとのことです。

幸い、この程度であれば誤解のまま運用し続けても大きな問題は起こらないかもしれませんが、 mbstring.internal_encoding という名前から PHP も Java のように「内部エンコーディング」があると勘違いしてしまうと、出所のわからないバグに遭遇してしまうかもしれません。 PHP が扱っている(マルチバイト)の文字列も所詮はただのバイト列という再認識が必要かと思われます。

今回得た知識を利用した関数が次のコードです。パソコン向けに添付ファイルを同梱しないメールを送るという用途には十分かと思われますが、おそらく PHPMailer の利用を検討した方が賢明です。

<?php
/**
 * 日本語によるメールを送信する。
 *
 * 日本語によるメールを送信します。
 * メールの送信に際して、データを次のように加工します。
 *
 *   - 件名 ($subject) を MIME エンコーディングを施す
 *   - 件名 ($subject) が長くなる場合に改行する
 *   - 件名 ($subject) の文字コードを JIS へ変換する
 *   - 本文 ($body) の文字コードを JIS へ変換する
 *   - 差出人 ($fromName) の文字コードを JIS へ変換する
 *
 * 引数の文字コードは mbstring.internal_encoding と同じである必要があります。
 * 別の文字コードを指定したい場合は $encoding に文字コード名を渡します。
 * もし $encoding を指定した場合は、一時的に mbstring.internal_encoding の
 * 設定値を $encoding へ変更しますが、関数の終了時に元の値へ戻します。
 *
 * また、この関数は次の用途には利用できません。
 *
 *   - モバイル環境へ対してメール送信したい場合
 *   - 添付ファイルを伴う(マルチパート)メール送信したい場合
 *   - Return-Path の設定を保証したい場合
 *
 * このような場合は次のスクリプトの利用を検討してください。
 *
 *   - http://phpmailer.codeworxtech.com/
 *   - http://techblog.ecstudio.jp/tech-tips/mail-japanese-advance.html
 *
 * ■必要な php.ini ディレクティブ
 *   - sendmail_path        [U/ ] sendmail へのパスとパラメータ
 *   - SMTP                 [ /W] SMTP サーバの IP アドレス
 *   - smtp_port            [ /W] SMTP サーバのポート
 *   - sendmail_from        [ /W] メールの From 部と Return-Path 部のアドレス
 *
 * ■必要の応じて設定する php.ini ディレクティブ
 *   - mb_internal_encoding [U/W] 引数データのデフォルトエンコーディング
 *
 * @access public
 * @param  string  $to         メールの宛先アドレス
 * @param  string  $subject    メールの件名
 * @param  string  $body       メールの本文
 * @param  string  $from       メールの差出人アドレス
 * @param  string  $fromName   メールの差出人の名前
 * @param  string  $returnPath メールの Return-Path ヘッダ
 * @param  string  $encoding   引数データのエンコーディング
 * @return boolean             MTA へのデータ送信が成功したかの真偽値
 */
function btaSendMailByJapanese($to, $subject, $body,
                               $from = null, $fromName = null,
                               $returnPath = null,
                               $encoding = null)
{
    assert('function_exists("mb_send_mail")');

    // PHP ディレクティブを回復できるように現在の値を待避する
    {
        $previousLanguage = ini_get("mbstring.language");
        $previousEncoding = ini_get("mbstring.internal_encoding");
        $previousWinFrom = ini_get("sendmail_from");
    }

    // 日本語メールを送信するために PHP ディレクティブを変更する
    {
        mb_language("Japanese");
        if (!is_null($encoding)) {
            mb_internal_encoding($encoding);
        }
    }

    // 追加ヘッダを構築
    $header = "";
    {
        $headers = array();

        // From ヘッダを構築
        if (!empty($from)) {
            if (!empty($fromName)) {
                $fromNameEncoding = mb_detect_encoding($fromName);
                $fromName = mb_convert_encoding($fromName, "JIS", $fromNameEncoding);
                $fromName = mb_encode_mimeheader($fromName);
                array_push($headers, sprintf("From:%s <%s>", $fromName, $from));
            } else {
                array_push($headers, sprintf("From:%s", $from));
            }
        }

        // mb_send_mail() の第4引数に空文字を渡すと
        // ヘッダが分断されて残りが本文となってしまうため、
        // 必ず第4引数が作られるようにする
        if (count($headers) == 0) {
            array_push($headers, "X-PlaceHolder:placeholder");
        }

        // 追加ヘッダを mb_send_mail() へ渡せる形式へ変換する
        $header = join("\r\n", $headers);
    }

    // MTA パラメータを構築
    $mtaOption = "";
    {
        // Return-Path の指定
        if (!empty($returnPath)) {
            $mtaOption = "-f ". $returnPath;
            ini_set("sendmail_from", $from);
        }
    }

    // メールを送信する
    $result = mb_send_mail($to, $subject, $body, $header, $mtaOption);

    // 変更した PHP ディレクティブを回復する
    {
        ini_set("mbstring.language", $previousLanguage);
        ini_set("mbstring.internal_encoding", $previousEncoding);
        ini_set("sendmail_from", $previousWinFrom);
    }

    return $result;
}
?>

トップページへ戻る / 前のページへ戻る

back to top