5.7. PdoInterceptorを用いたデータベースアクセス

5.7.1. Exampleについて

PdoInterceptorを用いたデータベースアクセスのExampleは、example/misc/pdo にあります。 各クラスファイル、設定ファイル、ディレクトリ構成は次になります。

example/misc/pdo/
 +-- classes/
 |    +-- Paginate.php
 |    +-- PdoInterceptor.php
 |    +-- SqlFileReader.php
 |    +-- StandardDto.php
 |    +-- StandardPdo.php
 +-- example/
 |    +-- config/
 |    |    +-- pdo.dicon
 |    |    +-- SqliteAPdo.php
 |    |    +-- SqliteBPdo.php
 |    +-- db/
 |         +-- example.sql
 |         +-- sqlite_a.db
 |         +-- sqlite_b.db
 |         +-- sqlite_c.db
 +-- tests/
      
clasesディレクトリにあるクラスがPdoInterceptorを使用するために必要となります。

PDOのデータソース設定は、configディレクトリに次の3つのファイルで行っています。 それぞれ、dbディレクトリのSqliteデータベースファイルをデータソースとしています。

  • SqliteAPdo.php : db/sqlite_a.db を使用するDSN設定
  • SqliteBPdo.php : db/sqlite_b.db を使用するDSN設定
  • pdo.dicon : db/sqlite_c.db を使用するDSN設定
各データベースはdb/example.sqlで構築しています。 また、PDOの設定については、PDOを用いたデータベースアクセスを参照下さい。


5.7.2. PdoInterceptorの概要

PdoInterceptorのアスペクト対象は、クラスとインターフェースです。クラスにアスペクトした場合は、Pointcutに 適合したメソッドが実行されます。PdoInterceptorは、メソッドの戻り値が文字列の場合は、SQLクエリとして扱います。 メソッドの戻り値がHash配列の場合は、SQL発行時のコンテキストとして扱います。 メソッドの戻り値が配列の場合は、1番目の値をSQLクエリ、2番目の値をSQL発行時のコンテキストとして扱います。 メソッドの戻り値が null の場合は、SQLファイルを探してSQLクエリを取得します。PdoIterceptorがインターフェースに アスペクトされている場合は、メソッドの呼び出しを行いません。メソッドの戻り値が null の場合と同様に、SQLファイルを探してSQLクエリを取得します。

SQLクエリが取得できた場合は、データベースに発行して結果を メソッドの戻り値として return します。 データベースへのSQLクエリの発行は、PDOのPrepared Statementが使用されます。 Prepared Statementにバインド されるvalueは、メソッド引数が使用されます。メソッドの戻り値が配列の場合は、2番目の値がバインドvalueとして扱われます。

PdoInterceptorは、自身が登録されているS2Containerに存在するPDOコンポーネントを使用してデータベースに接続します。 デフォルトでは、PdoInterceptorクラスとStandardPdoクラスは、ネームスペースpdoに登録されています。

PdoInterceptorには、O/Rマップ機能や自動SQL構築機能はありません。


5.7.3. クラスにアスペクトする

EMPテーブルにアクセスするDaoクラスを作成します。Daoクラスには、EMPテーブルから全件を取得するfindAllメソッドを実装します。 メソッドの戻り値としてデータベースに発行するSQLクエリを返します。
s2component関数で、Daoクラスをコンポーネントとして登録します。s2aspect関数でDaoクラスにpdo.interceptorをAspectします。

  • example/example010.php
<?php
require_once('S2Container/S2Container.php');
define('ROOT_DIR', dirname(dirname(__FILE__)));

use \seasar\container\S2ApplicationContext as s2app;
s2app::import(ROOT_DIR . '/classes');

StandardPdo::$DSN = 'sqlite:' . ROOT_DIR . '/example/db/sqlite_a.db';

class Dao {
    public function findAll() {
        return 'select * from emp';
    }
}

s2component('Dao');
s2aspect('pdo.interceptor', '/Dao$/');

$dao = s2app::get('Dao');
$rows = $dao->findAll();

5.7.4. メソッドの戻り値でバインド値を設定する

EMPテーブルにアクセスするDaoクラスを作成します。Daoクラスには、EMPテーブルからIDで検索するfindByIdメソッドを実装します。 メソッドの戻り値を配列とし、1番目の値にSQLクエリ、2番目の値にSQLクエリにバインドする値を配列で指定します。

  • example/example010.php
<?php
require_once('S2Container/S2Container.php');
define('ROOT_DIR', dirname(dirname(__FILE__)));

use \seasar\container\S2ApplicationContext as s2app;
s2app::import(ROOT_DIR . '/classes');

StandardPdo::$DSN = 'sqlite:' . ROOT_DIR . '/example/db/sqlite_a.db';

class Dao {
    public function findById() {
        $sql = 'select * from emp where EMPNO = :id';
        $context = array('id' => 7369);
        return array($sql, $context);
    }
}

s2component('Dao');
s2aspect('pdo.interceptor', '/Dao$/');

$dao = s2app::get('Dao');
$rows = $dao->findById();

5.7.5. インターフェースにアスペクトする

EMPテーブルにアクセスするIDaoインターフェースを作成します。IDaoインターフェースには、EMPテーブルから全件を取得するfindAllメソッドを定義します。
s2component関数で、Daoクラスをコンポーネントとして登録します。s2aspect関数でDaoクラスにpdo.interceptorをAspectします。

  • example/example020.php
<?php
require_once('S2Container/S2Container.php');
define('ROOT_DIR', dirname(dirname(__FILE__)));

use \seasar\container\S2ApplicationContext as s2app;
s2app::import(ROOT_DIR . '/classes');

StandardPdo::$DSN = 'sqlite:' . ROOT_DIR . '/example/db/sqlite_a.db';

interface IDao {
    public function findAll();
}

s2component('IDao');
s2aspect('pdo.interceptor', '/Dao$/');

$dao = s2app::get('IDao');
$rows = $dao->findAll();

findAllメソッドが呼ばれた際に発行するSQLクエリをSQLファイルに記述します。SQLファイルはインターフェースファイルと同じディレクトリに配置します。 SQLファイル名は、インターフェース名_メソッド名.sqlとなります。IDaoインターフェースのfindAllメソッドの場合は、IDao_findAll.sqlとなります。

  • example/IDao_findAll.sql
select * from emp


5.7.6. メソッド引数でバインド値を設定する

EMPテーブルにアクセスするIDaoインターフェースを作成します。IDaoインターフェースには、EMPテーブルからIDで検索するfindByIdメソッドを定義します。 findByIdメソッドの引数で検索するIDを指定します。

  • example/example020.php
<?php
require_once('S2Container/S2Container.php');
define('ROOT_DIR', dirname(dirname(__FILE__)));

use \seasar\container\S2ApplicationContext as s2app;
s2app::import(ROOT_DIR . '/classes');

StandardPdo::$DSN = 'sqlite:' . ROOT_DIR . '/example/db/sqlite_a.db';

interface IDao {
    public function findById($id);
}

s2component('IDao');
s2aspect('pdo.interceptor', '/Dao$/');

$dao = s2app::get('IDao');
$rows = $dao->findAll();

findAllメソッドが呼ばれた際に発行するSQLクエリをSQLファイルに記述します。SQLファイルはインターフェースファイルと同じディレクトリに配置します。

  • example/IDao_findById.sql
select * from emp where EMPNO = :id


5.7.7. @DTOアノテーションを使用する

PdoInterceptorは、デフォルトでは、StandardDtoクラスをPDO::FETCH_CLASSに指定します。 StandardDtoクラスは、__callメソッドを実装しており、カラムへのアクセッサメソッドを提供します。 カラム名が「ABC_XYZ」の場合、setAbcXyzメソッド、getAbcXyzメソッドでカラム値にアクセスすることができます。

PDO::FETCH_CLASSを指定する場合は、@DTOアノテーションで行います。 書式は次のようになります。

/**
 * @DTO(DTOクラス名)
 */

例として、EMPテーブルにアクセスするIDaoインターフェースを作成します。 IDaoインターフェースには、EMPテーブルからIDで検索するfindByIdメソッドを定義します。 PDO::FETCH_CLASSクラスには、BarDtoクラスを指定しています。

  • example/example030.php
<?php
require_once('S2Container/S2Container.php');
define('ROOT_DIR', dirname(dirname(__FILE__)));

use \seasar\container\S2ApplicationContext as s2app;
s2app::import(ROOT_DIR . '/classes');

StandardPdo::$DSN = 'sqlite:' . ROOT_DIR . '/example/db/sqlite_a.db';

class BarDto extends StandardDto{}
interface IDao {
    /**
     * @DTO('BarDto')
     */
    public function findById($id);
}

s2component('IDao');
s2aspect('pdo.interceptor', '/Dao$/');

$dao = s2app::get('IDao');

$rows = $dao->findById(7369);

5.7.8. DaoクラスでPDOを直接使用する

Daoクラスの中で直接PDOを使用する場合は、DaoクラスにPDOコンポーネントをインジェクションします。 次の例では、CdDaoクラスにStandardPdoコンポーネントをセッターメソッドインジェクションしています。 sampleTransactionメソッドでは、インジェクションされたPDOコンポーネントを使用してトランザクションを開始しています。

  • example/CdDao.php
<?php
class CdDao {
    private $pdo = null;
    public function setPdo(Pdo $pdo) {
        $this->pdo = $pdo;
    }
    public function sampleTransaction() {
        try {
            \seasar\log\S2Logger::getInstance(__NAMESPACE__)->info('start transaction.', __METHOD__);
            $this->pdo->beginTransaction();
            $this->insert(10, 'aaa', 'bbb');
            $this->updateTitle(10, 'AAA');
            $this->delete(10);
            \seasar\log\S2Logger::getInstance(__NAMESPACE__)->info('commit transaction.', __METHOD__);
            $this->pdo->commit();
        } catch (Exception $e) {
            \seasar\log\S2Logger::getInstance(__NAMESPACE__)->info($e->getMessage(), __METHOD__);
            \seasar\log\S2Logger::getInstance(__NAMESPACE__)->info('rollback transaction.', __METHOD__);
            $this->pdo->rollBack();
        }
    }
    public function insert($id, $title, $content) {
        return "insert into CD values(:id, :title, :content)";
    }
    public function updateTitle($id, $title) {
        return "update CD set title = :title where id = :id";
    }
    public function delete($id) {
        return "delete from CD where id = :id";
    }
}

実行ファイルは次になります。

  • example/example040.php
<?php
require_once('S2Container/S2Container.php');
define('ROOT_DIR', dirname(dirname(__FILE__)));

use \seasar\container\S2ApplicationContext as s2app;
s2app::import(ROOT_DIR . '/classes');
s2app::import(ROOT_DIR . '/example/CdDao.php');

StandardPdo::$DSN = 'sqlite:' . ROOT_DIR . '/example/db/sqlite_b.db';
s2aspect('pdo.interceptor', '/Dao$/', '/^(insert|update|delete)/');

$dao = s2app::get('CdDao');
$rows = $dao->sampleTransaction();

5.7.9. Paginateクラスでページング処理

Paginateクラスは、データベースからのデータ取得の際にページングを行うユーティリティクラスです。 例として、EMPテーブルにアクセスするEmpDaoクラスを作成します。EmpDaoクラスには、次のようなbyPaginateメソッドを実装します。

  • example/EmpDao.php
<?php
class EmpDao {
    public function byPaginate(\Paginate $paginate) {
        if (!$paginate->hasTotal()) {
            list($row) = $this->findAllTotal($paginate);
            $paginate->setTotal($row->total);
        }
        return $this->findAll($paginate);
    }
    public function findAllTotal(\Paginate $paginate) {
        return 'select count(*) as total from EMP order by EMPNO';
    }

    public function findAll(\Paginate $paginate) {
        $sql = 'select * from EMP order by EMPNO limit :limit offset :offset';
        $context = array('limit' => $paginate->getLimit(), 'offset' => $paginate->getOffset());
        return array($sql, $context);
    }
}

byPaginateメソッドでは、まずfindAllTotalメソッドで全件数を取得し、paginateインスタンスに設定します。 (Paginateクラスでページング処理を実施するために全件数が必要なため) その後、findAllメソッドを実行し、データを取得しています。findAllメソッドでは、limit/offset値にpaginateインスタンスが持つ 情報を設定して、ページングを実施しています。

次の処理を行うexample050.phpを作成します。

  1. 共通設定ファイルの読み込み
  2. 「Dao」クラスにPdoInterceptorを自動アスペクト
  3. S2ApplicationContextにclassesディレクトリ以下をimport
  4. S2ApplicationContextにsqlite_a.dbをDSNに設定するSqlitePdoA.phpをimport
  5. EmpDaoコンポーネントの取得
  6. Paginateインスタンスを生成し、1ページのアイテム数を設定
  7. byPaginateメソッドを実行
  8. 次のページに遷移
  9. byPaginateメソッドを実行
  • example/example050.php
<?php
require_once('S2Container/S2Container.php');
define('ROOT_DIR', dirname(dirname(__FILE__)));

use \seasar\container\S2ApplicationContext as s2app;
s2app::import(ROOT_DIR . '/classes');
s2app::import(ROOT_DIR . '/example/EmpDao.php');

StandardPdo::$DSN = 'sqlite:' . ROOT_DIR . '/example/db/sqlite_a.db';
s2aspect('pdo.interceptor', '/Dao$/', '/^find/');

$dao = s2app::get('EmpDao');
$paginate = new Paginate;
$paginate->setLimit(2);
$rows = $dao->byPaginate($paginate);
var_dump($rows);

$paginate->next();
$rows = $dao->byPaginate($paginate);
var_dump($rows);

5.7.9.1. Paginate API リファレンス

Paginate::getTotalPage メソッド. 

全件数を件数/ページ(limit)で割った全ページ数を返します。

    /**
     * @return integer
     */
    final public function getTotalPage();

Paginate::getPage メソッド. 

現在のページ番号を返します。

    /**
     * @return integer
     */
    final public function getPage();

Paginate::setPage メソッド. 

ページ番号を設定します。

    /**
     * @param integer $page
     * @throw Exception
     */
    final public function setPage($page);

Paginate::getOffset メソッド. 

現在のoffset位置を返します。

    /**
     * @return integer
     */
    final public function getOffset();

Paginate::setLimit メソッド. 

1ページあたりの件数を設定します。

    /**
     * @prama integer $limit
     */
    final public function setLimit($limit);

Paginate::getTotal メソッド. 

全件数を返します。

    /**
     * @return integer
     * @throw Exception 全件数が未設定の場合にスローされます。
     */
    final public function getTotal();

Paginate::setTotal メソッド. 

全件数を設定します。

    /**
     * @param integer $total
     */
    final public function setTotal($total);

Paginate::setWindow メソッド. 

ページウィンドウに表示するページ数を設定します。

    /**
     * @param integer $window
     */
    final public function setWindow($window);

Paginate::next メソッド. 

1ページ進めます。

    final public function next();

Paginate::isNext メソッド. 

次のページがあるかどうかを返します。

    /**
     * @return boolean
     */
    final public function isNext();

Paginate::prev メソッド. 

1ページ戻ります。

    final public function prev();

Paginate::isPrev メソッド. 

前のページがあるかどうかを返します。

    /**
     * @return boolean
     */
    final public function isPrev();

Paginate::pages メソッド. 

window内に収まるページ番号を列挙します。

    /**
     * @return array
     */
    final public function pages() {


© Copyright The Seasar Foundation and the others 2005-2010, all rights reserved.