スポンサーサイト

一定期間更新がないため広告を表示しています

PEARをcomposerで入れて、オートロードの仕組みを調査してみた

JUGEMテーマ:PHP
 

composerでPEARを入れて、オートロードの仕組みを調査してみた

仕事のコードがとても歴史あるプロジェクトで、
時代の流れもあってもPEARをがっつり使っているんだけど、
そこで色々困ったことがあるのでcomposer化したいという話。


困っていること
 
  • PEARに依存したアプリケーションコードが他のシステムへ持っていけない。
    • PEAR依存しているコード=システムに依存している。
    • 他のシステムにも同じPEARを入れればもちろん動くけど、バージョン依存関係を自分で解決するのが面倒くさい。
  • ロジックがリポジトリ内で閉じてないため、アプリケーションコードの見通しが悪い。
  • PEARのバージョン更新などはインフラチームに依頼しないといけない。
  • 実はシステム下以外にもあちこちにPEARコードがコピペで置かれまくっているので一元管理したい・・・(本当にひどい話
    • しかもPEARのソースコード内にはバージョン情報が書かれていないので、PEARの管理下以外にコピペで置かれたやつらはもうバージョン不明・・・何らかの仕組みでバージョン管理できるようにしたい(本当にひどい話。

太字の所が、そのままcomposerのようなバージョン管理ツールを使う理由なのかな、と思います。


composerでPEARを入れてみる

大したことはしてなくって、調べればいくらでも出てくる情報。
まずはcomposer.jsonにPEARリポジトリの定義と、
使用するPEARパッケージをrequireに記述する。
 
{
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear.php.net/"
        }
    ],
    "require": {
        "pear-pear.php.net/DB" : "1.7.4"
    },
}

その状態でcomposer update
 

php composer.phar update


これでvendor配下のpear-pear.php.netディレクトリにパッケージが入る。
使用するにはcomposerのautoloaderを使えばよく、以下のコードでもう使えてしまう。
 
require_once __DIR__ . '/vendor/autoload.php';

$dsn = 'mysqli://user:password@localhost/database_name';
$db =& DB::connect($dsn);
var_dump($db);


何故オートロードできるのか?
 
  1. 名前空間もなく、PSR-0にも準拠していないPEARが、何故composerのオートロードの仕組みに乗れるのか?
  2. またPEARパッケージ間にも依存関係で、お互いにrequire_onceしまくってるはずなのに問題ないのは何故なのか?

そこが気になるので、今回調べてみました。
comopserのdocumentationやコードを調べてみたところ、以下の仕組みで担保されていた。
https://getcomposer.org/doc/04-schema.md#include-path
簡単に言っちゃえば、PEARパッケージの数だけinclude_pathを追加しちゃう様子。

まずcomposerのclassmapの仕組みによって、各クラスが直接呼び出せるようになっている。
(classmapとは、composerが対象のパッケージの中身をスキャンして全クラスを抽出、そのクラスと定義されているファイルパスを結び付けてオートロードを実現する仕組み)
vendor/composer/autoload_classmap.phpを覗いてみると・・・。
 
'DB' => $vendorDir . '/pear-pear.php.net/DB/DB.php',
'DB_Error' => $vendorDir . '/pear-pear.php.net/DB/DB.php',
'DB_common' => $vendorDir . '/pear-pear.php.net/DB/DB/common.php',
'DB_dbase' => $vendorDir . '/pear-pear.php.net/DB/DB/dbase.php',
'DB_fbsql' => $vendorDir . '/pear-pear.php.net/DB/DB/fbsql.php',
'DB_ibase' => $vendorDir . '/pear-pear.php.net/DB/DB/ibase.php',
'DB_ifx' => $vendorDir . '/pear-pear.php.net/DB/DB/ifx.php',
'DB_msql' => $vendorDir . '/pear-pear.php.net/DB/DB/msql.php',
'DB_mssql' => $vendorDir . '/pear-pear.php.net/DB/DB/mssql.php',
'DB_mysql' => $vendorDir . '/pear-pear.php.net/DB/DB/mysql.php',
'DB_mysqli' => $vendorDir . '/pear-pear.php.net/DB/DB/mysqli.php',
'DB_oci8' => $vendorDir . '/pear-pear.php.net/DB/DB/oci8.php',
'DB_odbc' => $vendorDir . '/pear-pear.php.net/DB/DB/odbc.php',
'DB_pgsql' => $vendorDir . '/pear-pear.php.net/DB/DB/pgsql.php',
'DB_result' => $vendorDir . '/pear-pear.php.net/DB/DB.php',
'DB_row' => $vendorDir . '/pear-pear.php.net/DB/DB.php',
'DB_sqlite' => $vendorDir . '/pear-pear.php.net/DB/DB/sqlite.php',
'DB_storage' => $vendorDir . '/pear-pear.php.net/DB/DB/storage.php',
'DB_sybase' => $vendorDir . '/pear-pear.php.net/DB/DB/sybase.php',

こんな感じで、PEAR::DBパッケージの全てのクラスとファイルパスが定義されている。

ただこれだと上記1.の問題は解決できるが、2.の問題は解決できない。
PEAR::DB内のphpファイルではあちこちで
require_once 'PEAR.php' とか、
require_once 'DB/common.php' とかしてPEARの他ファイルをrequireしてしまっている。
こいつらはvendor/pear-pear.php.net/配下においてあるので、
include_pathが通ってなければこれらのrequire_onceは失敗する。

そこでさっきのcomposerのinclude-pathの仕組み。
ただただ単純に、パッケージの数だけinclude_pathを追加しまくる。
vendor/composer/include_paths.phpを覗いてみると・・・。
 
return array(
    $vendorDir . '/pear-pear.php.net/Console_Getopt',
    $vendorDir . '/pear-pear.php.net/Archive_Tar',
    $vendorDir . '/pear-pear.php.net/Structures_Graph',
    $vendorDir . '/pear-pear.php.net/XML_Util',
    $vendorDir . '/pear-pear.php.net/PEAR',
    $vendorDir . '/pear-pear.php.net/DB',
);

と、追加するinclude_pathがarrayで定義されていて、
次にvendor/composer/autoload_real.phpを覗いてみると・・・。
 
$includePaths = require __DIR__ . '/include_paths.php';
array_push($includePaths, get_include_path());
set_include_path(join(PATH_SEPARATOR, $includePaths));

と、set_include_pathメソッドでinclude_pathを突っ込みまくる。
この状態でget_include_pathメソッドとかで今のinclude_path定義を見ると、
数がすごいことになっていました(白目
この仕組みはcomposerのdocumentationにも書いてあるように、
レガシーなプロジェクト向けに存在しているので、極力使うべきではない。

 
DEPRECATED: This is only present to support legacy projects, and all new code should preferably use autoloading. As such it is a deprecated practice, but the feature itself will not likely disappear from Composer.


またコードを読んで気づいたこととして、
デフォルトのinclude_pathよりも高い優先度としてPEARのinclude_pathを突っ込んじゃうんですよね。
だから、仮にPEAR内部のphpファイルと同名ファイルが自分のアプリケーションコードにあって、
相対パスでrequire_onceするとPEARの方をrequireしちゃうことがあり得るってことですよね。
これは怖い・・・。

回避策としては絶対パスでrequireするか、
なるべくautoloadで他ファイルを参照するか、
そもそもcomposerのinclude-pathに頼らないようになんとかするか・・・。

スポンサーサイト

コメント