Share
Embed
色んなオートローダーを覗き見る #phpcon_okinawa
PHPカンファレンス沖縄2024の発表資料です
https://fortee.jp/phpcon-okinawa-2024/proposal/92abb418-8735-41d9-bbf8-ffc3109d018b
hideki kinjyo PRO
September 28, 2024
Transcript
96 slides
-
自己紹介 • 金城秀樹 / きんじょうひでき • GitHub: @o0h / 𝕏
: @o0h_ • 好きなFWはCakePHP • アイコンは美味しい鮭親子丼の写真です • 沖縄は23年ぶり16回目くらいです多分! -
クラス利用の流れ with オートロード クラスXを利用しようとする 処理を実行 クラスXが存在する? Fatal Error 定義済みクラスが存在しなかった場合に、
事前に決めた方法で解決を試みる ここで 一踏ん張り! -
spl_autoload_register 典型的な実装: 1. 与えられたクラス名に基 づいて、対応するファイ ル名(パス)を割り出す 2. 割り出したパスに対して requireを実行 3.
ファイルの新規読み込み により、新しいクラス定 義を解決する ちゃんと動くコードの例。 ʆ./classes/Command.php` が読み込まれる -
Composerがオートロードを提供するまでの流れ $ tree -L 3 app app └─ vendor
├─ autoload.php └─ composer ├── ClassLoader.php ├── autoload_real.php └── autoload_static.php 関係するファイルたち -
各ファイルの役割 (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() -
各ファイルの役割: ClassLoader 実質的なロジックを持つクラス • register: spl_autload_registerによるオートロードの登録を行う • loadClass: クラス名(FQCN)に対応するパスの解決とincludeを行う
vendor/composer/ ClassLoader.php \Composer\Autoload \ClassLoader +register() +loadClass() -
各ファイルの役割: ComposerStaticInitXXX 設定データを持つクラス • パスの解決に必要となる名前空間やクラス名とパスの 対応情報を保持していて、 ClasLoaderインスタンスに提供する vendor/composer/ autoload_static.php
\Composer\Autoload \ComposerStaticInitXXX +getInitializer() -
各ファイルの役割: ComposerAutloaderInitXXX 手続きの起動 • StaticInit, ClassLoaderを使役し、オートロードの登録手続きを呼び出す • autoload.filesに指定されたファイルのイーガーロードを行う vendor/composer/
autoload_real.php \ComposerAutoloaderInitXXX +getLoader() -
各ファイルの役割: autoload.php AutoloaderInitを読み込み、処理を委譲しているだけ vendor/autoload.php require_once __DIR__ . '/composer/autoload_real.php'; return
ComposerAutoloaderInitXXX::getLoader(); -
取り上げる3つのオートロード • Composerが提供する 主なオートロードの仕組み • Class Map • PSR-0 •
PSR-4 • いずれも、 `loadClass()`内で `findFile()`を用いて 解決する -
取り上げる3つのオートロード 事前知識として、 クラス名(FQCN))とファイルパスの対応を 辞書として保持して、解決に利用する • Composerが提供する 主なオートロードの仕組み • Class
Map • PSR-0 • PSR-4 • いずれも、 `loadClass()`内で `findFile()`を用いて 解決する 事前知識として、 ベースとなる名前空間とディレクトリパスの対応を 辞書として保持して、解決に利用する サブディレクトリの位置やファイル名は 命名規則から(=ルールベースで、動的に)割り出す ComposerStaticInitで 「事前知識」の部分を扱っている -
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}を探索しパスを解決する -
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()` -
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に代入される -
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' -
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' -
セクションのまとめ • Composerのオートロード、Class Map / PSR-4 / PSR-0に対応している • PSR-4
/ PSR-0は、名付けのルールは異なるけれども 「ルートの名前空間を定義する」 「サブ名前空間やディレクトリの区切り方のルールがある」 「名前空間とベースクラス名からパスを知る」 と、やっていることは同じ • いずれも「パスを見つけて→ファイルをinclude」をするだけ、単純! -
覗いてみましょう、色々な世界<フレームワーク> 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 -
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'` が記述されるのが一般的 -
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 -
3. Zend Framework2 • PSR-0準拠/Class Map形式のオートロードに対応している • 独自のオートローダーを利用 • 「モジュール」と呼ばれる仕組みが提供されているのが特徴的で、
任意のディレクトリ(=ルート名前空間)にMVC一式が格納される -
Zend Framework2: モジュールの例 • 同一プロジェクトの中に、 複数の名前空間を簡単に持てる • PSR-4でいうprefixに該当 • アプリケーションに対して
「この名前空間が有効である」を 伝える必要が生じる $ tree -L 2 app app ├── config ├── module │ ├── Album │ │ ├── Module.php │ │ ├── config │ │ ├── src │ │ └── view │ └── Artist └── public `\Album` ネームスペース `\Artist` ネームスペース -
https://book.cakephp.org/2/ja/models.html クラスパス: 名前空間的なもの。 `Model` はフレームワークのデフォルトで設定してあり、 `app/Model` に対応する クラス名 これで、
`AppModel` クラスを利用しようとすると `app/Model/AppModel.php` が遅延読込可能になる -
知っておくと良いこと・知らなくても良いこと • Composerのオートローダーは「PSR-4実装の1つ」にすぎず、 必要に応じて使ったり使わなかったりできる • 例えば「モジュール単位で名前空間を細かく分ける」思想であれば、 `composer.json`を都度いじる(=`dumpautload`を要する)のは面倒かも • 自前で実装すれば、別の方法で名前空間の追加ができる •
「当たり前」で留まらず一歩踏み込むと、細かく工夫もできるかも • 例えば「PSR-4を使っていればOK」から「Class Mapにするか?を考える」など • `composer dumpautoload` の `—optimize`オプションや PHPUnitの`composer.json` によるautoload.classmap指定などの例 -
Composerがオートロードを提供するまでの流れ $ tree -L 2 app app └── vendor
├── autoload.php ├── ClassLoader.php ├── autoload_real.php └── autoload_static.php 関係するファイルたち $3より再掲 -
各ファイルの役割 (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より再掲 -
余談: 自動生成されるクラス名について • 記述内容が毎回変わるので、クラス名が動的に変更される • dumpautoload時にsuffixを付与: デフォルトだとcontent-hashの値 • ex)`ComposerStaticInit67574dcf9d425e8bc6c10ce234303952` •
• ClassLoaderは変更がない(既存ソースのコピーで作成される) • ComposerAutoloaderInitXXXXX \Composer\Autoload\ComposerStaticInitXXXXX \Composer\Autoload\ClassLoader