App::import() は凄い

CakePHP Advent Calendar2010、残りちょうど10日となりました。
15日目担当のひろみです。よろしくお願いします。

いんとろだくしょん

どんなCakeアプリケーションでも使うといっても過言では無いのがApp::import()です。
直接使うことはなくても、ヘルパーやコンポーネントをコントローラで指定すると、間接的にApp::import()を使っていることになります。(もっと言えば、dispatcherを呼ぶだけでApp::import()は呼ばれますが・・・)
そんな名脇役、App::import()について、既知の事実も含めて、詳しい挙動を追いかけてみましょう。

App::import()の歴史

CakePHP1.1までは今となっては懐かしい、uses()やvendor()を使っていました。
ヘルパやコンポーネント、モデルの読み込みすらも、グローバル関数を使って読み込みをしていたのです。
ソースコメントによると、これらをまとめてAppクラスに移行したのは、CakePHP1.2.0.6001からとなっています。
それからは、(今でもほんの一部uses()などを使用している箇所がコアコードに存在してるとはいえ、)全面的にApp::import()を使うように推奨されてきました。
CakePHP1.3からは、古いグローバル関数は公式に非推奨となり、CakePHP2.0では削除されると宣言されています。(移行ガイドなど参照)

Appクラスのコードの場所

cake/libs/ ディレクトリを見ても、app.phpなんてないよ、あれ?となるかもしれません(自分はなりました)。
実はこっそりとconfigure.phpに、Configureクラスの定義と共に存在しています。
これは、ConfigureとAppの依存関係と、ConfigureからAppへ機能が遷移していったことが(多分)関係しています。

App::import()の挙動

さて、ようやく実際の挙動についてです。
以下、基本的にCakePHP1.3での解説になります。
1.2では違う挙動になることがあるかもしれませんが、ご容赦ください。

引数

App::import()は以下の最大6つの引数を取ることができます。

  • $type = null
  • $name = null
  • $parent = true
  • $search = array()
  • $file = null
  • $return = false

このうちよく使うのは$type, $name, $parentの3つぐらいでしょう。
$typeには、ライブラリのタイプを指定します。'Controller'や、'Core'、'Lib'(1.3から)などが指定できます。
これを省略して、

<?php
App::import('File');

と書くこともできます。この場合、$typeには'Core'がデフォルトとして指定されます。
また、以下のように第一引数、第三引数には連想配列による指定が可能です。

<?php
App::import(array(
	'name' => 'File',
	'return' => true,
));

App::import(
	'Lib',
	'MyPlugin.MyLibrary',
	array(
		'return' => true,
	)
);

・$parent
falseを指定すると親クラス(AppModelなど)を読み込みません。
・$search
探索するディレクトリをランタイムで指定できます。PEARなどを呼ぶ時に便利かもしれません。
・$file
$nameから自動的にファイル名を決定せず、直接指定するときに使います。
主に外部ライブラリのファイル名がcakephp標準になってないときに指定します。
$return
true/falseでなく、include()の返り値を返すかどうかを指定します。
この場合、読込先のファイルでは return ***;を指定する必要があります。(省略すると読み込みに失敗という結果になるでしょう。)

再帰的な読み込み

App::import()は再帰的にディレクトリを探索します。
この機能により、

app/
	controllers/
		admin/
			acl_controller.php
		member/
			posts/
				posts_controller.php
				post_comments_conroller.php

などといったディレクトリを切っても、正常に読み込まれます。
コンポーネントやヘルパーの分類をすることも容易ですね。


また、例外的にapp直下にあるものは再帰的に探索しません。
デフォルトでapp直下を探索するものは、

  • Controller
  • Model
  • Helper

の3種類です。
これに関して、例えば、users_controller.phpを設置すると、App::import('Controller', 'Users')で読み込むことができてしまいます
ファイル名がモデルとヘルパで衝突するなど、望くない結果になることもあるので、app直下にapp_***.php以外のファイルは極力置かないようにしましょう。

app > cake
  • AppControllerって作ってないのに継承できるよね。でも、APP/app_controller.phpを作ってもその中身継承してくれるし・・・どういうこと?
  • app/views/layouts/default.ctpにcake/libs/views/layouts/default.ctpをコピーして書き換えたら動作した。なんでこうなるん?

以上のような疑問をもった方は多数いらっしゃると思います。
これはApp::import()が探索するパスが、あらかじめデフォルトでいくつか設定されることによるマジックです。
例えば、App::controllersというプロパティがあります。

      'ROOT/app/controllers/'
      'ROOT/app/'
      'ROOT/cake/libs/controller/'

ダンプするとこのようになっています。
これらを順に探索してゆくので、結果的にappに入っているほうを優先的に読み込むことになります。
app/libsディレクトリも例外ではなく、これによってコアライブラリの代わりに任意のライブラリを読むことも出来ます。(なるべくしないほうが身のためです)。
これに関連して、view.phpをviews/以下のどこに置いてもViewクラスの代わりに読み込んでしまいます。
なので、ビューテンプレートの拡張子はphpになりえないのです。
間違ってview.ctpをview.phpと書いてしまうと、Class View not foundといってマジギレされます。注意しましょう。


また、上に挙げたパスは、App::build()で任意のパスを追加することができます。
追加されたパスは、優先順位がデフォルトのものより高いです。
つまり、既存のファイルを変更したくない場合に別ディレクトリに読み込み先を変えることもできます。

overload

PHP4対応の為、overload()をする必要がある場合、App::import()は自動的にこれをしてくれます。
include()やrequire()などで読み込むと、これがスキップされるため、App::import()を通したほうがいいという場面が存在するでしょう。

最後に

今回はプラグインについての説明は省略させていただきました。挙動はプラグインでないものとあまり変わりはしないはずです。(本当かな・・・)


App::import()は非常に強力な反面、意外な落とし道にハマったり、余計なことをしてくれることがあるかもしれません。
また、パフォーマンス的には多少重くなるでしょう。これについては、色々な意見があると思います。(cacheをmemcachedにすれば超早いよ、とか)
もちろん、App::import()は標準的な使い方でも十分に威力を発揮します。
しかし、ここまで強力な機能が備わっているのなら、簡単な使い方だけでパフォーマンスを食うとなると、もったいないですよね。
是非、その機能を理解したうえで使い倒してみましょう!楽しいですよ。


さて、明日の担当は、aerithさんです。よろしくお願いします!!