PHPの雑多なメモ

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関数を使います。

$today = date("Y/m/d");

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でこの現象を確認できました。

Open Source Web Architecture Top

(first uploaded 2002/07/01 last updated 2010/09/28, URANO398)

楽天モバイル[UNLIMITが今なら1円] ECナビでポインと Yahoo 楽天 LINEがデータ消費ゼロで月額500円〜!


無料ホームページ 無料のクレジットカード 海外格安航空券 解約手数料0円【あしたでんき】 海外旅行保険が無料! 海外ホテル