Skip to content

Composer解体新書 (実行時篇)

公開日:

札幌市中央区株式会社インフィニットループで開催された『PHPカンファレンス北海道出張版(仮) PHPオフ会』でレギュラーセッション(30分)として発表しました。

Download PDF

スライドテキスト

Page 1

Composer解体新書

Anatomy of Composer, in Runtime

PHPカンファレンス北海道出張版(仮)

PHPオフ会 #phpcondo2017

Page 2

お前誰よ

  • うさみけんた (@tadsan) / Zonu.EXE
  • GitHub/Packagistでは id: zonuexe
  • 砂川出身、北海道工業大学
  • 2012年11月にピクシブ株式会社に入社
  • Emacs Lisper, PHPer
  • 入社前は自宅警備をしながらRuby書いてた
  • Qiitaに記事を書いたり変なコメントしてるよ
  • 2017年、PHPカンファレンス全部でしゃべった

Page 3

Page 4

Composer使ってますか?

Page 5

Dependency Manager for PHP

Page 6

A. 使ってないB. FW経由でC. 直接使ってる

Page 7

分かれそう

Page 8

本日しない話

Page 9

PHPのクラスのオートローディングの説明

Page 10

プロジェクトでのComposerの運用方法

Page 11

過去の発表を読んでください

Page 12

https://niconare.nicovideo.jp/watch/kn1371

Page 13

Page 14

https://qiita.com/tadsan/items/86099d44d12f1103b0a0

Page 15

https://qiita.com/tadsan/items/a78c9160418200e2d47a

Page 16

Composerでできること

Page 17

ざっくり

Page 18

プロジェクトが依存するパッケージをダウンロードしてくる

Page 19

プロジェクトが依存するパッケージを利用可能にする

Page 20

今日は実行時の話をします

Page 21

基本はComposerのautoload.phpを読み込めば準備完了する

Page 22

Page 23

復習

Page 24

PHPスクリプトの読み込みかた

Page 25

require, require_once include, include_once

Page 26

場合によるけど基本だいたい同じ

Page 27

どこから?

Page 28

A. ファイルシステムの絶対パス

B. include_pathからの相対パスC. プロトコル (ラッパー)

Page 29

ファイルシステムの絶対パス

require '/path/to/file.php';

ファイルシステム内のフルパス

require __DIR__.'/../Klass.php';マジック定数__DIR__でスクリプトのディレクトリが入る

Page 30

include_pathからの相対パス

include_pathにディレクトリを追加する

だいたいデフォルトに "." が入ってる

require 'Hoge.php';

require 'Fuga/Piyo.php';

ディレクトリ内も探索される

Page 31

プロトコル(ラッパー)

fopen('php://stderr', 'r') とかfile_get_contents('http://example.com/')とかやるための仕組み

……が、セキュリティの懸念もあるので、デフォルトでは php.ini でallow_url_include = Off になってる

Page 32

改めてComposer

Page 33

オートロード機能ついてる

Page 34

Page 35

どうやって読み込んでるの?

Page 36

autoload.phpを読もう

Page 37

みなさまの手元のComposerプロジェクトを見比べながらお聞きください

Page 38

% composer --version Composer version 1.5.2 2017-09-11 16:59:25% composer install --no-dev --prefer-dist

Page 39

vendor/autoload.php

<?php

// autoload.php @generated by Composer require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitbed8ae7d525347237f9cdad08c8930b3::getLoader();

Page 40

vendor/autoload.php

1.autoload_real.phpを読み込む2.謎のクラスの静的メソッドを実行

たぶん変なバイトキャッシュを残さないためにcontent-hashが付いたクラス名

Page 41

Page 42

autoload_real.php

1.$loaderが生成済みなら返すだけ2.クラスローダーを読み込むだけのクラス

ローダーを登録して、使ったらすぐにspl_autoload_unregister する

  • なんか環境依存の

ワークアラウンドっぽい気がする

Page 43

autoload_real.php

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'));

Page 44

autoload_real.php

1.PHPの実行時環境によってクラス定義の読み込みを変更

2.autoload_static.php VS 三兄弟

  • PHP5.6以上で、HHVMじゃなくて、

zend_loader_file_encoded()が無効ならautoload_staticが利用される

Page 45

autoload_real.php

$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);}}

Page 46

autoload_static.php

プロジェクトの依存パッケージと、composer installのオプションに依存

デフォルトではComposer.jsonの定義通りのローディングを生成

--optimize-autoloader オプションでクラスマップを強制で生成する

Page 47

autoload_static.php (PSR-4のみ)

<?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);}

}

Page 48

autoload_static.php (optimized)

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',);

Page 49

autoload_static.phpの大事なとこ

Closure::bind()でスコープ束縛して、

メソッド呼び出しをけちりながら

privateメンバーにセットしてる

return \Closure::bind(function () use ($loader) {$loader->prefixLengthsPsr4 = ComposerStaticInitbedRy::$prefixLengthsPsr4;$loader->prefixDirsPsr4 = ComposerStaticInitbedRy::$prefixDirsPsr4;$loader->classMap = ComposerStaticInitbedRy::$classMap;

}, null, ClassLoader::class);

Page 50

ClassLoader.php

クラスローダーの実装本体いままで設定された情報をもとにクラス名の解決・ファイルロードをする

動的に変動する部分は分離されてるので、バイトコードがキャッシュされても特に問題なさそうに作ってるっぽい

Page 51

ClassLoader.php

spl_autoload_register()で呼ばれる

クラスマップにあったらそれを読む

なかったらpsr-4, psr-2を読む

PEARに依存するなどの場合はinclude_pathを読む

Page 52

これによって得られる知見

Page 53

Composerは、いろんなPHPのバージョンとか実行環境で動くように書かれてる

Page 54

実行時にcomposer.jsonとかcomposer.lockは使ってなくて、静的なPHPスクリプトファイルが書き出される

Page 55

サーバーでcomposer installの必要はなく、手元でインストールしてvendorディレクトリごと転送してやればいい

Page 56

よくある誤解

Page 57

「レンタルサーバーの最安プランでPHP動かしてるからComposer入らないんだ…」といったことはない

Page 58

本筋から外れた話

Page 59

Autoloader Optimization

https://getcomposer.org/doc/articles/autoloader-optimization.md

Page 60

Level 1本番運用時は--optimize-autoloaderが基本

Page 61

……そのほかは効果あるのかな?(私は使ってないです)

Page 62

予告

Page 63

PHP AdventCalendar 2017ではComposerとか全然考慮されてない(zipで配布されてる)SDKをむりやりComposerで管理する話を書きます