Edited at

未経験からweb系プログラマーになるための独学履歴~遅延静的束縛と名前空間~


このコーナーについて

私は根っからの文系なのでプログラミングや情報技術についてはITパスポート程度の知識しかなく、現在は全くの独学でコードや言語等のスキルの勉強はチュートリアル等でコードを書いて成果物を作りながら学んでいけるが、それらの過程で出てくる用語や技術についてはイマイチ学びきれない。

なので、折に触れて書籍及びQlita記事等のネットサーフィンで学んだ座学的知識をアウトプットすることがこのコーナーの目的である。


今回

パーフェクトPHPから万部遅延静的束縛と名前空間


遅延静的束縛


遅延静的束縛


<?php

class Foo {

public function helloGateway() {
// self::hello();
static::hello();
// 遅延静的束縛を行う。
}

public static function hello() {
echo __CLASS__, 'hello!', PHP_EOL;
}
}

class Bar extends Foo {
public static function hello() {
echo __CLASS__, 'hello!', PHP_EOL;
}
}

$bar = new Bar();
$bar->helloGateway();

?>



出力結果

Barhello!


self::を使った場合、self::が使われたメソッドが存在するクラス名を参照しそのクラス名を明示する。しかし、これは静的にメソッドやプロパティを参照するので、例えば今回のように継承した子クラスで親クラスのhelloGatewayメソッドを呼び出すと、selfが参照するクラス名は子クラスのBarではなく、親クラスFooのままになってしまう。したがって、このままでは子クラスでオーバーライドしたメソッドを正しく親クラスから呼び出すことができていない。

よって、ここでコールされたhelloメソッドは親クラスであるFooのものとなってしまう。

そこで、遅延静的束縛を行う。staticは非転送コールによって呼ばれたメソッド・クラス名を保持する機能を持つのでselfの部分をstaticにすることで、子クラスから親クラスのメソッドを参照しても、子クラス名を参照しながらhelloメソッドを呼び出してくれる。よって、コールされるhelloメソッドは子クラスであるBarのものになる。


転送・非転送コールとは

非転送コールはクラス名またはオブジェクト名を明示した呼び出し方である。



  $bar->helloGateway();

$Foo::hello();

これに対して転送コールはメソッド内でクラス名またはオブジェクト名を代名詞的に呼び出すやり方である。




self:: // 必ず記載されたクラス名を参照し、そのクラスのメソッド及びプロパティをコールする。
parent:: // 記載されたクラスの親クラスを参照し、そのクラスのメソッド及びプロパティをコールする。
static:: // 非転送コールによって呼ばれたメソッド及びクラス名を保持し、保持したクラスのメソッド及びプロパティをコールする。

これらはメソッド内で使用され各々クラス名を参照し代名詞のように記載することできる



名前空間

名前空間はディレクトリの考え方を利用し、関数名やクラス名の衝突を防ぎ、機能の参照をわかりやすくするためにクラスや関数の使える名前の集合を限定する機能である。

例えばチームで開発する場合、メンバー各々がコードを書いてそれぞれコードを読み込んだ時に、関数やクラスの名前が衝突することでエラーが出てしまう。

そういったことを解決するための機能。

名前空間がディレクトリでいうフォルダと考え、その中に名前空間を定義したファイルが存在し、さらにそのファイルの中関数やクラスなどが存在すると考えればいい、実際似たような書き方で表す。

一つのファイルに複数の名前空間を定義することは不可能ではないがリーダブルではないので、原則として、一つのファイルにつき一つの名前空間の関係性を保つことが望ましい。

複数のファイルで同じ名前空間を使うことはできるが、同じ名前空間で同名の関数諸々は使えない。

ちなみに名前空間の影響を受けるのはクラス・関数・constで定義した定数の3つである。

以下簡単な例。


tanaka.php


<?php

namespace tanaka;// 名前空間の宣言、必ず最初に行い、命名はファイル名と同じが望ましい

function getIntroduce() {
return '私は田中です。';

}

?>



yamada.php


<?php

namespace yamada;

function getIntroduce() {
return '私は山田です';
}

?>



call.php


<?php

require_once 'tanaka.php';
require_once 'yamada.php';

echo getIntroduce(); //そのまま関数を呼び出そうとすると、同名の関数が存在してしまうためエラーとなる。またファイル側に名前空間の定義がないと同様にエラーとなる。
echo sato\getIntroduce(); // 名前空間に属する関数を呼び出すときの書き方。satoという名前空間のgetIntroduce関数が呼び出される

?>


このように書くと同じメソッド名を用いてもエラーにはならない。

名前空間を定義した場合、名前空間の外に命令文を記述することはできない。




<?php

namespace yamada {
function getIntroduce() {
return "私は山田です。";
}

}

echo "Hello";

?>



Fatal error: No code may exist outside of namespace {}


サブ名前空間

名前空間をさらに階層化させる手法である。

例を見ると以下の通り。


例(kaneda.php)


<?php
namespace japan\tokyo\shibuya\kaneda;

function getIntroduce() {
return "私は金田です。";
}

?>


記述は名前空間\サブ名前空間\サブ名前空間という形になる。

ディレクトリで言えば

 japan/

┣ tokyo/
┣ shibuya/
┣ kaneda/
┣ kaneda.php

ということになる。

当然呼び出し行う関数も


call.php


<?php
require_once 'kaneda.php';

echo japan\tokyo\shibuya\kaneda\getIntroduce();

?>


と記述することになる。

ちなみに呼び出しを行う側の名前空間の記述はルート相対(完全修飾形式)及びドキュメント相対(修飾形式)パスで記述することになる。

同一階層の名前空間の参照は後者、別階層の名前空間は前者の記述を行う。

また、名前空間を定義しないもしくはnamespace宣言時に名前空間名を記載しなかった場合はグローバル扱いとなる。


useキーワード

useキーワードを使用すると、ある名前空間を短くするか、全くの同名のものを作ることができる。

前者がエイリアス、後者がインポートと呼ばれている。

useを使う場合名前空間の記述は必ず完全修飾形式で記述をする。


名前空間のエイリアスの作成


use 名前空間のすべて、または一部 as 別名;


例えば以下のコードを元に考える。


kaneda.php


<?php
namespace japan\tokyo\shibuya\kaneda;

class introduce {
public function getIntroduce() {
return "私は金田です。";
}

}

?>


これをコールする関数をエイリアスを使って書いてみる。


call.php

<?php 

require_once "kaneda.php"

use japan\tokyo\shibuya as area;

$a = new area\kaneda\introduce;
echo $a->getIntroduce();

?>


useキーワードを使った際に、名前空間の一部をareaと置き換え、インスタンス化の時にそれを使って名前空間を記述する。つまり。area=japan\tokyo\shibuya


クラスのインポート


use 完全修飾形式のクラス名;



kaneda.php


<?php
namespace japan\tokyo\shibuya\kaneda;

class introduce {
public function getIntroduce() {
return "私は金田です。";
}

}

?>



call.php

 <?php 

require_once "kaneda.php";

use japan\tokyo\shibuya\kaneda\introduce;

$b = new introduce();
echo $b->getIntroduce();

?>


useをインポートで使うと、先程までのように関数を呼び出すときに逐一名前空間の記述をしなくても良くなっているのがわかる。

つまり、インポートの場合はuseの時点で名前空間の定義は済んでいて、別ファイルで定義した関数をcall.phpで読み込んでいるということになる。

よって、エイリアスとは違い普通にインスタンス化を行う。

また、インポートはまとめて行うこともできる


クラスをまとめてインポートする例


use 完全修飾形式のクラス名, 完全修飾形式のクラス名, ......;

use 完全修飾形式 {クラス名,クラス名,......}; // PHP7.0.0より



クラスをまとめてインポートする際の記述例


<?php

use europa\uk\england\ {
sport,bird,flower
}
;
?>

// 上記をさらに細かくインポートして見てみる。

<?php
use europa\uk\england\ {
london\sport, // london.phpのsportクラス
birmingham\bird, // birmingham.phpのbirdクラス
glasgow\flower // glasgow.phpのflowerクラス

};
?>


インポートはクラスだけではなく、関数や定数もインポートできる。


関数をインポートする


use function 完全修飾形式の関数名;



関数をインポートする際の記述例(otyanomizu.php)


<?php
namespace japan\tokyo\otyanomizu;

function getUniversityName() {
return "Meiji University";
}

?>



call.php


<?php
require_once "otyanomizu.php";

use function japan\tokyo\otyanomizu\getUniversityName;

echo getUniversityName();

?>



定数をインポートする


use const 完全修飾形式の関数名;



関数をインポートする際の記述例(ueno.php)


<?php
namespace japan\tokyo\ueno;

const STATION = "Ueno Station";

?>



call.php


<?php
require_once "ueno.php";

use const japan\tokyo\ueno\STATION;

echo STATION;

?>



インポート・エイリアスの有効範囲

同じファイルにおいて別の名前空間でインポートないしエイリアスを作成し、別の名前空間で利用することができるのか?


例(kanagawa.php)


<?php

namespace asia\japan\kanto\kanagawa;

class city {
public function getCity() {
return 'yokohama';
}
}

class bird {
public function getBird() {
return 'kamome';
}
}

?>

```php:call.php

<?php

namespace a {
require_once 'kangawa.php';

use asia\japan\kanto;

use asia\japan\kanto\kanagawa\bird as tori;
}

namespace b {
require_once "kanagawa.php";

$a = new kanto\kanagawa\city;
echo $a=>getCity();

$b = new tori;
echo $b->getBird();
}

?>


上記の処理結果はエラーとなる。

つまり、同じファイルにおいても別の名前空間で定義したインポートないしエイリアスは、別の名前空間では有効ではないということになる。

では今度は同じ名前空間を別ファイルで利用した場合、インポート・エイリアスは利用することはできるのか?

検証するために、コールする関数は先程と同じにして、コール関数を2つ書いてみる。


call.php


<?php

// コールする関数が定義してあるファイルとは、別の名前空間を作り、そこにコールする関数で定義した名前空間のエイリアスを作成とインポートを行う。

namespace call;

require_once "kanagawa.php";

use asia\japan\kanto;

use asia\japan\kanto\kanagwa\bird as tori;

?>



call2.php


<?php

// call.phpで作った名前空間を利用して、クラスをインポートしてみる。

namespace call;

require_once "kanagawa.php";

$a-> new kanto\kanagawa\city;
echo $a->getCity();

$b-> new tori;
echo $b->getBird();

?>


結果は、エラーとなる。

つまり、複数のファイルで同じ名前空間を使えても、別ファイルで定義したエイリアスやインポートは有効ではないということになる。

同様に今度は別ファイルかつさらに別の名前空間で作成したエイリアスとインポートは有効なのだろうかということを確認してみる。

call2.phpを以下のように変更し、検証する。


call2.php


<?php

// call.phpとは別の名前空間をさらに作る。

namespace call2;

// call.phpでエイリアスとインポートの定義をしているのでそれをコールする関数のファイルとともにrequire_onceでロードしてみる。

require_once "kanagawa.php";
require_once "call.php";

$a = new kanto\kanagawa\city;
echo $a->getCity();

$b = new tori;
echo $b->getBird();

?>


結果はエラーとなる。

つまり、インポートとエイリアスは同じファイルかつ同じ名前空間でしか有効ではないということになる。

名前空間が複数のファイルで同じ名前空間を利用できるのとは違ってくるので忘れないようにしよう。


現在の名前空間を表示する


call.php


<?php

namespace call;

echo __NAMAESPACE__;

?>


名前空間の階層が複雑になってしまい、今弄っているファイルが名前空間のどの位置にあるのかということを確認したい場合に使う。


参考

パーフェクトPHP

【PHP超入門】名前空間(namespace・use)について