目次
モダンなPHPに必須の「 名前空間」について理解する
名前空間って、モダンPHPの話には頻繁に登場するワードです。
Laravel6.0みたいなフレームワークのドキュメントを見ても、これでもか・・と言う位でてきますし、前回PHPUnitの使い方の確認でも「名前空間」が登場しました。
ここは、きちんと理解しておかないといけないな・・と考えて、整理してみます。
名前空間は何故必要なのか
PHP: The Right Wayによれば、他の人のコードやライブラリなどを使った時に、メソッド名や変数などの衝突が発生しないようにする仕組みのことみたいです。
以下のように書かれてます。
自分の書くコードにも、名前空間を指定することが大切だ。
そうすれば、誰か他の人があなたのコードを使うときに「これ、他のライブラリと競合しないかな」 と悩まずに済む。
なるほどですね。
モダンなPHPでは、複数のライブラリ等を組み合わせて使うことが多いので、何の工夫もしてないと、名前が衝突するリスクは高くなるというのは理解できます。
それを回避する仕組みとして「名前空間」があります。
なるほどね。
昔、C++のプロジェクトでも、そんなのあったな・・と思い出しました。
それと同じ感じですね。
名前空間が適用されるとどうなるのか
名前空間の定義の仕方は簡単です。
namespace MyApp;
とか
namespace MyApp\SubName;
みたいに、<?phpのすぐ下当たりに書くだけです。
なんですが・・。
名前空間のポイントは、それがどのように解決されるのか?です。
名前解決のルールを詳しく調べるなら、こちらを見るべきです。
でも。
思い切りざっくりまとめるなら以下の2つです。
- 「\(バックスラッシュ)」で始まる場合は、そのまま解釈します。
- それ以外の場合は、現在の名前空間が 先頭に付け加えられます。
名前空間をソースコードで確認するサンプル
文章だけではわかりづらいので、ソースコードで整理します。
前回PHPUnitの動作確認で使ったソースを元にします。
PHPUnitのサイトに掲載されているサンプルなのですが、名前空間は考慮されていないので、PHPCSとかにかけると「クラスには名前空間を使え」と怒られます。
それを修正していこうというわけです。
先にソースをのせて、後でポイントを整理していきます。
まず、テスト対象のソース。
src\Email.php
<?php declare(strict_types=1); namespace MyApp; final class Email { private $email; private function __construct(string $email) { $this->ensureIsValidEmail($email); $this->email = $email; } public static function fromString(string $email): self { return new self($email); } public function __toString(): string { return $this->email; } private function ensureIsValidEmail(string $email): void { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new \InvalidArgumentException( sprintf( '"%s" is not a valid email address', $email ) ); } } }
次はテストクラスです。
tests\EmailTest.php
<?php declare(strict_types=1); namespace MyApp; use PHPUnit\Framework\TestCase; final class EmailTest extends TestCase { public function testCanBeCreatedFromValidEmailAddress(): void { $this->assertInstanceOf( Email::class, Email::fromString('user@example.com') ); } public function testCannotBeCreatedFromInvalidEmailAddress(): void { $this->expectException(\InvalidArgumentException::class); Email::fromString('invalid'); } public function testCanBeUsedAsString(): void { $this->assertEquals( 'user@example.com', Email::fromString('user@example.com') ); } }
ポイントです。
両方共通で、元のソースからの変更点は以下です。
- namespace MyApp; を追加する。
- InvalidArgumentExceptionの前に「\」をつけ「\InvalidArgumentException」にする。
namespace MyApp; で、このソースは「MyApp」という名前空間にはいります。
するとどうなるか?・・というと。
テストケース内で参照している「Email」は、「MyApp\Email」として探しにいかれるということです。
だから、テスト対象のソースとテストクラスのソースの名前空間が違うと面倒なことになるので、ここは同じMyAppにあわせているわけです。
ところが。
テストクラスの中で「InvalidArgumentException」を比較のために使ってます。
テスト対象のソースの中に存在しない場合は、自動的に名前空間の外のものとして扱ってくれるのですが、「InvalidArgumentException」は、Emailクラスの中で使っています。
なので、そのままにしておくと「MyApp\InvalidArgumentException」を探しに行ってしまうので、アサーションエラーになります。
なので、これはMyAppで修飾してはいけない・・ということを知らせるために、「\」をつける必要があるということです。
composerを使ってPHPUnitを入れている場合の注意点
composerを使ってPHPUnitを入れている場合は、上記のようにソースで配慮しただけではうまく行きません。
クラスが見つからないとかのエラーをはいて、すべてのテストがエラーになります。
そう。
オートローダに、名前空間の情報を定義してやらないといけないわけです。
今回の構成は。
pracというフォルダの下に以下のフォルダを作って、ソースを置いてます。
- src : テスト対象のソース(クラス)を置く
- tests : テストクラスを置く
なので、オートローダに、ソースは「src」の下にあって、かつ、定義した名前空間の情報を伝えてやらないといけません。
pracフォルダ直下の、composer.jsonに以下のように書きます。
{ "autoload": { "classmap": [ "src/" ], "psr-4": { "MyApp\\": "src/" } }, "require-dev": { "mockery/mockery": "^1.2", "phpunit/phpunit": "8" } }
autoload{}でくくった部分です。
名前空間で定義したMyAppの情報をオートローダーに教えます。
これで保存して。
composer dump-autoload
を実行して、autoload.phpを更新します。
これが前回の、PHPUnitを使う記事からの変更点です。
実行イメージ
prac直下のvendor以下にインストールしているので。
.\vendor\bin\phpunit
です。
OKですね。
名前空間の使い方や反映の仕方。
そしてComposerを使っている場合に気をつけること。
などなど・・理解できたと思います。
今回はこんなところで。
ではでは。