![](https://habrastorage.org/files/0c0/7fe/7f5/0c07fe7f5b294fecb66c428fca14b406.jpg)
前回の記事では、PHP 7型システムの利点、特に型付き戻り値の新しいサポートについて説明しました。 これ自体は、コードのサポートに役立つだけでなく、PHPを大きく前進させます。
これまで、型についてはクラスとインターフェイスのみに関連して説明してきました。 長年、私たちはそれら(および配列)しか使用できませんでした。 ただし、PHP 7では、
int
、
string
、
float
などのスカラー値を使用する機能も追加されています。
しかし、ちょっと待ってください。 PHPでは、ほとんどのプリミティブは交換可能です。
int
を必要とする関数に
"123"
を渡し、PHPを信頼できます。PHPはすべてを「正しく」実行します。 それでは、スカラー型は何のためにありますか
戻り値の型と同様に、スカラーはコードの明快さを高め、初期段階でより多くのエラーをキャッチすることを可能にします。 これにより、コードの信頼性が向上します。
PHP 7では、パラメーターまたは戻り値で指定できる4つの新しい型
int
、
float
、
string
および
bool
追加されています。 既存の
array
、
callable
、クラス、およびインターフェースに参加します。 新しい機能で前の例を補完しましょう:
interface AddressInterface { public function getStreet() : string; public function getCity() : string; public function getState() : string; public function getZip() : string; }
class EmptyAddress implements AddressInterface { public function getStreet() : string { return ''; } public function getCity() : string { return ''; } public function getState() : string { return ''; } public function getZip() : string { return ''; } }
class Address implements AddressInterface { protected $street; protected $city; protected $state; protected $zip; public function __construct(string $street, string $city, string $state, string $zip) { $this->street = $street; $this->city = $city; $this->state = $state; $this->zip = $zip; } public function getStreet() : string { return $this->street; } public function getCity() : string { return $this->city; } public function getState() : string { return $this->state; } public function getZip() : string { return $this->zip; } }
class Employee { protected $id; protected $address; public function __construct(int $id, AddressInterface $address) { $this->id = $id; $this->address = $address; } public function getId() : int { return $this->id; } public function getAddress() : AddressInterface { return $this->address; } }
class EmployeeRepository { private $data = []; public function __construct() { $this->data[123] = new Employee(123, new Address('123 Main St.', 'Chicago', 'IL', '60614')); $this->data[456] = new Employee(456, new Address('45 Hull St', 'Boston', 'MA', '02113')); } public function findById(int $id) : Employee { if (!isset($this->data[$id])) { throw new InvalidArgumentException('No such Employee: ' . $id); } return $this->data[$id]; } }
$r = new EmployeeRepository(); try { print $r->findById(123)->getAddress()->getStreet() . PHP_EOL; print $r->findById(789)->getAddress()->getStreet() . PHP_EOL; } catch (InvalidArgumentException $e) { print $e->getMessage() . PHP_EOL; }
変更は、文字列または数値を返す一部のメソッドに影響しました。 このような単純なステップを踏んでも、すでにいくつかの利点があります。
- 現在、
Address
クラスのさまざまなフィールドは単なる文字列であることがわかっています。 以前は、文字列であり、Street
オブジェクト(通り番号、名前、アパート番号で構成される)やデータベースの都市IDではないと想定できました。 もちろん、これらの両方は特定の状況では完全に合理的ですが、この記事では考慮されていません。 - 従業員IDは整数であることが知られています。 多くの企業では、従業員IDは英数字の文字列、または場合によっては先行ゼロです。 以前に調べる方法はありませんでしたが、現在は他の解釈は除外されています。
- 安全性もプラスです。
findById()
内で$id
がint
値であることを保証します。 もともとユーザー入力から来た場合でも、整数になります。 つまり、たとえばSQLインジェクションを含めることはできません。 ユーザー入力を操作する際の型チェックへの依存は、攻撃に対する唯一の防御ではなく、最高の防御でさえありませんが、別の保護層です。
最初の2つの利点は、ドキュメントでは冗長なようです。 コードに適切なdocブロックがある場合、
Address
は文字列で構成され、従業員IDは整数であることを既に知っていますか? 本当です。 ただし、誰もがコードのドキュメント化に夢中になっているわけではないか、単に更新を忘れています。 言語自体からの「アクティブな」情報を使用すると、非同期ではない場合にPHPが例外をスローするため、非同期がないことを確認できます。
明示的なタイピングは、より強力なツールへの扉も開きます。 プログラム-PHP自体またはサードパーティの分析ツール-は、受信した情報に基づいてソースコードを調べ、考えられるエラーまたは最適化の機会を見つけます。
たとえば、次のコードが正しくなく、実行しなくても常にクラッシュすることを確認できます。
function loadUser(int $id) : User { return new User($id); }
function findPostsForUser(int $uid) : array { // Obviously something more robust. return [new Post(), new Post()]; }
$u = loadUser(123); $posts = findPostsForUser($u);
loadUser()
常に
User
型のオブジェクトを返し、
findPostsForUser()
常に整数を返します。このコードをtrueにする方法はありません。 これは、機能とそれらの使用方法を見ることによってのみ言うことができます。 これは、IDEが事前に認識しており、起動前にエラーについて警告できることを意味します。 また、IDEは私たちよりも多くの部分を追跡できるため、コードを実行することなく、自分が気づくよりも多くのエラーを警告することもできます!
このプロセスは「静的分析」と呼ばれ、プログラムを評価してエラーを見つけて修正するための非常に強力な方法です。 これは、PHPの標準の弱い型付けによって妨げられています。 文字列を期待する関数に整数を渡すか、整数を期待する関数に文字列を渡すと、これはすべて機能し続け、PHPはいつものようにプリミティブを互いにサイレントに変換します。 私たちやユーティリティによる静的分析の有用性が低い理由。
強い型付けの導入は、新しいPHP 7型システムの基礎です。
デフォルトでは、スカラー型(パラメーターまたは戻り値)を操作する場合、PHPは可能なすべてのことを行って、値を期待される値にします。 つまり、
string
を期待する関数に
int
を渡すと問題なく動作し、
bool
を期待する
int
渡すと、整数0または1が得られます。これは、言語から予想される自然な動作です。
string
を期待する関数に渡されたオブジェクトには
__toString()
呼び出され、戻り値でも同じことが起こります。
1つの例外は、文字列を予想される
int
または
float
渡すことです。 伝統的に、関数が
int
/
float
値を取得して
string
が渡されると、PHPは文字列を最初の非数値文字に切り捨てるだけで、データが失われます。 スカラー型の場合、文字列が実際に数値の場合、パラメーターは
E_NOTICE
しますが、値が切り捨てられると、
E_NOTICE
が
E_NOTICE
ます。 すべてが機能しますが、現時点では、この状況は状態の軽微なエラーと見なされます。
自動変換は、ほとんどすべての入力データが文字列として(データベースまたはhttp要求から)送信される場合に意味がありますが、同時に型チェックの有用性を制限します。 このため、PHP 7では
strict_types
モードが推奨されています。 その使用はやや微妙で明白ではありませんが、十分な理解があれば、開発者は非常に強力なツールを手に入れることができます。
ストロングタイピングを有効にするには、次のようにファイルの先頭に広告を追加します。
declare(strict_types=1);
コードを実行する前に、この宣言をファイルの最初の行にする必要があります。 ファイルにあるロジックのみに影響し、このファイルの呼び出しと戻り値のみに影響します。
strict_types
仕組みを理解するために、コードをいくつかの個別のファイルに分割して、少し
strict_types
てみましょう。
// EmployeeRespository.php class EmployeeRepository { private $data = []; public function __construct() { $this->data[123] = new Employee(123, new Address('123 Main St.', 'Chicago', 'IL', '60614')); $this->data[456] = new Employee(456, new Address('45 Hull St', 'Boston', 'MA', 02113)); } public function findById(int $id) : Employee { if (!isset($this->data[$id])) { throw new InvalidArgumentException('No such Employee: ' . $id); } return $this->data[$id]; } }
// index.php $r = new EmployeeRepository(); try { $employee_id = get_from_request_query('employee_id'); print $r->findById($employee_id)->getAddress()->getStreet() . PHP_EOL; } catch (InvalidArgumentException $e) { print $e->getMessage() . PHP_EOL; }
何が保証されますか? 最も重要なことは、おそらく
findById()
内の
$id
が文字列ではなく
int
であることを知っていることです。
E_NOTICE
がスローされた場合でも、
$employee_id
が何であっても、
$id
は常に
int
になります。
strict_type
宣言を
EmployeeRepository.php
追加すると、何も起こりません。
findById()
内にも
int
があります。
ただし、
index.php
で厳密な型指定モードの使用を宣言し、同じ場所で
findById()
呼び出しを使用すると、
$employee_id
が文字列、
float
または
int
以外の場合
TypeError
が
TypeError
ます。
つまり、メソッドと関数は、適切に宣言されたファイルで呼び出された場合にのみ 、厳密な型指定の影響を受けます。 プロジェクトの他の部分のコードは影響を受けません。 ライブラリ開発者が厳密な型指定の原則に従うことを望んでも、コードだけに影響するわけではありません。
それで何がそんなに良いのですか? 賢明な読者は、私が最後のコードサンプルで非常に陰湿なバグを作成したことに気付くかもしれません。
EntityRespository
のコンストラクターを調べて、偽データを作成します。 2番目のエントリは、文字列ではなくintとして郵便番号を渡します。 ほとんどの場合、違いはありません。 ただし、米国では、北東部の郵便番号は先頭のゼロから始まります。
int
先頭がゼロの場合、PHPは8進数、つまり8の基本数として解釈します。
弱い型付けでは、これはボストンでは住所が郵便番号02113ではなく、10に基づいて1099である整数02113として解釈されることを意味します。これはPHPが郵便番号「1099」に変換するものです。 私を信じて、ボストンの人々はこれを嫌います。 その結果、このようなエラーはデータベースのどこかで、または検証中にコードで6桁の数字を入力するように強制されたときにキャッチできますが、その時点では1099年からどこに来たのかわからないでしょう。
代わりに、
EntityRespository.php
を厳格モードに切り替え、すぐに型の不一致をキャッチします。 コードを実行すると、非常に具体的なエラーが発生し、それらの場所を探して正確な行がわかります。 そして、優れたユーティリティ(またはIDE)は、起動前でもそれらをキャッチできます!
厳密な型指定モードが自動変換を許可する唯一の場所は、
int
から
float
です。 この
int
定義により浮動小数点値でもあるため、これは安全です(オーバーフローの結果がある非常に大きな値または小さな値の場合を除く)。
いつ強い型付けを使用すべきですか? 私の答えは簡単です。できるだけ頻繁に。 スカラータイピング、戻り値の型、およびストリクトモードは、デバッグとコードサポートに大きな利点を提供します。 これらはすべて最大限に使用する必要があり、その結果、より信頼性が高く、サポートされたバグの多いコードが存在することになります。
この規則の例外は、データベースや着信HTTP要求などの入力データチャネルと直接やり取りするコードです。 これらの場合、インターネットの閲覧は文字列を連結するより複雑な方法であるため、入力は常に文字列になります。 httpまたはデータベースを手動で操作する場合、データを文字列から必要な型に変換できますが、通常これは多くの不要な作業に変換されます。 SQLフィールドの型が
int
である場合、(IDEを知らなくても)数値文字列が常に返されることを知っているので、
int
を予期する関数に渡すときに安全性とデータの損失を確信できます。
これは、この例では、
EmployeeRespository
、
Employee
、
Address
、
AddressInterface
および
EmptyAddress
厳密モードが有効になっている必要があることを意味します。
index.php
は着信リクエストと(
get_from_request_query()
呼び出しを介して)対話するため、PHPを信頼して型を手動で処理するよりも信頼する方がおそらく簡単です。 要求からの未定義の値が型付き関数に転送されるとすぐに、強く型付けされた作業に切り替えることができます。
PHP 7への移行が、PHP 5の場合よりもはるかに速くなることを期待しましょう。本当に価値があります。 主な理由の1つは、まさに拡張型システムです。これにより、コードを相互に文書化し、相互およびツールに対してより理解しやすくすることができます。 その結果、「うーん、どうすればいいのかわからない」瞬間がかつてないほど少なくなります。