PHPWord でキーワードが見つからない場合の対応

PHPWord では 値を挿入する場所などを "${keyword}" のように 定義することができます。

WRITE_0938_01

$phpWord->setValue('keyword1', 'ああああ');
$phpWord->setValue('keyword2', 'いいいい');
$phpWord->setValue('keyword3', 'うううう');

ところが 次のように 値が設定されず キーワードが残ったままに なることがあります。

WRITE_0938_02

列を複写する場合などは 次のようなエラーも出ます。

Can not clone row, template variable not found or variable contains markup.

この「キーワードが残ったままになる(PHPWord がキーワードを見つけられない)」状態は Word ファイルを構成する XML の中で キーワードが分割されてしまっているために 発生するようです。

docx ファイルを zip ファイルに変更して XML として開くと 次のようになってたりします。

WRITE_0938_03

キーワードが途中で分割されているため PHPWord が キーワードとして判定できないようです。

修正方法ですが、普通に WORD ファイルとして開いて キーボードを一旦消します。 そして 手入力せずに クリップボードから貼り付けます。

そのため PHPWord 用のテンプレートファイルを作るときは キーワードを手入力するのではなく クリップボードから貼り付けるようにした方が無難です。

PHP復習メモ - 文字列と加算子

PHP の落とし穴シリーズ。
(ちなみに PHP のバージョンは 5.5.9 です)

まず、PHP には 文字列でも数値として解釈できそうであれば 頑張って計算してくれる優しさがあります。

たとえば、次の処理、、、

$a = '1yen';
$b = '3円';

$c = $a + 1;
$d = $b + 2;

var_dump($a);
var_dump($b);
var_dump($c);
var_dump($d);

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

string(4) "1yen"
string(4) "3円"
int(2)
int(5)

"1yen" や "3円" の数字部分だけが計算されました。

変数を使わなくても同じです。

var_dump('1yen' + 1);
var_dump('3円' + 2);
int(2)
int(5)

通常、こんな使い方はしないと思いますが、 数字部分だけ解釈したんだな、とまだ納得できます。

ところが、加算子の場合は、また微妙な動きをします。

$a = '1yen';
$b = '3円';

$a++;
$b++;

var_dump($a);
var_dump($b);
string(4) "1yeo"
string(4) "3円"

なんと "1yen" の "n" がインクリメントされて "o" になりました。 "円" の方はそのままです。

13 回インクリメントすると、、、

$a = '1yen';

foreach (range(1, 13) as $i) $a++;

var_dump($a);
string(4) "1yfa"

桁が上がり "1yen" の "e" まで "f" になってしまいました。

ちなみに減算子だとそのままです。

$a = '1yen';
$b = '3円';

$a--;
$b--;

var_dump($a);
var_dump($b);
string(4) "1yen"
string(4) "3円"

こういう動きをするから PHP は楽しいですね。

[参考]
PHP: 加算子/減算子 - Manual

PHP 復習メモ - 論理演算子 && and || or

PHP の落とし穴シリーズ。
(ちなみに PHP のバージョンは 5.5.9 です)

PHP の論理演算子の論理積と論理積には "&&"、"and"、"||"、"or" がありますが、 && は、and の別名や古い記法ではありません。 演算の優先順位が異なっている別の演算子なのです。

次のような 2つの処理では、結果が変わることがあります。

# 1
$result = func1() && func2();
# 2
$result = func1() and func2();

and は代入演算子より優先順位が低いため 以下のように解釈されます。

# 1'
$result = (func1() && func2());
# 2'
($result = func1()) and func2();

関数 func1() が true を返し、 関数 func2() が false を返す場合 変数 $result には #1 は、false が格納されますが #2 は、true が格納されます。

|| と or の関係も同じです。

ちゃんとカッコで囲んでおけば大丈夫ですが、 とりあえずは && と || を使うようにしておけば良いのかなと思います。

PHP 復習メモ - isset() は null のときも false

PHP の落とし穴シリーズ。
(ちなみに PHP のバージョンは 5.5.9 です)

まだ isset() が 言語構造だとかわかってなかったときに 落とし穴に落ちたことがあります。

次のように配列の要素を取り出そうとして そのキーが存在しない場合、警告が出ます。

$key = $my_array['key'];
Notice: Undefined index: key in ...

isset を使うと確認ができます。

$key = isset($my_array['key']) ? $my_array['key'] : NULL;

if文でも便利です。

if (isset($my_array['key']) && $my_array['key'] ==  ...

PHP の &&(and) や ||(or) は短絡評価なので 後続の式の評価はされません。(警告が出ません)

ただ、この isset ですが、 値が null の場合も true になってしまいます。

たとえば、 次のような処理を書いてしまうと isset() が false になってしまい is_null() が評価されません。

$my_array['key'] = null;

if (isset($my_array['key']) && is_null($my_array['key'])) {
    echo "null";
}
else {
    echo "not null";
}
not null

このような場合は array_key_exists などの他の手段を使う必要があります。

$my_array['key'] = null;

if (array_key_exists('key', $my_array) && is_null($my_array['key'])) {
    echo "null";
}
else {
    echo "not null";
}
null

とは言え、isset() は便利ですので 理解した上で、使い分ける必要があるのだと思います。

[参考]
PHP: array_key_exists - Manual

PHP 復習メモ - PHPの三項演算子は左結合

PHP が他の言語と違う変なトコの 1つです。
(ちなみに PHP のバージョンは 5.5.9 です)

あまりこんな書き方はしないと思うので 実害はないかもしれませんが PHP の三項演算子は 他の言語と違って左結合です。

$ php -r "echo true ? 1 : false ? 2 : 3;"
2

結果は 2 が返ってきます。

これは次のように左結合で解釈されるためです。

((true ? 1 : false) ? 2 : 3);

他の言語だとだいたい右結合ですよね。

(true ? 1 : (false ? 2 : 3));

とりあえず、三項演算子を使うときはカッコで囲みましょう。

余談ですが、1つ目の値(判定に使っている値)と 2つ目の値(TRUEの時の値)が同じ場合、 省略することができます。

$result = func() ?: null;

I/O 処理などがあり 2回実行したくないときに使えます。

PHP 復習メモ - 同じ関数名は定義できない

最近 PHP 力が下がってる気がするので基本から復習中。
(ちなみに PHP のバージョンは 5.5.9 です)

PHP では引数が違っていても同じ関数名は定義できません。

function a ($v1) {
    ...
}

function a ($v1, $v2) {
    ...
}

// Fatal error: Cannot redeclare a() in ...

Fatal error が発生します。

PHP では関数名の大文字小文字を区別しないため 大文字・小文字でもエラーになります。

function a ($v1) {
    ...
}

function A ($v1, $v2) {
    ...
}

// Fatal error: Cannot redeclare A() in ...

PHPの関数も再定義はできません。

function strlen ($v1) {
    ...
}

// Fatal error: Cannot redeclare strlen() in ...

クラスやメソッドも同じです。 (ちなみに、クラス名やメソッド名も大文字小文字を区別しません)

class Test {}

class Test {}

// Fatal error: Cannot redeclare class Test in ...
class Test {

    function b ($v1) {
        ...
    }

    function b ($v1, $2) {
        ...
    }
}

// Fatal error: Cannot redeclare Test::b() in ...

久しぶりに PHP でハマったこと - 配列とリファレンス

結構あるあるだと思うのですが、すっかり忘れていてハマってしまいました。 ちなみに PHP のバージョンは 5.5.9 です。

PHP の配列の代入は値渡しです。

$a = [1, 2, 3];

$b = $a[0];

$c = $a;
$c[0] = 4;

print_r($a);
// Array ( [0] => 1 [1] => 2 [2] => 3 )

上記のように代入先の配列 $c の要素を変更しても 元の配列 $a の要素は変更されません。

ところが、次のように 配列の要素を参照渡ししてしまうと その要素はリファレンスになってしまいます。

$a = [1, 2, 3];

$b =& $a[0];

$c = $a;
$c[0] = 4;
$c[1] = 6;

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

その結果、上記のように値渡ししたはずの配列 $c の要素を変更したのに 元の配列 $a の要素が変更されてしまいます。

このとき、更に怖いのが $a の 2つ目の要素は 変更されていないことです。 変数 $b に参照渡しした 1つ目の要素だけがリファレンスになります。

これは関数に引数として配列を渡すときも同じです。

function test($arr) {
    $arr[0] = 8;
}

$a = [1, 2, 3];

$b =& $a[0];

test($a);

print_r($a);
// Array ( [0] => 8 [1] => 2 [2] => 3 )

関数の内部で引数の配列を変数としてそのまま利用していると 変更した内容が、元の配列に反映されてしまします。

PHPのマニュアルにも書いてあります。

[参考]
PHP: リファレンスが行うことは何ですか? - Manual

通常あまり発生することではないと思いますが それゆえに忘れがちで気づきにくいですね。

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');