LoginSignup
11
0

Yii Framework 1.1 の autoload を理解する

Last updated at Posted at 2021-12-18

これは「弁護士ドットコムアドベントカレンダー」 の 19 日目の記事です。

18 日目の記事は @tttttt_621_s さんでした。

はじめに

弁護士ドットコムには Yii Framework 1.1(以下、Yii と表現します。) で作られたサービスがいくつかあります。
筆者は普段 Yii で作られたサービスの開発は行っていませんが、TFD という取り組みで開発に携わる機会があります。

本記事では TFD における Dii の導入、 Yii 上に実装されたコードを解析する取り組みにおいてハマった、 Yii の autoload について解説します。

本記事ではまず Composer の autoload について簡単に紹介します。
その後、 Yii 独自の autoload の仕組みについて解説し、最後にハマった事例をどう解決するか(したか)について紹介します。

Composer の autoload

上記は Composer の autoloading についてのドキュメントです。
Composer は依存パッケージマネージャとしての役割に加え、 autoload の仕組みも提供します。
 vendor/autoload.php を生成し、 vendor/autoload.php を利用することで、インストールしたパッケージを特別な手続きなしに利用することができます。

 vendor/autoload.php の先で何が行われているかについて詳細は割愛しますが、大きく以下のことをしています。

  • composer.jsonautoload の値からファイルパスを解決する PHP コードを生成
  • spl_autoload_register を呼び出し、loader を登録

 vendor/autoload.php は Web アプリケーションであれば public/index.php のようなエントリースクリプトで読み込まれますが、現代フレームワークは各々スケルトンパッケージを公開しており、それをそのまま利用する開発者が vendor/autoload.php を読み込ませる機会は滅多にないでしょう。

パッケージ開発においても、以下のようなスケルトンプロジェクトが公開されています。

上記以外にも様々なスケルトンプロジェクトがあります。 Packagist で探してみましょう。

Yii の autoload

Yii は 2008 年の 12 月に 1.0.0 がリリースされました。当時は、 autoload の仕組み自体はあるものの、 Composer のリリース前ということもあり、 Yii 独自の autoload が実装されています。以下が autoload の実装になります。

spl_autoload_register の呼び出し箇所は以下です。

autoload で読み込まれるファイルは以下になります。

  • Yii のコアコンポーネントクラス
  • include_path に登録されたパスに存在する名前空間を持たないクラス
  • パスエイリアスに登録された名前空間を持つクラス
  • classmap に登録されたクラス

動作確認用の簡単な例を以下より引用します。

<?php

use application\controllers\YiiNameSpacedController;
use NaokiTsuchiya\AdventCalendar2021\PSR4NameSpacedController;

require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../vendor/yiisoft/yii/framework/yii.php';

echo class_exists(CController::class) . PHP_EOL; // yii core component

echo get_include_path() . PHP_EOL;

Yii::setPathOfAlias('application', __DIR__ . '/../public/protected');
Yii::import('application.controllers.*');
Yii::import('application.components.*');
Yii::import('application.models.*');
Yii::$classMap['ClassMapController'] = __DIR__ . '/../public/classmaps/ClassMapController.php';

echo get_include_path() . PHP_EOL;

echo class_exists(SiteController::class) . PHP_EOL; // include from include_path
echo class_exists(UserIdentity::class) . PHP_EOL; // include from include_path
echo class_exists(ContactForm::class) . PHP_EOL; // include from include_path
echo class_exists(YiiNameSpacedController::class) . PHP_EOL; // include from alias path
echo class_exists(PSR4NameSpacedController::class) . PHP_EOL; // include from composer autoloader
echo class_exists(ClassMapController::class) . PHP_EOL; // include from classmap

上記では、 PSR4NameSpacedController を除くクラスが Yii の autolode で解決されます。

Yii のコアコンポーネントクラス

CController は Yii のコアコンポーネントクラスです。コアコンポーネントのパスはコード上に定義されており、定義されたパスから読み込みます。

include_path に登録されたパスに存在する名前空間を持たないクラス

SiteControllerUserIdentityContactFormsetPathOfAlias で設定したパス内に存在しています。クラスを呼び出す前に Yii::import('application.controllers.*'); のようにすることで、指定した path を include_path に動的に追加し、 autolode 可能にしています。

パスエイリアスに登録された名前空間を持つクラス

YiiNameSpacedController は名前空間 application\controllers を持ったクラスです。
application\controllersapplication.controllers に内部で変換し、 autolode 可能にしています。

classmap に登録されたクラス

ClassMapControllerclassMap に className => filePath 形式で登録することで autoload 可能になります。

パスエイリアスやインポートなどの仕組みについての公式ドキュメントは以下になります。

ハマった事例とどう解決したか

最後ハマった事例の紹介とそれをどう解決したかについて紹介します。

ファイルの include に失敗し、Warning エラーが発生する

これは Dii の導入時にハマりました。

問題となったのは以下の部分です。

if(self::$enableIncludePath===false)
{
    foreach(self::$_includePaths as $path)
    {
        $classFile=$path.DIRECTORY_SEPARATOR.$className.'.php';
        if(is_file($classFile))
        {
            include($classFile);
            if(YII_DEBUG && basename(realpath($classFile))!==$className.'.php')
                throw new CException(Yii::t('yii','Class name "{class}" does not match class file "{file}".', array(
                    '{class}'=>$className,
                    '{file}'=>$classFile,
                )));
            break;
        }
    }
}
else
    include($className.'.php'); // ファイルが存在しなくても include を実行する

上記のコメントにある通り、存在しないファイルをincludeする実装があります。
これは、class_exists(NotFoundClass::class) とするだけで発生してしまうので、非常に厄介でした。

この記事を執筆するにあたりいろいろ調べた結果からいうと、 Yii::enableIncludePath = false とするだけでよかったのではないかと思いますが、当時はこれを解決するために Yii の autoload を廃止する PR を作ったりしました。

上記の PR は結局マージせず、 直接的な原因となった存在しないアノテーションの warning を抑制する Silent annotation loader を実装して解決しました。

spl_autoload_register がどこで呼び出されているのかずっと見つからなかったのは今となってはいい思い出です :sweat_smile:

Could not read file: T.php

このエラーは、 PHPStan でテンプレートを利用した際に発生しました。こちらのエラーも発生原因は上記の存在しないファイルを include する実装によるものです。

解決方法としては以下の2つの方法があります。

  • Yii::enableIncludePath = false を設定する
  • spl_autoload_unregister([YiiBase::class, 'autolod']) を実行する

後者は Yii の autoload 自体を利用しなくする方法になります。こちらの場合、 composer.jsonclassmap に Yii のコアコンポーネントを読み込むようにするなどの設定が別途必要になります。

Could not read file: CUrlRule.php

こちらも静的解析実行時に発生したものです。CUrlRule は独立したファイルに定義されたクラスではなく、 CUrlManager.php 内に実装されたクラスになります。

こちらは何らかの方法でクラスが実装されたファイルを読み込んでしまえば解決します。

  • Composer の classmap に追加する
  • bootstrap で class_exists(CUrlManager::class) を実行する等

yii.php の代わりに yiilite.php を require する方法にすると上記の設定が不要になりますが、 PHPDoc がない実装を読み込むようになり、 Psalm が MixedAssignment のエラーを大量に吐くようになるため避けるのが無難でしょう。

まとめ

本記事では、 Composer の autolode と Yii の autolode について紹介し、実際にハマった Yii の autoload の事例をどう解決したか紹介しました。

これから Yii のツールを作る人や Yii 上に実装されたコードを解析したい人がツールの導入時に同じことに直面しても解決できるようにと考え、この記事を執筆しました。

明日は、 @blkclct さんです。

11
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
0