PHP

Last-modified: 2016-06-19 (日) 22:52:24

CSV

CSVの仕様

  • ExcelとRFC 4180の差異
    • フィールド内の改行はRFC 4180ではCR+LF、ExcelではLF。フィールド内にCR+LFがあると、Excelで開いたときに、未定義文字として表示される。
      きまぐれ ぷろぐらま語録
  • PHP fputcsv, fgetcsvは?

fgetcsv

文字エンコーディング問題

  • 発生しうる問題
    • 日本語文字がダブルクォートで囲まれていない場合に、日本語文字の一部が消える。(ダブルクォートで囲まれていると問題が発生しないらしい)
  • PHPの挙動
    • fgetcsvでは、システムのロケール設定が"考慮"される。
      PHPマニュアル fgetcsvより。
       この関数はロケール設定を考慮します。
       もし LANG が例えば en_US.UTF-8 の場合、 ファイル中の 1 バイトエンコーディングは間違って読み込まれます。
      • 「考慮」の内容が不明。
      • また、上記の2番目の文章「1バイトエンコーディングは間違って読み込まれます。」の意味がわからない・・・⇒マニュアルの誤訳。正しくは「1バイトのエンコーディングのファイルは間違って読み込まれます。」
  • OSのLANG設定に応じて、読み込みの挙動がどうかわるかは、softelメモが詳しい。

LANG="en_US.UTF-8"で、WindowsのExcelが作成したCSV(Shift_JIS)を読み取る場合、softelメモの方法で読み込むのが確実。

http://d.hatena.ne.jp/pasela/20081224/stream_filter も参照のこと。

バックスラッシュ問題

  • 1文字2バイト以上の文字に 0x5c(バックスラッシュ)が含まれている場合、同じ問題が発生するか(5C問題)は未確認。例えばCSVがShift_JISの場合は「ソ」。CSVがUTF-8なら、これに該当するケースはない。

http://d.hatena.ne.jp/miau/20141214/1418574582 も参照。

改行問題

  • 改行コードの解釈はPHPの設定 auto_detect_line_endings に依存する。
  • Windows ExcelとMac Excelとで、作成されたCSVの改行コードが異なる。行を区切る改行と、フィールド内の改行とでも異なる。
    Cocoaの日々

fputcsv

どのような場合にダブルクォートで囲まれるのか

<?php
$list = array (
    array('1,23', '456', '789'),
    array('aaa', 'bbb', 'ccc', 'dddd'),
    array('aa a', 'bbb', 'ccc', 'dddd'),
    array('"aaa"', 'bbb', 'ccc', 'dddd'),
    array("aa\na", 'bbb', 'ccc', 'dddd'),
    array("aa\r\na", 'bbb', 'ccc', 'dddd'),
    array('', 'bbb', 'ccc', 'dddd'),
    array(' ', 'bbb', 'ccc', 'dddd'),
    array('"', 'bbb', 'ccc', 'dddd'),
);
$fp = fopen('php://stdout', 'w');
foreach ($list as $fields) {
    fputcsv($fp, $fields);
}
fclose($fp);

結果

"1,23",456,789
aaa,bbb,ccc,dddd
"aa a",bbb,ccc,dddd
"""aaa""",bbb,ccc,dddd
"aa
a",bbb,ccc,dddd
"aa
a",bbb,ccc,dddd
,bbb,ccc,dddd
" ",bbb,ccc,dddd
"""",bbb,ccc,dddd
  • SplFileObject::fputcsv で保存した場合も同じ。

ExcelでCSV形式として保存した場合

"1,23",456,789,
aaa,bbb,ccc,ddd
aa a,bbb,ccc,ddd
"""aaa""",bbb,ccc,ddd
"a
aa",bbb,ccc,ddd
,bbb,ccc,ddd
 ,bbb,ccc,ddd
"""",bbb,ccc,ddd

Excelとの違いとして、fputcsvの場合は、

  • 半角スペースが入っていると(半角スペースのみの文字列の場合も含む)""で囲まれる

データ型

NULL

TODO

  • NULLに[]適用

オブジェクトからstringへの変換

class PersonName {
  var $name = "hoge";

  public function __toString() {
    return $this->name;
  }
}

$c = new PersonName();
$s = "The name is " . $c;
echo $c;
echo "\n---\n";
echo $s;
echo "\n---\n";
var_dump($c);
echo "\n---\n";
print_r($c);

結果

hoge
---
The name is hoge
---
object(PersonName)#1 (1) {
  ["name"]=>
  string(4) "hoge"
}

---
PersonName Object
(
    [name] => hoge
)

__toStringが存在しないと、echoや文字列演算でstringに変換しようとした時点でE_RECOVERABLE_ERROR レベルの致命的なエラー。

継承関係検査(instanceofなど)

AがBのクラスまたはサブクラスか。
AがBインタフェースを実装しているか。

AB処理備考
オブジェクトクラス名/インタフェース名A instanceof B※2
is_a(A, B)※3
クラス名/インタフェース名インタフェース名$class=new ReflectionClass(A); $class->implementsInterface(B);
クラス名/インタフェース名クラス名/インタフェース名$class=new ReflectionClass(A); $class->isSubclassOf(B);AがBと同じクラス/インタフェースの場合はfalse, サブクラス/サブインタフェースの場合にtrue
オブジェクトクラス名is_subclass_of(A, B)AがBクラスの場合はfalse, サブクラスの場合にtrue
クラス名クラス名is_subclass_of(A, B)AがBクラスの場合はfalse, サブクラスの場合にtrue ※1
※1
A, Bともにクラス名。インタフェースの継承関係は判定できない。
※2
Bはクラス名/インタフェース名を格納した変数でもよい( $b='Foo'; A instanceof $B ) 文字列リテラルはNG。
※3
当然、Bは文字列リテラルもOK。
 

Aがクラス名/インタフェース名の場合に、instanceof と同じ判定をする手段がない。implementsInterface(B)が最も近いが、Bがクラス名の場合に適用できない。

参照

TODO
つかうの?

XML宣言を含んだHTMLをincludeするとPHP Parse errorが発生する

XML宣言の <?xml の <? 部分がPHPの開始とみなされるため。

?をPHPの開始マークとして使わないように設定することで、この問題は回避できる。

.htaccessに以下を記述

# <?xml ... ?> がPHPと解釈されないようにshort_open_tagをoffに
php_flag  short_open_tag      off

isset

以下は、$foo->barがNULLでもエラーにならない。falseが得られる。

isset($foo->bar->baz)

var_dumpの出力内容が省略される

xDebugが制御しているため。

sudo vi /etc/php5/apache2/conf.d/xdebug.ini

以下の行を追加

 xdebug.var_display_max_data=10240;
   ※ 10240文字まで出力。-1を指定すれば無制限。デフォルトは512。
 xdebug.var_display_max_depth=10;
   ※ 10階層まで出力。デフォルトは3。

コネクション切断時にPHPの実行を止める。

以下の条件を満たせば、クライアントがコネクション切断した際にスクリプトは止まる。

  1. ignore_user_abort(false); // デフォルトでfalseなので、必ずしも明示的に実行の必要はない。
  2. PHPからクライアントへ何かを出力しようとする。単にechoしたのでは不十分でflushが必要。

connection_aborted()で得られる状態にも2の条件が適用される。このため、クライアントへの出力なしに、コネクション切断を検出することはできない。

なお、以下の説明によれば、register_shutdown_function() によるシャットダウン関数の登録有無により挙動が違うとのこと。ただ、この説明では上記2の条件はシャットダウン関数ありの場合のみ適用されるように読めるが、実際にはシャットダウン関数がなくても2の条件が適用されている。

参考

文字幅

文字幅取得用の関数 mb_strwidth が用意されているが、期待通りに動作しない。
マニュアルでは U+2000 - U+FF60 は全角と判定されることになっているが、実際には半角と判定される文字がある。
例: U+2605 ★

参考

文字コード表示

   $s = '&#131083;';
   var_dump(bin2hex($s));
   var_dump(bin2hex(mb_convert_encoding($s, 'UTF-16', 'UTF-8')));
   var_dump(bin2hex(mb_convert_encoding($s, 'UCS-4', 'UTF-8')));

結果

string(8) "f0a0808b"
string(8) "d840dc0b"
string(8) "0002000b"

TODO

  • セッションの寿命の制御、寿命のチェックをPHP側でもしているのか?
  • 1セッションにつき1リクエストしか同時処理されない問題
  • リクエスト中断時の振る舞い