PHP初心者、初級者だと初めて聞く存在だと思いますが、PHPには名前空間、トレイトという仕組みも実装されています。
では、今更ではあるのですが、この名前空間とトレイトを同時に使う場合(要は名前空間ごとに、同一名称のトレイトが存在する場合です。実務的な機会があるのかどうかはわかりませんが…)はどうすればいいのかを調査してみました。すると、色々と注意する部分があるようなので、備忘録としてプログラムと解説を置いておきます。
なお、本記事はオブジェクト指向を熟知しているPHP中級者以上を前提としています。また、名前空間、トレイトを知らない人は、参考記事を熟読しておいてください。
実例
【要件定義】各都道府県の果物と野菜をそれぞれ調べたい。名前空間にはそれぞれfruits、vegitableと定義され、その中に各地方の名称となったトレイト(kanto、kinkiなど)があり、その中に各都道府県のメンバ(kantoならgunma()、tochigi()など)が用意されている。
つまり、このような状態です。トレイト名が同じために、名前空間で定義している状態になります。また、トレイト内も同じ名称のメンバが入っていますが、それぞれ用意されている値は異なっています。
namespace fruits
{
trait Kanto
{
public function tochigi(){
$this -> fruits = "いちご";
}
public function gunma(){
$this -> fruits = "りんご";
}
}
}
namespace vegitable
{
trait Kanto
{
public function tochigi(){
$this -> vegi = "かんぴょう";
}
public function gunma(){
$this -> vegi = "キャベツ";
}
}
}
メンバのエイリアス指定
ここで気をつけないといけないのは、トレイト名ならびにメンバ名が同一であるため、このままブラウザで表示しようとするとトレイトの規約違反でコリジョンエラー(同じトレイト名やメンバー名にしていると、何を呼び出していいのかプログラムで判断できない)を起こしてしまいます。そこで、各メンバに対しエイリアスを指定する必要があります。
これを参考にして、以下のようにメンバ名を別名に定義します。
まずは、名前空間の外側を無名の名前空間で囲みます(名前空間の外側だとエラーになります)。
そして別名のメンバを必ずトレイト内のメンバとオーバーライドさせるようにします。
namespace
{
class Crops
{
//public $fruits;
//public $vegi;
//トレイト内で重複するメンバがないようにすること
use fruits\kanto, vegitable\kanto{
fruits\kanto::tochigi as f_tochigi;
vegitable\kanto::tochigi as v_tochigi;
fruits\kanto::gunma as f_gunma;
vegitable\kanto::gunma as v_gunma;
}
//必ずメンバをオーバーライドさせること。独自に定義した別メンバ名だとエラーになる
public function tochigi(){
$this -> f_tochigi();
$this -> v_tochigi();
}
public function gunma(){
$this -> f_gunma();
$this -> v_gunma();
}
}
}
エイリアスの定義
これだと、名前空間が煩雑なので、すっきりさせるためにエイリアスを定義します。
namespace
{
//名前空間のエイリアス定義はクラスの外側で行う
use fruits\kanto as f_kanto;
use vegitable\kanto as v_kanto;
class Crops
{
//public $fruits;
//public $vegi;
//トレイト内で重複するメンバがないようにすること
use f_kanto, v_kanto{
f_kanto::tochigi as f_tochigi;
v_kanto::tochigi as v_tocihi;
f_kanto::gunma as f_gunma;
v_kanto::gunma as v_gunma;
}
//必ずメンバをオーバーライドさせること
public function tochigi(){
$this -> f_tochigi();
$this -> v_tochigi();
}
public function gunma(){
$this -> f_gunma();
$this -> v_gunma();
}
}
}
ゲッタの設定
値を引き出せるようにゲッタをクラス内に設定します。
namespace
{
//名前空間のエイリアス定義はクラスの外側で行う
use fruits\kanto as f_kanto;
use vegitable\kanto as v_kanto;
class Crops
{
public $fruits;
public $vegi;
//トレイト内で重複するメンバがないようにすること
use f_kanto, v_kanto{
f_kanto::tochgi as f_tochigi;
v_kanto::gunma as v_tochigi;
f_kanto::tochigi as f_gunma;
v_kanto::gunma as v_gunma;
}
//必ずメンバをオーバーライドさせること
public function tochigi(){
$this -> f_tochigi();
$this -> v_tochigi();
}
public function gunma(){
$this -> f_gunma();
$this -> v_gunma();
}
//ゲッタの指定(共通でデータを抽出できる)
public function getter(){
$fruits = $this -> fruits;
$vegi = $this -> vegi;
return [$fruits,$vegi];
}
}
}
インスタンスからメンバの値を取り出す
あとはインスタンスを指定して、メンバにアクセスすればデータを取得できます。
$Crops = new Crops(); //インスタンス生成
$Crops -> tochigi();
list($fruits,$vegi) = $Crops -> getter();
echo "【栃木】果物:{$fruits}、野菜:{$vegi}<br>";
$Crops -> gunma();
list($fruits,$vegi) = $Crops -> getter();
echo "【群馬】果物:{$fruits}、野菜:{$vegi}<br>";
ブラウザなどでレビューをかけると
【栃木】果物:いちご、野菜:かんぴょう
【群馬】果物:りんご、野菜:キャベツ
このように表示させることができます。
全体の構造
備忘録として作成した原本プログラムはこのようになっています。トレイトの中はkinkiも入っており、メンバの中身もトレイトごとに違いますが、動作は全く同じです。また、トレイトやメンバの中をどんどん増やしていくことができます。その際には必ず逐一エイリアスも指定して、コリジョンを起こさないようにしてください。
<?php
namespace fruits
{
trait Kanto
{
public function tochigi(){
$this -> fruits = "いちご";
}
public function gunma(){
$this -> fruits = "りんご";
}
}
trait Kinki
{
public function wakayama(){
$this -> fruits = "うめ";
}
public function nara(){
$this -> fruits = "かき";
}
}
}
namespace vegitable
{
trait Kanto
{
public function tochigi(){
$this -> vegi = "かんぴょう";
}
public function gunma(){
$this -> vegi = "キャベツ";
}
}
trait Kinki
{
public function wakayama(){
$this -> vegi = "しょうが";
}
public function nara(){
$this -> vegi = "みょうが";
}
}
}
namespace
{
//エイリアスを指定する場合はクラスの外側で
use fruits\Kanto as f_kanto;
use vegitable\Kanto as v_kanto;
use fruits\Kinki as f_kinki;
use vegitable\Kinki as v_kinki;
class Crops
{
public $fruits;
public $vegi;
//トレイト内で重複するメンバがないようにすること
use f_kanto{
f_kanto::tochigi as f_tochigi;
f_kanto::gunma as f_gunma;
}
use v_kanto{
v_kanto::tochigi as v_tochigi;
v_kanto::gunma as v_gunma;
}
use f_kinki{
f_kinki::wakayama as f_wakayama;
f_kinki::nara as f_nara;
}
use v_kinki{
v_kinki::wakayama as v_wakayama;
v_kinki::nara as v_nara;
}
//必ずメンバをオーバーライドさせること
public function tochigi(){
$this -> f_tochigi();
$this -> v_tochigi();
}
public function gunma(){
$this -> f_gunma();
$this -> v_gunma();
}
public function wakayama(){
$this -> f_wakayama();
$this -> v_wakayama();
}
public function nara(){
$this -> f_nara();
$this -> v_nara();
}
public function getter(){
$fruits = $this -> fruits;
$vegi = $this -> vegi;
return [$fruits,$vegi];
}
}
$Crops = new Crops();
$Crops -> tochigi();
list($fruits,$vegi) = $Crops -> getter();
echo "【栃木】果物:{$fruits}、野菜:{$vegi}<br>";
$Crops -> gunma();
list($fruits,$vegi) = $Crops -> getter();
echo "【群馬】果物:{$fruits}、野菜:{$vegi}<br>";
$Crops -> wakayama();
list($fruits,$vegi) = $Crops -> getter();
echo "【和歌山】果物:{$fruits}、野菜:{$vegi}<br>";
$Crops -> nara();
list($fruits,$vegi) = $Crops -> getter();
echo "【奈良】果物:{$fruits}、野菜:{$vegi}<br>";
}
【栃木】果物:いちご、野菜:かんぴょう
【群馬】果物:りんご、野菜:キャベツ
【和歌山】果物:うめ、野菜:しょうが
【奈良】果物:かき、野菜:みょうが