ようこそ

時代遅れの情報がウェブ上にあふれている。そんな情報を見たPHP初心者は戸惑ってしまうだろう。そして、まずい手法やまずいコードが広まってしまう。 そんなのはもうやめよう。PHP: The Right Way は気軽に読めるクイックリファレンスだ。PHPの一般的なコーディング規約、 ウェブ上のよくできたチュートリアルへのリンク、そして現時点でのベストプラクティスだと執筆者が考えていることをまとめた。

大事なのは、 PHPを使うための正式なお作法など存在しない ってこと。 このサイトの狙いは、はじめて PHP を使うことになった開発者に、いろんなトピックを紹介すること。 経験豊富なプロの人にとっても、これまで深く考えることなく使ってきた内容について、新鮮な見方を伝えられるだろう。 このサイトは、決して「どのツールを使えばいいのか」を教えるものじゃない。 いくつかの選択肢を示して、それぞれの違いや使い道をできる限り紹介する。

このサイトは今も成長中なので、有用な情報やサンプルなどが見つかればどんどん更新していくつもりだ。

翻訳

PHP: The Right Way は、他の言語にも翻訳されている。

Book

最新版の PHP: The Right Way (英語版)はPDFやEPUBやMOBIでも公開されている。 Leanpubでゲットしよう。

協力するには

みんなでこのサイトをもっとよいものにしよう。PHPを勉強したい人たちのために良質な情報を集めるんだ! GitHubでね。

Back to Top

はじめに

最新の安定版を使う (8.3)

とりあえず PHP を試したいっていうのなら、最新の安定版である PHP 8.3 を使おう。 PHP 8.x では、PHP 7.x や 5.x 系にはない数々の強力な新機能が追加された。 エンジンが大幅に書き直され、今までのバージョンよりもずっと高速になっている。 PHP 8 は言語のメジャーアップデートであり、たくさんの新機能や最適化が含まれている。

最新版をできるだけ早めに試してみるべきだ。 PHP 7.4 は すでにサポートを終了している。 アップグレードはとても簡単だ。というのも、過去との互換性を損なうような変更(PHP 8.3), (PHP 8.2), (PHP 8.1), (PHP 8.0) がそんなに多くないからだ。 何かの関数や機能がどのバージョンに含まれているのかを知りたければ、php.net にあるマニュアルを調べよう。

ビルトインウェブサーバー

PHPの勉強を始めるときに、わざわざ本格的なウェブサーバーをインストールする必要はない。そう、PHP 5.4以降ならね。サーバーを立ち上げたかったら、ターミナルでプロジェクトのルートディレクトリに行って、こんなコマンドを実行するだけでいいんだ。

> php -S localhost:8000

macOS の人は

macOS には PHP が最初からインストールされているけど、最新の安定版からは微妙に遅れている。 最新の PHP を macOS にインストールするには、いくつかの方法がある。

Homebrew によるインストール

Homebrew は macOS 用の強力なパッケージ管理ツールで、 PHP やその拡張モジュールも簡単にインストールできる。 Homebrew のコアリポジトリで、PHP 7.4、8.0、8.1、8.2、そして PHP 8.3 用の “formulae” が公開されている。

最新版の PHP をインストールするには、こんなコマンドを実行すればいい。

brew install php@8.3

Homebrew の PHP のバージョンを切り替えるには、環境変数 PATH を設定すればいい。 brew-php-switcher を使えば、そのへんを自動的にやってくれる。

手動でリンクを消したり、追加したりすることで、使いたい PHP のバージョンに手動で切り替えることもできる。

brew unlink php
brew link --overwrite php@8.2
brew unlink php
brew link --overwrite php@8.3

Macports によるインストール

MacPorts プロジェクトはオープンソースのコミュニティによる取り組みで、 macOS 上のオープンソースソフトウェアのコンパイルやインストールそしてアップグレードを簡単にできるようにする仕組みだ。 コマンドラインのソフトからX11やAquaベースのソフトにまで対応している。

MacPorts はコンパイル済みのバイナリにも対応しているので、関連するライブラリなどを毎回ソースからコンパイルしなおす必要はない。 なので、まだ何もパッケージをインストールしていない状態でも、時間の心配をする必要はない。

現時点でインストールできるのは php54php55php56php70php71php72php73php74php80php81php82php83 のいずれかで、port install コマンドを使ってこのようにインストールする。

sudo port install php74
sudo port install php83

そして、select コマンドを使って、アクティブな PHP を切り替える。

sudo port select --set php php83

phpbrew によるインストール

phpbrew は、複数のバージョンの PHP をインストールして管理するためのツールだ。 使っているアプリケーションやプロジェクトによって、PHP のバージョンが異なる場合に特に便利で、 仮想マシンを用意する必要がなくなる。

Liip のバイナリインストーラーによる PHP のインストール

php-osx.liip.ch は、たった一行で PHP をインストールできる方法だ。バージョン 5.3 から 7.3 に対応している。 Apple がインストールした php のバイナリは上書きせず、まったく別の場所 (/usr/local/php5) にすべてをインストールする。

ソースからのコンパイル

もうひとつの選択肢がある。 自分でコンパイル することだ。この方法ならインストールする PHP のバージョンを完全にコントロールできる。 この場合は、Xcode あるいはその代用ツール “Command Line Tools for XCode” をインストールする必要がある。これらは、 Apple の Developer Center からダウンロードできる。

全部入りのインストーラー

ここまでで説明した方法は PHP そのものに関するものばかりで、それ以外のたとえば ApacheNginx、SQL server などは扱っていない。 MAMPXAMPP みたいな「全部入り」のインストーラーを使えば、このあたりのソフトも一緒に入れられるし設定もしてくれる。かんたん。 でも、インストーラーに縛られてしまって自由にいじれなくなるという弱点もある。

Windows の人は

windows.php.net/download からバイナリをダウンロードしよう。それを展開したら、 PHPフォルダのルート (php.exe がある場所) に PATH を通しておくといい。そうすれば、どこからでも PHP を実行できるようになる。

学習用にローカルで開発する場合は PHP 5.4 以降のビルトインウェブサーバーを使えばよいので、細かい設定を気にする必要はない。 もしウェブサーバーとかMySQLとかも含めた「全部入り」を使いたければ、XAMPPEasyPHPOpenServer、そしてWAMPなどがお勧めだ。これらを使えば Windows 用の開発環境を手早く構築できる。

とはいうものの、これらのツールは実際の運用環境と微妙に異なる。なので、たとえば「Windows で開発して Linux にデプロイ」 とかいう場合は環境の違いに気をつける必要がある。

Windows 上でシステムを実運用する場合は、IIS 7 を使うとよい。これが一番安定しており、かつパフォーマンスも優れている。 phpmanager (IIS 7 用の GUI プラグイン) を使えば PHP の設定は管理をシンプルにできる。 IIS 7 には FastCGI が組み込まれており、すぐに使える。 単に PHP をハンドラとして設定するだけでよい。 その値の詳しい情報は、dedicated area on iis.netに PHP 専用のエリアがある。

開発環境と運用環境が違っていると、いざ動かしたときにおかしなバグが発生しがちだ。 Windowsで開発したアプリケーションをLinux(などの非Windows環境)で動かしているのなら、仮想マシンを使うべきだ。

Chris Tankersleyは彼がWindowsでのPHP開発において使えるツールについての役に立つ記事を残しておいてくれている。

一般的なディレクトリ構造

webプログラミングを始めようとしている人たちからよく来る質問のひとつは「僕はどこに自分のプログラミングしたファイルを置けばいいんだい?」というものだ。 何年もの間、この答えは一貫して「 ドキュメントルート に置け」というものだった。この答えは完璧ではないが、ドキュメントルートはプロジェクトを始めるにはいい場所だ。

セキュリティ上の理由から、設定ファイルはサイト訪問者がアクセスできる場所に置いてはいけない。公開スクリプトは公開ディレクトリに置き、非公開の設定やデータは公開ディレクトリの外に置いておくのがいいだろう。

チームやCMS、フレームワークによってディレクトリ構造はそれぞれの使われ方をするし、基本はそれに従うのがいいだろう。しかし、そういったものなしにプロジェクトをひとりで始める場合は、どのようなファイルシステム構造を使うことを決めるかは困難だろう。

Paul M. JonesはPHP関連の何万ものgithubプロジェクトに共通する作法について素晴らしい調査を行っている。彼は調査に基づいて、ファイルとディレクトリ構造についてStandard PHP Package Skeletonにまとめている。 ディレクトリ構造は ドキュメントルートpublic/ ディレクトリとするべきであり、ユニットテストは tests/ ディレクトリ、composerでインストールするようなサードパーティのライブラリは vendor/ ディレクトリとするのがよいだろう。その他のファイルやディレクトリについても、Standard PHP Package Skeletonに従うことがプロジェクトのメンバーにとって最も理に叶うだろう。

Back to Top

コーディングスタイル

PHP のコミュニティはとてもでっかくて、いろんな人たちがいる。 そして、数え切れないほどのライブラリやフレームワークそしてコンポーネントが存在する。 そんな中からいくつか選んで、それを組み合わせてひとつのプロジェクトで使うっていうのもよくあることだ。 大切なのは、PHP のコードを書くときに、(できるだけ) 標準的なスタイルに従うことだ。 そうすれば、いろんなライブラリを組み合わせて使うのも簡単になる。

Framework Interop Group っていうところ が、おすすめのスタイルを提案している。 コーディングスタイルとは関係ないものもあるけれど、PSR-1PSR-12PSR-4、そして PER Coding Style はコーディングスタイルを扱っている。 これって要するに、 Drupal や Zend、Symfony、Laravel、CakePHP、phpBB、AWS SDK、FuelPHP、Lithium などのプロジェクトが採用しつつある規約をまとめただけのものなんだ。 自分のプロジェクトでこれを使ってもいいし、今までの自分のスタイルを使い続けてもいい。

理想を言えば、PHP のコードを書くときには、よく知られた何らかの標準規約に従うべきだ。 さっき説明したPSRの組み合わせでもいいし、PEARとかZendのやつでもかまわない。 そうすれば、他の人にもコードを読んでもらいやすくなるし、手助けも得やすくなるだろう。 また、コンポーネントを実装するアプリケーションがいろんなサードパーティのコードを組み合わせても、一貫性を保てる。

PHP_CodeSnifferを使えば、 自分のコードがこれらの標準のどれかひとつに準拠しているかどうかを確認できる。 あと、Sublime Textみたいなテキストエディタのプラグインを使えば、 書いているその場でリアルタイムのフィードバックが得られる。

コードのレイアウトを自動的に修正するツールとしては、二つの選択肢がある。

phpcs をシェルから手動で実行することもできる。

phpcs -sw --standard=PSR1 file.php

これは、エラーの内容とその修正方法を表示してくれる。 このコマンドをgit hookに仕込んでおけば便利だろう。 そうすれば、標準規約に反する変更を含むブランチは、それを修正するまでリポジトリに投入できなくなる。

PHP_CodeSnifferを使っている場合は、指摘されたコードレイアウトの問題を自動的に修正することもできる。そのためにはPHP Code Beautifier and Fixerを使えばいい。

phpcbf -w --standard=PSR1 file.php

もうひとつの選択肢はPHP Coding Standards Fixerで、 これは、実際に修正するまえにコードにどんな問題があったのかを表示してくれる。

php-cs-fixer fix -v --rules=@PSR1 file.php

変数や関数などのシンボル名、そしてディレクトリ名などのコード基盤なんかは、英語にしておくことをおすすめする。 コードのコメントに関しては、別に英語にこだわらなくてもかまわない。 そのコードを扱う(将来扱う可能性がある)すべての人が読みやすいものであれば、何語でもかまわない。

PHPでクリーンなコードを書くための資料としておすすめなのが Clean Code PHP だ。

Back to Top

言語仕様のポイント

プログラミングのパラダイム

PHP は柔軟性のある動的言語で、いろんなプログラミングテクニックに対応している。 長年の間に劇的に成長してきた。PHP 5.0 でのオブジェクト指向モデルの追加 (2004 年)、 PHP 5.3 での無名関数や名前空間の追加 (2009 年)、そして PHP 5.4 でのトレイトの追加 (2012 年) などが特筆すべきところだろう。

オブジェクト指向プログラミング

PHP には完全なオブジェクト指向プログラミングの機能が搭載されている。 クラスや抽象クラス、インターフェイス、継承、コンストラクタ、クローン、 例外などなどといった機能も、当然使える。

関数プログラミング

PHP は、ファーストクラスの関数をサポートしている。 つまり、関数を変数に代入できるってことだ。 自分で定義した関数だろうがもともと組み込まれている関数だろうが、 変数で参照したり動的に実行したりできる。 何かの関数を別の関数の引数として渡すこと ( 高階関数 っていう機能) もできるし、関数の返り値を別の関数にすることもできる。

再帰 (ある関数の中から自分自身を呼ぶこと) も PHP の機能としてサポートしている。 しかし、たいていの PHP コードはそれよりも逐次処理を重視している。

新型の無名関数 (クロージャにも対応したもの) が使えるようになったのは、PHP 5.3 (2009 年) 以降だ。

PHP 5.4 からは、クロージャをオブジェクトのスコープにバインドできるようになった。 また callable のサポートも強化され、ほとんどの場合で無名関数と互換性を持つようになった。

メタプログラミング

PHP はいろんな形式のメタプログラミングに対応しており、リフレクション API やマジックメソッドが使える。 マジックメソッドには __get()__set()__clone()__toString()、そして __invoke() などがあり、これらを活用すればクラスの振る舞いをフックできる。 Ruby の人がよく「PHP には method_missing がなくてさあ」とか言うけど、ちゃんと __call() とか __callStatic() があるよ。

名前空間

さっきも言ったとおり、PHP のコミュニティでは多くの人たちがいろんなコードを書いている。 ってことは、誰かが書いたライブラリと別の人が書いたライブラリでクラス名がダブってしまう可能性があるということだ。 両者が同じ名前空間を使っていたら、衝突して問題の原因となってしまう。

そんな問題を解決するのが 名前空間 だ。 マニュアルにあるとおり、名前空間っていうのは OS のファイルシステムのディレクトリと似てる。 同じ名前のふたつのファイルを同一ディレクトリに置くことはできないけど、 別々のディレクトリに分ければ共存できるというわけだ。 同様に、別々の名前空間に分ければ同じ名前のクラスがふたつあっても共存できる。簡単に言うと、そういうこと。

自分の書くコードにも、名前空間を指定することが大切だ。 そうすれば、誰か他の人があなたのコードを使うときに「これ、他のライブラリと競合しないかな」 と悩まずに済む。

名前空間のおすすめの使い方が PSR-4 にまとまっている。 この文書の狙いは、ファイルやクラスそして名前空間の命名規則を標準化して お互いに再利用しやすくすることだ。

2014年10月、PHP-FIG はオートローディングに関する標準であった PSR-0 を非推奨とした。 PSR-0もPSR-4も、今はどちらでも使える。 というのも PSR-4 は PHP 5.3 以降が必須だけれど、今でも PHP 5.2 のプロジェクトはたくさんあって、それらが PSR-0 に従っているからだ。

これから新しくアプリケーションやパッケージを作るときにオートローダーの使いかたの参考にするのなら、まず間違いなく PSR-4 だ。

Standard PHP Library

Standard PHP Library (SPL) とは PHP に組み込まれた標準ライブラリで、 さまざまなクラスやインターフェイスを提供する。 よく使うデータ構造 (スタックやキュー、ヒープなど) やイテレータなどが含まれており、SPL のインターフェイスを実装したクラスをつくれば それをイテレータで反復処理されることもできる。

コマンドラインインターフェイス

PHP はもともとウェブアプリケーションを書くために作られたものだが、 コマンドラインインターフェイス (CLI) のプログラムを書くのにも便利だ。 コマンドラインのプログラムを書けば、テストやデプロイといった よくある作業を自動化する助けとなる。

CLI の PHP プログラムが便利なのは、アプリケーションのコードを使うときに わざわざウェブの UI を用意せずに済むところだ。 ただ、CLI の PHP スクリプトをウェブサーバーの公開ディレクトリに置くことは 絶対禁止

PHP をコマンドラインで実行してみよう。

> php -i

-i は、PHP の設定情報を phpinfo 関数みたいに表示するオプションだ。

-a オプションで対話シェルを使えるようになる。ruby の IRB とか、Python の対話シェルと同じようなものだ。 それ以外にも、いろんな コマンドラインオプション がある。

じゃあ、シンプルな “Hello, $name” プログラムを書いてみよう。hello.php というファイルを作って、 こんな内容にする。

<?php
if ($argc !== 2) {
    echo "Usage: php hello.php <name>" . PHP_EOL;
    exit(1);
}
$name = $argv[1];
echo "Hello, $name" . PHP_EOL;

PHP のスクリプトを実行すると、コマンドラインの引数に関する変数がふたつ設定される。 $argc は整数値で、引数の を表し、 $argv は配列で、各引数の を含む。 最初の引数は、常に PHP スクリプトのファイル名となる。今回の場合なら hello.php だ。

exit() でゼロ以外の数値を返すと、コマンドが失敗したことをシェルに伝えることができる。 よく使われる終了コードは ここ で調べよう。

このスクリプトをコマンドラインから実行すると、次のようになる。

> php hello.php
Usage: php hello.php <name>
> php hello.php world
Hello, world

Xdebug

ソフトウェア開発におけるいちばん便利なツールといえば、よくできたデバッガだ。 こいつがあれば、コードを実行しながらその状態を追いかけて、スタックの中身を監視したりできる。 XdebugはPHP用のデバッガで、さまざまなIDEに組み込んで使える。 ブレークポイントを設定したりスタックの中身を見たりできるんだ。 さらに、PHPUnitやKCacheGrindといったツールと組み合わせれば、 コードカバレッジ解析やプロファイリングもできる。

「実はいま困ってるんだよ。仕方なしにvar_dump()とかprint_r()に頼ってるけど、もっといいやりかたがないかなあ…」 そんな君に必要なのは、たぶんデバッガだ。

Xdebugのインストールはちょっと面倒だけど、 重要な機能として「リモートデバッグ」が使えるようになる。 ふだんはローカルで開発しているけど、テストはVMや別のサーバーでしているという人は、 今すぐにでも使えるようにしたい機能だろう。

慣例にしたがって、ApacheのVHostあるいは.htaccessファイルにこんな値を設定する。

php_value xdebug.remote_host 192.168.?.?
php_value xdebug.remote_port 9000

“remote host”と”remote port”は、それぞれ開発に使っているホストとIDEがリスンしているポートに対応する。 あとはIDEの設定で「接続をリスンする」モードに変えて、こんなURLを読み込むだけだ。

http://your-website.example.com/index.php?XDEBUG_SESSION_START=1

これで、IDEがスクリプトの実行に割り込んで、ブレークポイントを設定したり メモリの中身を調べたりできるようになる。

グラフィカルなデバッガを使えば、コードをステップ実行したり変数の中身を調べたり、 今の実行環境上でコードを評価したりといったことが簡単にできるようになる。 たいていのIDEには、Xdebugを使ったグラフィカルなデバッグの仕組みが初めから組み込まれているか、 あるいはプラグインが用意されている。 MacGDBpというソフトもある。これは、Xdebugを使うためのフリーでオープンソースなmacOS用GUI環境で、スタンドアロンで使える。

Back to Top

依存関係の管理

PHP のライブラリやフレームワークやコンポーネントって、大量に存在する。 きっと、あなたのプロジェクトでも何個か使っているだろう。そういうのを、プロジェクトの依存関係という。 つい最近まで、PHP には依存関係をうまく管理する仕組みがなかった。 手作業で管理していたところで、オートローダーのことも気にしないといけない。 もううんざりだ。

現時点でよく使われているパッケージ管理システムは、ComposerPEAR である。 Composer は現在 PHP のパッケージマネージャーとしていちばん人気があるものだけれど、かつてはパッケージマネージャーといえば PEAR だった。 PEAR の歴史も知っておいたほうがいい。今でも PEAR を使っているところがあるかもしれないし、 たとえ決して使うことがないとしても、知っておいて損はないだろう。

Composer と Packagist

Composerは、PHP 用としておすすめの依存管理ツールだ。プロジェクト内の依存関係を composer.json ファイルに書いてシンプルなコマンドを打ち込めば、 Composer が自動的にそれをダウンロードしてくれるだけでなく、オートロードの設定までしてくれるんだ。 Composer は、node.js の NPM や Ruby の Bundler みたいなものだ。

Composer に対応したライブラリは既にいろいろ出回っていて、自分のプロジェクトですぐに使える。 そんなパッケージをまとめたのが Packagist。これは、Composer 対応の PHP ライブラリをまとめた公式リポジトリである。

Composer のインストール

composer をダウンロードするいちばん安全な方法は、公式サイトの指示に従うこと。 この方法だと、インストーラが壊れていたり改ざんされていたりしないかを確かめられる。 インストーラは、composer.phar のバイナリを カレントディレクトリ にインストールする。

お勧めは、グローバルにインストールする (要するに、/usr/local/bin にだけ置く) 方式だ。 そのためには、次のコマンドを実行すればいい。

mv composer.phar /usr/local/bin/composer

注意: パーミッションのエラーでこのコマンドが失敗する場合は、頭に sudo をつけて実行してみよう。

ローカルにインストールした Composer を実行するときには php composer.phar とする。グローバルにインストールしたのなら、単に composer と打つだけだ。

Windows でのインストール

Windowsの場合、一番簡単なのは ComposerSetup インストーラーを使う方法だ。 これは、すべてのユーザーで使えるようにインストールしたうえで $PATH も設定してくれるので、 あとはコマンドラインから composer を呼ぶだけで使えるようになる。

依存関係の定義とインストール

Composer は、プロジェクトの依存関係を composer.json というファイルで管理する。 このファイルを手で書き換えてもいいし、Composer を使って編集してもいい。 composer require を実行すると、プロジェクトの依存関係を追加する。 もしまだ composer.json がなければ、新しいファイルを作る。 この例は、プロジェクトの依存関係に Twig を追加するものだ。

composer require twig/twig:^2.0

あるいは、 composer init コマンドを実行して、 自分のプロジェクト用の完全な composer.json ファイルを作ることもできる。 どちらの方法にせよ、一度 composer.json ファイルを作ってしまえば、 あとは Composer がすべての依存ライブラリをダウンロードして vendor/ にインストールしてくれる。 次のコマンドは、すでに composer.json ファイルを含むプロジェクトをダウンロードした場合にも使える。

composer install

次に、アプリケーションで最初に呼ばれる PHP ファイルにこんな行を追加する。 これは、Composer のオートローダーを使ってプロジェクトの依存ライブラリを読むよう指示している。

<?php
require 'vendor/autoload.php';

これで、依存ライブラリが使えるようになった。実際に使う場面で、必要に応じて読み込まれる。

依存関係の更新

Composer は composer.lock というファイルを作る。 これは、最初に composer install を実行したときにダウンロードした、各パッケージの正確なバージョンを記録しておくものだ。 他の開発者とプロジェクトを共有するときに composer.lock も一緒に配布しておくと、 他の人が composer install を実行したときにもまったく同じバージョンがインストールされるようになる。 依存関係を更新するには、 composer update を実行しよう。 デプロイのときには composer update を使ってはいけない。必ず composer install を使うこと。 そうしないと、開発環境と運用環境で違うバージョンのパッケージを使ってしまうことになる。

これは、バージョンの要件を柔軟に定義できるので便利だ。 たとえば、バージョンに ~1.8 と書いた場合は「1.8.0 以降のバージョン。ただし 2.0.x-dev は含まない」と指定したことになる。 ワイルドカード * を使って 1.8.* のように指定してもいい。 これで、Composer で php composer.phar update を実行したときに、 定義した制約の範囲での最新版に依存ライブラリを更新してくれる。

更新通知

新バージョンのリリースの通知を受け取りたければ libraries.io にサインアップするといい。 このサービスは、依存ライブラリを監視して、更新があれば通知してくれるものだ。

依存ライブラリのセキュリティ問題のチェック

Local PHP Security Checker は、コマンドラインツールとして提供されている。 composer.lock ファイルを調べて、もし依存関係に更新が必要なら教えてくれるものだ。

Composer でのグローバルな依存関係の扱い

Composer は、グローバルな依存関係やそのバイナリを扱うこともできる。 使いかたはとても簡単で、単にコマンドの前に global をつけるだけでいい。 たとえば、PHPUnit をグローバルに使えるようインストールしたければ、こんなコマンドを実行する。

composer global require phpunit/phpunit

このコマンドは、 ~/.composer ディレクトリを作って、グローバルな依存関係をそこに置く。 インストールされたパッケージのバイナリを全体で使えるようにするには、 ~/.composer/vendor/bin ディレクトリを環境変数 $PATH に追加すればいい。

PEAR

古くからあるパッケージ管理ツールが PEAR だ。 Composer と同じような感じだけど、違うところもある。

PEARの個々のパッケージは、ルールに従った構造にしておかないといけない。 つまり、PEARで使えるようにするにはパッケージの作者がきちんと準備しておかないといけないってことだ。 PEARで使うことを想定していないプロジェクトは、PEARではうまく使えない。

PEARはパッケージをグローバル環境にインストールする。つまり、 何かパッケージをインストールすれば、同じサーバー上のすべてのプロジェクトでそのパッケージが使えるようになる。 すべてのプロジェクトが同じパッケージの同じバージョンを使ってるというのなら、これは便利だ。 でも、プロジェクトによって使っているバージョンが違うなんていう場合はちょっとまずいことになる可能性がある。

PEARのインストール

PEARをインストールするには、.phar インストーラをダウンロードして実行すればいい。 PEARのドキュメントを見れば、各種OS向けに インストール手順の詳しい説明 がある。

Linuxを使っている場合は、ディストリビューションが提供しているパッケージマネージャーもチェックしよう。 たとえば、DebianやUbuntuには php-pear というaptパッケージが用意されている。

パッケージのインストール

PEAR パッケージリストにあるパッケージなら、名前を指定して次のようにインストールできる。

pear install foo

別のチャネルで公開されているパッケージをインストールするには、まずそのチャネルを discover しないといけない。そして、インストールのときにもチャネル名を指定する。 詳しくは チャネルの使い方に関するドキュメント を参照すること。

PEARの依存関係をComposerで管理する

既にComposerを使っているけれどもPEARのコードもインストールしたいという場合は、 ComposerにPEARの依存関係を処理させることもできる。 PEAR のリポジトリは、Composer version 2 ではもう直接はサポートされない。よって、PEAR のパッケージをインストールするには、リポジトリを手動で追加する必要がある:

{
    "repositories": [
        {
            "type": "package",
            "package": {
                "name": "pear2/pear2-http-request",
                "version": "2.5.1",
                "dist": {
                    "url": "https://github.com/pear2/HTTP_Request/archive/refs/heads/master.zip",
                    "type": "zip"
                }
            }
        }
    ],
    "require": {
        "pear2/pear2-http-request": "*"
    },
    "autoload": {
        "psr-4": {"PEAR2\\HTTP\\": "vendor/pear2/pear2-http-request/src/HTTP/"}
    }
}

最初のセクションでは、"repositories"を使ってComposerにpearリポジトリを「初期化」 (PEARの用語でいうと「discover」)する。 そして、requireセクションではパッケージ名を次のように指定する。

pear-channel/package

“pear”というプレフィックスをハードコードすることで、衝突を回避している。 というのも、pearチャネルの名前と別のパッケージベンダーの名前が同じになってしまう可能性があるからだ。 このようにして、チャネルの短縮名(あるいは完全なURL)を使ってそのパッケージが属するチャネルを指定できるようにする。

このコードをインストールすると、venderディレクトリの中にチャネル名のディレクトリができあがって、 Composerのオートローダーを通して自動的に使えるようになる。

vendor/pear2/pear2-http-request/pear2/HTTP/Request.php

このPEARパッケージを使うには、単純にこのように参照するだけでいい。

<?php
require __DIR__ . '/vendor/autoload.php';

use PEAR2\HTTP\Request;

$request = new Request();

Back to Top

コーディングに関する慣習

基本

PHP はとても懐が深い言語で、いろんなレベルの技術者が使えるし、手早く効率的にコードを書くことができる。 しかし実際に使っているうちに、初めて身につけたときの基本を忘れてしまい、 横着する方法とかあまりよろしくない習慣とかばかり身につけてしまう。 そんな風潮に一石を投じるために書いたのがこのセクションだ。PHPでコードを書くときの基本を忘れないようにしよう。

日付や時刻の扱いかた

PHP の DateTime クラスを使えば、日付や時刻の読み書き、比較、そして計算ができる。 PHP には DateTime クラス以外にも日付や時刻がらみの関数が大量にあるけど、 DateTime クラスにはちゃんとしたオブジェクト指向のインターフェイスがあるので たいていの場合はこのクラスを使ったほうがいい。 タイムゾーンだって扱えるけど、ここではそこまでは深追いしない。

DateTime を使って何かの操作をするためには、日付や時刻を表す文字列をファクトリーメソッド createFromFormat() でオブジェクトに変換するか、あるいは new DateTime で現在の日時を取得する。format() メソッドを使えば、DateTime を文字列に戻して出力できる。

<?php
$raw = '22. 11. 1968';
$start = DateTime::createFromFormat('d. m. Y', $raw);

echo 'Start date: ' . $start->format('Y-m-d') . PHP_EOL;

DateTime を使った計算をするときに使えるのが DateInterval クラスだ。 DateTime には add()sub() といったメソッドがあって、その引数に指定するのがこの DateInterval となる。 1日が86400秒であることを前提としたコードを書いてはいけない。 サマータイムとかタイムゾーンの移動がからむと、この前提はあっさり崩れてしまうからだ。 そんなときには DateInterval を使う。二つの日付の差を計算するときには diff() メソッドを使う。このメソッドは DateInterval を返し、結果を表示するのも簡単だ。

<?php
// $start をコピーして、1か月と6日を足す
$end = clone $start;
$end->add(new DateInterval('P1M6D'));

$diff = $end->diff($start);
echo 'Difference: ' . $diff->format('%m month, %d days (total: %a days)') . PHP_EOL;
// Difference: 1 month, 6 days (total: 37 days)

DateTime オブジェクトどうしでごく普通に比較することもできる。

<?php
if ($start < $end) {
    echo "Start is before the end!" . PHP_EOL;}

最後にもうひとつ DatePeriod クラスの例を示そう。繰り返し発生するイベントを順に処理するときに使える。 開始日時と終了日時を表す二つの DateTime 、そしてイベントの間隔を受け取って、その間にあるすべてのイベントを返すものだ。

<?php
// $start から $end までの間のすべての木曜日を返す
$periodInterval = DateInterval::createFromDateString('first thursday');
$periodIterator = new DatePeriod($start, $periodInterval, $end, DatePeriod::EXCLUDE_START_DATE);
foreach ($periodIterator as $date) {
    // 毎木曜日を表示する
    echo $date->format('Y-m-d') . ' ';
}

PHP APIの拡張として有名なのが Carbon だ。 これは DateTime クラスのすべてを継承しているので、コードの書き換えを最小限に抑えられる。 さらに追加機能として、地域化のサポートや、 DateTime オブジェクトの加減算とフォーマットの方法の追加がある。 また、自分で選んだ日付と時刻をシミュレートする機能もあって、これはコードをテストするときに使える。

デザインパターン

アプリケーションをつくるときには、一般的なパターンに従ってコードを書くとよい。 同じくプロジェクトの全体構造についても、一般的なパターンに従おう。 なぜそうするといいかというと、自分のコードを管理しやすくなるし、 何がどうなっているのかを他の開発者にもわかってもらいやすくなるからだ。

フレームワークを使ってコードを書くと、上位レベルのコードやプロジェクトの構造のほとんどはそのフレームワークの流儀に従うことになる。 すでにそこには、いろんなパターンが適用されているだろう。 ただ、そのフレームワークの上で書く自分のコードの中でどんなパターンを適用するかは、自分次第だ。 一方、フレームワークを使わずにコードを書く場合はどうだろう。 自分が書こうとしているアプリケーションのタイプや規模に応じて、最適なパターンをみつける必要がある。

PHPでのデザインパターンや実装例については、これを見ればいい。

UTF-8の扱い

このセクションは、もともとAlex CabalPHP Best Practices向けに書いたものだ。この記事をもとに、UTF-8について説明する。

一発で済ませる方法はない。注意して、きちんと一貫性を保つこと。

今のところPHPは、低レベルではUnicodeをサポートしていない。 PHPでUTF-8文字列をきちんと処理する方法もあるにはあるが、簡単ではない。さらに、ウェブアプリケーションのあらゆるレベル (つまり、HTMLやSQLからPHPまで)に手を入れる必要がある。 ここでは、それらについて、現実的な範囲で手短にまとめた。

PHPレベルでのUTF-8

文字列の連結や変数への代入などの基本操作については、UTF-8だからといって何か特別なことをする必要はない。 しかし、大半の文字列関数(strpos()strlen() など)については、そういうわけにはいかない。 これらの関数には、対応する関数として mb_* が用意されていることが多い。 たとえば mb_strpos()mb_strlen() だ。 これらの mb_* 関数は マルチバイト文字列拡張モジュール が提供するもので、 Unicode文字列を扱えるように設計されている。

Unicode文字列を扱う場合は、常に mb_* 関数を使う必要がある。 たとえば、UTF-8文字列に対して substr() を使うと、その結果の中に文字化けした半角文字が含まれてしまう可能性がある。 この場合、マルチバイト文字列のときに使うべき関数は mb_substr() だ。

常に mb_* 関数を使うように覚えておくのが大変なところだ。 たとえ一か所でもそれを忘れてしまうと、それ以降の Unicode 文字列は壊れてしまう可能性がある。

そして、すべての文字列関数に mb_* 版があるわけではない。 自分が使いたい関数にマルチバイト版がないだって? ご愁傷様。

すべてのPHPスクリプトの先頭(あるいは、グローバルにインクルードするファイルの先頭)で mb_internal_encoding() 関数を使わないといけないし、スクリプトの中でブラウザに出力するつもりなら、それだけではなく mb_http_output() 関数も使わなければいけない。 すべてのスクリプトでエンコーディングを明示しておけば、後で悩まされることもなくなるだろう。

さらに、文字列を操作する関数の多くには、文字エンコーディングを指定するためのオプション引数が用意されている。 このオプションがある場合は、常に UTF-8 を明示しておくべきだ。 たとえば htmlentities() には文字エンコーディングを設定する引数があるので、 UTF-8 文字列を扱うなら常にそう指定しておかないといけない。 PHP 5.4.0 以降では、 htmlentities()htmlspecialchars() のデフォルトエンコーディングが UTF-8 に変わった。

最後に、他の人たち向けに配布するつもりのアプリケーションなど、その実行環境で mbstring が使えるかどうか定かではない場合は、 Composer の symfony/polyfill-mbstring パッケージを使うことも検討しよう。 これは、もし mbstring があればそれを使い、なければ非 UTF-8 関数にフォールバックするというものだ。

データベースレベルでのUTF-8

PHP スクリプトから MySQL に接続する場合は、上で書いた注意をすべて守ったにもかかわらず UTF-8 文字列がそれ以外のエンコーディングで格納されてしまう可能性がある。

PHP から MySQL に渡す文字列を確実に UTF-8 として扱わせるには、データベースとテーブルの文字セットや照合順序の設定を、すべて utf8mb4 にしておく必要がある。さらに、PDO の接続文字列にも、文字セットとして utf8mb4 を指定する。 詳細は以下のコードを参照すること。 これ、試験に出るよ。

UTF-8 を完全にサポートするには、文字セット utf8mb4 を使わないといけない。 utf8 はダメ!!! その理由が知りたければ「あわせて読みたい」を参照すること。

ブラウザレベルでのUTF-8

mb_http_output() 関数を使えば、PHP スクリプトからブラウザへの出力が UTF-8 文字列になることを保証できる。

あとは、HTTPレスポンスの中で、そのページをUTF-8として扱うようブラウザに指示する必要がある。 いまどきなら、普通はHTTPレスポンスヘッダの中でこんなふうに設定するだろう。

<?php
header('Content-Type: text/html; charset=UTF-8')

昔は、ページの <head> タグの中で charset の <meta> タグ を指定したりしていたものだ。

<?php
// PHP に対して、今後このスクリプトの中では UTF-8 文字列を使うことを伝える
mb_internal_encoding('UTF-8');
$utf_set = ini_set('default_charset', 'utf-8');
if (!$utf_set) {
    throw new Exception('could not set default_charset to utf-8, please ensure it\'s set on your system!');
}

// PHP に対して、ブラウザに UTF-8 で出力することを伝える
mb_http_output('UTF-8');

// UTF-8 のテスト用文字列
$string = 'Êl síla erin lû e-govaned vîn.';

// 何らかのマルチバイト関数で文字列を操作する。
// ここでは、デモの意味も込めて、非ASCII文字のところで文字列をカットしてみた。
$string = mb_substr($string, 0, 15);

// データベースに接続し、この文字列を格納する。
// このドキュメントにある PDO のサンプルを見れば、より詳しい情報がわかる。
// ここでの肝は、データソース名 (DSN) における `charset=utf8mb4` だ。
$link = new PDO(
    'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
    'your-username',
    'your-password',
    array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_PERSISTENT => false
    )
);

// 変換した文字列を、UTF-8としてデータベースに格納する。
// DBとテーブルの文字セットや照合順序が、ちゃんとutf8mb4になっているかな?
$handle = $link->prepare('insert into ElvishSentences (Id, Body, Priority) values (default, :body, :priority)');
$handle->bindParam(':body', $string, PDO::PARAM_STR);
$priority = 45;
$handle->bindParam(':priority', $priority, PDO::PARAM_INT); // intを求めていることをpdoに対して明示する
$handle->execute();

// 今格納したばかりの文字列を取り出して、きちんと格納できているかどうかを確かめる
$handle = $link->prepare('select * from ElvishSentences where Id = :id');
$id = 7;
$handle->bindParam(':id', $id, PDO::PARAM_INT);
$handle->execute();

// 結果をオブジェクトに代入して、後でHTMLの中で使う
// このオブジェクトがメモリを圧迫することはない。必要になったその時点ではじめてフェッチする
$result = $handle->fetchAll(\PDO::FETCH_OBJ);

// ラッパーのサンプル。これはデータをhtml出力用にエスケープする
function escape_to_html($dirty){
    echo htmlspecialchars($dirty, ENT_QUOTES, 'UTF-8');
}

header('Content-Type: text/html; charset=UTF-8'); // すでに default_charset が utf-8 になっているのであればこれは不要
?><!doctype html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>UTF-8 テストページ</title>
    </head>
    <body>
        <?php
        foreach($result as $row){
            escape_to_html($row->Body);  // 変換したUTF-8文字列が、ブラウザに正しく出力されるはず
        }
        ?>
    </body>
</html>

あわせて読みたい

国際化(i18n) とローカライズ(l10n)

初学者の皆さんへのお断り: i18n と l10n はヌメロニム(訳者注: 長い英単語を数字で省略して表現する語)であり、短縮に数字が使われる省略形の一種である - この場合、internationalization は i18n、localization は l10n となる。

まず初めに、これら2つの似た概念と、その他関連するものを定義する必要がある。

一般的な実装方法

PHP で書かれたソフトウェアを国際化する最も簡単な方法は、配列ファイルを使ってそれらの文字列を <h1><?=$TRANS['title_about_page']?></h1> のように、テンプレートで使うことだ。しかしながら、運用していく中でメンテナンスの問題が発生するので、この方法は本格的なプロジェクトでは全くお勧めできない。複数化など、プロジェクトの最初期に発生する問題もある。したがって、何ページも含まれるプロジェクトであれば、この方法を試さないでいただきたい。

最も古典的で、しばしば i18n や l10n の参考にされることが多いのは、gettextというUnixのツール である。これは1995年までさかのぼり、ソフトウェアを翻訳するための完璧な実装である。十分かんたんに実行でき、強力なサポートツールを備えている。ここでは Gettext に関して説明する。また、あなたがコマンドラインを使うことで混乱しないように、l10n のソースを簡単に更新できる優れたGUIアプリケーションを紹介する。

その他のツール

Gettext やその他の i18n の実装をサポートする一般的なライブラリが存在する。それらの中には、インストールが簡単なものや、追加の機能や i18n ファイルフォーマットを備えているものもある。本ドキュメントでは、PHPコアで提供されるツールにフォーカスするが、完全を期すために、以下で他のツールを列挙しておく:

他のフレームワークにも i18n モジュールが含まれているが、それらはフレームワークを使ったコードベース以外では利用できない。

抽出器が提供されていないライブラリの1つを選択する場合は、gettext フォーマットを使うのが良いだろう。そうすれば、本章の以降で説明するように、オリジナルの gettext ツールチェイン(Poedit を含む) が使える。

Gettext

インストール

apt-getyumなどのパッケージマネージャを使って、Gettext や関連するPHPライブラリをインストールする必要があるかもしれない。インストール後、extension=gettext.so (Linux/Unixの場合)、あるいは extension=php_gettext.dll (Windowsの場合)を php.ini に追加して有効にする。

ここでは、Poedit を使って翻訳ファイルを作成する。おそらくシステムのパッケージマネージャにもあると思う。Unix, macOS, Windows で使うことができ、ウェブサイトから無料でダウンロード することもできる。

Gettext の構造

ファイルの種類

gettext を使う際、通常扱うファイルは3つある。主なファイルは PO(Portable Object) ファイルと、MO(Machine Object) ファイルだ。前者は読み取り可能な「翻訳されたオブジェクト」のリストであり、後者は、ローカライズを行う際に、gettext によって解釈される対応するバイナリだ。これ以外に、POT (Template) ファイルがある。これはソースファイルからの既存のキーを全て含んでいて、すべてのPOファイルを生成、更新するためのガイドとして利用できる。これらのテンプレートファイルは必須ではなく、l10n を行う際に用いるツールによっては、PO/MO ファイルだけで事足りる。言語と地域ごとにPO/MO ファイルのペアが常に1つあるが、POT はドメインごとに1つだけだ。

ドメイン

大きなプロジェクトでは、同じ単語がコンテキストに応じて異なる意味を伝える場合、翻訳を分離する必要がある場合がある。そうした場合、それらを別の ドメイン に分割する。それらは基本的に、POT/PO/MOファイルの名前付きグループであり、その場合のファイル名は前述の 翻訳ドメイン である。中小規模のプロジェクトでは通常、単純化するために、1つのドメインのみを使用する。その名前は任意だが、我々のコードサンプルでは「main」を使う。Symfony プロジェクトでは、例えば、ドメインはバリデーションメッセージの翻訳を分離するために利用される。

ロケールコード

ロケールは単に、言語のあるバージョンを識別するコードであり、ISO 639-1ISO 3166-1 alpha2 仕様に沿って定義される。言語には2つの小文字、必要に応じて下線と国や地域のコードを識別する2つの大文字が続く。レアな言語 には、3文字が使われる。

一部の話者にとっては、国の部分が冗長に思える場合がある。実際、言語の中には、オーストリアドイツ語(de_AT) や (ブラジルで話される)ポルトガル語(pt_BR) のように、異なる国の方言を持つものがある。2つ目の部分は、これらの方言を区別するために使われる。2つ目の部分がない場合、言語の「汎用」バージョン、あるいは「ハイブリッド」バージョンとして扱われる。

ディレクトリ構造

Gettext を使うには、特定のフォルダ構造に従う必要がある。まず、ソースリポジトリ内の l10n ファイルに対して任意のルートを選択する必要がある。その中に、必要なロケール毎のフォルダと、すべてのPO/MOペアを含む、固定の LC_MESSAGES フォルダを用意する。たとえば、以下のようにする:

<project root>
 ├─ src/
 ├─ templates/
 └─ locales/
    ├─ forum.pot
    ├─ site.pot
    ├─ de/
    │  └─ LC_MESSAGES/
    │     ├─ forum.mo
    │     ├─ forum.po
    │     ├─ site.mo
    │     └─ site.po
    ├─ es_ES/
    │  └─ LC_MESSAGES/
    │     └─ ...
    ├─ fr/
    │  └─ ...
    ├─ pt_BR/
    │  └─ ...
    └─ pt_PT/
       └─ ...

複数形

この章の「はじめに」で述べたように、言語が異なれば、複数形の規則も異なる。しかしながら、gettext を 使えば、この問題は二度と起こらなくなる。新しい.poファイルを作成すると、その言語のための 複数形ルール を宣言する必要があり、複数形を区別する必要のある翻訳された部分はそれらのルールに対して異なる形式を持つ。コードの中からGettext を呼び出す場合、その文に関連する番号を指定する必要がある。また、必要に応じて文字列置換を使っていても、使用する正しい形式が決定される。

複数形のルールには使用可能な複数の数と、指定された数がどのルールに該当するかを定義する (カウントは0から始まる) n を用いたブーリアン型のテストが含まれる。例:

これで、どのように複数形ルールが機能するかの基礎を理解した。まだ理解できていないのであれば、LingoHubチュートリアル の詳しい説明を参照すること。必要なものを手で書くのではなく、リストからコピーしたいと思うかもしれない。

カウンタを使って文のローカライズを行うために Gettext を呼び出す場合、関連する数も同様に用意する必要がある。Gettext はどのルールを反映すべきかを判断し、正しくローカライズされたバージョンを使用する。定義されたそれぞれの複数形ルール毎に異なる文を .po ファイルに含める必要がある。

実装例

理論面が一通り終わったら、少し実用的なことを扱おう。ここに、.po ファイルの抜粋がある。フォーマットについては気にしないでいただきたいが、代わりに全体的な内容を示す。後で簡単に編集する方法を学ぼう。

msgid ""
msgstr ""
"Language: pt_BR\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

msgid "We are now translating some strings"
msgstr "Nós estamos traduzindo algumas strings agora"

msgid "Hello %1$s! Your last visit was on %2$s"
msgstr "Olá %1$s! Sua última visita foi em %2$s"

msgid "Only one unread message"
msgid_plural "%d unread messages"
msgstr[0] "Só uma mensagem não lida"
msgstr[1] "%d mensagens não lidas"

最初のセクションはヘッダのように機能し、msgidmsgstr は特別に空っぽだ。そこでは、ファイルエンコーディング、複数形、その他あまり関係ないことを説明している。 2つ目のセクションは単純な文字列を英語から(ブラジルで話される)ポルトガル語へ翻訳している。3つ目のセクションも同じことをしているが、sprintfからの文字列置換を使って、翻訳にユーザ名と訪問日が含まれるようにする。 最後のセクションは複数化形式の例で、単数と複数のバージョン英語でmsgidとして、対応する翻訳を msgstr 0と1として表示している(複数形ルールで指定された数字に続く)。ここで、文字列置換も使われているので、数字は%dを使って文の中で直接参照することができる。複数形は常に2つのmsgid(単数と複数) があるので、翻訳のソースとして複雑な言語を用いないことをお勧めする。

l10n のキーに関する議論

お気づきかもしれないが、我々は英語の実際の文をソースIDとして使っている。その msgid はすべての.po ファイルで同じように使われている。これはつまり、他の言語も同じフォーマットと同じmsgidフィールドを持っているが、msgstr行は翻訳されているということだ。

翻訳キーに関していえば、2つの主要な「流派」がある。

  1. 実際の文としての msgid. 主な利点は下記の通り:
    • 特定の言語で翻訳されていないソフトウェアがある場合、表示されるキーは何らかの意味を保持している。例:英語からスペイン語に翻訳したが、フランス語に翻訳するのに助けを必要とする場合、フランス語の文が欠けた新しいページを公開すると、そのウェブサイトの一部が代わりに英語で表示される。
    • 翻訳者にとっては、何が起こっているかを理解し、msgid に基づいて適切な翻訳を行う方がはるかに簡単だ。
    • ある言語(ソース言語)に対して、「無料の」l10n を提供する。
    • 唯一の欠点は、実際のテキストを変更する必要がある場合、複数の言語ファイルで同じ msgid を置換する必要があることだ。
  2. 一意な構造化されたキーとしての msgid これはアプリケーション内の文の役割を、構造化された方法で記述する。これには、文字列の内容ではなく、文字列が配置されたテンプレート、あるいはその一部が含まれている。
    • テキストの内容をテンプレートのロジックから分離して、コードを整理するにはすばらしい方法だ。
    • しかしながら、翻訳者がコンテキストを見逃す問題が発生するかもしれない。ソース言語ファイルは、他の翻訳の基礎として必要になる。例:理想的な形では、開発者は en.po ファイルを管理し、翻訳者は、例えば fr.po に何を書くかを理解するためにそれを読む。
    • 翻訳がないと、意味のないキーが画面に表示される(前述の翻訳されていないフランス語のページでは、Hello there, User! ではなく、top_menu.welcome が表示される)。 公開前に無理やり翻訳を完成させるのはよいことだが、翻訳の問題は画面上で非常にひどいものになるだろう。しかしながら、ライブラリの中には、他のアプローチと同様の振る舞いをもつ特定の言語を「フォールバック」として指定するオプションが含まれているものもある。

Gettextのマニュアル では、通常、翻訳者やユーザがトラブルに見舞われた場合に簡単に対処できるように、最初のアプローチを採用している。なのでここでもそのような作業を行う。しかしながら、Symfonyドキュメント では、キーワードに基づく翻訳を採用していて、テンプレートに影響を及ぼすことなくすべての翻訳を個別に変更できるようにしている。

お決まりの使い方

一般的なアプリケーションでは、ページに静的なテキストを書く際にGettext関数を用いる。これらの文は、.po ファイルに現れ、翻訳され、.mo ファイルにコンパイルされ、実際のインタフェースをレンダリングを行う際にGettext によって使われる。なので、これまで議論してきた内容を順を追って、サンプルコードでまとめてみよう。

1. いくつかの異なる gettext 呼び出しを含む、サンプルテンプレートファイル

<?php include 'i18n_setup.php' ?>
<div id="header">
    <h1><?=sprintf(gettext('Welcome, %s!'), $name)?></h1>
    <!-- 可読性のためだけに、こういう風にコードをインデントしている -->
    <?php if ($unread): ?>
        <h2><?=sprintf(
            ngettext('Only one unread message',
                     '%d unread messages',
                     $unread),
            $unread)?>
        </h2>
    <?php endif ?>
</div>

<h1><?=gettext('Introduction')?></h1>
<p><?=gettext('We\'re now translating some strings')?></p>

2. サンプルセットアップファイル(上で用いた i18n_setup.php)、正しいロケールの選択や、Gettext の設定を行う

<?php
/**
 * 指定された $locale を、プロジェクトでサポートしているかを確認する
 * @param string $locale
 * @return bool
 */
function valid($locale) {
   return in_array($locale, ['en_US', 'en', 'pt_BR', 'pt', 'es_ES', 'es']);
}

//参考情報として、ソースとなる/デフォルトのロケールを設定
$lang = 'en_US';

if (isset($_GET['lang']) && valid($_GET['lang'])) {
    // ロケールはクエリストリングで変更できる
    $lang = $_GET['lang'];    //無害化すべき!
    setcookie('lang', $lang); //再利用できるように、cookie に保存
} elseif (isset($_COOKIE['lang']) && valid($_COOKIE['lang'])) {
    // cookie が存在している場合、その値を使う
    $lang = $_COOKIE['lang']; //無害化すべき!
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
    // デフォルト: ユーザーが受け入れるとブラウザが言っている言語を探す
    $langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
    array_walk($langs, function (&$lang) { $lang = strtr(strtok($lang, ';'), ['-' => '_']); });
    foreach ($langs as $browser_lang) {
        if (valid($browser_lang)) {
            $lang = $browser_lang;
            break;
        }
    }
}

// 見つかった言語を指定して、グローバルなシステムロケールをここで定義する
putenv("LANG=$lang");

// こうしておくと、たとえば日付関数(LC_TIME) や 通貨のフォーマット(LC_MONETARY) で役立つかも
setlocale(LC_ALL, $lang);

// Gettext に ../locales/<lang>/LC_MESSAGES/main.mo を探すように指示する
bindtextdomain('main', '../locales');

// ファイルを読み出すときに、どのエンコーディングを使うかを指示する
bind_textdomain_codeset('main', 'UTF-8');

// アプリケーションに追加で、翻訳のドメインがある場合、既に説明したように、ここでバインドしておく
bindtextdomain('forum', '../locales');
bind_textdomain_codeset('forum', 'UTF-8');

// gettext() がコールされたときに、応答するデフォルトのドメインをここで指定する
textdomain('main');

// 以下のようにすると、main.mo ではなく、forum.mo にある文字列を探すようになる
// echo dgettext('forum', 'Welcome back!');
?>

3. 最初に実行するための翻訳の準備

Gettext が独自フレームワークの i18n パッケージに対して持っている大きな利点の1つは、広範で強力なファイルフォーマットだ。「ああ、Gettext のフォーマットを理解し、手で編集することは非常に困難だ。単純な配列の方が簡単だ!」間違いなく、Poedit のようなアプリケーションが 大いに 助けになる。彼らのウェブサイトからプログラムを入手することができ、すべてのプラットフォームで無料で利用することができる。これは、簡単に使えるツールであり、同時に非常に強力なツールである - なぜなら、Gettext が使えるすべての機能を使っているからだ。このガイドは PoEdit 1.8 に基づいている。

最初の実行では、メニューから、「File > New…」を選択する必要がある。言語は直接入力するように求められる。ここで翻訳したい言語を選択/フィルタするか、en_USpt_BR のような、前述した形式を使用できる。

次に、先に説明したディレクトリ構造を使ってファイルを保存する。さらに、「Extract from sources」をクリックし、ここで抽出タスクと翻訳タスクの様々な設定を行う。「Catalog > Properties」で、後からこれらすべてを見つけることができるようになる。

これらのポイントを設定した後、PoEditはソースファイルをスキャンしてローカライズの呼び出しを全て洗い出す。スキャンするたびに、PoEdit は検出されたものとソースファイルから削除された内容のサマリを表示する。新しいエントリは空のまま変換テーブルに入力され、ローカライズされたバージョンの文字列を入力し始める。それが保存されると、.mo ファイルは同じフォルダに(再び)コンパイルされ、プロジェクトが国際化される。

4. 翻訳文字列

すでにお気づきかもしれないが、ローカライズされた文字列には、2つの主なタイプがある。単数のものとそれを複数形にしたものだ。1つ目は、ソースとローカライズされた文字列の2つのボックスがあるだけだ。Gettext/Poedit にはソースファイルを変更する権限がないので、ソース文字列は変更できない。ソース自体を変更して、再スキャンする必要がある。ヒント:翻訳行を右クリックすると、その文字列が使われているソースファイルと行のヒントが表示される。 他方、複数形文字列には2つのソース文字列を表示する2つのボックスと、様々な最終形式が設定可能なタブが含まれている。

ソースを変更して翻訳を更新する必要がある場合はいつでも、Refresh をクリックするだけで Poedit はコードを再スキャンし、存在しないエントリを削除し、変更されたエントリをマージして新しいエントリを追加する。あなたが行った他の翻訳に基づいて、いくつかの翻訳を推測しようとする場合もある。それらの推論と変更されたエントリには「Fuzzy」マーカーが付与され、検証が必要であることが示され、リスト中に金色で表示される。もし翻訳チームがいて、誰かが不確かなことを書こうとする場合にも便利だ。Fuzzy をマークするだけで、他の誰かが後からレビューしてくれるからだ。

最後に、「View > Untranslated entries first」マークをつけておくことをお勧めする。これは、エントリを忘れないようにするのに 大いに 役に立つからだ。このメニューから、必要に応じて、翻訳者たちにコンテキストの情報を残すことができる UI をオープンすることもできる。

ヒントとコツ

キャッシュが問題を起こすかも

PHP を Apache 上のモジュール(mod_php) として実行している場合、.mo ファイルがキャッシュされる問題に直面するかもしれない。これは最初に読み込まれた時に発生し、更新するには、サーバを再起動する必要がある。Nginx と PHP5 では通常、翻訳キャッシュを更新するのに、数ページを更新するだけでよく、PHP7 ではほとんど必要ない。

追加のヘルパー関数

多くの人が好むやり方だが、gettext() の代わりに、_() を使う方が簡単だ。フレームワークに備わっている多くのカスタム i18n ライブラリも、変換されたコードを短くするために t() に似たものを使っている。しかしながら、これはショートカットを備えた唯一の関数だ。ngettext() のための__()_n()をプロジェクトに追加したり、華美な _r()gettext()sprintf()の呼び出しに追加したくなるかもしれない。php-gettextのGettext のような他のライブラリでも、これらのようなヘルパー関数が提供されている。

そうした場合、それらの新しい関数から文字列を抽出する方法をGettextユーティリティに指示する必要がある。しかし恐れる必要はない。とても簡単だ。そうした指示は .po ファイルにあるフィールド、あるいは Poedit の設定画面で行う。エディタでは、そのオプションは「Catalog > Properties > Source keywords」の中にある。覚えておいてほしいのは、Gettext は多くの言語のためのデフォルト関数がすでにわかっているので、そのリストが空っぽのように見えても恐れる必要はない、ということだ。特定の形式 に従って、これらの新しい関数の仕様を含める必要がある。

これらの新しいルールを.po ファイルに含めたあと、新たにスキャンを実行すると、以前と同様、簡単に新しい文字列が取り込まれる。

リファレンス

Back to Top

依存性の注入

Wikipediaによると、

依存性の注入とはソフトウェアデザインパターンの1つである。依存関係をソースコードから排除して、実行時あるいはコンパイル時にその依存関係を変更できるようにする。

とのことだが、この説明は必要以上に小難しく言っているように思える。 依存性の注入とはコンポーネントに依存関係を渡せる仕組みのことで、コンストラクタで注入したりメソッド呼び出しをしたりプロパティを設定したりといった方法がある。 それだけのことだ。

基本的な概念

考えかたを説明するために、シンプルな例を示そう。細かいところは手を抜いているけどね。

ここに Database クラスがある。こいつがデータベースとやりとりするためには、何らかのアダプターが必要だ。 そこで、コンストラクタの中でアダプターのインスタンスを作っている。アダプターの名前を直接指定してね。 こんな書きかただと Database クラスを単体でテストするのが難しくなるし、 このクラスがアダプターと密接に結びついてしまう。

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct()
    {
        $this->adapter = new MySqlAdapter;
    }
}

class MysqlAdapter {}

このコードを書き換えて依存性の注入を使うようにすれば、この依存関係を緩やかにできる。 ここでは、コンストラクタに依存性を注入することにし、クラスを横断してプロパティを使えるようにするために コンストラクタのプロモーション を使うことにしよう:

<?php
namespace Database;

class Database
{
    public function __construct(protected MySqlAdapter $adapter)
    {
    }
}

class MysqlAdapter {}

これで、依存関係を外部から Database クラスに渡せるようになった。このクラス自身に作らせる必要がなくなったんだ。 コンストラクタで指定する以外にも、依存関係を引数で受け取ってそれを設定するようなメソッドを新たに作ってもいいし、 あるいは $adapter という public プロパティを作って、依存関係を直接設定できるようにしてもいい。

複雑な問題

これまでに依存性の注入について調べたことがある人なら、 “制御の反転(IoC:Inversion of Control)”“依存関係逆転の原則(DIP:Dependency Inversion Principle)” といった言葉に見覚えがあるだろう。これらは複雑な問題で、依存性の注入によって解決できるものだ。

制御の反転

制御の反転とは、文字通り、システムの「制御を反転」することだ。システム全体の制御を、オブジェクトから切り離したままで行う。 依存性の注入の文脈では、依存関係の制御や作成を、システム内のどこか別の場所でやるってことを意味する。

PHPのフレームワークでも、制御の反転は実現されてきた。でも、実際のところ、何の制御を反転しているのだろう。そして、反転した結果、制御はどこに行ってしまったのだろう。 たとえば、たいていのMVCフレームワークには、あらゆるコントローラの親になる基底コントローラが用意されている。 そして、それを継承しなければ依存関係にアクセスできない。 これはこれで制御の反転だが、でも、依存関係を緩くしたというよりは、単純に依存関係を別の場所に移しただけのことだ。

依存性の注入を使えば、これをもっとすっきりと解決できる。依存関係が必要になったときに、必要なものだけを注入すればいい。 ハードコーディングする必要はない。

S.O.L.I.D.

単一責任の原則

単一責任の原則は、アクターと高レベルなアーキテクチャーに関するものだ。これは “クラスを変更する理由は、ひとつだけであるべきである” という原則だ。これは、個々のクラスは、ソフトウェアが提供する単一の機能のみについて責任を持つべきであるということだ。このアプローチを採用する最大の利点は、再利用性を高めることができることにある。我々のクラスがひとつのことだけをしていれば、他のプログラムでもそれを変更することなく使う(または再利用)することができる。

開放/閉鎖原則

開放/閉鎖原則(Open/Closed Principle) は、クラス設計と機能の拡張に関するものだ。これは”ソフトウェアの要素(クラスやモジュール、関数など)は、拡張に対しては開いており、修正に対しては閉じているべきである” という原則だ。これは、新しい機能が必要になった時は、既存のコードを変更せず、既存のコードを使って新しいコードを書けるようにクラスやモジュール、関数を設計すべきということだ。実用的な言い方をすると、インターフェイスに従ってクラスを実装し、特定のクラスではなく、インターフェイスを使ってタイプヒントを付けるべきということだ。

このアプローチを採用する最大の利点は、既存のコードを変更することなく、新しい機能をサポートできるよう簡単に拡張できることだ。こうすることで、QA にかかる時間を削減でき、アプリケーションを実質的に壊してしまうリスクや、悪い影響を避けることができる。さらにこうすることで、新しいコードを素早く、自信をもってデプロイできるようになる。

リスコフの置換原則

リスコフの置換原則は、サブタイピングと継承に関するものだ。これは “子クラスは、絶対に親クラスの型定義を壊してはいけない” という原則だ。Robert C. Martin の言葉を借りれば “サブタイプは、基底型を代替できなければならない” ということだ。

例をあげよう。embed() メソッドを定義したインターフェイス FileInterface があるとする。そして、このインターフェイスを実装したクラス AudioVideo があるとして、embed() の使い方は常に期待通りであるとしよう。後になって、FileInterface を実装した PDFGist クラスを作ることになっても、embed() メソッドが何をするかを我々はわかっているはずだ。このアプローチを採用する最大の利点は、柔軟性があり、簡単に設定可能なプログラムを作れることだ。なぜなら、オブジェクトの型 (ここでは FileInterface) を別の型に変更しても、プログラムを変更する必要はないからだ。

インターフェイス分離の原則

インターフェイス分離の原則(ISP)とは、ビジネスロジックとクライアントの通信に関するものだ。これは、”どのクライアントも、自分が使わないメソッドに依存してはいけない” という原則だ。これは、全てのクラスが従い、実装する必要がある単一のモノリシックなインターフェイスを持つのではなく、小さな、特定の概念を持つインターフェイスの集合を提供し、クラスはそれらをひとつ以上実装すべきということだ。

例をあげよう。CarBus クラスは steeringWheel() メソッドに関心があるとしよう。一方で、MotorcycleTricycle クラスはそのメソッドに関心がないとする。反対に MotorcycleTricyclehandlebars() メソッドに関心があるが、CarBus メソッドはそのメソッドに関心がないとする。この場合、これら全ての車両の型が steeringWheel()handlebars() を両方サポートするために、これらのメソッドを実装する必要はない。インターフェイスを分割すべきだ。

依存関係逆転の原則

依存関係逆転の原則は、複数の具象クラスの間では、密結合しないようにする原則だ。これを守ると、異なるクラスを渡すことで、新しい機能を利用できるようになる。 これは 「抽象に依存しろ。具象に依存するな」 という原則だ。 簡単に言うと、依存関係はインターフェイスや抽象クラスに対して設定すべきものであり、それを実装したクラスに対して設定してはいけないってこと。 先ほどの例をこの原則に沿って書き直すと、こんなふうになる。

<?php
namespace Database;

class Database
{
    public function __construct(protected AdapterInterface $adapter)
    {
    }
}

interface AdapterInterface {}

class MysqlAdapter implements AdapterInterface {}

これで Database クラスは、具象クラスではなくインターフェイスに依存するように変わった。で、いったい何がうれしいんだろう。

こんな場面を考えてみよう。君は今、とあるチームの一員として作業をしている。アダプターを作っているのは別のメンバーだ。 最初の例だと、その人がアダプターを完成させるまでは、自分のコードのユニットテストのモックを作れない。 インターフェイスに依存するようにした新しいバージョンだと、そのインターフェイスを使ってモックを作ることができる。 同僚が作るアダプターもそのインターフェイスに沿っているとわかっているからだ。

そんなことより、もっとすばらしいメリットもある。こっちの方式のほうが、コードがずっとスケーラブルになるんだ。 仮に将来、別のデータベースに移行することになったとしよう。 そんな場合でも、このインターフェイスを実装した新しいアダプターを書いたらそれでおしまいだ。 それ以外は何もいじる必要がない。 新しいアダプターがきちんと決まりごとに従っているということを、インターフェイスが保証してくれるからだ。

コンテナ

DIコンテナについてまず知っておくべきなのは、DIコンテナを使ってさえいれば「依存性の注入」ができるわけではないってことだ。 DIコンテナは、依存性の注入を実現するための便利な道具として使える。 でも、使い方をミスって、サービスロケーションというアンチパターンを作ってしまっていることも多い。 DIコンテナをサービスロケーターとしてクラスに組み込んでしまうと、 依存関係を別の場所に移そうとしていたはずなのに、よりきつい依存関係を作り込むことになる。 おまけにそのコードはわかりにくくなってしまうし、テストもしづらくなる。

いまどきのフレームワークにはたいていDIコンテナが用意されていて、依存関係をコード以外のところで設定できる。 要するに、アプリケーションのコードがすっきりと書けて、フレームワーク本体とあまり密接につながりすぎないようにできるということ。

あわせて読みたい

Back to Top

データベース

PHP のコードを書いていると、情報を保存するためにデータベースを使うことが多くなる。 データベースを操作するには、いくつかの方法がある。 PHP 5.1.0 の時代までは 、おすすめの方法は mysqlipgsqlmssql などのネイティブドライバを使うことだった。

このデータベースしか使わないよ!っていうのならネイティブドライバもいい。 でもたとえば、MySQL を使っているけど一部は MSSQL であるとか、 Oracle にもつなぐことがあるとか、そんな場合は同じドライバでは対応できない。 そして、データベースが変わるたびに新しい API を覚えないといけないことになる。 ばかげた話だ。

MySQL 用の拡張モジュール

PHP 用の mysql 拡張モジュールは既に思いっきり古くなっていて、これらの拡張モジュールがその後継になっている。

mysql 拡張モジュールの開発は大昔に終了しているだけでなく、PHP 7.0.0で公式に削除されている

いま使っているモジュールがどれなのかを知りたいなら、わざわざ php.ini の設定を調べるまでもない。 お好みのエディターで mysql_* を検索してみればいい。 mysql_connect() とか mysql_query() みたいな関数がヒットしたら、 mysql モジュールを使ってるってことだ。

当面は PHP 7.x 以降を使うつもりがないのだとしても、今ちゃんと考えておかないと、いざというときに大変なことになる。 いちばんいいのは、通常の開発スケジュールの中で、mysql モジュールを使っている部分を mysqliPDO に徐々に置き換えていくことだ。 そうすれば、後になってあせらずにすむ。

** mysql から mysqli への移行について、単に「mysql_*mysqli_* に置換すればOK」などと書いているような記事には用心すること。話を単純化しすぎているだけではなく、mysqli ならではの利点(パラメータのバインドなど。これは PDO でも用意されている)の活用ができなくなってしまう。 **

PDO 拡張モジュール

PDO はデータベースとの接続を抽象化するライブラリだ。PHP 5.1.0 以降に組み込まれていて、 いろんなデータベースを同じインターフェイスで扱える。 MySQLを使うコードもSQLiteを使うコードも、基本的には同じようになるってことだ。

<?php
// PDO + MySQL
$pdo = new PDO('mysql:host=example.com;dbname=database', 'user', 'password');
$statement = $pdo->query("SELECT some_field FROM some_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);

// PDO + SQLite
$pdo = new PDO('sqlite:/path/db/foo.sqlite');
$statement = $pdo->query("SELECT some_field FROM some_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);

PDO は、SQL のクエリをデータベースにあわせて変換するものではないし、 もともと存在しない機能をエミュレートするものでもない。 純粋に、いろんなデータベースに同じ API で接続するためのものだ。

もっと大切なことは、PDO を使えば、外部からの入力 (ID など) を安全に SQL クエリに埋め込めるということだ。データベースへの SQL インジェクション攻撃を心配しなくてもよくなる。 そのためには、PDO ステートメントとバインド変数を使えばよい。

数値の ID をクエリ文字列として受け取る PHP スクリプトを考えてみよう。 渡された ID を使って、データベースからユーザー情報を取り出す。 最初に示すのは 悪い方法 だ。

<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$pdo->query("SELECT name FROM users WHERE id = " . $_GET['id']); // <-- ダメ、ゼッタイ!

こんな恐ろしいコードを書いちゃいけない。 これは、クエリ文字列のパラメータを SQL に直に埋め込んでいることになる。 あっという間に SQLインジェクション 攻撃を食らうだろう。 どこかのずるがしこい攻撃者が id に渡す内容をひと工夫して http://domain.com/?id=1%3BDELETE+FROM+users みたいな URL を呼んだとしよう。 このとき変数 $_GET['id'] の内容は 1;DELETE FROM users となり、全ユーザーが消えてしまうことになる! こんな書き方ではなく、PDO のバインド変数で ID を受け取らないといけない。

<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$stmt = $pdo->prepare('SELECT name FROM users WHERE id = :id');
$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT); // <-- まずデータのフィルタリングを行う ([データのフィルタリング](#data_filtering) を参照)。特に INSERT や UPDATE などで重要
$stmt->bindParam(':id', $id, PDO::PARAM_INT); // <-- PDOが自動的にSQLのエスケープ処理をする
$stmt->execute();

これが正しい方法だ。この例では、PDO ステートメントでバインド変数を使っている。 外部からの入力である ID がエスケープされてからデータベースに渡るので、 SQL インジェクション攻撃を受けることがなくなる。

INSERT や UPDATE などの書き込み操作の場合は、まず データをフィルタリング することが大切だ。 その後で、その他 (HTML タグや JavaScript など) のエスケープを行う。 PDO はあくまでも SQL 用のエスケープを行うものであり、アプリケーション全体の面倒をみてくれるわけではない。

あと、データベースのコネクションはリソースを使うということにも気をつけよう。 コネクションを明示的に閉じることを忘れたせいでリソースを食いつぶしてしまうなんて話は珍しくない。 とはいえ、これは別にPHPに限った話でもないけどね。 PDOを使っている場合は、オブジェクトへの参照をすべて削除して(Nullを代入するなどして) オブジェクトを破棄してしまえば、暗黙のうちにコネクションを閉じることが保証される。 オブジェクトを明示的に破棄しない場合は、スクリプトの実行が終わった時点でPHPが自動的に接続を閉じる。 もちろん、持続的接続を使っている場合は別だ。

データベースとのやりとり

PHPを勉強し始めたばかりの開発者がやってしまいがちなのが、データベースとのやりとりと画面表示のロジックをごちゃまぜにしてしまうことだ。 たとえば、こんなコードになる。

<ul>
<?php
foreach ($db->query('SELECT * FROM table') as $row) {
    echo "<li>".$row['field1']." - ".$row['field1']."</li>";
}
?>
</ul>

これは、あらゆる意味でよろしくない。 まず何と言っても、デバッグしづらいし、テストもしづらいし、読みづらい。 あと、何も制限をかけていない場合に、大量のフィールドを出力してしまうことになる。

同じことをもっとすっきり行う方法はいろいろある。OOPが好きな人向けのやりかたもあれば 関数型プログラミングが好きな人向けのやりかたもある。 が、まずは、分離することからはじめよう。

これが第一歩だ。

<?php
function getAllFoos($db) {
    return $db->query('SELECT * FROM table');
}

$results = getAllFoos($db);
foreach ($results as $row) {
    echo "<li>".$row['field1']." - ".$row['field1']."</li>"; // BAD!!
}

少しはマシになった。この二つを別々のファイルに分けてしまえば、きれいに分離できるだろう。

次に、このメソッドを保持するクラスを用意する。「モデル」だ。 そして、シンプルな .php ファイルをもうひとつ作って、そこに画面表示ロジックを入れる。「ビュー」だ。 これで、何となく MVC っぽくなった。これは、多くの フレームワーク で使われている、OOPのアーキテクチャだ。

foo.php

<?php
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8mb4', 'username', 'password');

// モデルを読み込む
include 'models/FooModel.php';

// インスタンスを作る
$fooModel = new FooModel($db);
// Fooのリストを作る
$fooList = $fooModel->getAllFoos();

// ビューを表示する
include 'views/foo-list.php';

models/FooModel.php

<?php
class FooModel
{
    public function __construct(protected PDO $db)
    {
    }

    public function getAllFoos() {
        return $this->db->query('SELECT * FROM table');
    }
}

views/foo-list.php

<?php foreach ($fooList as $row): ?>
    <li><?= $row['field1'] ?> - <?= $row['field1'] ?></li>
<?php endforeach ?>

本質的にこれは、今どきのフレームワークがやっていることと同じだ。それを手作業でやってみた。 毎回こんなことをする必要はないかもしれないが、画面表示とデータベース操作を混在させすぎると、 ユニットテスト をしたくなったときにやっかいな問題が発生してしまう。

抽象化レイヤー

多くのフレームワークは、データベースの抽象化レイヤーを用意している。PDOを利用しているものもあれば、そうでないものもある。 あるデータベースには存在するけれども別のデータベースには存在しない機能などをエミュレートするために、 クエリーをPHPのメソッドでラップしたりするものだ。 PDOではデータベースへの接続の抽象化しかしないが、フレームワークの抽象化レイヤーは、それ以上のことをしてくれる。 もちろんそれなりのオーバーヘッドが発生するだろう。 でも、MySQLでもPostgreSQLでもSQLiteでも動くようなアプリケーションを書いているのなら、 多少のオーバーヘッドを割り引いてでも、すっきりしたコードを書けるほうがうれしいだろう。

以下の抽象化レイヤーは、 PSR-0PSR-4 で定められた標準名前空間に従っている。 これらはアプリケーションを問わずに利用できる。

Back to Top

テンプレート

テンプレートは、コントローラーやドメインロジックを、プレゼンテーションロジックから切り離すための便利な手段だ。 テンプレ—トには、アプリケーションで使うHTMLを含めることが多いが、それ以外のフォーマット(XMLなど)を含めることもある。 テンプレートのことを「ビュー」と呼ぶこともある。いわゆる model–view–controller (MVC) パターンの、二番目の要素 の一部 だ。

メリット

テンプレートを使う主なメリットは、画面に表示する内容をアプリケーションから切り離せることだ。 テンプレートは、フォーマット済みのコンテンツを表示するという責務だけを負う。 データを検索したり、データベースに保存したりなどといった、雑多なタスクは気にしない。 このおかげで、すっきりしたリーダブルなコードが書けるようになる。チームで開発する際などには、これが特に有用だ。 開発者はサーバー側のコード(コントローラやモデル)、そしてデザイナーはクライアント側のコード(マークアップ) などという作業分担をしやすくなる。

テンプレートには、プレゼンテーションのコードの構造を改善するという効果もある。 テンプレートは一般的に「views」などのフォルダにまとめられて、それぞれ個別のファイルになっていることが多い。 こうしておけばコードの再利用がしやすくなる。大規模なコードブロックを、小さめの再利用しやすいパーツ(パーシャルと呼ばれることもある)に分割できるからだ。 たとえば、サイトのヘッダやフッタをテンプレートにしておけば、各ページのテンプレートにそれをインクルードできるようになる。

利用するライブラリによっては、テンプレートを使うことで、よりセキュリティを確保できるようにもなる。 ユーザーが作るコンテンツを自動的にエスケープしてくれたりする機能を持つようなテンプレートが、それにあたる。 さらに、サンドボックス機能を提供するライブラリもある。これは、デザイナーが、あらかじめ許可された変数と関数しか利用できないようにする仕組みだ。

プレーンなPHPによるテンプレート

プレーンなPHPによるテンプレートとは、単にPHPのコードを使ったテンプレートという意味だ。 ごく自然な選択肢だとも言える。そもそもPHP自体がテンプレート言語だし。 あ、これって単に、PHPのコードをHTMLとかにも埋め込めるよねっていう以上の深い意味はないからね。 PHPの開発者にとっては、新しい構文を覚えずに済むというメリットがある。 どんな関数が使えるのかもわかっているし、ふだんPHPを書いているエディタのシンタックスハイライトや 自動補完機能も、そのまま使える。 その上、プレーンなPHPのテンプレートは高速であることが多い。コンパイルが不要だからだ。

いまどきのPHPフレームワークは、たいてい何らかの仕組みのテンプレートシステムを持っている。 その多くでデフォルトになっているのが、プレーンPHPによるテンプレートだ。 フレームワーク以外では、PlatesAura.View といったライブラリがプレーンPHPテンプレートを使いやすくしてくれる。継承やレイアウト、拡張などの便利なテンプレート機能を用意してくれるんだ。

プレーンPHPテンプレートのシンプルな例

Plates ライブラリを使った。

<?php // user_profile.php ?>

<?php $this->insert('header', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->escape($name)?></p>

<?php $this->insert('footer') ?>

プレーンPHPテンプレートで継承を使う例

Plates ライブラリを使った。

<?php // template.php ?>

<html>
<head>
    <title><?=$title?></title>
</head>
<body>

<main>
    <?=$this->section('content')?>
</main>

</body>
</html>
<?php // user_profile.php ?>

<?php $this->layout('template', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->escape($name)?></p>

コンパイル形式のテンプレート

PHPはオブジェクト指向言語として成熟してきてはいるものの、テンプレート言語としては いまいち だ。 コンパイル形式のテンプレート、たとえば TwigBrainySmarty* が、この穴を埋めてくれる。テンプレートに特化した、新しい構文を用意してくれるんだ。 自動エスケープから継承や制御構文まで、コンパイル形式のテンプレートは、いかに読み書きしやすく、安心して使えるかを重視して作られている。 さらに、コンパイル形式のテンプレートは、別の言語でさえも使うことができる。Mustache がそのよい例だ。 テンプレートをコンパイルする時間がかかるので、多少はパフォーマンスに影響する。 しかし、適切にキャッシュをすれば、その影響は微々たるものだ。

*Smartyには自動エスケープ機能があるけど、これはデフォルトでは無効になっている。

コンパイル形式のテンプレートのシンプルな例

Twig ライブラリを使った。

{% include 'header.html' with {'title': 'User Profile'} %}

<h1>User Profile</h1>
<p>Hello, {{ name }}</p>

{% include 'footer.html' %}

コンパイル形式のテンプレートで継承を使う例

Twig ライブラリを使った。

// template.html

<html>
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>

<main>
    {% block content %}{% endblock %}
</main>

</body>
</html>
// user_profile.html

{% extends "template.html" %}

{% block title %}User Profile{% endblock %}
{% block content %}
    <h1>User Profile</h1>
    <p>Hello, {{ name }}</p>
{% endblock %}

あわせて読みたい

記事やチュートリアル

ライブラリ

Back to Top

エラーと例外処理

エラー

例外処理を重視するプログラミング言語では、何か問題が起こったらすぐに例外を投げる。 それはそれでいいことではあるが、PHPはそうではなく、「例外処理も使える」プログラミング言語だ。 PHPには例外処理の仕組みがあるし、コアの中でもオブジェクトを扱うときには例外処理を行うことが増えている。 でも、PHPは基本的に、よっぽど致命的なエラーが発生しない限りは何があろうと処理を続行しようとする。

たとえば、こんなコードを考えてみよう。

$ php -a
php > echo $foo;
Notice: Undefined variable: foo in php shell code on line 1

単純にnoticeレベルのエラーになるだけで、PHPはそのまま処理を続行する。 例外処理を重視する世界からやってきた人にとっては、これは少しキモいと思うかもしれないね。 たとえばPythonなら、未定義の変数を参照しようとすると、例外が発生する。

$ python
>>> print foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined

実際の違いは、こういうことだ。 Pythonは、些細なことにまでこだわることで、プログラマーが余計な心配(「もし○○だったら、その場合は…」と考えたり、エッジケースを考慮したりなど) をせずに済むようにしている。 一方PHPは、どうしようもないエラーが発生しないかぎりは、一応エラーを報告したうえで処理を続行する。

エラーの深刻度

PHPのエラーは、何段階かの深刻度レベルに別れている。PHPでよく使われるメッセージの形式は、 エラー(error)と注意(notice)そして警告(warning)だ。 それぞれ別々の深刻度レベルが設定されていて、 E_ERRORE_NOTICE、そして E_WARNING になる。 「エラー」は実行時の致命的な問題で、ふつうはコードの書きかたがまずいせいで発生する。 これは修正しなければいけない。というのも、これが発生するとPHPの実行がそこで止まってしまうからだ。 「注意」は助言みたいなもので、問題を起こす可能性があるスクリプトを実行したときに発生する。 スクリプトの実行は止まらない。 「警告」は致命的ではない問題で、これもスクリプトの実行は止まらない。

もうひとつ、コンパイル時に発生する E_STRICT という形式のメッセージもある。 これは、相互運用性や将来のバージョンのPHPとの互換性を考えたときに、コードを書き換えたほうがいいと提案するためのメッセージだ。

PHPのエラー報告の挙動の変更

エラー報告の挙動は、PHPの設定で変更することもできるしPHPの関数で変更することもできる。 組み込みの関数 error_reporting() にエラーレベル定数を渡せば、そのスクリプトの実行中に どのレベルのエラー処理をするのかを設定できる。 たとえば、エラー(error)とか警告(warning)は表示させたいけれども、別に注意(notice)は見たくないという場合は、こんなふうにすればいい。

<?php
error_reporting(E_ERROR | E_WARNING);

画面に表示するかしないか(開発時に有効)だけでなく、ログに出力するかしないか(運用時に有効)も制御できる。 詳細は エラーレポート を参照。

インラインでのエラーの抑制

「ここでのエラーは無視すること」とPHPに指示することもできる。そのときに使うのが、 エラー制御演算子 @ だ。この演算子を何かの式の前に書くと、 その式で発生したあらゆるエラーは黙って揉み消される。

<?php
echo @$foo['bar'];

これは、もし $foo['bar'] が存在すればその内容を出力するが、仮に変数 $foo やキー 'bar' が存在しなくてもエラーにはならない。単純に null を返して何も表示しないだけだ。 もしエラー制御演算子がなかったら、 PHP Notice: Undefined variable: foo だとか PHP Notice: Undefined index: bar のようなエラーになる。

一見、よさげな機能だと感じるかもしれない。でも、これを使うと、あまり望ましくない代償を払うことになる。 まず、PHPでは、 @ つきの式は @ なしの式よりも処理効率が落ちてしまう。 「早まった最適化は諸悪の根源だ」とはいうものの、 もしパフォーマンスを重視するアプリケーションやライブラリを作っているのなら、 エラー制御演算子がパフォーマンスに及ぼす悪影響を知っておくべきだ。

次に、エラー制御演算子を使うと、発生したエラーが 完全に 隠蔽されてしまう。 画面にも表示されなければ、ログに書き出されることもない。 また、運用中のPHPシステムのエラー制御演算子を無効にする仕組みはない。 今見ているエラーが仮に些細なものだとしよう。だとしても、それ以外に無視できないエラーが発生するかもしれない。 そんなエラーも、同様に隠蔽されてしまう。

エラー制御演算子を使わずにすむ道があるなら、その方向で考えるべきだ。 たとえばさっきのコードなら、こんなふうに書き直せばいい。

<?php
// Null Coalescing Operator
echo $foo['bar'] ?? '';

エラーの抑制を使うのが理にかなっている場面として考えられるのは、 たとえば fopen() を使っていてファイルの読み込みに失敗した場合だ。 もちろん実際に読み込む前にファイルがあるかどうかをチェックするだろうが、 そのチェックが終わってから実際に fopen() で読み込むまでの間に ファイルが削除されるかもしれない(まあありえないだろうけど、可能性はゼロではない)。 そんな場合、 fopen() が false を返し、 そして エラーも発生する。 ほんとはPHP側で何とかしてもらいたいところだけれど、現状ではエラーを抑制するくらいしか手がない。

さっき、稼働中のPHPシステムではエラー制御演算子を無効化できないといったけれど、 実は、 Xdebug の設定 xdebug.scream を使えば、エラー制御演算子を無効化できる。 php.ini に、こんなふうに書けばいい。

xdebug.scream = On

実行時に設定するなら、 ini_set 関数を使えばいい。

<?php
ini_set('xdebug.scream', '1')

この機能が役立つのは、コードのデバッグ中など、エラーから情報を読み取りたいときだ。 screamを使うときには気をつけて、あくまでも一時的なデバッグ用のツールとして使うようにしよう。 エラー制御演算子を無効にしたままでは正常に動かないというライブラリって、結構多いよ。

ErrorException

PHPは、例外が大好きな人たちにも対応したプログラミング言語だ。 ほんの数行のコードを足すだけで、例外に対応できる。 基本的に、エラーが発生したときには、 ErrorException クラスを使って「例外」を投げればいい。 このクラスは、 Exception クラスを継承したものだ。

これは、SymfonyやLaravelみたいな今どきのフレームワークでもよく使われている方法だ。 これらのフレームワークのデバッグモード (開発モード) は、 スタックトレース をいい感じに表示してくれる。

エラーや例外の処理や表示をうまい具合にやてくれるパッケージもある。 たとえば Whoops! もそのひとつだ。 これはLaravelをデフォルトでインストールするとついてくるものだけど、ほかのフレームワークでも使える。

開発中は、エラーを例外として投げるようにしておくと、その処理をしやすくなる。 開発中にもし例外が発生したら、それをcatch文でラップして、その状況に対応する処理を書くこともできる。 例外をひとつキャッチするたびに、アプリケーションはほんの少しずつ頑丈になっていく。

もっと詳しいことが知りたい、あるいは ErrorException クラスを使ったエラー処理について調べたいという人は、 ErrorException クラス のドキュメントを読もう。

例外処理

最近はやりのプログラミング言語には、たいてい例外処理の仕組みがある。でも PHP の人は見落としがちだ。 たとえば Ruby なんかは例外処理を使いまくっている。 HTTP リクエストに失敗したときもデータベースへのクエリが失敗したときも、画像ファイルが見つからなかったときでさえも、 Ruby のプログラム (あるいはその中で使っている gem) は例外を投げる。 なので、画面を見ればすぐに「ああ、何か間違えたんだな」とわかる。

PHP はそのあたりが極めて緩くて、たとえば file_get_contents() が失敗しても単に FALSE を返して警告を出すだけだ。古いフレームワークの多くもそんな感じだ。 たとえば CodeIgniter の場合は、単に false を返してログファイルにメッセージを書き出すだけ。 原因を知りたければ $this->upload->get_error() みたいなメソッドを実行することになる。 何が問題かというと、まずエラーが発生したのかどうかを自分で調べないといけないこと。 そして次に、エラー情報を取得する方法をドキュメントで調べないといけないこと。 もっとはっきりわかるようにしてくれたらいいのに。

別の問題もある。何かのクラスがエラーを画面に投げっぱなしにしてそのまま終わるような場合だ。 そんなことをすれば、エラーがあったときにプログラム中で動的に対応することができなくなってしまう。 そんな場合は、エラーではなく例外を発生させないといけない。 例外にしておけば開発者がエラーに気づけるし、プログラムの中で対応できるようになる。 たとえばこんな感じだ。

<?php
$email = new Fuel\Email;
$email->subject('タイトル');
$email->body('ごきげんいかが?');
$email->to('guy@example.com', '誰かさん');

try
{
    $email->send();
}
catch(Fuel\Email\ValidationFailedException $e)
{
    // 検証に失敗した
}
catch(Fuel\Email\SendingFailedException $e)
{
    // ドライバがメールを送れなかった
}
finally
{
    // 例外が発生してもしなくても、ここは必ず実行される
}

SPL の例外

汎用的な Exception クラスには、開発者がデバッグするためのコンテキスト情報がほとんど含まれていない。 これを改善するには、特化型の Exception を作ればいい。つまり、Exception クラスのサブクラスを作るってことだ。

<?php
class ValidationException extends Exception {}

こんなふうにすれば、catch ブロックを複数用意してそれぞれの例外で別の処理をできるようになる。 その結果、自作の例外クラスが 大量に できあがってしまうかもしれないが、 SPL 拡張モジュール が用意する例外クラスを使えば少しはましになるだろう。

たとえば、マジックメソッド __call() を使っているときに、無効なメソッドを要求されたとしよう。 標準の Exception クラスを使うのは曖昧すぎるし、そのためだけに専用の例外クラスを作るのも何だし、 という場合には単に throw new BadMethodCallException; とすればよい。

Back to Top

セキュリティ

今まで見たPHPのセキュリティに関する資料の中でいちばんだったのは、Paragon Initiative による The 2018 Guide to Building Secure PHP Softwareだ。

ウェブアプリケーションのセキュリティ

PHP開発者は全員、Webアプリケーションセキュリティの基本 を学ぶべきだ。内容としてはだいたいこんな感じになる。 down into a handful of broad topics:

  1. コードとデータの分離
    • データをコードとして実行するときにはSQLインジェクションやクロスサイトスクリプティングやファイルインクルード攻撃の恐れがある
    • コードをデータとして表示するときには情報漏洩の恐れがある (ソースコードが晒されてしまったり、C言語の場合ならアドレス空間配置のランダム化もある ASLR)
  2. アプリケーションロジック
    • 認証・認可の制御不全
    • 入力の検証
  3. 運用環境
    • PHPのバージョン
    • サードパーティのライブラリ
    • OS
  4. 暗号化の弱点

世の中には悪い人たちがいて、あなたの書いたウェブアプリケーションもきっと狙われている。 必要な対策をして、ウェブアプリケーションのセキュリティを固めておくことが大切だ。 ありがたいことに、The Open Web Application Security Project (OWASP) の人たちが、既知のセキュリティ問題とその対策をまとめてくれている。 セキュリティが気になる開発者は必読だ。 Padraic Bradyが書いたSurvive The Deep End: PHP Security も、 ウェブアプリケーションセキュリティに関してPHP向けに書かれたよいドキュメントだ。

パスワードのハッシュ処理

誰もがいつかは、ログイン機能を持つ PHP アプリケーションを書くことになる。 ユーザー名とパスワード (のハッシュ) をデータベースに保存して、 ユーザーのログイン時にそれを使って認証するというやつだ。

データベースにパスワードを保存するときは、適切に ハッシュ することが大切だ。 ハッシュと暗号化は まったく違うもの なのに、混同されることが多い。

パスワードのハッシュは不可逆な操作で、ユーザーのパスワードに対して一方通行で行う。 できあがる結果は固定長の文字列で、元には戻せない。 つまり、このハッシュを別のハッシュと比較すれば元の文字列どうしが一致するかどうかは判断できるが、 元の文字列が何だったかはわからないってことだ。 パスワードをハッシュせずにデータベースに保存していると、 万一第三者に不正アクセスされた場合に、すべてのユーザーアカウントが乗っ取られてしまう。

ハッシュと違って暗号化は元に戻せる (鍵さえ手元にあればね)。 暗号化そのものは使う場面を選べば便利なものだけど、ことパスワードの保存に関してはうまい手段ではない。

パスワードには個別に ソルト が必要だ。ランダムな文字列をパスワードに付けてからハッシュするってこと。 そうしておけば、辞書攻撃から守れるし「レインボーテーブル(ありがちなパスワードとそのハッシュをまとめた変換テーブル)」による攻撃も防げる。

ハッシュとソルトは欠かせない。だって、たいていのユーザーはいろんなサービスでパスワードを使い回すものだし、 パスワード自体も決して強力なものだとは言えないから。

あと、速度が売りの汎用ハッシュ関数 (SHA256とか) じゃなくて パスワードのハッシュ に特化したアルゴリズム を使うこと。 2018年6月時点でパスワードのハッシュに使ってもかまわないアルゴリズムはこんな感じ。

ありがたいことに、最近の PHP ならこのあたりも使いやすい。

password_hashによるパスワードのハッシュ

PHP 5.5からは、新たにpassword_hash()関数が使えるようになった。 現時点では、この関数はBCryptを使っている。これは、現在のPHPがサポートしているアルゴリズムの中では最強のものだ。 必要に応じて、将来はもっと強力なアルゴリズムをサポートするように更新されるだろう。 この関数をPHP 5.5より前のバージョンでも使えるようにするため、password_compatライブラリも作られた。 このライブラリはPHP 5.3.7以降で使える。

この例では、文字列をハッシュした後でそのハッシュを新たな文字列と比較している。 二つの文字列は違っている(‘secret-password’と’bad-password’)ので、このログインは失敗する。

<?php
require 'password.php';

$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);

if (password_verify('bad-password', $passwordHash)) {
    // パスワードが一致した
} else {
    // パスワードが一致しなかった
}

password_hash() は、パスワードのソルトをあなたに代わって管理してくれる。ソルトの情報は、アルゴリズムの情報と “コスト” と一緒に、ハッシュの一部として保存される。password_verify() はこの情報を展開してパスワードをチェックする方法を決める。よって、ソルトをデータベースに保存する必要はない。

データのフィルタリング

PHP のコードに外部から渡される入力は、絶対に信用してはいけない。 外部からの入力は、常に検証してから使うようにしよう。 filter_var() 関数や filter_input() 関数で、入力の検証や書式の判定 (メールアドレスなど) ができる。

外部からの入力にはいろいろな種類がある。フォームから渡される $_GET$_POST もあれば、 スーパーグローバル $_SERVER に含まれるものもある。そして fopen('php://input', 'r') でやってくる HTTP リクエストのボディもそうだ。 外部からの入力といっても、ユーザーがフォームで入力したものばかりとは限らないことに注意。 アップロードしたりダウンロードしたりしたファイル、セッションのデータ、 クッキーのデータ、サードパーティのウェブサービスからのデータ。 これらはみんな外部からの入力となる。

外部からのデータをいったん保存して、何かと組み合わせて後で使うとしよう。 それでも、そのデータが外部からの入力であるという事実は変わらない。 そのデータを処理したり出力したり何かとつなげたりコードに組み込んだりするときには 「適切にフィルタリングできてる?」「信頼できる?」と確認しよう。

データのフィルタリング方法は、その利用目的によって異なる。 たとえば、外部の入力を何も処理せずに HTML ページに出力すると、 あなたのサイト上で任意の JavaScript が実行できてしまうことになる! これが、いわゆるクロスサイトスクリプティング (XSS) である。 とても危険な攻撃だ。こんなときに XSS を回避する方法のひとつは、 strip_tags() で入力からすべての HTML タグを取り除くか、 あるいは htmlentities()htmlspecialchars() でエスケープして HTML エンティティに変換することだ。

別の例として、外部の入力をコマンドラインのオプションとして渡すことを考えよう。 これって非常に危険なことだし、ふつうはあまりやるべきではないことだ。 でも、もしやるなら、組み込みの関数 escapeshellarg() を使えばコマンドの引数を実行されてしまうことが防げる。

最後の例は、外部の入力に基づいてファイルシステム上のファイルを読み込むというものだ。 このときは、ファイル名のかわりにファイルパスを渡されてしまうという攻撃が考えられる。 外部の入力から”/”や”../”、null バイトなどを取り除いて、 隠しファイルや公開すべきでない場所のファイルを読み込まないようにする必要がある。

サニタイズ

サニタイズとは、外部の入力から危険な文字を取り除く (あるいはエスケープする) ことだ。

たとえば、外部の入力を HTML に含めたり SQL クエリに組み込んだりする前に、 サニタイズが必要となる。PDO でバインド変数を使う場合は、 PDO が入力をサニタイズする。

外部の入力を HTML として組み込むときに、いくつかの安全な HTML タグはそのまま使わせたいという場合もある。そんな要求を実現するのは非常に難しいので、 たいていの場合は Markdown や BBCode などのフォーマットを代替手段として使うのだが、 どうしてもという場合は HTML Purifier のようなホワイトリストライブラリを使える。

サニタイズフィルター

アンシリアライズ

ユーザーからの入力など、信頼できないところから渡されたデータを unserialize() するのは危険だ。 悪意のあるユーザーが送り込んだオブジェクト(ユーザー定義のプロパティつきのもの)のインスタンスを生成できてしまい、 たとえそのオブジェクトを一切使わなくても そのデストラクタは実行されてしまう。 なので、信頼できないデータのアンシリアライズは避けるべきだ。

シリアル化したデータをユーザーに渡す必要がある場合は、(json_decodejson_encode 経由で) JSON のような安全で標準的なデータ交換フォーマットを使うようにしよう。

バリデーション

バリデーションとは、外部の入力が期待通りであるかどうかを確かめること。 たとえばユーザー登録の処理では、 メールアドレスや電話番号、あるいは年齢などを検証することになるだろう。

バリデーションフィルター

設定ファイル

自作のアプリケーションで設定ファイルを使うときには、これらの指針に従うのがお勧めだ。

Register Globals

注意: PHP 5.4.0 からは register_globals という設定項目がなくなったので、この設定は使えない。 このページは単に、大昔のアプリケーションをアップグレードしている人たち向けの警告として用意したものでしかない。

register_globalsを有効にすると、$_POST$_GETそして$_REQUEST などの内容にアプリケーションのグローバルスコープでアクセスできるようになる。 これを使うとセキュリティの問題が発生しやすくなる。 というのも、そのデータがどこからきたものなのかをアプリケーション側で判断できなくなるからだ。

たとえば $_GET['foo'] の内容に $foo でアクセスできることになるのだが、 これは、宣言済みの変数の中身を自動的に上書きしてしまうことにつながる。 PHP 5.4.0 より前のバージョンを使っている場合は、 確実に register_globalsoff にしておこう。

エラーレポート

エラーを記録しておくと、アプリケーションに何か問題があったときにその原因を見つけやすくなる。 しかしその一方で、アプリケーションの構造に関する情報を外部に公開してしまうことにもなる。 エラーメッセージを出すことで起こる問題からアプリケーションを守るには、 開発環境と本番環境でサーバーの設定を切り替える必要がある。

開発環境

開発 環境で、起こりうるエラーをすべて表示するときには、php.iniで次のように設定する。

display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On

値に-1を指定すると、仮に将来のバージョンのPHPで新しいレベルと定数が追加されたとしてもすべてのエラーを表示するようになります。E_ALL 定数も、PHP 5.4以降これと同じ挙動になります。 - php.net

E_STRICTエラーレベル定数は5.3.0で導入されたもので、当時は E_ALLには含まれていなかった。でも5.4.0からはE_ALLに含まれるようになった。 だからどうなんだって? あらゆるエラーを表示させたいときには、5.3の場合は -1あるいはE_ALL | E_STRICTを使わないといけないってことだ。

PHPのバージョン別の、すべてのエラーを表示させるための設定

本番環境

本番 環境でエラーの情報を見せないようにするには、php.iniで次のように設定する。

display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On

この本番環境用の設定をしても、ウェブサーバーのエラーログにはエラーの内容がきちんと残る。 しかし、ユーザーにはエラーが見えなくなる。これらの設定項目についてもっと詳しく知りたければ、 PHP のマニュアルを読もう。

Back to Top

テスト

PHPのコードを書くときには、自動化されたテストも書くのがよい習慣だとされている。 そうすれば、頑丈なアプリケーションが作れるようになる。自動テストを活用すれば、 何かを変更したり機能を追加したりしたときにもアプリケーションがきちんと動くことを確認できる。 欠かせないツールだ。

PHP で使えるテスト用ツール (あるいはフレームワーク) にはいくつかのものがあり、 それぞれ異なる手法を使っている。が、目指すところは同じ。 手作業でのテストをなくす、そして最新の変更で既存の機能を壊していないかどうかを確かめるためだけに 大規模な品質保証チームを使うなんてことをなくす、というのが目標だ。

テスト駆動開発

Wikipedia によると、

Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: first the developer writes a failing automated test case that defines a desired improvement or new function, then produces code to pass that test and finally refactors the new code to acceptable standards. Kent Beck, who is credited with having developed or ‘rediscovered’ the technique, stated in 2003 that TDD encourages simple designs and inspires confidence.

アプリケーションのテストには、いくつかの種類がある。

ユニットテスト

ユニットテストとはプログラミングの手法のひとつだ。 関数やクラスやメソッドが期待通りに動いていることを開発中に常に確かめる。 さまざまな関数やメソッドの入出力の値をチェックすれば、 その内部ロジックが正しく動いていることを確認できる。 依存性注入の仕組みを利用してクラスのモックやスタブを使えば、 依存ライブラリが正しく使われていることを確かめられる。

クラスや関数を作るときに、その振る舞いを確かめるためのユニットテストも同時に作る。 最も基本的なレベルだと、間違った引数を渡した場合にエラーになることや、 正しい引数を渡したときに正常に動くことなどを確認しないといけない。 こうしておけば、後にクラスや関数に手を加えたときにも 今までの機能が期待通りに動くかどうかを確かめられるようになる。 test.php で var_dump() とかいうやり方もあるけど、 まともなアプリケーションを作るつもりならそれはあり得ない。

それ以外にもユニットテストの使い道はある。オープンソースに貢献する手段として使えるのだ。 うまく機能していないことを示すためにテストを書く。そして動くように修正する。 最後にテストが通ることを確認する。こんなパッチを送れば、きっと受け入れてもらいやすくなるだろう。 もし何かプロジェクトを運営していて pull request を受け付けているのなら、 「パッチにはテストをつけること」という条件をつけておくといいだろう。

PHPUnitは、PHPアプリケーションでユニットテストを書くための デファクトスタンダードのフレームワークだ。しかしそれ以外にも選択肢がある。

インテグレーションテスト

Wikipedia によると、

Integration testing (sometimes called Integration and Testing, abbreviated “I&T”) is the phase in software testing in which individual software modules are combined and tested as a group. It occurs after unit testing and before validation testing. Integration testing takes as its input modules that have been unit tested, groups them in larger aggregates, applies tests defined in an integration test plan to those aggregates, and delivers as its output the integrated system ready for system testing.

ユニットテスト用のツールの多くはインテグレーションテストにも使える。 ほぼ同じような指針で行うものだからである。

機能テスト

受け入れテストと呼ばれることもある。実際にアプリケーションを使う観点での自動テストを作ってその動きを確認する。 単にコード片が正しく動くとか、個々のパーツがお互いに正しくやりとりできるかとかいうレベルのテストではない。 このレベルのテストでは、実際のデータを使ったりアプリケーションの実際のユーザーをシミュレートしたりすることが一般的だ。

機能テスト用のツール

振る舞い駆動開発

振る舞い駆動開発 (BDD) には二種類ある。SpecBDD と StoryBDD だ。 SpecBDD はコードの技術的な振る舞いを重視し、StoryBDD は業務的あるいは機能的な振る舞いを重視する。 PHP には、これら二種類の BDD 用のフレームワークが存在する。

StoryBDD では、人間が読める形式のストーリーを書いてアプリケーションの振る舞いを表す。 そしてそのストーリーを、アプリケーションのテストとして実行する。 PHP アプリケーションで StoryBDD をするために使えるフレームワークが Behat で、これは Ruby の Cucumber の影響を受けたフレームワークである。 Gherkin DSL を使ってフィーチャを記述できる。

SpecBDD では、実際のコードのあるべき振る舞いをスペックとして書く。 関数やメソッドを単独でテストするのではなく、その関数やメソッドがどのように振る舞うのかを記述するのだ。 PHP で SpecBDD をするときに使えるフレームワークが PHPSpec で、これは Ruby の RSpec project の影響を受けている。

BDD に関するリンク

その他のテスト用ツール

これまでに取り上げたテストツールや振る舞い駆動開発フレームワーク以外にも、 いろいろなフレームワークやヘルパーライブラリがある。これらも有用に使える。

ツールへのリンク

Back to Top

各種サーバーへのデプロイ

PHP のアプリケーションをデプロイして本番サーバーで運用するための方法を紹介する。

Platform as a Service (PaaS)

PaaS を使えば、PHP アプリケーションをウェブ上で動かすために必要なシステムやネットワーク環境を用意してくれる。 ほとんど何も設定せずに、PHP のアプリケーションやフレームワークを実行できるということだ。

最近は、PHP アプリケーションのデプロイ先として PaaS を使うことが多くなった。あらゆる規模のアプリケーションを扱える。 PHP 用の PaaS “Platform as a Service” プロバイダ を、情報源 にまとめた。

仮想サーバーあるいは専用サーバー

サーバー管理が苦にならない人、あるいはサーバー管理を勉強してみたい人は、仮想サーバーあるいは専用サーバーを選ぶといい。 そうすれば、アプリケーションの運用環境を完全に制御できる。

nginx と PHP-FPM

PHP に組み込まれた FastCGI Process Manager (FPM) は nginx と組み合わせるのに最適だ。nginx は、軽量でパフォーマンスに優れたウェブサーバーである。 Apache よりも少ないメモリで動き、同時にさばけるリクエストの数も多い。 これは特に、共有メモリの少ない仮想サーバーでは重要だ。

Apache と PHP

PHP と Apache は長い付き合いだ。 Apache はいろんな設定が可能で、さまざまな モジュール で機能を拡張できる。共有サーバーで PHP のフレームワークを動かしたり、WordPress みたいなアプリケーションを動かしたりするときにはよく使われる選択肢だ。 残念ながら Apache は、デフォルトでは nginx よりもメモリを食うし、 同時にさばけるユーザー数も nginx より少ない。

Apache で PHP を動かすにはいくつかの選択肢がある。 一番よく使われていて簡単に設定できるのが、prefork MPMmod_php の組み合わせだ。 メモリの使用効率はそれほどよくないが、とりあえず動かして使うには一番シンプルだ。 サーバー管理方面にあまり足を突っ込みたくない場合は、この方法がいいだろう。 注意すべき点は、mod_php を使う場合は必ず prefork MPM を使わないといけないということだ。

Apache 本来のパフォーマンスや安定性をもっと絞り出したいという場合は、nginx と同じように FPM を使うこともできる。 この場合は、worker MPM あるいは event MPM に mod_fastcgi あるいは mod_fcgid を組み合わせる。この設定はメモリの利用効率がよくて高速に動作するが、設定に手間がかかる。

Apache 2.4 以降なら、mod_proxy_fcgiが使える。簡単にセットアップできるし高性能だ。

共有サーバー

PHP の人気のおかげで、いろんな共有サーバーで PHP が使える。 むしろ PHP が使えない共有サーバーを見つけるほうが難しいだろう。 ただし、最新バージョンが使えるかどうかは要注意だ。 共有サーバーでは、あなただけでなく他の開発者も同じマシンにウェブサイトをデプロイする。 その利点は、安上がりに使えるということだ。 ただ欠点もあって、同じサーバーに同居しているお隣さんが何をしでかすかがわからない。 めちゃめちゃ負荷のかかることをしてしまったり、セキュリティホールを作り込んでしまったりといった恐れがある。 もし充分な予算があるのなら、できるだけ共有サーバーは避けよう。

共有サーバーでは、最新バージョンのPHPが使えることを必ず確認すること。

アプリケーションのビルドとデプロイ

まさか、データベースのスキーマを変更したりテストを実行したりとかいったことを手作業でやってるなんてことはないよね? ちょっと待った!新しいバージョンのアプリケーションをデプロイするときに手作業がひとつでも増えると、 致命的な間違いを犯してしまう可能性もそのぶん増えてしまうんだ。たとえ単純な更新作業だとしても、 きちんとしたビルド手順にしたがうこと。継続的インテグレーションの戦略にしたがって、 ビルドの自動化 をしておくといい。

自動化できるタスクには、こんなものがある。

デプロイツール

デプロイツールとは、ソフトウェアのデプロイにからむありがちな作業を処理するスクリプトをまとめたものだと言える。 デプロイツール自体は君が作るソフトウェアの一部ではない。ソフトウェアを「外部から」支援するものだ。

ビルド自動化やデプロイの助けとなるオープンソースのツールがたくさん公開されている。PHPで書かれているものもあれば、 そうじゃないものもある。PHP製じゃないからといって、それを使わない理由はない。 もし自分のやりたいことに適したツールがあるのなら、使うべきだ。いくつか例をあげよう。

Phing を使えば、パッケージングやデプロイそしてテストといった処理をシンプルなXMLビルドファイルで設定できる。 PhingはApache Ant をベースに作られたもので、 Webアプリのインストールやアップデートに必要となるタスク群を提供する。 カスタムタスクで機能を追加することもでき、カスタムタスクはPHPで書ける。 古くからあるツールだけあって堅実で安定してるけど、ちょっと古臭さも感じる (設定をXMLファイルで管理するところとかね)。

Capistrano中級から上級のプログラマー 向けのシステムだ。構造化された、繰り返し可能な形式で、 複数のリモートマシン上でコマンドを実行できる。 Ruby on Railsのアプリをデプロイするように設定されているが、 PHP のアプリもデプロイできる。 Capistranoを使いこなすには、RubyとRakeに関するそれなりの知識が必要だ。 Dave Gardnerのblog記事[PHP Deployment with Capistrano][phpdeploy_capistrano] は、Capistranoに興味のあるPHP開発者への入門記事としておすすめだ。

Ansistrano はデプロイプロセス(デプロイやロールバック) を簡単に管理するための Ansible のロールだ。PHP や Python や Ruby のようなスクリプト言語で書かれたアプリケーションで使える。これは、Capistrano を Ansible に移植したもので、既にたくさんの PHP を利用している会社で使われている。

Deployer はPHPで書かれたデプロイツールで、シンプルかつ機能的だ。 タスクを並列に実行し、アトミックなデプロイを行い、サーバー間の整合性を維持する。 SymfonyやLaravel、Zend Framework、そしてYiiなどで使える、一般的なタスクのレシピが用意されている。 Younes Rafieの記事Easy Deployment of PHP Applications with Deployer は、Deployerを使ってアプリケーションをデプロイするためのよいチュートリアルになっている。

Magallanes もPHPで書かれたツールで、YAMLでのシンプルな設定ができる。 複数サーバーや複数環境、アトミックなデプロイに対応していて、 一般的なツールやフレームワークで使える組み込みのタスクが用意されている。

あわせて読みたい:

サーバーの構成管理

サーバーの構成管理は、大量のサーバーを扱うようになると特に大変なタスクだ。 いろんなツールが用意されているので、こういったインフラの構築を自動化できる。 ツールを使えば、適切なサーバーが適切な構成になっていることを確実にできる。 これらのツールは大規模なクラウドホスティングプロバイダー (Amazon Web Services, Heroku, DigitalOceanなど) にもインスタンスの管理用に統合されていることが多くて、 より簡単にアプリケーションをスケールできるようになっている。

Ansible は、YAMLファイルでインフラを管理するツールだ。 気軽に使い始められるし、複雑で大規模なアプリケーションにも使える。 クラウドのインスタンスを管理するためのAPIも用意されていて、 対応したツールを使えば動的インベントリを通じてインスタンスを管理できる。

Puppet は、独自の言語やファイルタイプを使ってサーバーや構成を管理する。 マスター/クライアント形式で使うこともできるし、「マスターレス」モードで使うこともできる。 マスター/クライアントモードの場合は、所定のインターバルでクライアントが中央サーバーをポーリングして、 新しい構成が見つかったら自分自身を更新する。 マスターレスモードでは、変更内容を各ノードにプッシュする。

Chef はRubyで作られた強力なシステムインテグレーションフレームワークで、 サーバー環境や仮想マシンをまるごと構築できる。 Amazon Web Servicesとも統合されていて、OpsWorksというサービスを通じて利用する。

あわせて読みたい:

継続的インテグレーション

継続的インテグレーションはソフトウェア開発のプラクティスのひとつで、 チームのメンバーが自分たちの作業を頻繁に統合するというものだ。 通常は、各自が少なくとも一日に一度は統合する。一日に何度も統合することもある。 多くのチームが、この方針のおかげで統合時の問題が少なくなるし、 きちんとしたソフトウェアをより素早く開発できるようになると実感している。

– マーティン・ファウラー

PHPで継続的インテグレーションを実践する方法はいろいろある。 Travis CI のおかげで、 ちょっとしたプロジェクトにも簡単に継続的インテグレーションを組み込めるようになった。 Travis CIは継続的インテグレーションのホスティング環境で、オープンソースコミュニティに開放されている。 GitHubと統合されており、PHPを含むさまざまな言語に対応している。 GitHub は、継続的インテグレーションのワークフローとして GitHub Actions を提供している。

あわせて読みたい

Back to Top

仮想化

開発環境だの本番環境だの、いろいろ違う環境でアプリケーションを動かしていると、おかしなバグに出くわしてしまいかねない。 「開発環境だと問題ないのに、本番環境だと動かない」みたいなやつだ。 また、チームで開発しているときに、開発環境のいろんなライブラリのバージョンをきちんと統一しておくのも、面倒だ。

Windowsで開発してLinux(などWindows以外の環境)にデプロイしていたり、チームで開発していたりする場合は、 仮想マシンを使うことを考えるべきだ。 何も難しいことはない。VMwareやVirtualBoxみたいな有名どころだけでなく、 仮想環境を簡単に準備するためのツールも用意されている。

Vagrant

Vagrantを使えば、既知の仮想環境を使って自分用のボックスを作れて、その環境の構成も、たったひとつの設定ファイルだけでできる。 このボックスを手動で設定することもできるし、 PuppetとかChefみたいな「プロビジョニング」ソフトにおまかせすることだってできる。 ベースとなる環境を配布できるようにしておけば、複数の開発環境をまったく同じ状態に構築できる。 複雑怪奇なコマンドを羅列した「環境構築手順書」だとかいうのもいらなくなるってこと。 ベース環境を「破棄」したり作りなおしたりするのもそんなに手間がかからないので、 まっさらな環境を用意するのもお手軽にできる。

Vagrantは、フォルダを作って、ホストと仮想マシンの間でコードを共有する。 つまり、ホストマシンで作ったり編集したりしたコードを、そのまま仮想マシンの中で実行できるっていうことだ。

Docker

Docker - 完全仮想化されたマシンの軽量な代替 - は、すべてが “コンテナ” であるため、そのように呼ばれている。コンテナは、もっとも簡単なケースでは、特定のタスクをひとつだけ(たとえば Webサーバー)を実行するビルディングブロックだ。”イメージ” は、コンテナをビルドするために使うパッケージだ。Docker は、イメージを配布するためのリポジトリを持っている。

典型的な LAMP アプリケーションの場合、コンテナは3つ必要になる。Webサーバー、PHP-FPM プロセス、そして MySQL のコンテナだ。共有フォルダを Vagrant 内で使うのと同じように、アプリケーションのファイルはそのままで、Docker にどこを探せばよいのかを指示することができる。

コンテナはコマンドライン (以下の例を見よう) から作成することもできるし、メンテナンスを簡単にするために docker-compose.yml ファイルをあなたのプロジェクト向けにビルドすることもできる。このファイルでは、何のコンテナを作るかを指定し、それらが互いにどう通信するのかを指示する。

Docker は、複数の Web サイト を開発し、仮想マシン上でそれぞれの Webサイト のインストール環境を分離したい場合に役立つ。だが、すべてを最新の環境にしておくために、追加のディスクスペースや時間は必要ない。Docker は効率的だ: イメージのダウンロードやインストールは高速だし、何度イメージが必要になっても、保存しておく必要があるイメージのコピーはひとつだけだ。また、同じ OS のカーネルを共有するので、必要なメモリは少なくて済む。よって、多くのサーバを同時に実行できるし、停止や起動に掛かる時間も秒単位で済む。サーバマシンが完全に起動するのを待つ必要がないのだ。

例:PHPアプリケーションをDockerで実行する

マシンにDockerをインストールしたら、あと一手間だけで、PHP が使える Apache 環境を用意できる。 次のコマンドは、最新版のPHP入りのApache環境をダウンロードして、 ディレクトリ /path/to/your/php/fileshttp://localhost:8080 で見られるようにするものだ。

docker run -d --name my-php-webserver -p 8080:80 -v /path/to/your/php/files:/var/www/html/ php:apache

これで、コンテナを初期化して実行できる。-d は、バックグラウンドで実行するオプションだ。 コンテナを停止したり、再開したりしたくなったりした場合は、nameに指定した名前を使って docker stop my-php-webserverdocker start my-php-webserver などとするだけだ。 さっきのパラメータを何度も指定する必要はない。

Dockerについてもっと知りたい

ここで紹介したのは、基本的なサーバーをお手軽に実行するためのコマンドだ。 だけど、Docker にできることはまだまだたくさんある(そして、たくさんのビルド済みのイメージが Docker Hub にある)。Docker を最大限活用するために、Docker ユーザーガイド を読み、時間を掛けてそれらに関する用語を学んでいこう。そして、ダウンロードしたコードを安全かどうか確認せずに実行しないように注意しよう。非公式なイメージには最新のセキュリティパッチが当たっていない場合があるからだ。よくわからなければ、オフィシャルのリポジトリ を常に使うようにしよう。

PHPDocker.io サイトは、あなたが選んだ PHP のバージョンや拡張機能を含めた形で、全ての機能を備えた LAMP/LEMP スタックに必要なファイルを全て自動生成してくれる。

Back to Top

キャッシュ

PHP 自体は極めて高速だけど、リモート接続やファイルの読み込みなどが絡むとボトルネックになるかもしれない。 ありがたいことに、いろんなツールを活用すればアプリケーションを高速化できるし、 時間のかかる処理の実行回数を減らすこともできる。

オペコードキャッシュ

PHPファイルを実行するときには、まずそれをオペコード (CPU用の機械語の指示) にコンパイルしなければいけない。 ソースコードに変更がなければ、オペコードも同じものになる。 ということは、PHP ファイルに変更がなければコンパイル処理は CPU リソースの無駄遣いになるということだ。

オペコードキャッシュは、コンパイル済みのオペコードをメモリに格納し、それ以降の呼び出しで再利用することで、 冗長なコンパイルを回避している。よくあるのは、ファイルのシグネチャや更新時刻をチェックして変更の有無を判断する方式だ。

オペコードキャッシュを使えば、アプリケーションの実行速度が相当向上する可能性がある。 PHP 5.5 からは、 Zend OPcache というオペコードキャッシュが標準で組み込まれるようになった。 使っている PHP パッケージやディストリビューションにもよるけど、普通はデフォルトで有効になっていることが多い。 opcache.enable や、 phpinfo() の出力で確認しよう。 古いバージョンのPHPなら、PECL の拡張モジュールが使える。

オペコードキャッシュについて詳しく知りたければ、以下を参照すること。

オブジェクトキャッシュ

コード内の個々のオブジェクトをキャッシュできれば便利なこともある。 持ってくるのにコストがかかるデータや、結果がほとんど変わらないデータベースへの問い合わせなどだ。 オブジェクトキャッシュ用のソフトウェアを使えば、こういったデータをメモリ上に保持でき、 その後のアクセスの高速化につながる。 一度取得したデータをどこかに格納しておいて、それ以降のリクエストではそこから直接取り出す。 そうすればパフォーマンスは劇的に向上するし、データベースサーバーにかかる負荷も減らせる。

バイトコードキャッシュ用ソリューションの多くはカスタムデータも同様にキャッシュできるので、さらに活用できる。 APCu や WinCache は API を提供しており、PHP コードのデータをメモリキャッシュに格納できる。

オブジェクトキャッシュシステムとして最もよく使われているのは APCu と memcached だ。 APCu はオブジェクトキャッシュの選択肢として最適で、 シンプルな API を使ってデータをメモリキャッシュに格納できる。 セットアップも簡単で、すぐに使えるようになる。 APCu の制約のひとつは、インストールしたサーバーと密接に結合してしまうことだ。 一方、Memcached は個別のサービスとしてインストールするものであり、 ネットワーク越しにアクセスできる。つまり、 中央で管理している超高速なストレージにオブジェクトを格納して、 いろんなシステムからそれを取り出せるということだ。

PHPを(Fast-)CGIアプリケーションとしてウェブサーバーで実行するときには、 すべてのPHPプロセスが自身のキャッシュを持つことになる。つまり APCuのデータもワーカープロセス間では共有されないということだ。 こんなときには、かわりにmemcachedを検討すればいい。 こっちはPHPのプロセスには結びついていない。

ネットワーク環境において、アクセス速度の面では APCu のほうが memcached より優れている。 しかし、memcached のほうが、より手軽にスケールアップできる。 複数のサーバーを使う予定がないとか memcached の追加機能が不要だという場合は、 オブジェクトキャッシュに APCu を選ぶのが最適だろう。

APCu を使うロジックの例を示す。

<?php
// 'expensive_data' がキャッシュに保存されているかどうかを調べる
$data = apcu_fetch('expensive_data');
if ($data === false) {
    // データがキャッシュにないときは、コストのかかる操作をして取得する。
    // そして、その結果を保存してあとで使えるようにする。
    apcu_add('expensive_data', $data = get_expensive_data());
}

print_r($data);

PHP 5.5 より前のバージョンでは、APC がオブジェクトキャッシュとバイトコードキャッシュの両方の機能を提供していた。 新しい APCu は、APC のオブジェクトキャッシュ機能を PHP 5.5 以降で使えるようにするものだ。 というのも、PHP 5.5 以降ではバイトコードキャッシュ(OPcache)の機能が標準で組み込まれるようになったからだ。

オブジェクトキャッシュシステムについての参考資料

Back to Top

コードをドキュメント化する

PHPDoc

PHPDocは、非公式ながら、PHPのコードにコメントを書くときの標準として使われているものだ。 さまざまな [タグ] が使える。 すべてのタグの一覧や実際の使用例は、 PHPDoc のマニュアル を参照すること。

いくつかのメソッドを持つクラスのドキュメントを書く例を示そう。

<?php
/**
 * @author A Name <a.name@example.com>
 * @link https://www.phpdoc.org/docs/latest/index.html
 */
class DateTimeHelper
{
    /**
     * @param mixed $anything Anything that we can convert to a \DateTime object
     *
     * @throws \InvalidArgumentException
     *
     * @return \DateTime
     */
    public function dateTimeFromAnything($anything)
    {
        $type = gettype($anything);

        switch ($type) {
            // Some code that tries to return a \DateTime object
        }

        throw new \InvalidArgumentException(
            "Failed Converting param of type '{$type}' to DateTime object"
        );
    }

    /**
     * @param mixed $date Anything that we can convert to a \DateTime object
     *
     * @return void
     */
    public function printISO8601Date($date)
    {
        echo $this->dateTimeFromAnything($date)->format('c');
    }

    /**
     * @param mixed $date Anything that we can convert to a \DateTime object
     */
    public function printRFC2822Date($date)
    {
        echo $this->dateTimeFromAnything($date)->format('r');
    }
}

クラス全体のドキュメントの最初にあるのが @author タグと @link タグだ。 @author タグは、コードの作者を表す。作者が複数いる場合は、何度も繰り返し使ってもかまわない。 @link タグは、そのコードに関連するウェブサイトへのリンクを示すために使う。

クラスの中に目を移すと、最初のメソッドには @param タグが記されている。 これは、このメソッドに渡すパラメータの型と名前そして説明を記述するものだ。 さらに、@return タグと @throws タグも書かれている。 これらはそれぞれ、戻り値の型と、発生する可能性のある例外を記述するものだ。

二番目と三番目のメソッドはほぼ同じだ。まず、最初のメソッドと同様に @param タグが書かれている。 二番目と三番目のメソッドの大きな違いは、 @return タグの有無だ。 @return void は、このメソッドが何も戻さないことを明示している。 一方、歴史的に、 @return void を省略した場合も同じ意味(何も値を戻さない)になる。

Back to Top

情報源

ソースから

フォローすべき人たち

PHPコミュニティの中で注目すべき人を見つけるのは、コミュニティに加わったばかりの人たちにとっては難しいものだ。 PHPコミュニティの主要メンバーの一覧が、ここにまとまっている。

メンタリング

PHP PaaS プロバイダ

フレームワーク

PHP 開発者の多くは、ウェブアプリケーションを作るときに車輪の再発明を避けてフレームワークを使っている。 フレームワークを使えば低レベルの作業の多くを抽象化でき、 便利で使いやすいインターフェイスでよくある作業をこなせる。

別に、フレームワークを使わないといけないというわけじゃない。 ふつうに PHP で書くべき場面だってある。 もしフレームワークを使うのなら、フレームワークはこんな感じに分類できることを知っておこう。

マイクロフレームワークというのは本質的にはラッパーで、 HTTP リクエストを手っ取り早くコールバックに振り分けるだけのものだ。 場合によっては、開発を支援するためのちょっとした追加ライブラリが付属することもある。 データベースの基本的なラッパーなどだ。マイクロフレームワークの主な使い道は、 リモート HTTP サービスの構築である。

多くのフレームワークは、マイクロフレームワークが持つ機能に加えて大量の機能を用意している。 この種のフレームワークのことを、フルスタックフレームワークと呼ぶ。 ORM や認証パッケージなどが含まれることが多い。

コンポーネントフレームワークとは、特定の目的のための専用ライブラリをとりまとめたフレームワークである。 この種のフレームワークのコンポーネントを各種組み合わせて、 マイクロフレームワークやフルスタックフレームワークを作ることもできる。

コンポーネント

先ほど説明したように、「コンポーネント」っていうのは 共有するコードを作ったりそれを配布したりするための手段のひとつだ。 コンポーネントを登録するリポジトリにもいろいろあるけど、中でも有名なのがこのふたつだ。

どちらのリポジトリについてもコマンドラインのツールが存在し、 コンポーネントのインストールやアップグレードを簡単にできる。 詳細は[依存関係の管理][dm]を参照すること。

コンポーネントベースのフレームワークもあれば、フレームワークを持たずにコンポーネントだけを提供するベンダーもある。 こういったプロジェクトが提供するパッケージは、他のパッケージや特定のフレームワークへの依存がほとんどない。

たとえば[FuelPHPのValidationパッケージ][fuelval]を使うときには、 別にFuelPHPフレームワークを使う必要はない。

Laravelの Illuminate コンポーネント も、将来的には Laravel フレームワークからきちんと切り離されるようになるだろう。 現段階では、Laravel フレームワークからうまく切り離せているコンポーネントだけをリストにあげておいた。

その他の資料

チートシート

ベストプラクティス

PHPやウェブ開発コミュニティ界隈のニュース

ニュースレターを購読すれば、新しいライブラリや最新ニュースやイベントなどの通知を受け取れる。 それだけじゃなくて、新旧さまざまな記事も紹介してくれるだろう。

その他のプラットフォームにも同じような週刊ニュースレターがある。 その一部をまとめた

PHP界

動画チュートリアル

YouTubeチャンネル

有償の動画

書籍

PHPに関する本はいっぱい出てるけど、絶望的に古くなってしまっていてもはや正しい情報ではなくなっているものもある。 なぜか、この世に存在すらしていない「PHP 6」向けの本まで出ていたりする。 これが、PHP 5.6の次のメジャーバージョンの名前が「PHP 7」に決まった理由のひとつかもしれない

このセクションでは、PHPでの開発全般でおすすめの書籍を紹介する。 自分の本を載せて欲しいという人は、プルリクエストを出してもらえばレビューのうえで追加する。

無償の書籍

有償の書籍

Back to Top

コミュニティ

PHP のコミュニティは、規模が大きくなるにつれて多様化しており、 どこのコミュニティでも新たな仲間を歓迎している。 近所のユーザーグループに参加したり、ちょっと大きめのカンファレンスに参加したりすれば、 このページで紹介したことよりもずっと多くを学べるだろう。 IRC なら irc.freenode.com に #phpc というチャンネルがあるし、 @phpcMastodon をフォローするといい。 いろんなところに飛び込んで、新しい人と出会って、いろんなことを学んで、友達になるんだ。 StackOverflow にもPHPプログラマー向けのコミュニティがある。

PHP 公式サイトのイベントカレンダー

PHP ユーザーグループ

大都市に住んでいるなら、きっと近場に PHP ユーザーグループがあるはずだ。 近場の PHP ユーザーグループを見つけるには、PHP.ug を使えばいい。 それ以外にも、Meetup.com を使ってもいいし、 お好みのサーチエンジン (たとえば Google とか) で php user group near me みたいに検索してみるとかね。 小さな町なら、もしかしたらユーザーグループがないかもしれない。 だったら、作ればいい!

数々のグループの中でも、特筆すべきものがある。NomadPHPPHPWomen だ。 NomadPHP は、オンラインでのミーティングを毎月2回開催している。 そのミーティングでは、PHP コミュニティの有名人によるプレゼンも行われている。 PHPWomen は、PHP 界の女性をターゲットにしたユーザーグループだったが、今はそれにとどまらない。 より多様性のあるコミュニティを目指そうという人なら、誰でも受け入れている。 PHPWomen は、サポートやメンターシップ、教育などのネットワークを提供している。 そして「女性にやさしい」プロフェッショナルな空気を広めることを目指している。

PHP Wiki のユーザーグループ情報

PHP カンファレンス

世界各国で、地域レベルあるいは国レベルの大規模なカンファレンスが開かれている。 PHP界の有名人が登壇することも多いので、彼らから直接学ぶチャンスだ。

PHP のカンファレンスを探す

ElePHPants

ElePHPant は PHP プロジェクトのマスコットだ。 もともとは 1998 年に Vincent Pontier がデザインしたもの。つまり彼は、世界中の elePHPant たちの父なる神である。 その約10年後には、かわいらしいぬいぐるみもできあがった。 いまやあちこちのPHPカンファレンスでelePHPantが見られるし、自分のPCのそばにelePHPantを置いているPHP開発者も多い。

Vincent Pontierへのインタビュー