そうだ、コアを読もう2 -ZendFramework-
昨日は CakePHP だったが、今日は ZendFramework だ。
我ながら忙しい。
今回の躓きは DB 接続。
PHP であれこれするのに DB で躓くなんて素人なの?死ぬの?
はい。素人です。
名前は知ってたけど、使ったのは今回が初めて。
今でこそ PHP のフレームワークは Cake が一強状態だが、かつては ZF がぶいぶい言わせていた。
らしい。
今回の案件は DB 接続するのがたったの1箇所のみ。
と言うことでお鉢が回ってきたわけです。
しかも、なぜか DB が Oracle なわけよ。
MySQL じゃないんだぜ。
ZF のバージョンは2.3くらいだったはず。
ZF2 の本が会社にあったので読んでみたら、DB 接続を ServiceManage に登録して行っていた。
ServiceManage ってのはインスタンスを作成しておいて常に保持しておいて、いちいち作り直さずに適宜使いましょうね、みたいな物だったと思う。
一般的な PHP サイトを構築する場合、DB をもりもり使うのが普通だ。
でも今回はほとんど DB を使わないので、ある特定のクラスが使われるときにのみDBを起動したかった。
実のところそんなに難しくなかった。
DB に接続する Adapter クラスのインスタンスを作成して、必要な情報を突っ込んでやるだけだった。
今回は Oracle ということで、
driver = 'oci8'
を指定してやり、username や password を指定して出来上がり。
開発環境ではこれだけでさくっと動いた。
が、本番前の試験環境では動かなかった。
なぜだ、と思ったら DB 名の指定の仕方が不味かったらしい。
dbname = '' で指定していたのだが、 Oci8 クラスの場合 connection_string = '' で指定する必要があったそうだ。
今回も検索したがほしい情報が全く見つからず。
むしろそもそもからして ZF2 の情報が少ない。
まぁ、仕方ないよねってことでまたコアを読みに。
library/Zend/Db/Adapter/Driver/Oci8/Connection.php
の163行目に DB 接続を行っていると思しき connect 関数を発見。
更に下を見ていくと 193~199行目にかけて oci_new_connect やら oci_connect を行っている。
if ($isUnique == true) {
$this->resource = oci_new_connect($username, $password, $connectionString, $characterSet, $sessionMode);
} elseif ($isPersistent == true) {
$this->resource = oci_pconnect($username, $password, $connectionString, $characterSet, $sessionMode);
} else {
$this->resource = oci_connect($username, $password, $connectionString, $characterSet, $sessionMode);
}
oci_connect というのは PHP 自体の関数で、 Oracle への接続を行ってくれる。
さて問題は、渡している引数だ。
oci_connect($username, $password, $connectionString, $characterSet, $sessionMode);
と、196,198行目にある。
そしてその値は183~187行目で決定している。
$username = $findParameterValue(array('username'));
$password = $findParameterValue(array('password'));
$connectionString = $findParameterValue(array('connection_string', 'connectionstring', 'connection', 'hostname', 'instance'));
$characterSet = $findParameterValue(array('character_set', 'charset', 'encoding'));
$sessionMode = $findParameterValue(array('session_mode'));
$findParameterValue() という見慣れぬ記述があるが、あわてず少し上の173~180行目をみる。
$findParameterValue = function (array $names) use ($p) {
foreach ($names as $name) {
if (isset($p[$name])) {
return $p[$name];
}
}
return null;
};
ややこしい書き方をしているけれど、プライベートな関数を作っている。
引数として受け取った配列の値を $p っていう連想配列に存在しているか探して、その値を返すというもの。
$p も少し上の170行目に書いてある。
$p = $this->connectionParameters;
が、connectionParameters って何だよ。
98~102行目に
public function setConnectionParameters(array $connectionParameters)
{
$this->connectionParameters = $connectionParameters;
return $this;
}
とあり、ここでどうやら設定している。
で、これがどこから呼ばれているのかと言うと、53行目の コンストラクタである。
public function __construct($connectionInfo = null)
{
if (is_array($connectionInfo)) {
$this->setConnectionParameters($connectionInfo);
} elseif ($connectionInfo instanceof \oci8) {
$this->setResource($connectionInfo);
} elseif (null !== $connectionInfo) {
throw new Exception\InvalidArgumentException('$connection must be an array of parameters, an oci8 resource or null');
}
}
インスタンスが作成されたときに渡された引数を、56行目で setConnectionParameters に渡して connectionParameters の値として設定している。
回りくどい。
そしてこのインスタンスが呼び出されるのは、同じフォルダのなかにある Oci8.php のコンストラクタである。
54~56行目に、
if (!$connection instanceof Connection) {
$connection = new Connection($connection);
}
$connection が Connection のインスタンスではないとき、 Connection のインスタンスを作る。
と言う記述だが、要するにすでにあるなら新しく作らないで再利用、なければインスタンスを作るよ、ってことだ。
さて、コンストラクタの記述ということは、 Oci8 のインスタンスが作成されたときに実行されるということで、それはどこかというと、
2つ上の階層の
library/Zend/Db/Adapter/Adapter.php
の259行目にある createDriver と言う関数の中の288行目に
$driver = new Driver\Oci8\Oci8($parameters);
とある。
$parameters は createDriver が受け取る引数だ。
さらに createDriver はどこで呼び出されているかと言うと、ここのコンストラクタだ。
public function __construct($driver, Platform\PlatformInterface $platform = null, ResultSet\ResultSetInterface $queryResultPrototype = null, Profiler\ProfilerInterface $profiler = null)
{
// first argument can be an array of parameters
$parameters = array();
if (is_array($driver)) {
$parameters = $driver;
if ($profiler === null && isset($parameters['profiler'])) {
$profiler = $this->createProfiler($parameters);
}
$driver = $this->createDriver($parameters);
} elseif (!$driver instanceof Driver\DriverInterface) {
throw new Exception\InvalidArgumentException(
'The supplied or instantiated driver object does not implement Zend\Db\Adapter\Driver\DriverInterface'
);
}
Adapter クラスのインスタンスを作成するときに渡す $driver が配列だった場合、createDriver にその値を突っ込むとしている。
謎は全て解けた!
- DB 接続をしよう
- 第1引数に DB の設定が入った連想配列を渡して Adapter のインスタンスを作るよ
- Adapter のコンストラクタでその引数が createDriver に渡されるよ
- 渡された配列のキーに driver があって、それが oci8 だったら、その引数を更に渡して Oci8 のインスタンスを作るよ
- Oci8 のコンストラクタから、またその引数を渡して Connection のインスタンスを作るよ
- Connection のコンストラクタで受け取った引数を setConnectionParameters に渡すよ
- $connectionParameters として設定するよ
- ひとまず Connection のインスタンスが出来たので 6 に戻るよ
- そのインスタンスを registerConnection に渡すよ
- Connection インスタンスの setDriver を呼ぶよ
- 実はインスタンス作っただけで終了で、続きは実際にクエリー実行するときだよ
- クエリーを実行したい
- SQL 文を Adapter インスタンスの createStatement に突っ込むよ
- Oci8 インスタンスの createStatement にそのまま突っ込まれるよ
- Connection インスタンスの isConnected を実行して DB 接続されてるか確認するよ
- 初めてだから接続されてなかったよ
- Connection インスタンスの connect 関数を実行するよ
- $connectionParameters を使うときが来たよ
- 中身をチェックするよ
- 見つかった username, password, connectionString, characterSet, sessionMode を php の oci_connect に渡すよ
- 接続に成功したら 3に戻って SQL 文の実行準備完了だよ
って感じ。
長いよ!?
で、 Oracle 接続するときに Adapter に渡す必要がある連想配列は
ユーザー名 : username
パスワード : password
データベース名 : connection_string, connectionstring, connection, hostname, instance のうちどれかとして
文字コード : character_set, charset, encoding
とのことだ。
んで、話は戻って今回の躓きポイントはデータベース名の設定。
開発環境だとなぜかデータベース名を指定しなくても実行できた。
Oracle は普通 "ユーザー名 = データベース名" だからだ。
ところが試験環境だと、別途データベース名を指定する必要があった。
で、Mysql のノリで dbname='' で指定していたものだから、繋がらなかった、と言うお話。
解決するのに、コアを追いかけて2時間ばかりかかってしまった。
不慣れなフレームワークに、不慣れな DB と、余計な苦労をしてしまった。