ZendFrameworkのindexにあるグローバルな奴らを考える

ZendFrameworkはindex.phpでフロントコントローラーを作って、そこからリクエスト内容を見てコントローラー・アクションを分けていくんですが、index.phpに色んな設定を書いていくとグローバル変数が何個もできます。

 
< ?php
// Zend_Session
Zend_Session::start();
 
$admin = new Zend_Session_Namespace('admin');
Zend_Registry::set('admin', $admin);
 
// Zend_Config
$config = new Zend_Config_Ini($dir . '/config/config.ini', 'production');
Zend_Registry::set('config', $config);
 
// Zend_Layout
Zend_Layout::startMvc(array('layoutPath' => $dir . '/layouts'));
 
// Zend_Db
$dbAdapter = Zend_Db::factory(Zend_Registry::get('config')->database);
Zend_Registry::set('dbAdapter', $dbAdapter);
 
/**
 * Setup Controller
 */
$front = Zend_Controller_Front::getInstance();
$front->addModuleDirectory($dir . '/modules');
$front->throwExceptions(true);
// run
$front->dispatch();
 

Zend_Registryでどこからでもアクセスできるようにはしてるけど、グローバル変数が残ってることにはかわりない。

起動ファイルのコードのスコープ - いしなお! (2007-06-06)

ここにも書いてある。なんか気持ち悪い。

で、bootstrap.phpを作ってindex.phpから読み込むことにする。

ディレクトリ構造はこんな感じ

Directory

index.phpはbootstrap.phpを読み込むだけにするので

 
< ?php
set_include_path( /* path */ );
require_once "../application/bootstrap.php";
Bootstrap::init();
 

とかだけにしておく。

boostrapの中でクラスを作ってそこで設定を初期化する。

 
< ?php
/**
 * Bootstrap Class
 */
class Bootstrap {
 
    static public function init()
    {
        // なんか初期化的な処理をゴリゴリ
    }
 
}
 

Bootstrapの中でも処理ごとにメソッドをわけておいたほうが後から変更するときに分かりやすくていいかな。

ZendFramework::Zend_Loader

ZendFramework::Zend_Loader を読む

Zend_Controllerを読もうとしたけど色々他のコンポーネントを読み込んでるみたいなのでそっちからとりあえず読んでいく。
でもすぐにver1.5が公開されそう。すでにver1.5RC1が出てるし。

RCを読む気しないので1.0.4。出たら出たでマニュアルに変更点が書かれるだろうから諦める。

loadClass()

public static function loadClass($class, $dirs = null)
{

$classが探すクラス名、$dirsが探すディレクトリのパス

if (class_exists($class, false) || interface_exists($class, false)) {
    return;
}

if ((null !== $dirs) && !is_string($dirs) && !is_array($dirs)) {
    require_once 'Zend/Exception.php';
    throw new Zend_Exception('Directory argument must be a string or an array');
}

$classが存在するclassやinterfaceで定義済みなら終了
$dirsがnullか、文字列か配列のどれでもないと終了

if (null === $dirs) {
    $dirs = array();
}
if (is_string($dirs)) {
    $dirs = (array) $dirs;
}

// autodiscover the path from the class name
$path = str_replace('_', DIRECTORY_SEPARATOR, $class);
if ($path != $class) {
    // use the autodiscovered path
    $dirPath = dirname($path);
    if (0 == count($dirs)) {
        $dirs = array($dirPath);
    } else {
        foreach ($dirs as $key => $dir) {
            if ($dir == '.') {
                $dirs[$key] = $dirPath;
            } else {
                $dir = rtrim($dir, '\\/');
                $dirs[$key] = $dir . DIRECTORY_SEPARATOR . $dirPath;
            }
        }
    }
    $file = basename($path) . '.php';
} else {
    $file = $class . '.php';
}

最初の5行ぐらいはどうでもいい。

DIRECTORY_SEPARATORを「_」で置き換える。Zend_Hoge.phpだとZend/Hoge.php
$pathが$classと同じでないなら(つまり、$classにHogeClassとかが渡ると $path == $class になる。この場合$fileにHogeClass.phpが格納される)条件文の中に突入。

$dirPathにはZend_Hogeだとすると、dirname('Zend/Hoge')でZend。
foreachの部分は$dirsが配列の場合。

説明しにくい。要するに

$dirs = array(
	'/home/hoge1',
	'/home/hoge2',
	'.'
);

こういうのが渡ると、

Array
(
    [0] => /home/hoge1/Zned
    [1] => /home/hoge2/Zned
    [2] => Zned
)

こういうふうに返ってくる。(ちょっと上に書いた$dirPathの場合)

で、あとは拡張子を一緒にしてloadFile()に渡す。

loadFile()

/**
 * @param  string        $filename
 * @param  string|array  $dirs - OPTIONAL either a path or array of paths
 *                       to search.
 * @param  boolean       $once
 * @return boolean
 * @throws Zend_Exception
 */
public static function loadFile($filename, $dirs = null, $once = false)
{

$filenameに拡張子までを含めたファイル名、$dirsに文字列か配列でパスを渡す、$onceがtrueだとincludeがinclude_onceになる

if (preg_match('/[^a-z0-9\-_.]/i', $filename)) {
    require_once 'Zend/Exception.php';
    throw new Zend_Exception('Security check: Illegal character in filename');
}

ファイル名のチェック。セキュリティチェック。
ファイル名に、a-z0-9\-_. 以外の文字が含まれているとZend_Exceptionに投げられて終了。

if (is_null($dirs)) {
    $dirs = array();
} elseif (is_string($dirs))  {
    $dirs = explode(PATH_SEPARATOR, $dirs);
}
foreach ($dirs as $dir) {
    $filespec = rtrim($dir, '\\/') . DIRECTORY_SEPARATOR . $filename;
    if (self::isReadable($filespec)) {
        return self::_includeFile($filespec, $once);
    }
}

$dirsの中身探す。

30分ぐらい

$dirs = explode(PATH_SEPARATOR, $dirs);

の部分の存在理由がわからなかった。
$dirsの中にどうやったら「;」とか入るんだよって思ってたけど、$dirsがnullだとinclude_pathを読み込むからか。

で、パスとファイル名を合体させてからパスごとにisReadableに渡してtrueが返ってきたらinclude

if (self::isReadable($filename)) {
    return self::_includeFile($filename, $once);
}

上のループでファイルが見つからなかった場合、あるいは$dirsに何も渡さなかった場合にinclude_pathから探してくる。
それでも見つからなかったらZend_Exceptionさんに助けてもらう

_includeFile()

protected static function _includeFile($filespec, $once = false)
{
    if ($once) {
        return include_once $filespec;
    } else {
        return include $filespec ;
    }
}

特に何もない。
$filespecがZend/Hoge/hoge.php、$once = trueだと、require_once 'Zend/Hoge/hoge.php'が返される。

isReadable()

public static function isReadable($filename)
{
    if (!$fh = @fopen($filename, 'r', true)) {
        return false;
    }

    return true;
}

ファイルをオープンできなければfalse

autoLoad() registerAutoload()

SPLがどうのこうのって書いてあるけど、SPLわかんないのでよくわからん。
飛ばす。

それにしても書くのに時間がすげぇかかる。
Zend_Loaderなんて250行ぐらいしかないのに。Controllerとかどうするべ。
続くのかどうか不明。

ZendFramework::Zend_Debug

var_dumpそのままだと思って使ったら15分ぐらい迷った。

Zend_Debug::dump($var, $label=null, $echo=true);

$varにデバッグしたい変数、$labelが$varの前に表示させたいやつを、で、$echoがfalseならechoされないでreturnで返すだけ。

普通に変数渡せばいいだろうと思って

$a = array(
	'hoge1',
	'hoge2',
	10
);

Zend_Debug::dump($a);

って書いたら

 <pre>
<b>array</b>
  0 <font color='#888a85'>=&gt;</font> <small>string</small> <font color='#cc0000'>'hoge1'</font> <i>(length=5)</i>
  1 <font color='#888a85'>=&gt;</font> <small>string</small> <font color='#cc0000'>'hoge2'</font> <i>(length=5)</i>
  2 <font color='#888a85'>=&gt;</font> <small>int</small> <font color='#4e9a06'>10</font>
</pre>

htmlがそのまま表示されちまフ。

ソース見るとhtmlspecialcharsでエスケープされてる。

$a = array(
	'hoge1',
	'hoge2',
	10
	);

$b = htmlspe Zend_Debug::dump($a, '', false);

echo htmlspecialchars_decode($b);

デコードすればいいけど、普通にvar_dumpでいいような。
dumpの前に

Zend_Debug::setSapi('cli');

ってやればエスケープされないけど、Sapiってなんだ。なんだSapiって。

ZendFramework::Zend_Db(2)

プレースホルダを利用する

try {

	$dbConfig = new Zend_Config_Int('Config/config.ini', 'database');
	$db = Zend_Db::factory($dbConfig);

	$stt = $db->prepare('INSERT INTO Book(isbn, title, price, publish, pulished) VALUES(:isbn, :title, :price, :pulish, :published)');

	$stt->bindParam(':isbn', $_POST['isbn']);
	$stt->bindParam(':title', $_POST['title']);
	$stt->bindParam(':price', $_POST['price']);
	$stt->bindParam(':publish', $_POST['publish']);
	$stt->bindParam(':published', $_POST['published']);
	※今はセキュリティとかなしで

	$stt->execute();

} catch (Zend_Exception $e) {
	die($e->getMessage());
}

bindParamの代わりに

$stt->execute(array(':isbn' => $_POST['isbn'], ...));

でもいい。

$arr = array(
	':isbn' => '0123456789',
	':title' => 'php book',
	':price' => '980'
	...
	...
);

$stt->execute($arr);

これも一緒。

bindValue()とbindParam()があるけど、ソースを見ると

public function bindValue($parameter, $value, $type = null)
{
    return $this->bindParam($parameter, $value);
}

とかなってて必要なのかよくわからない

ZendFramework::Zend_Db(1)

接続


require_once 'Zend/Db.php';

$db = new Zend_Db_Adapter_Pdo_Mysql(array(
	'host' => 'localhost',
	'username' => 'root',
	'password' => '',
	'dbname' => 'drill'
));

$select = $db->select('Address')
	->from(
		'Customers',
		array(
			'Address'
			)
		);

$db->query('set names utf8' );

$res =  $db->fetchAll($select);

set namesしないと文字化けった。

Zend_Config使うと、


require_once 'Zend/Config.php';
require_once 'Zend/Db.php';

$config = new Zend_Config(
    array(
        'database' => array(
            'adapter' => 'pdo_mysql',
            'params' => array(
		'host' => 'localhost',
                'dbname' => 'drill',
                'username' => 'root',
                'password' => ''
            )
        )
    )
);

$db = Zend_Db::factory($config->database);

とかやる。


require_once 'Zend/Db.php';

$params = array(
	'host' => 'localhost',
	'username' => 'root',
	'password' => '',
	'dbname' => 'drill'
	);

$db = Zend_Db::factory('pdo_mysql', $params);

こんなんでもいい。

ZendFramework的にはどれを使うべきなんだろう。

個人的にはZend_Configで別ファイルの設定を読み込むのがいい。上に書いてないけど。


echo $select->__toString();  // SELECT `Customers`.`Address` FROM `Customers`

とやると発行するクエリが表示される。