32
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PHP の PSR-0 と PSR-4 で autoloader の違いと名前空間の基礎

Last updated at Posted at 2019-10-05

この 1 年(2019 年)で更新・作成された Qiita 記事を「php "psr-4" "psr-0" 違い」でググってもタイトルから判断できる記事がヒットしなかったので、自分のググラビリティとして。

TL; DR (今北産業)

PHP において PSRPSR-0PSR-4 は、どちらもオートロード(外部ファイルの自動読み込み)の仕様を制定したもの。

  • PSR-0
  • PSR-4
    • 初期設定項目は多いが、名前空間/ファイル/ディレクトリの構成に柔軟性がある
    • PSR-4: Autoloader @ PHP-FIG.org
  • PHP がデフォルトで持っているオートロード機能(spl_autoload_register )を使い「俺様オートローダー」を実装する際に準拠したい仕様。はたまた、オートローダーを使いたい場合に、統一した使い方を知りたい場合に参考にすべき仕様

TS; DR (kwsk)

PSR-0

名前空間と実在のディレクトリ構成を同じにする仕様。

名前空間[?]に、実在するディレクトリをルート・ディレクトリとして割り当て、それ以下のディレクトリ名が同じになる仕様です。2014/10/21 より推奨されていませんDeprecated になりました)。

  • 名前空間の例:\hoge\fuga
  • 割り当て例: "hoge\fuga\": "src/"(名前空間 \hoge\fuga./src/ を割り当て)
    • ファイルの設置先例:src/hoge/fuga/myClassPiyo.php
    • クラス利用時の例:\hoge\fuga\myClassPiyo
書き方(composer.json)
{
    "autoload": {
        "psr-0": {
            "hoge\\fuga\\": "src/"
        }
    }
}
PSR-0の利用イメージ
<?php
namespace MyVendorName\MyPackageName;

// オートローダーの読み込み
require_once(__DIR__ . '../vendor/autoload.php');

use hoge\fuga\myClassPiyo;   // 設置先: src/hoge/fuga/myClassPiyo.php
use hoge\fuga\myClassMogera; // 設置先: src/hoge/fuga/myClassMogera.php

$obj1 = new myClassPiyo();
$obj2 = new myClassMogera();

PSR-4

名前空間が実在のディレクトリ構成に依存しない仕様

名前空間に検索パスを紐付けしておき、名前空間からパスを、クラス名からファイル名を判断する仕様です。

  • 名前空間の例:\hoge\fuga\
    • 紐付けられた検索ディレクトリのパスの例
      • ./src/lib/foo/
      • ./vendor/someone/some_package/bar/
  • ファイルの設置先の例:
    • ./src/lib/foo/myClassPiyo.php
    • ./vendor/someone/some_package/bar/myClassMogera.php
      設置先の違いと、後述の利用時(use)の呼び出し方に注目。
書き方(composer.json)
{
    "autoload": {
        "psr-4": {
            "hoge\\fuga\\": [
                "src/lib/foo",
                "vendor/someone/some_package/bar"
            ]
        }
    }
}
PSR-4の利用イメージ
<?php
namespace MyVendorName\MyPackageName;

// オートローダーの読み込み
require_once(__DIR__ . '../vendor/autoload.php');

use hoge\fuga\myClassPiyo;   // 設置先(以下のどちらか):
                             //   src/lib/foo/myClassPiyo.php
                             //   vendor/someone/some_package/bar/myClassPiyo.php
use hoge\fuga\myClassMogera; // 設置先(以下のどちらか):
                             //   src/lib/foo/myClassMogera.php
                             //   vendor/someone/some_package/bar/myClassMogera.php

$obj1 = new myClassPiyo();
$obj2 = new myClassMogera();

リファレンス

参考文献/あわせて読みたい


名前空間とは

TL; DR (今北産業)

名前空間とは、、、

  1. 変数にスコープ[?]があるように、スクリプトに俺様スコープを指定して名前を付けること。
  2. 名前にカテゴリや階層的な情報を \(バックスラッシュ)で持たせた仕様。
  3. 名前が長ったらしくなっても、使う際に標準機能で短くすることができる。

関数名にカテゴリを含めた場合と、名前空間付きにした場合の違い:

  • foo_bar_piyo();
    • \foo\bar\piyo(); piyo()\foo\bar に属する)
  • hoge_fuga_piyo();
    • \hoge\fuga\piyo(); piyo()\hoge\fuga に属する)
  • hoge_fuga_moge();
    • \hoge\fuga\moge(); moge()\hoge\fuga に属する)

長ったらしい名前を使う時に短くする(エイリアスを張る)例:

  • use function \hoge\fuga\piyo as piyopiyo;
    • \hoge\fuga\piyo()piyopiyo() として利用可能になる。

つまり、同名の関数やクラスがある場合でも「名前空間」を使うことでお互いに影響を与えないようにすることができます。


TS; DR 〜名前空間を完全に理解した気になるコマケーこと〜

autoloader の恩恵を存分に受けるためには「名前空間」の理解が必要です。使う場合は、namespace と宣言するので、直訳で「名前空間」と呼ばれます。

しかし「名前空間」と言われても、ラノベ脳がすぎるので亜空間とか空間転送とか宇宙空間のようなものを想像してしまい、たいそうなものである印象を受けます。

PHP の「名前空間」は、どちらかと言うと公園や施設などにある「なんとかスペース」と言った空間の方の「空間」です。

喫煙所を「タバコ・スペース」と言うようなもので、その場合、喫煙できるスコープは「タバコ・スペース」のみに限定されます。

スコープとは

「スコープ」とは「ターゲットを限定するために参照できる範囲を絞ったもの」です。

ゴルゴ(13)さんが、ターゲットに狙いを定める時のスコープのイメージです。スコープ外のものは参照できません。

つまり「変数にスコープがある」とは「変数を参照するには範囲がある」ということです。例えば、異なる関数内で定義された変数は、「同じ変数名」でも異なるものになります。

同じ変数名 $hoge でもスコープが違うと別のもの
function foo(): string
{
    $hoge = 'one';
    $fuga = $hoge; // 上記 $hoge と同じスコープなので 'one' が代入される。

    return $fuga; // Output: one
}

function bar(): string
{
    $hoge = 'uno';
    $fuga = $hoge; // 上記 $hoge と同じスコープなので 'uno' が代入される。

    return $fuga; // Output: uno
}

echo foo() . PHP_EOL; // one
echo bar() . PHP_EOL; // uno

// Note: PHP_EOL は、PHP が動いている OS の改行コードが入っている PHP 定数

このように、同じ関数内であれば変数を参照できます。しかし、違う関数内では同じ変数名でも参照するターゲットが異るのです。

「... ... まぁ ... そうだよね」と感じると思います。

アホみたいな話しですが、実は、この範囲を業界用語で「スコープ」と呼んでいるだけです。響きがかっこいいし。

空間に閉じ込めてみる

さて「名前空間」を掘り下げる前に、もう 1 つアホみたいに基本的な仕様を再確認する必要があります。

それは、同じファイル内には同じ関数名は宣言できないということです。

<?php

function foo(){ return 'hoge'; }
function foo(){ return 'fuga'; }

echo foo() . PHP_EOL;

// PHP Fatal error:  Cannot redeclare foo() (previously declared in /workspace/Main.php:3) in /workspace/Main.php on line 4

このように、Cannot redeclare エラーが発生します。

「... ... まぁ ... そうだよね」と感じると思います。

実は、これもスコープが関連しています。

同じスコープ内、つまり同じ空間内に同じ関数名が存在しようとするからです。上記の場合は、「グローバル空間」と呼ばれる汎用でデフォルトの空間に存在しようとしています。

つまり、「ターゲットの男を狙え」と言われてスコープを覗いたら「おっさんが二人映り込んでた」ようなもので、どっちを狙っていいのか分からないのと同じ状態です。

この場合、「ターゲットの髭面ひげづらの男を狙え」と限定されると狙いが定まります。

関数名fooを、各々違うnamespace名で閉じ込める
<?php

namespace target\beard
{
    function foo(){ return 'hoge'; }
}

namespace target\glasses
{
    function foo(){ return 'fuga'; }
}

namespace enemy
{
    function foo(){ return 'piyo'; }
}

namespace golgo13
{
    echo \target\beard\foo() . PHP_EOL;   // hoge
    echo \target\glasses\foo() . PHP_EOL; // fuga
    echo \enemy\foo() . PHP_EOL;          // piyo
}

ネームスペースがディレクトリ構成のように見えて来たのではないでしょうか。実際、似たようなものです。

名前空間は仮想ディレクトリのようなもの

ここで注意すべき点は「名前空間の区切りは "\"(, バックスラッシュ)であること」です。

名前空間を見聞きするも遠慮してしまう理由に "\"(, バックスラッシュ)があります。普段使わないだけでなく、Windows ユーザの場合 "¥" (, 円マーク)と表示されることが多いためです。

古い Windows ユーザーで、海外の人とのやりとりの FAX や、英語の本を読むとディレクトリの指定に円マークが使われていないことに違和感を持った人も多いのではないでしょうか。受けとったデータでは円マークで表示されるのに。

macOS ユーザでも、日本語キーボードの場合、バックスラッシュと円マークは同じキー(バックスラッシュは alt+円マーク)であるため探しづらいなどあります。これらの理由は、シフト JIS にはバックスラッシュが存在しなかったので円マークで代用していた名残りです。

時代は変わり、UTF-8 がデファクト・スタンダードになりました。UTF-8 では、バックスラッシュと円マークは明確に別物となり、各々文字コードを持っています。そのため、フォントによっては同じ円マークに見えても違うコードだったりするので Windows ユーザーは特に注意しましょう。

次に注意すべき点は「名前にバックスラッシュが含まれると、名前空間を持っていると判断される」ことです。

例えば、以下のように別の名前空間の関数を利用する場合、行頭の \(バックスラッシュ)に注意します。記載を省略すると、現在の名前空間に属すると判断されて動きません。

<?php

namespace target\beard
{
    function foo(){ return 'hoge'; }
}

namespace golgo13
{
    echo \target\beard\foo() . PHP_EOL;  // \target\beard の空間にある foo() を探すので動く。
    echo target\beard\foo() . PHP_EOL;   // \golgo13\target\beard の空間にある foo() を探す。
                                         // 存在しない空間なのでエラーになる。
}

// PHP Fatal error:  Uncaught Error: Call to undefined function golgo13\target\beard\foo() in /workspace/Main.php:11

イメージとして file_get_contents('hoge.txt'); だと現在のディレクトリ、file_get_contents('/fuga/piyo/hoge.txt'); とした場合は /fuga/piyo/ のディレクトリを覗きにいくようなものに近いと思います(Win ユーザの場合 c:¥¥ がないのでピンと来ないかもしれませんが)。

名前空間が「仮想的なディレクトリのようなもの」として、以下のシンプルな例をご覧ください。

<?php

namespace target\beard
{
    function foo(){ return 'hoge'; }
}

namespace target\glasses
{
    function foo(){ return 'fuga'; }
}

namespace target
{
    function foo()
    {
       // 各々、頭に \ がないので、この階層(target)より下の階層の
       // 空間の foo() を使う
       $result = beard\foo() . ' ' . glasses\foo();

       return $result;
    }
}

namespace golgo13
{
    echo \target\foo() . PHP_EOL;   // hoge fuga
}

\target 名前空間と言う「なんちゃって仮想ディレクトリ」直下に foo() を定義して、その下の階層 beardglasses にある各々の foo() を関数内で使っているのがわかりますでしょうか。

1 ファイル 1 空間がベスト

<?php

namespace target\beard
{
    function foo(){ return 'hoge'; }
}

namespace target\glasses
{
    function foo(){ return 'fuga'; }
}

上記は、1 つのファイルに複数の名前空間を定義しています。しかし、このような 1 つのファイルに複数の名前空間を持たせることは推奨されていません動いちゃうけど

おそらく理由は単純で以下の 2 つが主にあると思います。

  1. 同じファイルなら関数名の方を変えた方が可読性は高い。
  2. 1 ファイル 1 名前空間で定義した方がメンテナンス性が高い。

先の例をファイルに分けると以下のようになります。namespace ... { ... }{} で括らなくても namespace ...;(セミコロン付き)にするとファイル全体が対象になります

この時、namespace \target\beard; ではなく、namespace target\beard; と頭に \ が付かないことに注意します。

beared.php
<?php

namespace target\beard; // ファイル全体が同じ名前空間になる。 \target\beard; ではないことに注意。

function foo(){ return 'hoge'; }
glasses.php
<?php

namespace target\glasses; // ファイル全体が同じ名前空間になる

function foo(){ return 'fuga'; }
enemy.php
<?php

namespace enemy; // ファイル全体が同じ名前空間になる

function foo(){ return 'piyo'; }

各々を別ファイルにしたら、メインのスクリプトで include して利用します。

main.php
<?php

namespace MyName\MyApp;

include('beared.php');  // namespace -> \target\beard 
include('glasses.php'); // namespace -> \target\glasses
include('enemy.php');   // namespace -> \enemy

echo \target\beard\foo() . PHP_EOL;   // hoge
echo \target\glasses\foo() . PHP_EOL; // fuga
echo \enemy\foo() . PHP_EOL;          // piyo

include が増えると目が遊ぶので、1 つにまとめる。(composer は、この機能を標準で持っています。)

include.php
<?php

include('beared.php');
include('glasses.php');
include('enemy.php');

そして、最終的には以下のようになります。

main.php
<?php

namespace MyName\MyApp; // このファイルの名前空間を MyName\MyApp にする

include('include.php');

echo \target\beard\foo() . PHP_EOL;   // hoge
echo \target\glasses\foo() . PHP_EOL; // fuga
echo \enemy\foo() . PHP_EOL;          // piyo

このように、恐れずに言えば名前空間付きであれば同じ関数名・クラス名であっても気がねなく include/require できると言うことです。

ここまでなら、「えっ? target_beard_foo()\target\beard\foo() と名前を変えているのと一緒じゃん」と思われるかもしれません。

正解です

target_beard_foo() を「検索した時に同じ名前が並ぶと確認する箇所が増えるので、名前を見ただけでわかるようにする一元管理的な手法」だとすると、\target\beard\foo() は「名前は短く保ち、仮想ディレクトリでカテゴライズして整理する手法が名前空間を使った方法」と言えます。

しかし、target_beard_foo() のように、ファイル名に全てを含める手法は長い目で見ると問題を孕んでいます。

例えば、アプリや俺様ライブラリ(短編コード集)が肥大・増えてくると関数名が長くなるのでエイリアス(ショートカット)を作りたくなるなど、です。

名前空間のエイリアス

少し冗長ですが、先ほどの例にエイリアスを貼って見たいと思います。\target\beard\foo()\target\glasses\foo() が長いので短くする例です。

そこから、よくあると思われる流れを追って、問題を見たいと思います。

main.php
<?php

namespace MyName\MyApp;

include('include.php'); // include 集をインポート

// エイリアス作成
function louisYamadaLIII()
{
    return \target\beard\foo();
}

// エイリアス作成
function higuchiKun()
{
    return \target\glasses\foo();
}

echo louisYamadaLIII() . PHP_EOL; // hoge
echo higuchiKun() . PHP_EOL;      // fuga

さらに、function の定義が邪魔なので hige_danshaku.php にまとめます。ここでのポイントは main.php と同じ名前空間にしていることです。

hige_danshaku.php
<?php

namespace MyName\MyApp; // main.php と同じ名前空間

function louisYamadaLIII()
{
    return \target\beard\foo();
}

function higuchiKun()
{
    return \target\glasses\foo();
}

include.php に新規作成した hige_danshaku.php を追加します。

include.php
<?php

include('beared.php');
include('glasses.php');
include('enemy.php');
include('hige_danshaku.php');

すると、メインのコードは以下のようにスッキリします。

main.php
<?php

namespace MyName\MyApp;

include('include.php');

echo louisYamadaLIII() . PHP_EOL; // hoge
echo higuchiKun() . PHP_EOL;      // fuga

しかし、スッキリはしたものの、\target\...\foo() を前提とした別の人がこのコードを読んだ場合、louisYamadaLIII()higuchiKun() の動きを知るために include.php をほじくって行かないと行けません。

実は PHP の名前空間にはエイリアスを貼る機能があります

use function <オリジナル> as <エイリアス>

これを利用すると hige_danshaku.php(俺様エイリアス・ファイル)は必要ない、かつ他のユーザーはエイリアスの元が何であったか知ることができます。

main.php
<?php

namespace MyName\MyApp;

include('include.php'); // 'hige_danshaku.php' はこの中にいらない

// エイリアスを貼る
use function \target\beard\foo as louisYamadaLIII;
use function \target\glasses\foo as higuchiKun;

echo louisYamadaLIII() . PHP_EOL;   // hoge
echo higuchiKun() . PHP_EOL;        // fuga

名前空間を短くするだけでなく、別名で使いたい場合にもエイリアス(use function)を使うメリットが出てきます。特に、一般的なパッケージを使う場合など、「このスクリプト内では別名を割り当てている」ということが一見して他の人に伝わるからです。

名前空間を使うシチュエーション

おそらく「クラス」を普段から活用している人は名前空間の感覚は掴みやすいかもしれません。

関数名がぶつかる場合は、クラスのメソッド(クラス関数)にしてしまえば良いからです。つまり、関数をクラスと言うスコープに閉じ込めてしまうことができるので、その感覚に近いからです。

しかし、むしろ「名前空間」は、オブジェク糖嗜好の甘いものが苦手なおとこの中の漢の関羽のような人に向いているかもしれません。つまり「名前空間」は関数好きのための情報整理術の 1 つと言えるかもしれません。

基本的に、アプリがシンプルな場合は無理して名前空間を使う必要はないと思います。つまり、今まで通り(デフォルトのグローバル空間に設置するタイプ)で良いと思います。

外部ライブラリを使うにしても、ライブラリが名前空間を持っていれば、インポートして名前空間をフルパスで指定すれば良いだけの話しです。

では、「自分のアプリに名前空間を持たせる必要があるか」の判断ポイントとして、いくつか挙げてみたいと思います。

  1. 名前がカテゴリ情報を持ち始めた。
  2. composer などで使える俺様ライブラリに昇格させたくなった。
  3. 名前を見ただけで機能が把握できる名前にしたものの、似たものが増えちゃったので微妙に変えないといけなくなった。

名前空間を前提として設計しないと、後々面倒なことになるケースもあります。最近でいえば、よく使われそうなユーザー関数名が本家で採用されちゃった function fn(){} です。

PHP 7.4 から使えるようになったアロー関数を定義するのが fn() になったため、意外に多くの老舗のライブラリが使えなくなっています。

名前空間を付ければ良いだけかと思いきや、グローバル空間で使うことを前提としていたため、ライブラリを利用しているアプリは下位互換性を保たせるために奮闘しているのです。

とりあえず、これからアプリを作るのであれば名前空間を付けておくのが得策だと思います。

<?php
namespace MyName/MySeries/MyApp;

function foo(): string
{
   return 'bar' . PHP_EOL;
}

echo foo();

  1. PSR とは PHP Standards Recommendations の略で、雑に訳すと「『PHP のスタンダード』のオススメ集」です。詳しく言うと、PHP の有志のコミュニティ「PHP-FIG」によって制定されたスタンダードの推奨ガイドラインです。

    「スタンダード」とは日本語で「標準」「基準」「規格」「定番」などの意味を持ちますが、ここでは「標準」の意味合いで使われます。「標準」とは 2 者以上で何かを行う際に「ベースとなる考え方や方法を取り決めた・定めたもの」です。「標準」から大きく外れないことで足並みを揃える目的があります。

    この時、ゼロから考えて標準を決めるより「あるある」な「標準」をまとめたものがあると便利です。それらをまとめようというのが「PHP-FIG」です。

    「PHP-FIG」は PHP Framework Interoperation Group の略で「PHP フレームワークの相互利用支援団体」です。この団体の主な目的は、用途が同じなら異なるフレームワークでも同じ使い方が出来るようにプロトコルを制定することです。

    日本産業規格の JIS、国際標準化機構の ISO、各種インターネット技術の標準化団体の W3C、Python の PEP、Java の JSR などのようなものです。それらが本家の団体が制定しているのに対し、PSR は公認であるものの有志のコミュニティが制定しているため PSR 規格に準拠するのは「より任意的なもの」になってるなどの違いがあります。そのため、あくまでも「推奨」されたガイドラインとされます。

    このように PSR は PHP を扱う上で必須の規格ではありません。しかし、オープンソースなど、他者と共同開発する際のプロトコルとして採用されていることが多くあります。

    PSR-0 や PSR-4 は、その規格(PSR)のうちオートローダーに関して制定したものになります。独自にオートローダーを実装することも問題ありませんが、パッケージ管理に(他者の開発したライブラリを利用するのに)便利な composer が PSR に準拠したオートローダーを採用しているため、独自で実装するにしても PSR の仕様に準拠する方が良いとされます。また、composer は PSR-0 の採用を廃止したので、現状 PSR-4 がオートローダーのデファクト・スタンダードと言えると思います。

    ● 参考文献:PHP Standards Recommendations PHP-FIG 公式
    ● 「PHP Standard Recommendation」@ Wikipedia(英語)
    ● あわせて読みたい: PSRの誤解 @ Qiita

  2. PHP のオートローダーとは、一度読み込んでおくだけで、他の外部ファイルを include もしくは require する手間を省くための仕組みです。

    例えば、肥大化したスクリプトを複数ファイルに分けた場合、各々を includerequire で追記する必要がありますが、その手間が省けます。特にメンテナンス性を上げるためにファイルをクラスごとに分けた場合などです。具体的には、外部ファイルを include require する代わりに、オートローダーのスクリプトを 1 度 require することで、クラス・ファイルなどの外部ファイルを自動的に読み込みます。

    オートローダーの機能には大きく 2 種類あり、事前に読み込む静的な方法と、クラスなどが呼び出された時に読み込む動的な方法があります。psr-4psr-0 の代わりに files を使った場合は事前に読み込みます。

    オートローダーを採用することによる速度低下ですが、オートローダーの実装方法によるものの大きな差はないとされます。もちろん、直接 include require した方が速いのですが、オートローダーを利用した方がメンテナンス性や開発における利便性が増すことにより、結果的に安定性も増すため総合してオートローダーを採用した方が良いという傾向があります。

    また、PSR-4 で制定されたオートローダーの仕様で実装しておくと、同じく準拠したフレームワークなどで利用できるというメリットがあります。そのため、標準でオートローダーを持っている composer を利用するのが車輪の再発明防止につながるとされます。この場合、require_once('vendor/autoloader.php'); のオートローダーを読み込むだけで使えるようになります。詳しくは上記の「あわせて読みたい」の参考文献をご覧ください。

32
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?