Slide 1

Slide 1 text

色んなオートローダーを覗き見る PHPカンファレンス沖縄2024 Hideki Kinjyo GitHub: o0h / X: @o0h_ [公開用]

Slide 2

Slide 2 text

自己紹介 • 金城秀樹 / きんじょうひでき • GitHub: @o0h / 𝕏 : @o0h_ • 好きなFWはCakePHP • アイコンは美味しい鮭親子丼の写真です • 沖縄は23年ぶり16回目くらいです多分!

Slide 3

Slide 3 text

このお話は? 感じてもらいたいこと • 「オートローダーって、種が分かれば単純なんだな!」の気持ち • 「今日のデファクトスタンダードってこうなんだ」 「それ以外の実装もあるんだな」の気持ち 話さない • 詳細な実装方法や利用時の注意点といった細かい事柄 • 資料の「付録」で少し踏み込んでいます

Slide 4

Slide 4 text

おしながき 1.「オートロード」って何してるの? 2.モダン・スタンダードの世界 3.ちょっとレトロな世界、各地の方言 4.まとめ

Slide 5

Slide 5 text

1. 「オートロード」って何? && 何してるの? 2. モダン・スタンダードの世界 3. ちょっとレトロな世界、各地の方言 4. まとめ

Slide 6

Slide 6 text

PHPでクラスを使ってみよう! PHPでなにかのクラスを使いたい時、 どうすれば良いでしょう?

Slide 7

Slide 7 text

クラス利用の流れ クラスXを利用しようとする 処理を実行 クラスXが存在する? Fatal Error 何かのクラスを利用しようとした時には、 その定義を利用できる必要がある

Slide 8

Slide 8 text

クラス利用の流れ クラスXを利用しようとする 処理を実行 クラスXが存在する? Fatal Error 同じファイルで定義されていれば 問題なく利用できる

Slide 9

Slide 9 text

クラス利用の流れ クラスXを利用しようとする 処理を実行 クラスXが存在する? Fatal Error 別ファイルであっても、 読み込んであれば問題なし

Slide 10

Slide 10 text

クラス利用の流れ クラスXを利用しようとする 処理を実行 クラスXが存在する? Fatal Error 利用したかったクラス定義が 解決できない場合、致命的なエラーに

Slide 11

Slide 11 text

オートロード? クラスのオートローディングは、 使おうとしたクラスが存在しなかった時に 遅延読み込みをする仕組み ※ Class, Interface, Trait, Enumで有効

Slide 12

Slide 12 text

クラス利用の流れ with オートロード クラスXを利用しようとする 処理を実行 クラスXが存在する? Fatal Error 定義済みクラスが存在しなかった場合に、 事前に決めた方法で解決を試みる ここで 一踏ん張り!

Slide 13

Slide 13 text

spl_autoload_register • 「事前に決めたルール」を登録する方法がspl_autoload_register() • 「要求されたクラス名を引 数にとって何かをする」動 作を記述したコールバック を登録するもの • `__autoload()` 関数は PHP8.0で廃止されました

Slide 14

Slide 14 text

spl_autoload_register 典型的な実装: 1. 与えられたクラス名に基 づいて、対応するファイ ル名(パス)を割り出す 2. 割り出したパスに対して requireを実行 3. ファイルの新規読み込み により、新しいクラス定 義を解決する ちゃんと動くコードの例。 ʆ./classes/Command.php` が読み込まれる

Slide 15

Slide 15 text

spl_autoload_register 必須なのは「ファイルを読 み込む」ではなく「クラス を解決する」なので、必ず しもrequire/includeを行わ なくても良い

Slide 16

Slide 16 text

1. 「オートロード」って何? && 何してるの? 2. モダン・スタンダードの世界 〜Composerが提供するオートロード〜 3. ちょっとレトロな世界、各地の方言 4. まとめ

Slide 17

Slide 17 text

この章の話 「最近、オートロードといえばComposer!」 「実際にはどんなことをやってるんだ??」を概念レベルで話します アップロードしている資料では、「付録」として実コード(を改変したもの)を用いて解説しています 詳しくはそちらをご覧ください

Slide 18

Slide 18 text

Composerがオートロードを提供するまでの流れ $ tree -L 3 app app └─ vendor ├─ autoload.php └─ composer ├── ClassLoader.php ├── autoload_real.php └── autoload_static.php 関係するファイルたち

Slide 19

Slide 19 text

Composerがオートロードを提供するまでの流れ (app/index.php) vendor/autoload.php vendor/composer/ autoload_real.php vendor/composer/ autoload_static.php vendor/composer/ ClassLoader.php 各ファイルの依存関係: 矢印の向きにincludeする

Slide 20

Slide 20 text

各ファイルの役割 (app/index.php) vendor/autoload.php vendor/composer/ autoload_real.php vendor/composer/ autoload_static.php vendor/composer/ ClassLoader.php \ComposerAutoloaderInitXXX +getLoader() \Composer\Autoload \ComposerStaticInitXXX +getInitializer() \Composer\Autoload \ClassLoader +register() +loadClass()

Slide 21

Slide 21 text

各ファイルの役割: ClassLoader 実質的なロジックを持つクラス • register: spl_autload_registerによるオートロードの登録を行う • loadClass: クラス名(FQCN)に対応するパスの解決とincludeを行う vendor/composer/ ClassLoader.php \Composer\Autoload \ClassLoader +register() +loadClass()

Slide 22

Slide 22 text

各ファイルの役割: ComposerStaticInitXXX 設定データを持つクラス • パスの解決に必要となる名前空間やクラス名とパスの 対応情報を保持していて、 ClasLoaderインスタンスに提供する vendor/composer/ autoload_static.php \Composer\Autoload \ComposerStaticInitXXX +getInitializer()

Slide 23

Slide 23 text

各ファイルの役割: ComposerAutloaderInitXXX 手続きの起動 • StaticInit, ClassLoaderを使役し、オートロードの登録手続きを呼び出す • autoload.filesに指定されたファイルのイーガーロードを行う vendor/composer/ autoload_real.php \ComposerAutoloaderInitXXX +getLoader()

Slide 24

Slide 24 text

各ファイルの役割: autoload.php AutoloaderInitを読み込み、処理を委譲しているだけ vendor/autoload.php require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInitXXX::getLoader();

Slide 25

Slide 25 text

取り上げる3つのオートロード • Composerが提供する 主なオートロードの仕組み • Class Map • PSR-0 • PSR-4 • いずれも、 `loadClass()`内で `findFile()`を用いて 解決する

Slide 26

Slide 26 text

取り上げる3つのオートロード 事前知識として、 クラス名(FQCN))とファイルパスの対応を 辞書として保持して、解決に利用する • Composerが提供する 主なオートロードの仕組み • Class Map • PSR-0 • PSR-4 • いずれも、 `loadClass()`内で `findFile()`を用いて 解決する 事前知識として、 ベースとなる名前空間とディレクトリパスの対応を 辞書として保持して、解決に利用する サブディレクトリの位置やファイル名は 命名規則から(=ルールベースで、動的に)割り出す ComposerStaticInitで 「事前知識」の部分を扱っている

Slide 27

Slide 27 text

Class Map // in \Composer\Autoload\ ComposerStaticInitXXXX public static $classMap = [ 'Composer\\InstalledVersions' => __DIR__ . '/../composer/InstalledVersions.php', 'Mobile_Detect' => __DIR__ . '/../mobiledetect/mobiledetectlib/Mobile_Detect.php', ]; // in \Composer\Autoload\ClassLoader public function findFile($class) { if (isset($this->classMap[$class])) { return $this->classMap[$class]; } クラス名(FQCN)をキーにして、 自クラスのstatic memberから 該当する{fqcn: path}を探索しパスを解決する

Slide 28

Slide 28 text

PSR-0, PSR-4 • PSR-0/4: 名前空間・クラスと、それに対応させるファイルパスの規則 • Class Mapが「クラスとパス」の対応を利用したのに対し、 「ルートとなる名前空間(名前空間プリフィックス)」と 「対応するディレクトリパス(ベースディレクトリ)」情報を利用する // in \Composer\Autoload\ClassLoader public function findFile($class) { // தུ $file = $this->findFileWithExtension($class,'.php'); return $file; 命名規則から動的にFQCN->パスを解決する 実装は`findFileWithExtension()`

Slide 29

Slide 29 text

PSR-0, PSR-4: prefix map public static $prefixDirsPsr4 = [ 'React\\Promise\\' => [0 => __DIR__ . '/..' . '/react/promise/src'], 'Psr\\Log\\' => [0 => __DIR__ . '/..' . '/psr/log/src'], 'Psr\\Container\\' => [0 => __DIR__ . '/..' . '/psr/container/src'], ]; public static $prefixesPsr0 = [ 'D' => [ 'Diff' => [0 => __DIR__ . '/..' . '/phpspec/php-diff/lib'], ], 'H' => [ 'HTMLPurifier' => [0 => __DIR__ . '/..' . '/ezyang/htmlpurifier/library'], ], ]; ComposerStaticInitXXXXがmapを持っている。ClassLoaderに代入される

Slide 30

Slide 30 text

PSR-0: FQCN->パスの変換@findFileWithExtension 1. 名前空間(prefix)からディレクトリのパスを取得 2. 名前空間(prefix)より下位の`\`と全ての`_` を ディレクトリの区切り文字に変換してサブディレクトリとファイル名に 3. {1 + 2 + '.php'}でフルパスを取得 ొ࿥৘ใ($classLoader->prefixesPsr0): 'HTMLPurifier' => [0 => __DIR__ . '/..' . '/ezyang/htmlpurifier/library'] ೖྗ: 'HTMLPurifier_AttrDef_CSS_AlphaValue' ग़ྗ: '/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php'

Slide 31

Slide 31 text

PSR-0:公式のサンプル PSR-0: Autoloading Standard - PHP-FIG https://www.php-fig.org/psr/psr-0/

Slide 32

Slide 32 text

PSR-4: FQCN->パスの変換@findFileWithExtension 1. 名前空間(prefix)からディレクトリのパスを取得 2. 名前空間の区切り文字をディレクトリの区切り文字に変換して サブディレクトリとファイル名に 3. {1 + 2 + '.php'}でフルパスを取得 ొ࿥৘ใ($classLoader->prefixesPsr4): 'React\\Promise\\' => [0 => __DIR__ . '/..' . '/react/promise/src'], ೖྗ: '\React\Promise\Exception\CompositeException' ग़ྗ: '/vendor/react/promise/src/Exception/CompositeException.php'

Slide 33

Slide 33 text

PSR-4:公式のサンプル PSR-4: Autoloader https://www.php-fig.org/psr/psr-4/

Slide 34

Slide 34 text

セクションのまとめ • Composerのオートロード、Class Map / PSR-4 / PSR-0に対応している • PSR-4 / PSR-0は、名付けのルールは異なるけれども 「ルートの名前空間を定義する」 「サブ名前空間やディレクトリの区切り方のルールがある」 「名前空間とベースクラス名からパスを知る」 と、やっていることは同じ • いずれも「パスを見つけて→ファイルをinclude」をするだけ、単純!

Slide 35

Slide 35 text

1. 「オートロード」って何? && 何してるの? 2. モダン・スタンダードの世界 3. ちょっとレトロな世界、各地の方言 〜現代、Composer誕生期、Composer以前〜 4. まとめ

Slide 36

Slide 36 text

この章の話 • 「最近、オートロードといえばComposer!」 「それ以外の道や、過去はどうしてたの?」を概念レベルで話します • 目的: Composer/PSR-4だけが唯一の実装でないんだな、と知る 目的でない: Composerより優れた道があるぞ、と提案する

Slide 37

Slide 37 text

覗いてみましょう、色々な世界<フレームワーク> Composerのデファクト化以後〜登場前のオートロードの世界を覗きます 1. 最近の有名どころなものたち w/Composer 2. Yii 2 (2014〜) 3. Zend Framework 2 (2012〜) 4. CakePHP 2 (2011〜) ※ Composerの1.0.0-alpha1が2013 ※ PSR-0(SplClassLoader)の提案が2010

Slide 38

Slide 38 text

1. Laravel, CakePHP(3+),Slim etc ─今風のFWたち • PSR-4準拠のオートロードに対応している • オートローダーの実装はComposerに依存 • = `composer require``composer create-project`した時点で使える • comopser.jsonのautoload.psr-4フィールドを設定して利用 • index.phpなど、エントリーポイントとなるファイルにおいて `require __DIR__ . '/../vendor/autoload.php'` が記述されるのが一般的

Slide 39

Slide 39 text

2. Yii2 • PSR-4準拠/Class Map形式のオートロードに対応している • ただし、Composer由来のオートローダーだけではなく、 FW自前の機能を付与したオートローダーを提供 • ザックリ言うと、FW独自の規則・設定に則っていれば composer.jsonの改変をせずともクラスファイルの読み込みが可能になっている 鍵となる概念: クラスのオートロード | Yii 2.0 決定版ガイド | Yii PHP Framework https://www.yiiframework.com/doc/guide/2.0/ja/concept-autoloading

Slide 40

Slide 40 text

Yii2のオートロード@超ざっくりイメージ

Slide 41

Slide 41 text

Yii2のオートロード@超ざっくりイメージ アプリケーションのconfigファイルに、 「エイリアス名」と「ディレクトリパス」を設定する エイリアスを利用したパスの解決が行われる ex)`special\SugoiSuteki` => `{PJ_ROOT}/app/specialSugoiSuteki.php`

Slide 42

Slide 42 text

3. Zend Framework2 • PSR-0準拠/Class Map形式のオートロードに対応している • 独自のオートローダーを利用 • 「モジュール」と呼ばれる仕組みが提供されているのが特徴的で、 任意のディレクトリ(=ルート名前空間)にMVC一式が格納される

Slide 43

Slide 43 text

Zend Framework2: モジュールの例 • 同一プロジェクトの中に、 複数の名前空間を簡単に持てる • PSR-4でいうprefixに該当 • アプリケーションに対して 「この名前空間が有効である」を 伝える必要が生じる $ tree -L 2 app app ├── config ├── module │ ├── Album │ │ ├── Module.php │ │ ├── config │ │ ├── src │ │ └── view │ └── Artist └── public `\Album` ネームスペース `\Artist` ネームスペース

Slide 44

Slide 44 text

モジュール作成の例@ざっくりイメージ

Slide 45

Slide 45 text

モジュール作成の例@ざっくりイメージ モジュールごとの構成クラスを作成し、 {ルート名前空間名: ディレクトリパス}を設定する Class Mapを利用する際も、同メソッドの返り値にて設定する

Slide 46

Slide 46 text

4. CakePHP2 • 独自の遅延読み込み機構を提供 • PHP5.2.8以上をサポートしており、 「spl_autoload_registerは使えるが、名前空間は利用しない」 という制約がある • 開発者が、利用したいクラスを明示的に宣言して登録する

Slide 47

Slide 47 text

https://book.cakephp.org/2/ja/models.html

Slide 48

Slide 48 text

https://book.cakephp.org/2/ja/models.html Class Mapへの登録を行うメソッド

Slide 49

Slide 49 text

https://book.cakephp.org/2/ja/models.html クラスパス: 名前空間的なもの。 `Model` はフレームワークのデフォルトで設定してあり、 `app/Model` に対応する クラス名 これで、 `AppModel` クラスを利用しようとすると `app/Model/AppModel.php` が遅延読込可能になる

Slide 50

Slide 50 text

App::uses()の実態

Slide 51

Slide 51 text

実際にオートローダーを登録しているところ

Slide 52

Slide 52 text

セクションのまとめ • 「Composer以後」「Composer(PSR-0)誕生期」「Composer以前」 のような、時代に応じて解決方法が異なる雰囲気 • フレームワークが提供したい機能に応じて、 独自のオートローダーを用意して利便性を高めるアプローチもある

Slide 53

Slide 53 text

1. 「オートロード」って何? && 何してるの? 2. モダン・スタンダードの世界他のやつ 3. ちょっとレトロな世界、各地の方言 4. まとめ

Slide 54

Slide 54 text

最近の開発では・・・ • Composerの提供するオートローダーを利用するのが一般的 • その内容は、Class Map・PSR-4・PSR-0の実装が主となっている • 解剖してみると、とっても単純! • 事前に「map」を用意してルールベース(文字列置換)でファイルを見つけるだけ

Slide 55

Slide 55 text

知っておくと良いこと・知らなくても良いこと • Composerのオートローダーは「PSR-4実装の1つ」にすぎず、 必要に応じて使ったり使わなかったりできる • 例えば「モジュール単位で名前空間を細かく分ける」思想であれば、 `composer.json`を都度いじる(=`dumpautload`を要する)のは面倒かも • 自前で実装すれば、別の方法で名前空間の追加ができる • 「当たり前」で留まらず一歩踏み込むと、細かく工夫もできるかも • 例えば「PSR-4を使っていればOK」から「Class Mapにするか?を考える」など • `composer dumpautoload` の `—optimize`オプションや PHPUnitの`composer.json` によるautoload.classmap指定などの例

Slide 56

Slide 56 text

まとめ • 『遠回りこそが俺の最短の道だった』という言葉があります • 一見、無駄と思える雑学レベルの知識── 「関係ない実装」「昔の話」が、いつかヒントになるかも知れません • ぜひ、身の回りのことに興味を持ってみましょう!! • この発表が、誰かの「ちょっと面白そう」のきっかけになれば幸いです!

Slide 57

Slide 57 text

おしまい! お付き合いいただき ありがとうございました!!

Slide 58

Slide 58 text

Appendix 1. 実際のコード(っぽい)で追う Composerのオートロード処理

Slide 59

Slide 59 text

Composerがオートロードを提供するまでの流れ $ tree -L 2 app app └── vendor ├── autoload.php ├── ClassLoader.php ├── autoload_real.php └── autoload_static.php 関係するファイルたち $3より再掲

Slide 60

Slide 60 text

Composerがオートロードを提供するまでの流れ (app/index.php) vendor/autoload.php vendor/composer/ autoload_real.php vendor/composer/ autoload_static.php vendor/composer/ ClassLoader.php 各ファイルの依存関係: 矢印の向きにincludeする $3より再掲

Slide 61

Slide 61 text

各ファイルの役割 (app/index.php) vendor/autoload.php vendor/composer/ autoload_real.php vendor/composer/ autoload_static.php vendor/composer/ ClassLoader.php \ComposerAutoloaderInitXXX +getLoader() \Composer\Autoload \ComposerStaticInitXXX +getInitializer() \Composer\Autoload \ClassLoader +register() +loadClass() $3より再掲

Slide 62

Slide 62 text

余談: 自動生成されるクラス名について • 記述内容が毎回変わるので、クラス名が動的に変更される • dumpautoload時にsuffixを付与: デフォルトだとcontent-hashの値 • ex)`ComposerStaticInit67574dcf9d425e8bc6c10ce234303952` • • ClassLoaderは変更がない(既存ソースのコピーで作成される) • ComposerAutoloaderInitXXXXX \Composer\Autoload\ComposerStaticInitXXXXX \Composer\Autoload\ClassLoader

Slide 63

Slide 63 text

Composerのautoloader よくみるアレ

Slide 64

Slide 64 text

autoload@超ざっくりイメージ

Slide 65

Slide 65 text

autoload@超ざっくりイメージ 実態は、自動生成された別クラスへのデ リゲーションを行っているだけ

Slide 66

Slide 66 text

autoload_real@超ざっくりイメージ

Slide 67

Slide 67 text

autoload_real@超ざっくりイメージ オートロードのための ロジック(クラス)を読み出し&インスタンス化 ClassLoaderが扱うクラスの情報を読み出 し & オートーローダーの登録

Slide 68

Slide 68 text

autoload_real@超ざっくりイメージ 静的な読み出しの実行(イーガーロード)。 こっちはPHPのオートロードに関係しないので割愛 (see: https://getcomposer.org/doc/04-schema.md#files)

Slide 69

Slide 69 text

ClassLoader::register@超ざっくりイメージ

Slide 70

Slide 70 text

ClassLoader::register@超ざっくりイメージ 自身が持つメソッドを、 オートロードの手続きとして登録

Slide 71

Slide 71 text

ClassLoader::loadClass@超ざっくりイメージ

Slide 72

Slide 72 text

ClassLoader::loadClass@超ざっくりイメージ $class(FQCN)に対応するファイル の存在を確認しつつ 要するに `include` を呼び出しているだけ

Slide 73

Slide 73 text

ClassLoader::findFile@超ざっくりイメージ

Slide 74

Slide 74 text

ClassLoader::findFile@超ざっくりイメージ 「クラスマップ」形式での探索 {class-name: file-path}形式の 辞書(連想配列)を使った解決 PSR-0, PSR-4対応の探索

Slide 75

Slide 75 text

$this->classMapとは???

Slide 76

Slide 76 text

[再掲] ClassLoaderへのClass Map(等)の登録部分 autoload_real(先述)の処理。 ClassLoaderが扱うクラスの情報を読み出し & オートーローダーの登録

Slide 77

Slide 77 text

ComposerStaticInit::getInitializer

Slide 78

Slide 78 text

ComposerStaticInit::$classMap@超ざっくりイメージ

Slide 79

Slide 79 text

ComposerStaticInit::$classMap@超ざっくりイメージ FQCNと、vendor配下の絶対パスが紐づいているので、 これを探ってファイル名を引き出す

Slide 80

Slide 80 text

PSR-4周りの登録部分@超ざっくりイメージ

Slide 81

Slide 81 text

PSR-4周りの登録部分@超ざっくりイメージ ネームスペース(prefix)と、 vendor配下のディレクトリの絶対パスが紐づいている。 ディレクトリは複数可なので、配列で宣言

Slide 82

Slide 82 text

[再掲] ClassLoader::findFile@超ざっくりイメージ

Slide 83

Slide 83 text

ファイル探索に至る部分 PSR-0, PSR-4対応の探索

Slide 84

Slide 84 text

PSR-4でのファイル探索処理@超ざっくりイメージ

Slide 85

Slide 85 text

PSR-4でのファイル探索処理@超ざっくりイメージ FQCNの`\`(バックスラッシュ)を`/`に変換して、 拡張子(`.php`) を付与する ex)`Psr\Log\LogLevel` => `Psr/Log/LogLevel.php`

Slide 86

Slide 86 text

PSR-4でのファイル探索処理@超ざっくりイメージ `\`が最後に出てくる位置で、名前空間名とクラス名を分割 ex)`Psr\Log\LogLevel` => `$search = Psr\Log\`

Slide 87

Slide 87 text

PSR-4でのファイル探索処理@超ざっくりイメージ FQCNからだけでは、 名前空間の「サブ」と「prefix」の 見分けがつかないので セパレータ(`\`)で区切って再帰的 に照合していく

Slide 88

Slide 88 text

PSR-4でのファイル探索処理@超ざっくりイメージ ディレクトリ名を除いたファイル名を取得する ex)`Psr\Log\LogLevel` => `LogLevel.php`

Slide 89

Slide 89 text

PSR-4でのファイル探索処理@超ざっくりイメージ {名前空間: ディレクトリパス}の辞書とファイル名を利用して クラスファイルのフルパスを取得 ex)`Psr\Log\LogLevel` => `{PJ_ROOT}/vendor/psr/log/src/LogLevel.php`

Slide 90

Slide 90 text

[復習] ファイル名がわかればincludeできる!

Slide 91

Slide 91 text

PSR-0周りの登録部分@超ざっくりイメージ

Slide 92

Slide 92 text

PSR-0周りの登録部分@超ざっくりイメージ ネームスペースの先頭1文字目でグルーピング。 ネームスペース(prefix)と、 vendor配下のディレクトリの絶対パスが紐づいている。 ディレクトリは複数可なので、配列で宣言

Slide 93

Slide 93 text

PSR-0でのファイル探索処理@超ざっくりイメージ

Slide 94

Slide 94 text

PSR-0でのファイル探索処理@超ざっくりイメージ FQCNの`\`(バックスラッシュ)を`/`に変換して、 拡張子(`.php`) を付与する ex)`HTMLPurifier_Generator` => `HTMLPurifier_Generator.php`

Slide 95

Slide 95 text

PSR-0でのファイル探索処理@超ざっくりイメージ ネームスペース部分以外のアンダースコアを`/`に変換 ex)`HTMLPurifier_Generator` => `HTMLPurifier/Generator.php`

Slide 96

Slide 96 text

PSR-0でのファイル探索処理@超ざっくりイメージ {名前空間: ディレクトリパス}の辞書とファイル名を利用して クラスファイルのフルパスを取得 ex)`HTMLPurifier_Generator` => `{PJ_ROOT}/vendor/ezyang/htmlpurifier/library/ HTMLPurifier/Generator.php`