● PHPと日本語文字コード
PHPのバージョン4台では、「mbstring」というモジュールがあり、
日本語などのマルチバイト文字のエンコーディング変換がサポートされています。
例えば、
$b = mb_convert_encoding($a, "EUC-JP", "SJIS");
とすると、シフトJISで書かれた文字列$aを日本語EUCに変換して、
$bに代入します。3番目の引数は省略できて、
その場合はPHPのシステムエンコーディングから、
2番目の引数で指定したエンコーディングへの変換となります。
これを使うと、シフトJISで書かれたテキストファイルを読んで、
日本語EUCで書かれたPHPスクリプトによってWebブラウザに表示する、
といったことが可能になります。
● 先月の末日は何日?
PHPで業務システムを作っていて結構あるのが、
先月の初日から末日までのデータをファイルやデータベースから抽出する、
という処理です。それで、末日の日付は何日だっけ?
という話なんです。OracleだとLAST_DAYという関数があるので、
現在の日時(SYSDATE)が属する月の末日を簡単に分かるのですが、
そうでない場合とか、あるいは、
集計期間(YYYYMMDD):
〜
このような↑入力フォームを表示したときのデフォルト期間として、
先月1日〜先月末日をセットしたい、という機会もあるでしょう。
その場合の簡単なコードです:
<?php
/* 先月の初日〜末日を求め、表示します。*/
$now = time();
list($h, $mi, $s, $d, $mo, $y) = localtime($now);
$y += 1900; $mo++;
print "$y $mo $d $h $mi $s\n";
/* 今月1日 */
$tsuitachi = mktime(0, 0, 0, $mo, 1, $y);
/* その1日前=先月末 */
$sengetsumatsu = $tsuitachi - 24 * 60 * 60;
list($h, $mi, $s, $d, $mo, $y) = localtime($sengetsumatsu);
$y += 1900; $mo++;
printf("%4d%02d%02d - %4d%02d%02d\n", $y, $mo, 1, $y, $mo, $d);
?>
● Perl互換の正規表現
PHPには幾つかの系統の正規表現の関数群がありますが、
Perl互換の「PCRE」という関数群が便利です。
そのpreg_matchの使い方は
$v = "1234.56";
if(preg_match("/^[1-9\.]+$/", $v)) print "マッチします。";
else print "マッチしません。";
こんな感じです。
PCREでは、Perlと同じように、正規表現をスラッシュ/で囲む必要があります(※1)。
その代わり、正規表現内部の\マークはエスケープさせる必要はありません。
※1 正規表現の中に/が含まれる場合、エスケープする必要があり見にくくなります。
そのような場合、/以外の文字を使うこともできます 。
例えばパイプ(縦棒)文字|を使って、「|^[1-9\.]+$|」
のようにすることもできます。#なども良く使われます。
また変り種として、中括弧 { } で囲んで表現することも可能です。
また、括弧で囲んだサブパターンを取得することもできます。
if(preg_match("/(.+)\.(.+)/", basename($path), $subs)){
$all = $subs[0]; $main = $subs[1]; $ext = $subs[2];
}
このように、正規表現中に括弧を指定すると、
それら各々に適合する部分文字列の配列が3番目の引数に格納されます。
(3番目の引数で指定する変数は、初期化してある必要はありません)
[0]は適合した文字列全体、[1]が1番目の括弧に適合した部分文字列、
[2]が2番目の括弧に適合した部分文字列…となります。
● 文字列が全角文字を含んでいるかどうかを知りたい。
厳密ではありませんが、ASCII文字だけからなるか、
8バイト文字コードを含んでいるかの判別なら次のように可能です。
if(preg_match("/[\\x80-\\xFF]/", $english_name)){
die("おおっと! 英語名には半角文字のみを使用して下さい。");
}
● ファイル名からのディレクトリと主ファイル名の切り出し
フルパスのファイル名からディレクトリだけを切り出すには
dirname 、主ファイル名だけを切り出すには
basename という関数があります。
● 文字列の検索
C言語のstrstrのように、ある文字列の中に別の文字列が含まれるかどうかを調べるには、
名前も同じstrstrという関数があります。
(strchrという関数もありますが、これはstrstrの別名です)
$result = strstr($target, $pattern);
この例では、$targetの中に$patternを含めばその最初に出会った位置以降の文字列を、
含まなければFALSEを返します。
● 文字列AがBで始まるか調べる
strposという関数があります。
if(strpos($target, $prefix) === 0){
print "targetはprefixで始まります。 ";
}
「strpos(A, B)」とすると、文字列Aの中にBが部分文字列として現れるとき、
その始まる位置(先頭が0)を返し、見つからなければboolean FALSEを返します。
で、問題は戻り値が0 (AはBで始まる)とFALSE(Aの中にBは見つからない)
の区別が難しいことです。上記のように、PHP4から導入された「===」
(値が等しく、データ型も等しい)演算子を使って、「=== 0」「=== FALSE」
とすればどちらを意図しているのかの可読性も保たれます。
● 配列変数に特定のキーが存在するかを調べる
if(array_key_exists($key, $array)){
● クラスとセッション:このエラーは何?
Fatal error: The script tried to execute a method or access a property of
an incomplete object. Please ensure that the class definition bunnorecord of
the object you are trying to operate on was loaded _before_ the session was started
これは分かりにくいメッセージですが、こういうことです。
セッション変数の値が、自分で定義したクラスのオブジェクトである場合で、
その変数のsession_register()のコールが、
そのクラスの定義よりも後で行われている、ということです。
でも、普通
session_start();
session_register("obj");
class objclass {
// ...
}
と、クラス定義が後でもエラーにはなりません。
これは、クラスの定義がページ全体をロードしたときに読み込まれた後、
スクリプトを実行していくときにsession_register()となるからです。
しかし、
session_start();
session_register("obj");
include "classdef.php";
と、クラス定義をインクルードファイルにした途端、上のエラーが出るはずです。
なぜならinclude文は実行時に解釈されるため、
session_register()→include文→クラスobjclassの定義を読む、
となりますから、session_register()を実行している段階ではそのクラス定義が分からない、
といってエラーになるわけです。
むむ。これは、分かりにくいですね。私も延々悩みました。
● 数値をカンマ区切りにする
number_formatという関数があり、これで「3456789」⇒「3,456,789」
のように変換してくれます。とても便利。
● 今日の日付を文字列で求める
最も簡単にはdate関数を使います。
mとdはいずれも左0詰で必ず2桁になります。
左0詰を行わない月と日を得るには、mとdの代わりにそれぞれnとjを使います。
● 2つの日付の間の日数を求める
日付からtime_t秒数を得るには、mktime 関数を使いますが、
C言語やPerlなどの慣例とは引数の並び順が違っていて、時分秒月日年の並びなので要注意。
<?php
$ds1 = $argv[1]; $ds2 = $argv[2];
if(! preg_match("/^(\d\d\d\d)(\d\d)(\d\d)/", $ds1, $da1)){
die("date1 $date1 unrecognized, expected YYYYMMDD.\n");
}
if(! preg_match("/^(\d\d\d\d)(\d\d)(\d\d)/", $ds2, $da2)){
die("date2 $date2 unrecognized, expected YYYYMMDD.\n");
}
$date1 = mktime(0, 0, 0, $da1[2], $da1[3], $da1[1]);
$date2 = mktime(0, 0, 0, $da2[2], $da2[3], $da2[1]);
$dates = abs($date1-$date2) / (24*60*60);
print "dates=$dates\n";
?>
● チェックボックス(checkbox)タグで配列パラメータをPHPに渡す
フォームでチェックボックス(checkbox)を使って、
複数の可変数の選択肢を同時に選択したいときがあります。
例えば、DBのレコードを一覧表示して、選択した任意数のレコードを削除したいときー。
この場合は、そのnameの値を「delkey」のような普通の名前ではなく、「delkey[]」
のように角括弧をつけます。
すると、PHPの世界では「$_POST["delkey"]」で、配列データとして取得できます。
<?php foreach($rset as $e){ ?>
<tr>
<td><input type="checkbox" name="delkey[]" value="<?= $e[1] ?>"></td>
<td><?= $e[0] ?></td>
<td><?= $e[1] ?></td>
<td><?= $e[2] ?></td>
</tr>
<?php } ?>
ここで、角括弧をつけない名前で同じようにしても、最後にチェックされたレコードの
valueの値しかPHP世界には渡されないので、要注意です。
● 配列の各要素を(キー,値)の組ごとに処理する際の慣用記法
先にちょっと古い方法から。listとeachの両関数を下のように使います。
while(list($key, $value) = each($_SESSION)){
print "[" . $key . "] = [" . $value . "]<br/>\n";
}
しかし、foreach文を使う方法がモアポピュラーと思います。
どちらも典型的な慣用記法なので覚えておくべかめれ。
foreach($_SESSION as $key => $value){
print "[" . $key . "] = [" . $value . "]<br/>\n";
}
● PHPとAjaxと日本語のPOSTパラメータ
JavaScriptから次のように送られてくる日本語データがあるとします。
xmlhttp.open('POST', url, false); // 同期通信
xmlhttp.setRequestHeader("content-type",
"application/x-www-form-urlencoded;charset=UTF-8");
xmlhttp.send(encodeURI(postdata));
このUTF-8の日本語をシフトJIS環境のPHPで受け取るには?
php.iniのmbstringセクションは次のように設定
[mbstring]
mbstring.language = Japanese
mbstring.internal_encoding = SJIS
mbstring.http_input = auto
mbstring.http_output = SJIS
; 残りはコメント
PHPスクリプトはシフトJISコードで、以下のように変換すればokです:
$indata = $_POST['indata'];
$indata = mb_convert_encoding($indata, 'Shift_JIS', "UTF-8");
● (Windows)コマンドプロンプトで実行できるコマンドが、system()で実行できない
正確にはよく分かりませんが、
system()関数で実行する際のPATHは、「システム環境変数」だけが有効で、
「ユーザ環境変数」の定義は参照されないようです。
したがって、ユーザ環境変数で通しているパスのプログラムは、
コマンドプロンプトでは実行できますが、PHPのsystem()では実行できない、
という現象が起きるようです。
● 「<?= 〜 ?>」がPHPに解釈されず、そのまま表示される
「<?= 〜 ?>」は正式ではない短いタグという扱いのようです。
そのためこの記法を使いたい場合は、
php.iniで「short_open_tag = On」と設定する必要があります。
● 文字列リテラルの中身を変えただけで、画面が出たり真っ白になったりする
シフトJISで書いているとき、
「表」など2バイト目がエスケープ文字と衝突する漢字を文字列リテラルに含めると、
正しく処理されず画面が真っ白になってしまうことがありました。
そのときの解決策?「表」という漢字を使わずに、別の意味の言葉に置き換えましたとさ…
● 他にも画面が真っ白になる場合があるぎゅう
画面が真っ白になってしまうのはほとんどの場合PHPの文法エラーぎゅう。
典型的なミスとしては、
ヒアドキュメントの開始が<<になっている(<が1つ足りない)
括弧の開閉の対応がおかしい
があるので、冷静にコードを確認しましょう。
● Warning: Cannot modify header information - headers already sent by...
メッセージ通りの意味としては、header()関数を使う前に、
HTTP応答の一部が既に送信されてしまっているので、ヘッダ情報を変えられませんということで、
例えば、
header()を使う前に<html>などのHTML要素を記述している
header()を使う前にrequireなどで取り込んでいる他のスクリプトが、エラー出力を行っている
PHPスクリプトの先頭に空の行がある(極端な例)
などの原因が考えられます。
しかし、上記は全部完璧な場合でもこのエラーが出てしまうことがあります。
それはphp.iniで
「output_buffering = Off」
になっている場合です。ここを
「output_buffering = On」に変更すると解決すると思います。
私はPHP 5.2.6でこの現象を確認できました。
(first uploaded 2002/07/01 last updated 2010/09/28, URANO398)