名前空間を活用した【PHP超入門】名前空間(namespace・use)についてが網羅的で良かったので、ふだんから考へてることをまとめたポエムです。結論はないです。
はじめに
オブジェクト指向的な設計の観点からはユーティリティクラス(静的メソッドのみを持つクラス)は、均衡を崩すものとして忌み嫌はれる存在であることは意識しておいた方が良いです。関数とユーティリティクラスは禁止 - 株式会社アークシステム(infoARK)のような議論についてはしっかりと咀嚼する価値があります。
…………が、そんな高尚な設計の話をしたり設計の批判をするのは簡単ですが現実世界では動くコードが正義です。オブジェクト指向の大先生にならなきゃプログラムを書いちゃいけないなんてことは断じてありません。あなたの目的を達成するためにプログラミング言語は存在するのだから、利用できる機能はなんでも利用しませう。
そんなわけで、この記事では大手を振って函数とユーティリティクラスの礼賛をします。それと記事の題名について最初に個人的な見解を伸べると、私は静的メソッドを利用することの方が比較的多いです。
名前空間について
はじめにPHPマニュアルの名前空間をひととほり読むことをおすすめします。読みましたね?
函数呼び出しっぽいものの分類
函数とかメソッドとかいろいろあるので、呼び出しの書式ごとに分類を整理します。一般的にはクラスに属しないものを「函数」、クラスに属するものを「メソッド」と呼びます。でもPHPで定義するための文法上のキーワードはメソッドふくめてぜんぶfunction
です。 へんなの。
コード | 説明 |
---|---|
hoge() |
自身と同じ名前空間*(なければトップレベル名前空間)*のhoge 函数 |
\fuga() |
トップレベル名前空間のfuga 函数 |
foo\bar\buz() |
foo\bar 名前空間に属するbuz 函数 |
$obj->cool() |
インスタンスオブジェクト$obj のcool メソッド |
foo\Bar::hot |
foo 名前空間に属するBar クラスのhot 静的メソッド |
$piyo() |
callableなローカル変数 $piyo の呼び出し |
callableな値$piyo
の実態は無名関数だったり、配列だったり、文字列だったりします。詳しくは可変関数を呼んでください。
$piyo = 'hoge'; // hoge() と同じ
$piyo = '\fuga'; // \fuga() と同じ
$piyo = 'foo\bar\buz' // foo\bar\buz() と同じ
$piyo = [$obj, "cool"]; // $obj->cool() と同じ
$piyo = ['foo\Bar', 'hot']; // foo\Bar::hot() と同じ
$piyo = function () use ($v) { ... }; //無名函数(クロージャ)
名前空間付き函数について
【PHP超入門】名前空間(namespace・use)についてに概ね網羅されてますので、よくお読みください。
クラスの静的メソッド
静的メソッドとは、インスタンス化($obj = new HogeClass
)しなくても利用できるメソッドのことです。
final class HogeUtil
{
/**
* "foo"を$n回繰り返した文字列を返す
*
* @param int $n
* @return string
*/
public static function foo($n)
{
return str_repeat("foo", $n);
}
}
一切インスタンス化させずに静的メソッドを利用するために用意するクラスのことを「ユーティリティクラス」と呼びます。
オートローディング
ところで、PHPにはクラスのオートローディングと呼ばれる仕組みがあります。モダンなPHPでは、いちいちファイルごとにinclude_once
とかrequire_once
と書かずに読み込む方法がベストプラクティスとされてます。
モダンPHPアンチパターン #ファイルごとにrequire/includeを書くとかにちょろっと書いたんですが、基本はComposerをちゃんと設定すれば、それだけでクラスを自動ロード化することができます。 (ちょっと小声でステマをさせていただくと、WEB+DB PRESS Vol.91に名前空間とクラスについてしっかりと説明した記事を書いたので、よろしければ買って読んでね!)
オートローディングの限界
さて、クラスは自動ロードできるのですが、名前空間に直接定義された函数はできません。事前に全て読み込んでおくか、必要に応じてinclude_once
またはrequire_once
させるしかありません。
名前空間付き函数をComposerのライブラリとして提供するには、composer.json
的には{"autoload": {"files": ["src/functions.php"]}}
のような定義で事前に読み込んでおくことになります。
ちなみに私がふだん仕事で携ってるプロジェクトの共通ロジック部分には1286クラス、7421メソッドありました。こんなの全部を事前ロードはあんまりやりたくない。
grepの敗北
use
を利用すると長い名前空間・クラス名・函数名を省略することが簡単にでき、とてもべんりです… 便利ですが、たとへば函数の名前を変更しようと思ったとき、あるいは引数の仕様を変更しようと思ったときにuse
を使ってると少々厄介なことになります。
人間、数多くのコードを書いたり息を吸ったり吐いたりしてると、この「メソッドいまいちだったな」なんて気分になることは割とよくあります。名前空間なしのクラスの静的メソッドHoge::fuga()
のメソッドをリネームするのは簡単です。grep
などのテキスト検索ツールでgrep -r Hoge::fuga( .
なんて検索してやれば利用箇所を洗い出して置換していくことができます。極端な話、sed
でリファクタリングができます。
世のなかにはPHP-Parserのようなライブラリも多くあるので、こういったものをうまく組み合せればもっとうまくリファクタリングができるはずなんですが…
静的メソッドの良くないところと対処
バッドノウハウです。わかってやってます(断言)
- 継承したクラスから呼び出すことができる
- 継承されると、単純にgrepできない
- → ユーティリティクラスを
final class Hoge
のように定義すれば継承されないことを保証できる
- インスタンス化して呼び出すことができる
- 静的メソッドとして定義しても
$obj->fuga()
として呼び出すことが可能 - → コンストラクタを
private function __construct()
で定義してインスタンス化を封じる
- 静的メソッドとして定義しても
traitってどうなの?
トレイト(trait
)はPHP 5.4で導入された仕組みで、継承とは別の軸でクラスに機能を組込むことが可能です。拙作のBaguettePHP/objectsystemはtrait
を活用してオブジェクトを機能的に拡張することができます1。
さて、実はtrait
に定義した静的メソッドは継承しなくても呼び出すことができます。またクラスにuse
して利用できるようになるので、RubyのMathモジュールをinclude
するような使用感2になって良いのではないか、と期待したのですが……。use
しちゃったらgrep
がめんどくさくなる問題は何も解決しないので私はやめました。
trait
はうまく利用すれば効果的で素晴しい機能なのですが、下手な作りにすると非常にコードが追ひにくくなって「ユーティリティクラスにした方がはるかに読みやすい」事態にも容易になりえるので、現実とはままならないものです。
特に落ちはなく結び
世の中のPHPerはどうやってリファクタリングを断行してるんだろう… ちゃんと静的解析できればリネームに限らずスマートにリファクタリングできるはずなんだけど、手元のツールできちんとそれが実現できそうなのはPhpStormしかなかった。
水は低きに流れる。みんなgrep
が好きなので静的メソッドに流れる。あんまり健康な状態じゃないのでテクノロジーで反抗して勝利していきたい。
裏技
AspectMockと名前空間を悪用活用すると、PHPの標準函数をモック化できるぞ! たぶん!
脚注
-
private/protectedなプロパティを外部から読み込み可能にする - Qiitaとかインスパイヤされて掲示板を作りたくなった(3) - Qiitaあたりで利用方法を解説してます ヾ(〃><)ノ゙ ↩
-
ちなみにRubyの
Math
メソッドのような仕様の ↩