[CakePHP] index.phpがあるディレクトリの名前と、index.phpにアクセスするときのURLのパス名が異なる場合、index.phpのWEBROOT_DIRを設定する必要がある。

環境

この記事の内容は、Ubuntu 6.10, Apache 2.0.55, PHP 5.1.6, CakePHP 1.1.12.4205で確認しました。

結論

例えば、cake/app/webrootを/home/foo/public_htmlにコピーして公開するとします。すなわち、CakePHPhttp://example.com/~foo/index.phpというURLでアクセスします。このようにindex.phpが存在するディレクトリの名前(例でいうとpublic_html)と、URLのパス名(例でいうと~foo)が異なる場合、index.phpのWEBROOT_DIR定数を設定する必要があります(例でいうと、'~foo'にします)。

詳細

CakePHPは、cake/dispatcher.phpのDispatcher::baseUrlというメソッドで、自分のURLを導出しています。

<?php
/**
 * Returns a base URL.
 *
 * @return string   Base URL
 */
    function baseUrl() {
        $htaccess = null;
        $base = $this->admin;
        $this->webroot = '';

        if (defined('BASE_URL')) {
            $base = BASE_URL.$this->admin;
        }

        $docRoot = env('DOCUMENT_ROOT');
        $scriptName = env('PHP_SELF');
        $r = null;
        $appDirName = str_replace('/', '\/', preg_quote(APP_DIR));
        $webrootDirName = str_replace('/', '\/', preg_quote(WEBROOT_DIR));

        if (preg_match('/'.$appDirName.'\\'.DS.$webrootDirName.'/', $docRoot)) {
            $this->webroot = '/';

            if (preg_match('/^(.*)\/index\.php$/', $scriptName, $r)) {

                if(!empty($r[1])) {
                    return  $base.$r[1];
                }
            }
        } else {
            if (defined('BASE_URL')) {
                $webroot = setUri();
                $htaccess = preg_replace('/(?:'.APP_DIR.'(.*)|index\\.php(.*))/i', '', $webroot).APP_DIR.'/'.$webrootDirName.'/';
            }

            if (preg_match('/^(.*)\\/'.$appDirName.'\\/'.$webrootDirName.'\\/index\\.php$/', $scriptName, $regs)) {

                if(APP_DIR === 'app') {
                    $appDir = null;
                } else {
                    $appDir = '/'.APP_DIR;
                }
                !empty($htaccess)? $this->webroot = $htaccess : $this->webroot = $regs[1].$appDir.'/';
                return  $base.$regs[1].$appDir;

            } elseif (preg_match('/^(.*)\\/'.$webrootDirName.'([^\/i]*)|index\\\.php$/', $scriptName, $regs)) {
                !empty($htaccess)? $this->webroot = $htaccess : $this->webroot = $regs[0].'/';
                return  $base.$regs[0];

            } else {
                !empty($htaccess)? $this->webroot = $htaccess : $this->webroot = '/';
                return $base;
            }
        }
        return $base;
    }
?>

メソッドの前のコメントには「URLを返す」とありますが、実際にはそれ以外にも自分のwebrootメンバーを設定しています。このwebrootメンバーが、このあとコントローラや、末端のHtmlヘルパーまで伝わります。さて、上のままでは複雑なので、webrootに関するコード以外を削除します。BASE_URLという定数もありますが、.htaccessを使用していればこの定数は設定されないので、これも削除します。BASE_URLが設定されないと、$htaccessという変数も使われないので、これも削除します。すると、以下のようになります。

<?php
    function baseUrl() {
        $docRoot = env('DOCUMENT_ROOT');
        $scriptName = env('PHP_SELF');
        $appDirName = str_replace('/', '\/', preg_quote(APP_DIR));
        $webrootDirName = str_replace('/', '\/', preg_quote(WEBROOT_DIR));

        if (preg_match('/'.$appDirName.'\\'.DS.$webrootDirName.'/', $docRoot)) {
            # (1)
            $this->webroot = '/';
        } else {
            if (preg_match('/^(.*)\\/'.$appDirName.'\\/'.$webrootDirName.'\\/index\\.php$/', $scriptName, $regs)) {
                # (2)
                if(APP_DIR === 'app') {
                    $appDir = null;
                } else {
                    $appDir = '/'.APP_DIR;
                }
                $this->webroot = $regs[1].$appDir.'/';
            } elseif (preg_match('/^(.*)\\/'.$webrootDirName.'([^\/i]*)|index\\\.php$/', $scriptName, $regs)) {
                # (3)
                $this->webroot = $regs[0].'/';
            } else {
                # (4)
                $this->webroot = '/';
            }
        }
    }
?>

URLを判定している箇所を、順に見ていきます。

(1)では、cake/app/webrootがドキュメントルートになっていないか調べています。この場合、webrootは"/"になります。

(2)では、アクセスしているスクリプトがapp/webroot/index.phpではないか調べています。CakePHPをいわゆる開発用にインストールし、/foo/app/webroot/index.phpのようにアクセスする場合が、これに該当すると思います。このとき、webrootは"/foo"になります。.htaccessによるURL書き換えがあるので、"/foo/app/webroot"にはしません。

(3)では、アクセスしているスクリプトが/foo/webrootにないか調べています(おそらく。'([^\/i]*)|index\\\.php$/'を追加している理由が分かりません)。app/webrootを切り離して、いわゆる公開用に設定している場合が、これに当たると思います。このとき、webrootは"/foo/webroot"になります。

(4)は、どれでもない場合です。webrootは"/"にしていますが、おそらくここには到達しないと想定されているような気がします。

さて、(3)で比較しているのは、$webrootDirNameと$scriptNameです。これらは各々、

<?php
        $scriptName = env('PHP_SELF');
        $webrootDirName = str_replace('/', '\/', preg_quote(WEBROOT_DIR));
?>

として設定されています。ここで、env関数はcake/basics.phpで定義されている関数で、指定された環境変数の値を返します。PHP_SELFの場合、/~foo/cake/app/webroot/index.phpのように、URLのパスとなります。一方、WEBROOT_DIR定数は、index.phpで設定される定数で、

<?php
    if (!defined('WEBROOT_DIR')) {
         define('WEBROOT_DIR', basename(dirname(__FILE__)));
    }
?>

のように、__FILE__定数から求められています。ここで、__FILE__は、index.phpが設置されているパスで、/home/foo/cake/app/webroot/index.phpのようになります。すなわち、$scriptNameと$webrootDirNameは異なるものであり、一致しない場合が出てきます。例えば、cake/app/webrootをcake/appから切り離して、/home/foo/public_htmlとする場合です。このとき、$scriptNameは/~foo/index.phpとなり、$webrootDirNameはpublic_htmlとなります。そのため、(3)では該当せず(4)となり、スタイルシートが適用されないなどの問題が発生します。これを解決するためには、WEBROOT_DIRをURLのパス名に変更します。すなわち、

<?php
    if (!defined('WEBROOT_DIR')) {
         define('WEBROOT_DIR', '~foo');
    }
?>

などとします。

問題点

app/webroot/index.phpには、

<?php
///////////////////////////////
//DO NOT EDIT BELOW THIS LINE//
///////////////////////////////
    if (!defined('WEBROOT_DIR')) {
         define('WEBROOT_DIR', basename(dirname(__FILE__)));
    }
?>

とあるのですが、"DO NOT EDIT BELOW THIS LINE"って、「ここから下はいじるな」という意味じゃないんでしょうか...?