そうだ、コアを読もう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 にその値を突っ込むとしている。

 

謎は全て解けた!

 

  1. DB 接続をしよう
  2. 第1引数に DB の設定が入った連想配列を渡して Adapter のインスタンスを作るよ
  3. Adapter のコンストラクタでその引数が createDriver に渡されるよ
  4. 渡された配列のキーに driver があって、それが oci8 だったら、その引数を更に渡して Oci8 のインスタンスを作るよ
  5. Oci8 のコンストラクタから、またその引数を渡して Connection のインスタンスを作るよ
  6. Connection のコンストラクタで受け取った引数を setConnectionParameters に渡すよ
  7. $connectionParameters として設定するよ
  8. ひとまず Connection のインスタンスが出来たので 6 に戻るよ
  9. そのインスタンスを registerConnection に渡すよ
  10. Connection インスタンスの setDriver を呼ぶよ
  11. 実はインスタンス作っただけで終了で、続きは実際にクエリー実行するときだよ
  1. クエリーを実行したい
  2. SQL 文を Adapter インスタンスの createStatement に突っ込むよ
  3. Oci8 インスタンスの createStatement にそのまま突っ込まれるよ
  4. Connection インスタンスの isConnected を実行して DB 接続されてるか確認するよ
  5. 初めてだから接続されてなかったよ
  6. Connection インスタンスの connect 関数を実行するよ
  7. $connectionParameters を使うときが来たよ
  8. 中身をチェックするよ
  9. 見つかった username, password, connectionString, characterSet, sessionMode を php の oci_connect に渡すよ
  10. 接続に成功したら 3に戻って SQL 文の実行準備完了だよ

って感じ。

長いよ!?

で、 Oracle 接続するときに Adapter に渡す必要がある連想配列

ユーザー名 : username

パスワード : password

データベース名 : connection_string, connectionstring, connection, hostname, instance のうちどれかとして

文字コード : character_set, charset, encoding

とのことだ。

 

んで、話は戻って今回の躓きポイントはデータベース名の設定。

開発環境だとなぜかデータベース名を指定しなくても実行できた。

Oracle は普通 "ユーザー名 = データベース名" だからだ。

ところが試験環境だと、別途データベース名を指定する必要があった。

で、Mysql のノリで dbname='' で指定していたものだから、繋がらなかった、と言うお話。

解決するのに、コアを追いかけて2時間ばかりかかってしまった。

不慣れなフレームワークに、不慣れな DB と、余計な苦労をしてしまった。