HTML5 の multiple 属性と PHP の受信

HTML5 で追加された multiple 属性を使うと 1つの input タグから 複数のファイルをアップロードすることができます。

<input type="file" ... multiple>

見た目(↓はFirefox)は、 multiple 属性を付けない場合と同じです。

WRITE_0801_01

ファイルを選択するときに 複数選択できます。

WRITE_0801_02

ファイルの選択後、 Firefox では 選択したファイル数が表示されます。

WRITE_0801_03

ファイルを 1 つしか選択しない場合は multiple 属性を付けない場合と同じで 選択したファイル名が表示されます。

WRITE_0801_04

次に サーバ側の PHP で multiple 属性を使った input タグから 複数のファイルを受けてみます。

まず ダメなパターンですが、 <input> タグに test_files という 名前を付けます。

<input type="file" name="test_files" ... multiple>

"1.txt" と "2.txt" を選択して送信(submit)します。

PHP 側で $_FILES の内容を出力してみます。

var_dump($_FILES['test_files']);
array(1) {
  ["test_files"]=> array(5) {
    ["name"]=> string(5) "2.txt"
    ["type"]=> string(10) "text/plain"
    ["tmp_name"]=> string(14) "/tmp/php2zriSB"
    ["error"]=> int(0)
    ["size"]=> int(1610229)
  }
}

ファイルを複数送信したのに "2.txt" しかありません。

これは PHP で 1つの名前で複数の値を送る場合 input タグの名前を配列にしないとならないからです。 (通常のテキストボックスやラジオボタンなどでも同様です)

名前に [] を付けて配列の指定にします。

<input type="file" name="test_files[]" ... multiple>
array(1) {
  ["test_files"]=> array(5) {
    ["name"]=> array(2) {
      [0]=> string(5) "1.txt"
      [1]=> string(5) "2.txt"
    }
    ["type"]=> array(2) {
      [0]=> string(10) "plain/text"
      [1]=> string(10) "plain/text"
    }
    ["tmp_name"]=> array(2) {
      [0]=> string(14) "/tmp/php3MKNhg"
      [1]=> string(14) "/tmp/phpyJ9uB0"
    }
    ["error"]=> array(2) {
      [0]=> int(0)
      [1]=> int(0)
    }
    ["size"]=> array(2) {
      [0]=> int(310934)
      [1]=> int(1610229)
    }
  }
}

今度は、上のような内容で受け取れました。

対応していないブラウザでも、今までと同じ動作ができるのが良いですね。

PHP でファイルのアップロードの上限サイズを変更するv

メモです。

関連するパラメータは upload_max_filesize post_max_size memory_limit です。

upload_max_filesize がアップロードされるファイルの最大サイズ。 post_max_size がPOSTデータに許可される最大サイズ。 memory_limit がスクリプトが確保できる最大メモリ。

値は次のようになるようにしておきます。

memory_limit > post_max_size > upload_max_filesize

.htaccess が使える場合、.htaccess に定義することもできます。

php_value upload_max_filesize 10M
php_value post_max_size 12M

PHP: Invalid argument supplied for foreach()

Invalid argument supplied for foreach()

この警告は、 PHP の foreach で、 配列として扱えないものを回そうとしたときに出ます。

実際あったのが SimpleXML を使っていて、 XPath の取得結果でした。

foreach($xml->xpath('xxxx') as $data) {}

この場合、XPath で取得できないと FALSE が返ってきます。 その結果、foreach で警告が出ます。

PHP でコマンドラインから引数を受け取る

コマンドラインで PHP を使っているときに コマンドラインの引数を受け取るメモです。

PHP が自動で用意する変数 $argc に引数の数、$argv に引数の値が配列で 格納されます。

<?php
var_dump($argc);
var_dump($argv);

次のようになります。

$ php test.php arg1 arg2

int(3)
array(3) {
  [0]=>
  string(8) "test.php"
  [1]=>
  string(4) "arg1"
  [2]=>
  string(4) "arg2"
}

ただし register_argc_argv が無効になっていると $argc と $argv は使用できません。

[参考]
PHP: $argc - Manual PHP: $argv - Manual

PHP から標準エラー出力

コマンドラインで PHP を使っているときに 標準エラー出力に出力するメモです。

<?php
fputs(STDERR, "Error!!");

標準入出力や標準エラー出力は、 PHP がストリームを開いてくれているので 定数を使うだけで利用することができます。

<?php
$string = fgets(STDIN);   //標準入力
fputs(STDOUT, "標準出力");
fputs(STDERR, "標準エラー出力");

これらのストリームは閉じるのも PHP が自動でやってくれます。

[参考]
PHP: I/O ストリーム - Manual

PHP5.4 の新機能『ビルトイン Web サーバ』を使ってみる 3

ビルドイン Web サーバには、ファイルを指定して起動すると そのファイルを Web サーバのルータースクリプトとして使うそうです。

ようするに、アクセスに対して常にそのファイルを呼び出してくれるわけです。

例えば、次のように起動します。

$ cd /var/www
$ /usr/local/php540/bin/php -S 0.0.0.0:3000 

この場合、次のファイルがルータスクリプトになります。

/var/www/test.php

ドキュメントルート以外のファイルを指定することもできます。

$ cd /var/www
$ /usr/local/php540/bin/php -S 0.0.0.0:3000 /tmp/test.php

このようにファイルを指定して起動した場合は、 次のように色々なアクセスをしても 常にそのファイルが呼び出されます。

http://127.0.0.1:3000/
http://127.0.0.1:3000/aaa.php
http://127.0.0.1:3000/a/b/c/d/e?a=1

処理を分岐させることもできますが、 そのファイルだけをテストしたいときも アクセスが簡単になるので便利です。

PHP5.4 の新機能『ビルトイン Web サーバ』を使ってみる 2

PHP5.4 の新機能『ビルトイン Web サーバ』の続きです。

ビルトイン Web サーバは、apache が持ってるような SSI などの機能は使えません。 あくまで PHP を試すためのものですね。

今回は $_ENV や $_SERVER の値を見てみます。

まず /var/www をドキュメントルートにして /usr/local/php540/bin/php を起動します。

$ cd /var/www
$ /usr/local/php540/bin/php -S 0.0.0.0:3000

次の URL でアクセスします。

http://127.0.0.1:3000/test.php/a/r/r?a=1

$_ENV は次のようになりました。

Array
(
    [TERM] => xterm
    [SHELL] => /bin/bash
    [XDG_SESSION_COOKIE] => e60d136e5f757051cf175ad04f5ec5ad-1333074947.75417-1176305813
    [SSH_CLIENT] => 192.168.1.123 4454 22
    [SSH_TTY] => /dev/pts/0
    [USER] => odin
    [LS_COLORS] => rs=0:di=01;34:ln=01;36:hl=44;37:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:
    [MAIL] => /var/mail/odin
    [PATH] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
    [PWD] => /var/www
    [LANG] => en_US.UTF-8
    [SHLVL] => 1
    [HOME] => /home/odin
    [LOGNAME] => odin
    [SSH_CONNECTION] => 192.168.1.123 4454 192.168.1.123 22
    [LESSOPEN] => | /usr/bin/lesspipe %s
    [LESSCLOSE] => /usr/bin/lesspipe %s %s
    [OLDPWD] => /var/www/php
    [_] => /usr/local/php540/bin/php
)

$_SERVER は次のようになりました。

Array
(
    [DOCUMENT_ROOT] => /var/www
    [REMOTE_ADDR] => 192.168.1.123
    [REMOTE_PORT] => 4509
    [SERVER_SOFTWARE] => PHP 5.4.0 Development Server
    [SERVER_PROTOCOL] => HTTP/1.1
    [SERVER_NAME] => 0.0.0.0
    [SERVER_PORT] => 3000
    [REQUEST_URI] => /test.php/a/r/r?a=1
    [REQUEST_METHOD] => GET
    [SCRIPT_NAME] => /test.php
    [SCRIPT_FILENAME] => /var/www/test.php
    [PATH_INFO] => /a/r/r
    [PHP_SELF] => /test.php/a/r/r
    [QUERY_STRING] => a=1
    [HTTP_HOST] => 192.168.1.123:3000
    [HTTP_USER_AGENT] => Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko/20100101 Firefox/11.0
    [HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    [HTTP_ACCEPT_LANGUAGE] => ja,en-us;q=0.7,en;q=0.3
    [HTTP_ACCEPT_ENCODING] => gzip, deflate
    [HTTP_CONNECTION] => keep-alive
    [HTTP_COOKIE] => XXXXXXXXXXXXXX
    [REQUEST_TIME_FLOAT] => 1333192830.1065
    [REQUEST_TIME] => 1333192830
    [argv] => Array
        (
            [0] => a=1
        )

    [argc] => 1
)

ついでに $_GET 。 これは当然、次のようになりました。

Array
(
    [a] => 1
)

PHP5.4 の新機能『ビルトイン Web サーバ』を使ってみる 1

PHP5.4 には PHP だけで Web サービスを提供できる 『ビルトイン Web サーバ』という新機能があります。

PHP5.4 をインストールしたので さっそく試してみます。

[参考]
PHP: ビルトインウェブサーバー - Manual

ビルトイン Web サーバは、起動したディレクトリが ドキュメントルートになります。 (-t オプションで指定は可能)

PHP のスクリプトを書いて すぐに試したいときは非常に便利です。

とりあえず起動。
PHP5.4.0 は /usr/local/php540 に入れています。

$ cd /var/www
$ /usr/local/php540/bin/php -S 0.0.0.0:3000
PHP 5.4.0 Development Server started at Thu Mar 29 15:04:02 2012
Listening on 0.0.0.0:3000
Document root is /var/www
Press Ctrl-C to quit.
$ netstat -ant
tcp    0  0 0.0.0.0:3000        0.0.0.0:*           LISTEN

"-S" オプションで起動して LISTEN する IP アドレスとポートを指定します。

終了するときは [Ctrl]+[C] を送ります。

http://127.0.0.1:3000/

PHP のスクリプトファイルを指定していない場合(ディレクトリまでの指定の場合) index.php → index.html の順に探して 見つからなければ 404 を返します。

ログは、サーバを起動したコマンドのターミナルに出力されます。

$ /usr/local/php540/bin/php -S 0.0.0.0:3000
PHP 5.4.0 Development Server started at Thu Mar 29 15:04:02 2012
Listening on 0.0.0.0:3000
Document root is /var/www
Press Ctrl-C to quit.
[Thu Mar 29 23:04:04 2012] 192.168.1.100:2669 [200]: /
[Thu Mar 29 23:04:04 2012] 192.168.1.100:2670 [200]: /default.css
[Thu Mar 29 23:04:04 2012] 192.168.1.100:2674 [200]: /logo.gif

ちなみに、このビルトイン Web サーバは、開発用に設計されたもので 実運用に使ってはいけないそうです。

Ubuntu10.04 に PHP5.4.0 をインストールしてみる

PHP5.4 の新機能を試すために インストールしてみます。

ソースのダウンロード。

$ wget http://jp.php.net/get/php-5.4.0.tar.gz/from/this/mirror \
       -O php-5.4.0.tar.gz

解凍。

$ tar xzvf php-5.4.0.tar.gz
$ cd php-5.4.0/

configure
今回は /usr/local/php540 に入れます。

$ ./configure --prefix=/usr/local/php540

次のようなエラーが出たので libxml2 を入れました。

configure: error: xml2-config not found.
Please check your libxml2 installation.
$ sudo apt-get install libxml2 libxml2-dev

再度 configure 。

$ ./configure --prefix=/usr/local/php540

make & make install 。

$ make
$ make test
$ sudo make install

make test でエラーが出たんですが 今回は新機能のお試しが目的なので とりあえずインストール終了。

PHP の配列 その 6

今回は配列カーソルの話です。

PHP の配列には、『現在配列のここを見てますよ』というのを覚えておくカーソルというものがあります。

例えば次のような配列があるとします。

$fruit = array('a' => 'apple', 'b' => 'banana', 'c' => 'cranberry');

each 関数は 配列のキーと値のペアを返しますが 使用するたびに次の要素に移動します。

$a = each($fruit); // 'a', 'apple' が代入される
$b = each($fruit); // 'b', 'banana' が代入される
$c = each($fruit); // 'c', 'cranberry' が代入される
$d = each($fruit); // FALSE が代入される

こんなことができるのは 配列が内部的に現在の位置を覚えているためです。

次の while ができるのも同じ理由です。

while (list($key, $val) = each($fruit)) {
    echo "$key => $val\n";
}

カーソルを初期化するための reset という関数があります。

reset($fruit);
while (list($key, $val) = each($fruit)) {
    echo "$key => $val\n";
}

他で参照されている可能性がある場合 上のようにループ処理の前では カーソルを初期化します。

気をつけなくてはならないのは、 カーソルは「配列を別の変数に代入すると初期化される」という仕様です。 「配列に変数を」ではなく「変数に配列を」なのがわかりにくいところです。

次のコードは無限ループになってしまいます。

$fruit = array('a' => 'apple', 'b' => 'banana', 'c' => 'cranberry');

while (list($key, $val) = each($fruit)) {
    echo "$key => $val\n";
    $food = $fruit;  // この処理でカーソルが初期化される
}

上の処理は次の処理と同じです。

$fruit = array('a' => 'apple', 'b' => 'banana', 'c' => 'cranberry');
$food = $fruit;

while (list($key, $val) = each($fruit)) {
    echo "$key => $val\n";
    reset($fruit);  // この処理でカーソルが初期化される
}

PHP の配列 その 5

今回は list の話です。

次のように、配列の形で値を返す関数があるとします。

function test() {
    return array(1, 2, 3);
}

この関数から返る値を 配列を使わずに受ける場合 list という PHP の言語要素を使用します。

list($a, $b, $c) = test();

変数 $a,$b,$c に それぞれ 1,2,3 が格納されます。

list は関数ではなく言語の構造なので 変わった記述ができます。 例えば次のように 受け側の数が少なくても OK です。

list($a, $b) = array(1, 2, 3, 4);
var_dump($a, $b);
int(1)
int(2)

受け側の変数が 2 つなので 配列も 2 つ目までが格納されます。

次のように変数を省略することもできます。

list(, $a, , $b) = array(1, 2, 3, 4);
var_dump($a, $b);
int(2)
int(4)

この場合、変数を省略した 1 つ目、3 つ目は飛ばされて 2 つ目と 4 つ目の値が格納されました。

あと、list は数字がキーの要素のみ扱うので 文字列がキーの配列は無視されてしまいます。

例えば次の例では、2 つ目と 3 つ目の文字列がキーの 要素は無視されるので $a,$b には 0,1 が入ります。

list($a, $b) = array(0 => 0, 'a' => 1, 'b' => 2, 1 => 3);

次の例の場合、キーが 1 の要素が存在しないため NOTICE が発生して $b には NULL が入ります。

list($a, $b) = array(0 => 0, 2 => 2);

要するに list は次のような処理になるわけです。

list($a, $b, $c) = $array;
  // ↑
  // この 2 つは同じ処理
  // ↓
$a = $array[0];
$b = $array[1];
$c = $array[2];

そのため、キーが負の数字の場合も無視されます。

list($a, $b) = array(1 => 1, -5 => -5, 0 => 0);

この場合、$a,$b には 0,1 が入ります。

なかなか便利な list ですが、 受け側も配列の場合、気をつけなくてはならないことがあります。

list は 右側から順に値を格納していきます。

つまり次のような場合、キーの値と定義順が逆になります。

list($a[0], $a[1]) = array(1, 2, 3, 4);
print_r($a);
Array
(
    [1] => 2
    [0] => 1
)

格納される値は変わりませんが キーが 1,0 の順で定義されています。 そのため、for と foreach で結果が変わってしまうようなことになります。

for ($i = 0, $c = count($a); $i < $c; $i++) echo $a[$i];
int(1)
int(2)
foreach ($a as $v) echo $v;
int(2)
int(1)

さらにキーを省略した場合は もっと大変なことになります。

list($a[], $a[], $a[]) = array(1, 2, 3, 4);
print_r($a);
Array
(
    [0] => 3
    [1] => 2
    [2] => 1
)

右側から順に格納されるため キーまで逆に付いてしまいます。

オマケですが、次のように list をネストさせることもできます。

list($a, list($b, $c)) = array(1, array(2, 3));

PHP の配列 その 4

前回から間が空いてしまいましたが PHP の配列に関する話を またやっていこうと思います。

PHP の配列は キーを省略して定義することができます。

$a[] = "a";
$a[] = "b";
$a[] = "c";
print_r($a);

キーを省略した場合は その配列の最大のキーの値に 1 を足した値がキーになります。 (配列が空の場合は 0 になります)

Array
(
    [0] => a
    [1] => b
    [2] => c
)

ただし、ここで最大値を求めるキーは 数値のキーのみになります。 文字列のキーが存在してもそれは無視されます。

$a["test1"] = "a";
$a["test2"] = "b";
$a[] = "c";
$a[] = "d";
print_r($a);
Array
(
    [test1] => a
    [test2] => b
    [0] => c
    [1] => d
)

このように文字列のキーは無視して 数値のキーが設定されます。

次のようなこともできます。

$a["test1"] = "a";
$a[] = "b";
$a["test2"] = "c";
$a[] = "d";
print_r($a);
Array
(
    [test1] => a
    [0] => b
    [test2] => c
    [1] => d
)

この仕様を利用して 配列のキーを 1 から始めることもできます。

$a[1] = "a";
$a[] = "b";
$a[] = "c";
print_r($a);
Array
(
    [1] => a
    [2] => b
    [3] => c
)

次のようにキーの値の間が空いていても詰められません。

$a[10] = "a";
$a[] = "b";
$a[20] = "c";
$a[] = "d";
$a[5] = "e";
$a[] = "f";
print_r($a);
Array
(
    [10] => a
    [11] => b
    [20] => c
    [21] => d
    [5] => e
    [22] => f
)

PHP 4.3 からキーの最大値が、 負の数値の場合
キーは最大値 +1 ではなく 0 になります。

$a[-5] = "a";
$a[] = "b";
Array
(
    [-5] => a
    [0] => b
)

PHP の配列 その 3

配列を宣言するときに、同じキーを使うことができてしまうため 次のように書くと後から書いたキーで上書きされてしまいます。

$array("1" => "a", "1" => "b");
print_r($a);
Array
(
    [1] => b
)

また、配列は foreach で要素を順番に回すことができますが この「順番」というのは、キーの並びにはなりません。 ソートなどをしてない状態では、登録した順番になります。

$a[3] = "c";
$a[1] = "a";
$a[2] = "b";

foreach ($a as $key => $value) {
    echo $value;
}
c
a
b

マップであることを考えれば当然なのですが インデックス型の配列だと思っていると間違えてしまうので注意が必要です。

PHP の配列 その 2

ちょっと用語を整理しておきます。

このサイトだけの定義ですが、配列の添え字はキー、数字がキーの配列はインデックス型配列、文字列がキーの配列は連想配列と呼ぶことにします。 単に配列という場合は PHP の配列のことです。

PHP の配列は マップでインデックス型配列と連想配列を表現しているわけなんですが そのためか、キーが数字のみの文字列だったり、小数を含んだ数値だったりすると、変換されてしまうケースがあります。

キーに小数を含んだ数値を指定

キーに小数を含んだ数値を指定すると、小数部分が切捨てられ 整数部分のみキーとして使用されます。

$a[3.1] = "aaaa";
print_r($a);
Array
(
    [3] => aaaa
)

キーにはマイナスの数値も指定できますが その場合も同様です。

$a[-2.5] = "bbbbb";
print_r($a);
Array
(
    [-2] => bbbbb
)

次のようにすると、配列が上書きされてしまいます。

$a[3.1] = "3.1";
$a[3.2] = "3.2";
print_r($a);
Array
(
    [3] => 3.2
)

キーに数字のみの文字列を指定

キーに数字のみの文字列を指定すると 数値に変換されてインデックス型配列になります。

$a["13"] = "13a";
print_r($a);
Array
(
    [13] => 13a
)

マイナスの場合も同様ですが、小数を含んだ数値の場合は 文字列として扱われます。

$a["-13"] = "13b";
$a["2.5"] = "2.5b";
print_r($a);
Array
(
    [-13] => 13a
    ["2.5"] => 2.5b
)

1 つの配列に インデックス型配列と連想配列を混在させる場合 (あまり無いと思いますが) 文字列でキーを指定したつもりが 数値のキーを上書きしてしまうかもしれないので 注意が必要です。

キーに TRUE, FALSE, NULL を指定

オマケです。

$a[TRUE] = "TRUE";
$a[FALSE] = "FALSE";
$a[NULL] = "NULL";
print_r($a);
Array
(
    [1] => TRUE
    [0] => FALSE
    [""] => NULL
)

PHP の配列 その 1

PHP でプログラミングしていると 配列を非常に良く使います。 終始配列を操作してることもあるくらいです。 そんな PHP の配列について 復習も兼ねて書いていきたいと思います。

PHP の配列は、次の (1) のように 数字を添え字にした使い方と (2) のように文字列を添え字にした いわゆる連想配列やハッシュなどと呼ばれる使い方があります。

$a[2] = 3;      //(1)
$b['ten'] = 10; //(2)

配列の種類が 2 つあるように見えますが これは実は同じもので PHP の配列はキーに対して値を設定する マップになっています。

なので、上記の数字と文字列が添え字の配列を 1 つの配列にまとめることができます。

$a[2] = 3;      //(1)
$a['ten'] = 10; //(2)

関数の戻り値など以外で 配列を作るには array() や上記のような [] 指定を使います。 (array() は echo() などと同様に言語の要素なので通常の関数とはちょっと違います)

$a = array(1, 3, 7, 9);

上のように宣言した場合、添え字(キー)は 先頭から順に 0,1,2 …となります。 [] 指定だと次のようになります。

$a[0] = 1;
$a[1] = 3;
$a[2] = 7;
$a[3] = 9;

文字列を添え字(キー)にするときは次のようにします。

$a = array("a" => 2, "b" => 4, "c" => 6);

これは次の指定と同じです。

$a["a"] = 2;
$a["b"] = 4;
$a["c"] = 6;

配列は、最初に書いたように混在させることができます。

$a = array(1, 3, "a" => 2, 7, "b" => 4, "c" => 6);

なぜ指定の異なるものを混在させることができるのでしょうか?

それは指定の少ない方(キーを宣言しない方)は 暗黙的にキーが指定されているのです。

つまりキー(添え字)を指定しない次の式は

$a = array(1, 3, 7, 9);

実は暗黙的に次のように指定していることになります。

$a = array(0 => 1, 1 => 3, 2 => 7, 3 => 9);

このようにして PHP は 1 つのマップで 数字を添え字にした配列と 文字列を添え字にした配列を使えるようにしているわけです。

PHP でクラスのオートロード

PHP でクラスをロードする場合、 クラスを定義したファイルを読み込んでおく必要がありますが PHP5 からは オートロードという自動化の仕組みが追加されました。

[参考]
PHP: クラスのオートローディング - Manual

Sample というクラスを Sample.php に定義した場合 PHP4 では次のようにインスタンスを作成していました。

include 'Sample.php';
$obj  = new Sample();

使用するクラスが少なければこれでも良いのですが クラスが大量にある場合は面倒です。

PHP5 からは __autoload 関数を定義しておけば 未定義のクラスを使用した時に この関数を実行してくれます。 引数にクラス名が入るので この関数の中でファイルの読み込めば 自動化できるわけです。

function __autoload($name) {
    include $name . '.php';
}

$obj1  = new Sample1();
$obj2  = new Sample2();
$obj3  = new Sample3();

これでソースをすっきりさせることができます。

var_dump() の戻り値を文字列に格納する

PHP の便利な関数 var_dump() ですが 結果を画面に出力してしまいます。 通常なんら不都合はありませんが、文字列に格納しておいて 後で利用したい場合は、少し手を加えます。

まず var_dump() の結果を文字列に格納するためには バッファを使用する必要があります。

PHP のバッファはネストすることができるので すでに何らかのバッファが設定されている場合でも大丈夫です。

次のようになります。

ob_start();
var_dump($_SERVER);
$server = ob_get_contents();
ob_end_clean();
  • ob_start() でバッファを設定する
  • バッファされてる状態で var_dump() を出力する
  • ob_get_contents() で現在のバッファの内容を取得する
  • ob_end_clean() はバッファを出力せずに解除する

この方法は他の直接出力する関数でも使用することができます。

PHP で、大文字・小文字を無視して比較する

PHP には strcasecmp() という大文字・小文字を無視して比較できる関数があります。

[参考]
PHP: strcasecmp - Manual

この関数、真偽値を返すのではなく、『str1 が str2 より小さい場合は負、str1 が str2 より大きい場合は正、等しい場合は 0 を返します。』というものなので 次のような使い方をすると結果が逆になってしまいます。

if (strcasecmp('aaaa', 'AAAA')) {
...

等しい場合が 0 なので FALSE 扱いになるわけです。 一致していることを判定するには、次のように 0 と比較するか、否定します。

if (strcasecmp('aaaa', 'AAAA') == 0) {
...

if ( ! strcasecmp('aaaa', 'AAAA')) {
...

ただ、否定だとあとで意味を取り違えることがあるかもしれないので 0 で比較する方が良いと思います。

PHP で外部のデータを取得する(プロキシ経由)

前に file_get_contents() 関数を使ったネタを書きましたが 社内などでプロキシを経由している場合は エラーになってしまいます。

プロキシの接続情報を含め、追加情報を設定するには 第 3 引数にコンテキストを設定します。

echo file_get_contents('http://www.yahoo.co.jp/', FALSE, $context); 

コンテキストを作成するには stream_context_create() 関数を使います。

[参考]
PHP: stream_context_create - Manual

HTTP の場合次のようにすると POST 送信になります。

$context = stream_context_create(
  array(
    "http" => array(
      "method" => "POST",
    )
  ));

設定できるパラメータはマニュアルを参照してください。

[参考]
PHP: コンテキストオプションとパラメータ - Manual

プロキシの設定は次のように tcp://host:port で指定します。

$context = stream_context_create(
  array(
    "http" => array(
      "proxy" => "tcp://192.168.1.200:3128",
      "request_fulluri" => TRUE,
    )
  ));

プロキシ経由の場合 request_fulluri も指定します。

プロキシにユーザ認証がある場合ですが ネットで検索したら proxy_user, proxy_pass を設定するとありました。

$context = stream_context_create(
  array(
    "http" => array(
      "proxy" => "tcp://192.168.1.200:3128",
      "request_fulluri" => TRUE,
      "proxy_user" => "xxxxx",
      "proxy_pass" => "yyyyy",
    )
  ));

[参考]
プロキシ経由でfile_get_contents - Road To Nowhere

ただ、この方法では自分の環境では認証エラーになってしまいました。 仕方ないので 次のように プロキシ用のヘッダを生成して使用しています。 自分の環境のプロキシは Basic schema なので Base64 でエンコードするだけです。

$url = "http://www.google.co.jp/";

$proxy_host = "192.168.1.200";
$proxy_port = "3128";
$proxy_user = "xxxxx";
$proxy_pass = "yyyyy";
$proxy_auth = base64_encode("$proxy_user:$proxy_pass");

$context = stream_context_create(
  array(
    "http" => array(
      "proxy" => "tcp://$proxy_host:$proxy_port",
      "header" => "Proxy-Authorization: Basic $proxy_auth",
      "request_fulluri" => TRUE,
      )
  ));

echo file_get_contents($url, FALSE, $context);

PHP の大文字・小文字

PHP の変数は大文字・小文字を区別します。
ところが予約語は、大文字・小文字の区別がありません。

次のようなスクリプトを記述します。
echo 命令が大文字・小文字の 2 パターンあります。

$a = "small";
$A = "large";

echo $a;
ECHO $a;
echo $A;
ECHO $A;

結果は次のようになります。
変数だけ大文字・小文字が区別されています。

small
small
large
large

関数(自作含む)なども大文字・小文字の区別はされません。

クラスの場合 メソッドは区別なしで、変数は区別ありとなります。

class Test {
    var $v1 = "abc";
    function f1() {
        return $this->v1;
    }
}

$t = new Test();
echo $t->f1();
echo $t->F1();
echo $t->v1;
echo $t->V1;   // この行だけエラー

PHP で外部のデータを取得する

PHP はファイルの中身を丸ごと取得する file_get_contents() という関数があります。 この関数は http や ftp などのプロトコルを通して 外部にあるファイルも取得できるようです。

allow_url_fopen という設定が On になっていることが 前提ですが 5.3 のデフォルトでは On になっています。

allow_url_fopen という名前からもわかりますが fopen() 関数も同じ機能があります。 (というより file_get_contents() が fopen() の機能を使用しているのだと思います)

あとは次のように ファイル名の代わりに URI を指定します。

echo file_get_contents('http://www.yahoo.co.jp/'); 

http や ftp の他にも色々なプロトコルをサポートしています。

[参考]
PHP: サポートするプロトコル/ラッパー - Manual

正規表現 行末の $ と終端

正規表現で $ は終端ではなく行末を意味します。

たとえば次のような正規表現があります。

if (preg_match('/^abcdef$/', $value) ....

この場合、"abcdef" だけでなく "abcdef + 改行" もマッチしてしまいます。

ですので、次のような処理を書いて英小文字だけを期待しても

$input = $_GET['data'];
if (preg_match('/^[a-z]+$/', $input) ....

xxxxx.php?data=abcde とかだけでなく
xxxxx.php?data=abcde%0A もマッチしてしまうわけです。 (%0A は改行)

このようなときは次のように書くそうです。

$input = $_GET['data'];
if (preg_match('/\\A[a-z]+\\z/', $input) ....

[参考]
もし『よくわかるPHPの教科書』の著者が徳丸浩の『安全なWebアプリケーションの作り方』を読んだら - ockeghem(徳丸浩)の日記

PHP の header() 関数と HTTP レスポンスコード

ちょっとハマったのでメモを残しておきます。

PHP の header() 関数では 第 3 引数で HTTP レスポンスコードを指定することができます。

ところが次のように第 1 引数に NULL や空文字を指定すると HTTP レスポンスコードが設定されませんでした。

header(NULL, TRUE, 404); 
header('', TRUE, 404); 

次のようなレスポンスが返ります。

HTTP/1.1 200 OK
Date: Sun, 11 Sep 2011 02:58:13 GMT
Server: Apache/2.2.14 (Ubuntu) 

404 を期待しているのに 200 になってしまいます。

次のように、何か文字列を指定すれば 404 を返してくれます。

header('xxxx', TRUE, 404); 
HTTP/1.1 404 Not Found
Date: Sun, 11 Sep 2011 02:59:47 GMT 
Server: Apache/2.2.14 (Ubuntu) 

PHP のバージョンを出力しないようにする

PHP 5.3 のデフォルトでは expose_php という設定項目が On になっています。 この項目が On の場合、HTTP レスポンスヘッダに PHP のバージョンを出力します。

HTTP/1.1 200 OK
Date: Sat, 03 Sep 2011 04:24:15 GMT
Server: Apache/2.2.14 (Ubuntu)
X-Powered-By: PHP/5.3.2-1ubuntu4.9
Vary: Accept-Encoding
Content-Length: 1703
Connection: close
Content-Type: text/html

expose_php を Off にします。

HTTP/1.1 200 OK
Date: Sat, 03 Sep 2011 04:26:20 GMT
Server: Apache/2.2.14 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 1703
Connection: close
Content-Type: text/html

X-Powered-By の行が消えました。

Apache の設定で ServerSignature も Off にします。

HTTP/1.1 200 OK
Date: Sat, 03 Sep 2011 04:28:18 GMT
Server: Apache
Vary: Accept-Encoding
Connection: close
Content-Type: text/html 

Apache のバージョンも消えました。
サーバを公開する場合、情報は少ないほうが良いですね。

php5-xdebug の var_dump のオーバーロード

php5-xdebug を入れているのに 最近使っている環境では var_dump の表示が綺麗にならず(オーバーロードされず) 原因がわからなかったのですが、下記のサイトに書いてありました。

[参考]
php/インストール - zaininnari - livedoor Wiki(ウィキ)

xdebug で var_dump をオーバーロードさせるには html_errors が On になっていないとダメなようです。

html_errors は PHP_INI_ALL なので ini_set() します。

ini_set('html_errors', 1);

var_dump(array(1, 2, 3));

これで var_dump がオーバーロードされました。

PHP の定数

PHP には最初から定義されている定数が色々あります。

たとえば実行環境にあわせた行末文字の PHP_EOL 。

echo "Hello!" . PHP_EOL;

こういうのは意外と助かりますね。

PHP のバージョンを返す PHP_VERSION とかもあります。 古いバージョンに無い関数を使うときなどに判定に使えます。

if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
    ......
}

他にも TRUE, FALSE, NULL や、E_ALL などのエラー用の定数とかがあります。 定数で用意されているものは、定数を使いましょう。

[参考]
PHP: 定義済みの定数 - Manual

Apache の設定ファイルに PHP の設定を記述する

PHP を Apache のモジュールとして実行していて 設定項目が PHP_INI_ALL か PHP_INI_PERDIR の場合 Apache の設定ファイル (httpd.conf, apache2.conf) や .htaccess に PHP の設定を記述できます。

詳しくは次の表を参照。

モード php.ini Apache
設定ファイル
.htaccess スクリプト内
ini_set()
PHP_INI_ALL
PHP_INI_SYSTEM
PHP_INI_PERDIR
PHP_INI_USER

.htaccess に記述する場合 php_value と php_flag の 2 つのディレクティブを使うことができます。 php_value は論理値以外の値用で php_flag は論理値に使用します。

php_value default_charset UTF-8
php_flag html_errors on

Apache の設定ファイルに記述する場合 さらに php_admin_value と php_admin_flag の 2 つのディレクティブを使うことができます。

こちらのディレクティブで設定した場合 .htaccess や ini_set() では変更できないようになります。 PHP の設定ファイルである php.ini に記述した設定では そのようなことができないので 個々のユーザに変更されたくない サーバ全体の固い設定を作るときに良いですね。

PHP の 1 行コメント

PHP の 1 行コメントには # と // の 2 種類があります。

#  どちらもコメント
// こちらもコメント

Perl などで # のコメントに馴染んでいたり PHP でバッチ処理を作ったときに Bash などと同様に # で コメントアウトできるのはありがたいですね。

PHP の閉じタグ

PHP は次のように <?php 〜 ?> の間に PHP のロジックを書くことができます。

<?php
  echo "hello!";
?>

後ろに PHP のロジックしか無い場合 PHP の閉じタグ( ?> )を省略することができます。

<?php
  echo "hello!";

これは単に記述が少なくなるということではありません。 閉じタグの後に改行が来るとその部分は HTML 側の出力となってしまい ブラウザに改行が送られてしまいます。

PHP で XML を出力しなくてはならないときに 先頭に不要な改行コードがあると XML として 読み込みエラーになることがあります。

こんなことを防ぐためにも HTML 側の出力を持たない PHP の場合 閉じタグを付けずに終わるようにします。

タイプヒンティング

PHP5 からタイプヒンティングを使用することができます。 これは関数の引数の型を指定することができます。

次のように引数の前に型を宣言します。

function test(array %values) {
    .....
}

これで $values は配列を指定しないとエラーになります。

ただし配列とオブジェクト型だけで int などのネイティブ型には指定することができません。

配列定義の優しさ

PHP では配列を定義するとき次のように 最後の要素の後ろにカンマがあってもエラーになりません。

$a = array(1, 3, 4,);
                 ~~

このように定義した場合、配列の要素数は 3 です。 最後のカンマは無視されるわけです。

なぜこうなってるかですが、 配列は、要素が長かったり連想配列だと次のように定義することがあります。

$b = array('a' => 3,
           'b' => 2,
           'c' => 5);

4 つめの要素を追加するときは、最後の要素の後ろにカンマを付けて・・・と やらなくてはなりません。

ところが次のように定義してある場合、全ての要素の行を同じように扱うことができます。

$b = array(
        'a' => 3,
        'b' => 2,
        'c' => 5,
);

書く人に、とても優しいですね。 ちゃんと規約にしてしまえば、こちらの方がミスも少なくて良いんじゃないでしょうか。

これは Perl や JavaScript, Java なんかでも同じです。

PHP の配列の結合

次のような 2 つの配列があります。 キー "a" がどちらの配列にもあります。

$array1 = array('a' => 1, 'b' => 2);
$array2 = array('a' => 5, 'c' => 6);

この 2 つの配列を結合するのですが、 + 演算子と array_merge() 関数で キー "a" の値を上書きするかどうかを使い分けることができます。

まず + 演算子を使ってみます。

print_r($array1 + $array2);
Array
(
    [a] => 1
    [b] => 2
    [c] => 6
)

キー "a" は、1 のままです。

次に array_merge() 関数。

print_r(array_merge($array1, $array2));
Array
(
    [a] => 5
    [b] => 2
    [c] => 6
)

キー "a" は、5 で上書きされました。

このように + 演算子では上書きせず、 array_merge() 関数では上書きします。

PHP の preg_replace_callback 関数

Perl の文字列置換では、"e" オプションを付けると 置換先の指定を実行文として処理できるので、 条件にマッチした部分をサブルーチンで処理させることができます。

ややこしいので実際の例で説明します。

sub replace {
    return "[".($1*2)."]";
}

$xx = "123456789";
$xx =~ s/(\d)/replace($1)/ge;
print $xx;

文字列置換のときに replace() サブルーチンが実行されます。
実行結果は次のようになります。

$ perl replace.pl

[2][4][6][8][10][12][14][16][18]

これと同じことを PHP でやりたい場合 preg_replace_callback() という関数を使用します。

<?php
function replace($matches) {
    return "[".($matches[1]*2)."]";
}

$xx = "123456789";
$xx = preg_replace_callback("/(\d)/", "replace", $xx);
print $xx;
?>

実行結果は同じになります。
PHP の preg_replace_callback() では条件にマッチした部分を 第二引数のコールバック関数に渡すような形になります。

Perl の "e" オプションは、実行文として処理するだけなので サブルーチンを使用せずに 次のようにも書けます。

$xx = "123456789";
$xx =~ s/(\d)/"[".($1*2)."]"/ge;
print $xx;

PHP でも別の場所にわざわざ関数を定義したくない場合 create_function() で無名関数を作るという手があります。

<?php
$xx = "123456789";
$xx = preg_replace_callback("/(\d)/"
          , create_function('$matches'
                           ,'return "[".($matches[1]*2)."]";')
          , $xx);
print $xx;
?>

PHP で CSV ファイルを読み込む

PHP には、CSV ファイルからデータを読み込むための fgetcsv という便利な関数があります。

CSV の文字列を分解する str_getcsv という関数もあるのですが こちらは PHP 5.3 以上からになっています。

というわけで、今回は fgetcsv を紹介します。

【参考サイト】
PHP: fgetcsv - Manual

Manual に載っている次のソースを試してみます。

<?php
$row = 1;
$handle = fopen("test.csv", "r");
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
    $num = count($data);
    echo "<p> $num fields in line $row: <br /></p>\n";
    $row++;
    for ($c=0; $c < $num; $c++) {
        echo $data[$c] . "<br />\n";
    }
}
fclose($handle);
?>

CSV データは次のようになります。

"abc def","123",11,abc
"abc,def","12""3",11,abc

ダブルクォーテーションの間にカンマがあったり ダブルクォーテーションの中にダブルクォーテーションを付けたりしています。

結果は次のようになります。

 4 fields in line 1:
abc def
123
11
abc

4 fields in line 2:
abc,def
12"3
11
abc

カンマもダブルクォーテーションも問題なく読み込めています。

PHP 5.3.0 リリース

PHP の 5.3.0 がリリースされました。

名前空間や無名関数などが追加されたそうです。

無名関数は、JavaScript などでも見かける次のようなものです。

$function = function($message)
{
    echo "$message\n";
};

$function('Hello World');