日本語版 PHP The Right Wayの必要そうなところだけ抜粋してまとめ直して自分なりの注釈を入れました。2年前ぐらいに調べたものなので若干今と違うかもしれないのでお気をつけてお読みください。
PHP Right WayではPHPで開発するにあたりの言語の仕様的なものと、周辺の技術やテクニックについて解説している。
![http://www.phptherightway.com/images/banners/btn1-120x90.png][1]
[1]: http://www.phptherightway.com/images/banners/btn1-120x90.png
PHPを始める
- 新しいバージョンを使う
- バージョンが上がる毎にPHPは進化
- 特に4から5、5.2から5.3、5.3から5.4
- 7は出てるけど積極的に使うならちょっと早いかも 2016-05
- ビルドインサーバを使って開発環境の検証を手軽に実行
ビルドインサーバ起動
$ echo "<?php phpinfo();" > index.php
$ php -S localhost:8000
ブラウザでアクセス
http://localhost:8000/
コーディングスタイル
PHPをどういったルールで記述する?
Fuelphp core部分の規約 => http://fuelphp.jp/docs/1.8/general/coding_standards.html
FuelPHP Coreの規約例
PHP タグの閉じ方
PHP のコードだけしか含まないファイルは常に PHP の閉じタグ (?>) を省略します。
これは見つけにくい「死の白い画面」を避けるためです。
行の末尾
行の末尾は Unix 形式の LF にします。
ファイル名
すべてのファイル名は、すべて小文字にしなければなりません。例外はありません。
エンコーディング
ファイルは UTF-8 で保存し、BOM は使用しません。
既存のコーディングスタイルのルール
- PSR-0, 1, 2, 4
- PEARのコーディング規約
- Zendのコーディング規約
- Symfonyのコーディング規約 etc...
PHP_CodeSniffer
これらの標準のどれかひとつに準拠しているかどうかを確認できる。
PHP言語仕様
バージョンを追う毎に成長
- オブジェクト指向モデル (2004)
- 無名関数、名前空間 (2009)
- トレイト(コード再利用の仕組み) (2012)
- ジェネレータ(yield) (2013)
- メモリ管理、オブジェクト指向の強化 (2014)
コマンドライン・インタフェース(CLI)
- PHPをコマンドラインから実行する
- 簡単な処理の確認やバッチ処理を実行できる
PHP info表示
$ php -i
PHP実行
<?php
if ( 2 != $argc ) {
exit(1);
}
$name = $argv[1];
echo "Hello, $name\n";
?>
$ php hoge.php yuya
Hello, yuya
依存処理
Composer
予め設定ファイル(composer.json)に書き込まれていた依存関係のあるソースコードを自動的に設置してくれるパッケージ管理ツール。CakePHPやFuelPHPの次期バージョンのインストールにはComposerを利用してインストールを自動的に行う。
使い方
$ curl -s https://getcomposer.org/installer | php
$ ./composer.phar install
呼出し方
<?php
require 'vendor/autoload.php';
// 処理開始
?>
PEAR
- 古くからあるパッケージ管理ツール
- 詳しくは -> PEAR
Debian, Ubuntu -> php-pear
$ pear install foo
コーディング
基本
比較演算子
緩い比較と厳格な比較の違い
<?php
$a = 5; // 5はinteger型
var_dump($a == 5); // 値の比較。trueを返す
var_dump($a == '5'); // 値の比較(型は無視)。trueを返す
var_dump($a === 5); // 型と値の比較(integer vs. integer)。trueを返す
var_dump($a === '5'); // 型と値の比較(integer vs. string)。falseを返す
/**
* 厳格な比較
*/
if (strpos('testing', 'test')) { // 'test' は 0 番目の位置にあり、これはboolean型の'false'と見なされる
// コード...
}
// vs
if (strpos('testing', 'test') !== false) { // 厳格な比較が行われるので、これは成立する(0 !== false)
// コード...
}
?>
条件式
If 文
elseは必須ではない
<?php
function test($a)
{
if ($a) {
return true;
} else {
return false;
}
}
// vs
function test($a)
{
if ($a) {
return true;
}
return false; // 別にelseがなくたっていいよね
}
?>
Switch 文
- ifとelseifを延々と書き連ねる必要がない
- Switch文は値を比較するだけで、型は比較しない('==')
- マッチする条件が見つかるまでcaseを順に評価
- マッチするものがない場合、defaultが定義されていればそれを使う
- 'break' がなければそのまま次のcaseに進み、breakかreturnに達するまで止まらない
- 関数の中で 'return' を使うときは 'break' は不要だ。その時点で関数を終了する
<?php
$answer = test(2); // 'case 2'のコードと'case 3'のコードを両方実行する
function test($a)
{
switch ($a) {
case 1:
// コード...
break; // breakでswitch文を抜ける
case 2:
// コード... // breakしてないので'case 3'の評価に進む
case 3:
// コード...
return $result; // 関数の中で'return'すると、ここで関数を抜ける
default:
// コード...
return $error;
}
}
?>
グローバル名前空間
- 名前空間を使っていると組み込みの関数が実行できなくなる
- 関数名の前にバックスラッシュをつける
<?php
namespace phptherightway;
function fopen()
{
$file = \fopen(); // 組み込みの関数と同じ名前の関数を定義しているので、
// 組み込みのfopen関数を実行するには"\"を追加する
}
function array()
{
$iterator = new \ArrayIterator(); // ArrayIteratorは組み込みのクラスである。名前にバックスラッシュをつけずに使うと、
// phptherightway名前空間でこの名前を探してしまう。
}
?>
文字列
連結
- 代入演算子でつなげるよりも連結演算子を使う
- 連結演算子で次の行に移るときはインデント
<?php
$a = 'Multi-line example'; // 連結代入演算子 (.=)
$a .= "\n";
$a .= 'of what not to do';
// vs
$a = 'Multi-line example' // 連結演算子 (.)
. "\n" // 改行してインデント
. 'of what to do';
?>
文字列型
シングルクォート
- 「リテラル文字列」を表す
- 特殊文字をパースしたり変数を展開したりはしない。
<?php
echo 'This is my string, look at how pretty it is.'; // 単純な文字列で、パースする必要がない
/**
* 出力は、
*
* This is my string, look at how pretty it is.
*/
?>
ダブルクォート
- 変数を展開する
- 特殊文字もパースする
- \n を改行変換 \t タブ変換
<?php
echo 'phptherightway is ' . $adjective . '.' // シングルクォートを使った例。複数の要素を連結し、
. "\n" // 変数の埋め込みやエスケープを使っている
. 'I love learning' . $code . '!';
// vs
echo "phptherightway is $adjective.\n I love learning $code!" // ダブルクォートを使えば、別々の要素に分けずに
// ひとまとめにできる
?>
変数を含める
<?php
$juice = 'プラム';
echo "$juice ジュース大好き"; // 出力:プラム ジュース大好き
?>
変数名を波括弧で囲む
<?php
$juice = 'plum';
echo "I drank some juice made of $juices"; // $juiceがパースできない
// vs
$juice = 'plum';
echo "I drank some juice made of {$juice}s"; // これで、変数は$juiceだとわかる
/**
* 配列などの場合も波括弧で囲む
*/
$juice = array('apple', 'orange', 'plum');
echo "I drank some juice made of {$juice[1]}s"; // これで、$juice[1]がパースできる
?>
Nowdoc 構文
- 5.3で導入された構文でシングルクォートと同じような動きをする
- 複数行にまたがる文字列を、連結演算子なしで表すのに適する
<?php
$str = <<<'EOD' // 最初は <<<
Example of string
spanning multiple lines
using nowdoc syntax.
$a does not parse.
EOD; // 終了文字列はそれ単体でひとつの行に書く。また行頭に書かないといけない
/**
* 出力は、
*
* Example of string
* spanning multiple lines
* using nowdoc syntax.
* $a does not parse.
*/
?>
ヒアドキュメント構文
- 内部的にはダブルクォートと同じような動き
- 複数行にまたがる文字列を、連結演算子なしで表すのに適する
<?php
$a = 'Variables';
$str = <<<EOD // 最初は <<<
Example of string
spanning multiple lines
using heredoc syntax.
$a are parsed.
EOD; // 終了文字列はそれ単体でひとつの行に書く。また行頭に書かないといけない
/**
* 出力は、
*
* Example of string
* spanning multiple lines
* using heredoc syntax.
* Variables are parsed.
*/
?>
どっちが速い?
シングルクォートで囲んだほうが、ダブルクォートで囲むよりもちょっとだけ速くなるという迷信が、蔓延している。 でもこれは、間違いだ。
文字列と任意の型の値を連結したり、ダブルクォートで囲んだ文字列に変数を埋め込んだりしたときの結果は、場合によって異なる。
三項演算子
<?php
$a = 5;
echo ($a == 5) ? 'yay' : 'nay';
?>
```
読みやすさを無視して、とにかく行数を減らそうとだけ考えてしまうと、こんな羽目になる。
```php
<?php
echo ($a) ? ($a == 5) ? 'yay' : 'nay' : ($b == 10) ? 'excessive' : ':('; // やりすぎ。もはや読めない :-(
?>
三項演算子で値を ‘return’ する
<?php
$a = 5;
echo ($a == 5) ? return true : return false; // この書きかただとエラーになる
// vs
$a = 5;
return ($a == 5) ? 'yay' : 'nope'; // この書きかたなら 'yay' を返す
?>
boolean値なら三項演算子は使わなくても良い
<?php
$a = 3;
return ($a == 3) ? true : false; // $a == 3 なら true、そうでなければ false を返す
// vs
$a = 3;
return $a == 3; // これでも同じこと。$a == 3 なら true、そうでなければ false を返す
?>
括弧を活用すればコードの可読性を挙げられる
<?php
$a = 3;
return ($a == 3) ? "yay" : "nope"; // $a == 3 なら yay、そうでなければ nope を返す
// vs
$a = 3;
return $a == 3 ? "yay" : "nope"; // $a == 3 なら yay、そうでなければ nope を返す
?>
<?php
return ($a == 3 && $b == 4) && $c == 5;
?>
<?php
return ($a != 3 && $b != 4) || $c == 5;
?>
変数の宣言
変数宣言のメモリ消費
<?php
$about = 'A very long string of text'; // メモリを2MB消費する
echo $about;
// vs
echo 'A very long string of text'; // メモリの消費は1MBだけ
?>
http://ja.phptherightway.com/pages/The-Basics.html
デザインパターン
なるべく既存のパターンを流用を行ったほうが良い (フレームワーク、書き方 etc...)
-
コードを管理しやすい
-
他の開発者にもわかってもらいやすい
-
フレームワークの導入
- 上位レベルのコードのフレームワークの流儀に従うことになる
- フレームワーク自体のパターンが適用されている
- フレームワーク上のコードは自分次第
-
フレームワーク未導入
- アプリケーションのタイプや規模に応じて、最適なパターンを見つける必要あり
UTF-8
- ようやくPHP5.6からUTF-8がデフォルトになりました
依存性の注入
- 依存性の注入とはコンポーネントに依存関係を渡せる仕組み
- コンストラクタで注入
- メソッド呼び出し
- プロパティを設定
- PHPばかにする人もいるけどあたり理解してないからじゃない?
基本的な概念
<?php
namespace Database;
class Database
{
protected $adapter;
public function __construct()
{
$this->adapter = new MySqlAdapter;
}
}
class MysqlAdapter {}
?>
依存関係を外部からDatabaseクラスにデータを入れれるようになる
<?php
namespace Database;
class Database
{
protected $adapter;
public function __construct(MySqlAdapter $adapter)
{
$this->adapter = $adapter;
}
}
class MysqlAdapter {}
?>
あわせて読みたい
- Learning about Dependency Injection and PHP
- What is Dependency Injection?
- Dependency Injection: An analogy
- Dependency Injection: Huh?
- Dependency Injection as a tool for testing
データベース
- PHP5.5でmysql_*関数は廃止
- PDOかmysqli関数に変更必須
- バージョンが上がる、DBの種類が変わる毎に書き方が変わるのはナンセンス
- PDO使え
PDO
- PDO はデータベースとの接続を抽象化するライブラリ
- 複数のDB操作を同じインターフェイスで扱える
<?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']);
?>
絶対こんなコードを書くなよ! (SQLインジェクション)
<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$pdo->query("SELECT name FROM users WHERE id = " . $_GET['id']); // <-- ダメ、ゼッタイ!
?>
データベースとのやりとり
DBとのやりとりを直接Viewに描写しないほうがベター
<ul>
<?php
foreach ($db->query('SELECT * FROM table') as $row) {
echo "<li>".$row['field1']." - ".$row['field1']."</li>";
}
?>
</ul>
- あらゆる意味でよろしくない
- デバッグしづらい
- テストもしづらい
- 読みづらい
- 何も制限をかけていない
- 大量のフィールドを出力してしまう
DBの処理と、画面表示ロジックに分割する何となく MVCっぽく修正する。
foo.php
<?php
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');
// モデルを読み込む
include 'models/FooModel.php';
// インスタンスを作る
$fooList = new FooModel($db);
// ビューを表示する
include 'views/foo-list.php';
?>
models/FooModel.php
<?php
class Foo()
{
protected $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function getAllFoos() {
return $this->db->query('SELECT * FROM table');
}
}
?>
views/foo-list.php
<?php foreach ($fooList as $row): ?>
<?= $row['field1'] ?> - <?= $row['field1'] ?>
<?php endforeach ?>
テンプレート
メリット
- 画面に表示する内容をアプリケーションから切り離せる
- テンプレートはフォーマット済みのコンテンツを表示するだけ
- サーバー側のコード、クライアント側のコードの作業分担
- 大規模なコードブロックを、小さめの再利用しやすいパーツに分割
- サイトのヘッダやフッタをテンプレートし、インクルードするように
- ライブラリによってはよりセキュリティを確保できる
- ユーザーが作るコンテンツを自動的にエスケープする機能
- サンドボックス機能
- あらかじめ許可された変数と関数しか利用できないようにする仕組み
シンプルなテンプレート
<?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は例外処理も使えるPG言語
- 多少のエラーであれば処理は実行される
PHP: 単純にnoticeレベルのエラーになるだけで処理を続行
$ php -a
php > echo $foo;
Notice: Undefined variable: foo in php shell code on line 1
Python: 未定義の変数を参照しようとすると例外発生
$ python
>>> print foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined
エラーの深刻度
- error (E_ERROR) 実行時の致命的な問題
- notice (E_NOTICE) 問題を起こす可能性がある
- warning (E_WARNING) 致命的でない問題
- strict (E_STRICT) 将来的な互換性の提案
noticeは表示しない場合
error_reporting(E_ERROR | E_WARNING);
インラインでのエラー抑制
echo @$foo['hoge'];
エラー自体の隠蔽処理が行われているため@付の書式は処理速度が落ちる。
結構シャレにならないぐらい速度が落ちるため、使わない方向で考えること。
代替手段
echo isset($foo['hoge']) ? $foo['hoge'] : '';
fopenなどの処理のエラー処理、ファイルが存在するか確認ぐらいはするが、確認後に読み込もうとした時に削除されたなどが起きるかもしれないが、現状fopenのエラーが起きたかどうかぐらいしか手がない。
if ( $FP = fopen() ) {
// 処理
}
例外処理
try - catch - finally
<?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
{
// 例外が発生してもしなくても、ここは必ず実行される
}
?>
セキュリティ
ウェブアプリケーションのセキュリティ
既知のセキュリティ問題とその対策をまとめてくれている
パスワードのハッシュ処理
- DBにパスワードを保存する際にはハッシュ化すること
- ハッシュ化 → 基には戻せない
PHP5.5からのパスワードハッシュ関数 PHPがサポートしているアルゴリズムの中で最強 BCrypt を利用している。
こんな感じで使う
<?php
require 'password.php';
$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);
if (password_verify('bad-password', $passwordHash)) {
// パスワードが一致した
} else {
// パスワードが一致しなかった
}
?>
データのフィルタリング
-
外部から渡される入力は絶対に信用してはいけない
-
外部入力の例
- $_GET, $_POST, $_SERVER, fopen('php://input', 'r') etc...
- アップロードしたファイル
- セッションデータ
- クッキー
- 外部API
-
適切にフレームワークなどを設定している自動的にフィルタリングしている場合もある
サニタイズ
外部の入力から危険な文字を取り除く
バリデーション
外部の入力が期待通りであるかどうか確かめる(メールアドレス等)
設定ファイル
DBやパスワードのソルト値などを外部ファイルとして持つ場合
- アプリ側から直接アクセスできたり、ファイルシステムで外部から見れる状況にしないこと
- 設定ファイルの拡張子をphpにして実行されたとしてもブラウザ経由で見れないようにする
- パーミッションちゃんとしておくこと
Register Globals
5.4からregister_globalsは使えなくなった。
有効にすると$_POST['foo']が$fooでアクセスできるようになるため。5.3以前の人はregister_globalsをoffにしておくこと。
エラーレポート
起こりうる全てのエラーを表示する場合(php.ini)
display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On
エラーを見せないようにする場合
display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On
テスト
- 自動化されたテストを書くのが良い習慣
- 変更・追加された場合にきちんと動くことを確認できる
テスト駆動開発
ユニットテスト
- 関数やクラスが期待通りに動いたいることを開発中に確かめる
- 入出力の値がチェックできればロジックは正しいと確認できる
- 依存性注入の仕組みを利用すればモックやスタブを使えば依存ライブラリが正しく使われていることを確認できる
- 逐次
var_dump()
はダサい
ユニットテストの選択肢
機能テスト
- アプリケーションを使う観点での自動テスト
- 実際のデータを利用してユーザのシミュレートを行う
機能テスト用のツール
振る舞い駆動開発(BDD)
仮想化
Vagrant
補助ツール
- Rove: PHPなどのオプション込みで、一般的なVagrantビルドを作ってくれるサービス。プロビジョニングにはChefを使う。
- Puphpet: PHPの開発用の仮想マシンを作ってくれる、シンプルなGUI。 PHPに特化している。 ローカルVM以外に、クラウドサービスにデプロイすることもできる。 プロビジョニングにはPuppetを使う。
- Protobox: vagrant をラップしたウェブ GUI で、ウェブ開発向けの仮想マシンを用意してくれる。 シンプルな YAML ドキュメントを使って、仮想マシン上にインストールするすべてものを制御できる。
- Phansible: 使いやすいインターフェイスで、PHP プロジェクト用の Ansible Playbook を生成してくれる。
Docker
docker run -d --name my-php-webserver -p 8080:80 -v /path/to/your/php/files:/var/www/html/ php:apache
キャッシュ
オペコードキャッシュ
PHPを実行する際には裏側で、オペコードにコンパイルしてから実行している。PHPファイルに変更がなければオペコードも同じものになる。コンパイル後のファイルをキャッシュし冗長な処理を回避する。
- PHP5.5以降にはオペコードキャッシュが標準で組み込まれている
オブジェクトキャッシュ
オブジェクトをメモリキャッシュに格納する。何度も使われるオブジェクト(DB接続など)をメモリ上に保持することに寄ってパフォーマンスを上げる。
情報源
PHPDoc
非公式ながらPHPのコードのコメントの標準
http://www.phpdoc.org/docs/latest/index.html
<?php
/**
* @author A Name <a.name@example.com>
* @link http://www.phpdoc.org/docs/latest/index.html
* @package helper
*/
class DateTimeHelper
{
/**
* @param mixed $anything Anything that we can convert to a \DateTime object
*
* @return \DateTime
* @throws \InvalidArgumentException
*/
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');
}
}
?>
日本語版 PHP The Right Wayの必要そうなところだけ抜粋して変更を加えた。