Page 1
Composer解体新書
Anatomy of Composer, in Runtime
PHPカンファレンス北海道出張版(仮)
PHPオフ会 #phpcondo2017
公開日:
by USAMI Kenta@tadsan
に札幌市中央区の株式会社インフィニットループで開催された『PHPカンファレンス北海道出張版(仮) PHPオフ会』でレギュラーセッション(30分)として発表しました。
Anatomy of Composer, in Runtime
PHPカンファレンス北海道出張版(仮)
PHPオフ会 #phpcondo2017
Composer使ってますか?
Dependency Manager for PHP
A. 使ってないB. FW経由でC. 直接使ってる
分かれそう
本日しない話
PHPのクラスのオートローディングの説明
プロジェクトでのComposerの運用方法
過去の発表を読んでください
https://niconare.nicovideo.jp/watch/kn1371
https://qiita.com/tadsan/items/86099d44d12f1103b0a0
https://qiita.com/tadsan/items/a78c9160418200e2d47a
Composerでできること
ざっくり
プロジェクトが依存するパッケージをダウンロードしてくる
プロジェクトが依存するパッケージを利用可能にする
今日は実行時の話をします
基本はComposerのautoload.phpを読み込めば準備完了する
復習
PHPスクリプトの読み込みかた
require, require_once include, include_once
場合によるけど基本だいたい同じ
どこから?
A. ファイルシステムの絶対パス
B. include_pathからの相対パスC. プロトコル (ラッパー)
require '/path/to/file.php';
ファイルシステム内のフルパス
require __DIR__.'/../Klass.php';マジック定数__DIR__でスクリプトのディレクトリが入る
include_pathからの相対パス
include_pathにディレクトリを追加する
だいたいデフォルトに "." が入ってる
require 'Hoge.php';
require 'Fuga/Piyo.php';
ディレクトリ内も探索される
fopen('php://stderr', 'r') とかfile_get_contents('http://example.com/')とかやるための仕組み
……が、セキュリティの懸念もあるので、デフォルトでは php.ini でallow_url_include = Off になってる
改めてComposer
オートロード機能ついてる
どうやって読み込んでるの?
autoload.phpを読もう
みなさまの手元のComposerプロジェクトを見比べながらお聞きください
% composer --version Composer version 1.5.2 2017-09-11 16:59:25% composer install --no-dev --prefer-dist
<?php
// autoload.php @generated by Composer require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitbed8ae7d525347237f9cdad08c8930b3::getLoader();
1.autoload_real.phpを読み込む2.謎のクラスの静的メソッドを実行
たぶん変なバイトキャッシュを残さないためにcontent-hashが付いたクラス名
1.$loaderが生成済みなら返すだけ2.クラスローダーを読み込むだけのクラス
ローダーを登録して、使ったらすぐにspl_autoload_unregister する
ワークアラウンドっぽい気がする
if (null !== self::$loader) {return self::$loader;}
// Composer\Autoload クラスを読み込むだけのローダー
spl_autoload_register(array('ComposerAutoloaderInitbedRy', 'loadClassLoader'),true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInitbedRy', 'loadClassLoader'));
1.PHPの実行時環境によってクラス定義の読み込みを変更
2.autoload_static.php VS 三兄弟
zend_loader_file_encoded()が無効ならautoload_staticが利用される
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') &&(!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());if ($useStaticLoader) {require_once __DIR__ . '/autoload_static.php';call_user_func(\Composer\Autoload\ComposerStaticInitbedRy::getInitializer($loader));} else {$map = require __DIR__ . '/autoload_namespaces.php';foreach ($map as $namespace => $path) {$loader->set($namespace, $path);}$map = require __DIR__ . '/autoload_psr4.php';foreach ($map as $namespace => $path) {$loader->setPsr4($namespace, $path);}$classMap = require __DIR__ . '/autoload_classmap.php';if ($classMap) {$loader->addClassMap($classMap);}}
プロジェクトの依存パッケージと、composer installのオプションに依存
デフォルトではComposer.jsonの定義通りのローディングを生成
--optimize-autoloader オプションでクラスマップを強制で生成する
<?php // autoload_static.php @generated by Composer namespace Composer\Autoload;
class ComposerStaticInitbed8ae7d525347237f9cdad08c8930b3 {public static $prefixLengthsPsr4 = array ('T' =>array ('Teto\\' => 5,
),);public static $prefixDirsPsr4 = array ('Teto\\' =>array (
0 => __DIR__ . '/../..' . '/src',),);public static function getInitializer(ClassLoader $loader) {return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitbed8ae7d525347237f9cdad08c8930b3::$prefixLengthsPsr4;$loader->prefixDirsPsr4 = ComposerStaticInitbed8ae7d525347237f9cdad08c8930b3::$prefixDirsPsr4;
}, null, ClassLoader::class);}
}
public static $classMap = array ('Teto\\Object\\Helper' => __DIR__ . '/../..' . '/src/Object/Helper.php','Teto\\Object\\MethodAlias' => __DIR__ . '/../..' . '/src/Object/MethodAlias.php','Teto\\Object\\ObjectArray' => __DIR__ . '/../..' . '/src/Object/ObjectArray.php','Teto\\Object\\PrivateGetter' => __DIR__ . '/../..' . '/src/Object/PrivateGetter.php','Teto\\Object\\PrivateStrictGetter' => __DIR__ . '/../..' . '/src/Object/PrivateStrictGetter.php','Teto\\Object\\PropertyLikeMethod' => __DIR__ . '/../..' . '/src/Object/PropertyLikeMethod.php','Teto\\Object\\ReadOnly' => __DIR__ . '/../..' . '/src/Object/ReadOnly.php','Teto\\Object\\ToArrayInterface' => __DIR__ . '/../..' . '/src/Object/ToArrayInterface.php','Teto\\Object\\TypeAssert' => __DIR__ . '/../..' . '/src/Object/TypeAssert.php','Teto\\Object\\TypeDefinition' => __DIR__ . '/../..' . '/src/Object/TypeDefinition.php','Teto\\Object\\TypedProperty' => __DIR__ . '/../..' . '/src/Object/TypedProperty.php',);
return \Closure::bind(function () use ($loader) {$loader->prefixLengthsPsr4 = ComposerStaticInitbedRy::$prefixLengthsPsr4;$loader->prefixDirsPsr4 = ComposerStaticInitbedRy::$prefixDirsPsr4;$loader->classMap = ComposerStaticInitbedRy::$classMap;
}, null, ClassLoader::class);
クラスローダーの実装本体いままで設定された情報をもとにクラス名の解決・ファイルロードをする
動的に変動する部分は分離されてるので、バイトコードがキャッシュされても特に問題なさそうに作ってるっぽい
spl_autoload_register()で呼ばれる
クラスマップにあったらそれを読む
なかったらpsr-4, psr-2を読む
PEARに依存するなどの場合はinclude_pathを読む
これによって得られる知見
Composerは、いろんなPHPのバージョンとか実行環境で動くように書かれてる
実行時にcomposer.jsonとかcomposer.lockは使ってなくて、静的なPHPスクリプトファイルが書き出される
サーバーでcomposer installの必要はなく、手元でインストールしてvendorディレクトリごと転送してやればいい
よくある誤解
「レンタルサーバーの最安プランでPHP動かしてるからComposer入らないんだ…」といったことはない
本筋から外れた話
Autoloader Optimization
https://getcomposer.org/doc/articles/autoloader-optimization.md
Level 1本番運用時は--optimize-autoloaderが基本
……そのほかは効果あるのかな?(私は使ってないです)
予告
PHP AdventCalendar 2017ではComposerとか全然考慮されてない(zipで配布されてる)SDKをむりやりComposerで管理する話を書きます
