ZendFramework2ガイド

入門編

データベース操作

ZendFramework2でのデータベース操作

Zend2でデータベース操作をするにはいくつか方法があります。
いずれの方法でもデータベース接続情報と、それをもとにデータベースへの接続状態をインスタンスとして保持しているアダプタークラスが必要です。
アダプタークラスのインスタンス生成についてはモデルのところで少し書いていますのでそちらを参照してください。

Zend\Db\TableGataway\TableGatawayクラスを利用

モデルのところで少し書きましたが、TableGatawayはデータベーステーブルをクラス化したようなイメージのクラスで、SELECT、INSERT、UPDATE、DELETEを実行するメソッドが用意されています。
TableGataway自体は、例えばCakePHPにおけるActiveRecordのようにO/Rマッピングの機能を提供するクラスではありません。基本的にはSQL文構築を抽象化したクラスと考えられます。ただしRowGatewayというクラスが存在し、これはO/Rマッパとしての機能を持っています。TableGatawayはFeatureという仕組みでRowGatewayとの連携が可能になっているので、これによりO/Rマッパ的に利用することも可能ですが、ここではTableGataway単体でSQL文構築クラス的なアプローチで利用する方法を示したいと思います。

Zend\Db\Sql\Sqlクラスを利用

SqlクラスはSql文構築を支援するクラスといった位置付けで、Select、Insert、Update、DeleteなどのSQL文そのもののオブジェクトを発行する役目を持っています。そして例えばSelectクラスであれば、where、orderといったような、SELECT文の構文そのもののメソッドを持ち、あたかもSELECT文を書くかのような感覚でその構築を抽象化しており、かなり複雑なSQL文の構築も可能になっています。

SQL文を直接記述

WEBシステムはテーブル構造も単純でSQLが複雑にならない場合が多く、上記の様なライブラリが特に有効と言えると思います。
しかしフレームワークの用意したライブラリでは対応出来ないような複雑なSQLが出てきた場合には直接SQL文をプログラムに埋め込むしかありません。
また、SQLに精通した人ほど生のSQLが一番読みやすく、可読性の意味で敢えてSQLは生で記述するというポリシーということもあります。
Zend2で用意されているデータベースのドライバーとしてはいくつかありますが、コアな処理部分は抽象化されていて、どのようなドライバーを利用するとしても同じようなアプローチで利用できます。そのような中に、SQL文を直接発行するメソッドは用意されているので、それを利用します。

SQL文を直接記述して実行する

Adapterクラスのqueryメソッドでの実行が最も単純な方法です。

$result = $adapter->query('SELECT * FROM order_header');

この場合、WHERE条件などの指定でユーザー入力をSQLに利用する場合には、入力をSQL文の一部として埋め込む必要があります。これだとSQLインジェクションの危険性に常にさらされる状態となってしまうため、入力に対してサニタイズが必要になります。
しかし、そもそも直接文字列として入力を埋め込むのではなく、SQL文をプリペアし、パラメータを別途送信する方が安全です。
その方法が以下です。

$sql = 'SELECT * FROM order_header WHERE order_id = :order_id';
$params = array(
    'order_id' => $orderId,
);
$statement = $adapter->createStatement($sql);
$result = $statement->execute($params);

プリペアするSQLとしては、パラメータの値部分は変数的に記述するわけですが、変数として認識するための記号をはMySQLやPostgreSQLデータベースにより異なります。上記の例は列名の前に「:」を付加したものを使用していますが、これはMySQLの場合です。
MySQL以外で動かすことが絶対にないアプリケーションであればよいですが、あり得る場合はそれを考慮した作りにしておくべきです。この場合、以下のようにします。

$column1 = 'order_id';
$column1ParamName = $adapter->getDriver()->formatParameterName($column1);
$sql = 'SELECT * FROM order_header WHERE ' . $column1 . ' = ' . $column1ParamName ;

TableGatewayによる簡単なSELECT

TableGatewayにはselectというメソッドがあり、これによって簡単にSELECT文を実行し、結果セットを取得できます。

$table = new TableGateway('example', $adapter);

// 複数行
$rowset = $table->select();
foreach ($rowset as $row) {
    $userId = $row->user_id;
    $name = $row->name;
    $addr = $row->address;
}

// 単一行
$where = array(
    'user_id' => $userId,
);
$rowset = $table->select($where);
$row = $rowset->current();
$userId = $row->user_id;
$name = $row->name;
$addr = $row->address;

SELECTメソッドはResultSetというクラスのインスタンスを返します。
このResultSetはレコード×フィールドの2次元構造をしていますが、Iteratorインターフェイスを実装しており、配列と同じようにforeachで各レコードを順番に取り出す事ができますし、currentメソッドで1行取り出すなどの事も可能です。
取り出したレコードはArrayObjectなので、配列として扱うこともできるし、オブジェクト的に要素へアクセスすることも可能になっています。上記の例ではオブジェクト的にアクセスしてみました。
Iteratorインターフェイスについては公式マニュアルを参照してください。
ArrayObjectについても公式マニュアルを参照してください。

また、単に配列として結果セットを得たい場合、ResultSetのtoArrayメソッドによって単なる2次元配列に変換することもできます。

$table = new TableGateway('example', $adapter);
$rowset = $table->select();
$rows = $rowset->toArray();
foreach ($rows as $row) {
    $userId = $row['user_id'];
    $name = $row['name'];
    $addr = $row['address'];
}

実際にモデルとしてTableGatewayを利用する場合、「モデル」の章でも解説している通り、TableGatewayを継承したクラスをデータモデルクラスとする方法が考えられます。

use Zend\Db\TableGateway\TableGateway;

class ExampleTable extends TableGateway
{	

~~~(中略)~~~

    public function getRecord($id)
    {
        $where = array(
            'id' => $id,
        );
        $rowset = $this->select($where);
        return $rowset;
    }
}

TableGatewayのselectメソッドではとても楽にSELECT分を実行することが出来ますが、パラメータとしてもWHERE条件の指定しかできず、極簡単なSELECTにしか対応できません。ORDER BYすらできず、機能としては不足しています。

Sqlクラスを利用した柔軟なSELECT

Zend\Db\Sql\Sqlというクラスを利用すると柔軟なSQLが構築できます。SqlクラスはDB接続アダプタのインスタンスをパラメーターにnewすることも可能ですが、TableGatewayを利用している場合、getSqlメソッドにより取得することも可能です。

Sqlにはselectというメソッドが存在し、これはZend\Db\Sql\Selectクラスのインスタンスが得られます。
SelectクラスはSELECT文を構築するためのクラスです。
利用方法を見てみましょう。先ほどのTableGatewayのselectメソッドを利用した例と比べてみてください。結果的には同じです。

use Zend\Db\TableGateway\TableGateway;

class ExampleTable extends TableGateway
{	

~~~(中略)~~~

    public function getRecord($id)
    {
        // Selectのインスタンスを取得
        $select = $this->getSql()->select();
        // WHERE条件指定
        $select->where(array(
            'id' => $id,
        ));
        // 実行
        $rowset = $this->selectWith($select);
        return $rowset;
    }
}

TableGatewayクラスのgetSqlでSqlクラスのインスタンスを取得し、SqlクラスのselectメソッドでSelectのインスタンスを取得します。
WHERE条件を設定する場合にはSelectクラスのwhereメソッドを利用して配列で指定します。
そしてTableGatewayクラスのselectWithメソッドで実行します。

Selectクラスにはwhere以外にもたくさんのSQL構築用のメソッドが存在します。

基本的なSQL文

Selectクラスにはwhere以外にもたくさんのSQL構築用のメソッドが存在します。
以下に基本的なものを紹介します。

$select = $this->getSql()->select();

// 取得する列指定
$select->columns(array('user_id'));

// WHERE
$select->where(array('delete_flag' => 0));

// GROUP BY
$select->group(array('user_id'));

// ORDER BY
$select->order(array('user_id'));

// LIMIT
$select->limit(10);

// OFFSET
$select->offset(10);

echo $select->getSqlString();
SELECT "user_id" FROM "example_table" WHERE  "delete_flag" = '0' GROUP BY "user_id" ORDER BY "user_id" DESC
LIMIT '10' OFFSET '10'

Sqlクラスではテーブル結合を含むSQLの構築も可能です。

$select = $this->getSql()->select();
$select->columns(array('user_id'));
$select->join(
    array('T2' => 'order_detail'),  // JOINするテーブル名
    'T2.user_id = user_id',    // 結合条件
    array('user_name')    // JOINするテーブルから取得する列名
);
SELECT "example_table"."user_id" AS "user_id", "T2"."user_name" AS "user_name" FROM "order_header"
INNER JOIN "order_detail" AS "T2" ON "T2"."user_id" = "user_id"

TableGatewayによる簡単なINSERT

TableGatewayのinsertメソッドを利用したINSERT文の実行です。

use Zend\Db\TableGateway\TableGateway;

class ExampleTable extends TableGateway
{	

~~~(中略)~~~

    public function insertRecord($userId, $payType)
    {
        $values = array(
            'user_id'  => $userId,
            'pay_type' => $payType,
            'date'     => date(),
        );
        $result = $this->insert($values);
        return $result;
    }
}

とても単純です。
insertメソッドの戻り値はINSERTされた行数が返ってきます。
insertメソッドは1行追加するメソッドなので、成功したら必ず1が返ることになりますね。

Sqlクラスを利用したINSERT

Sqlクラスを利用してINSERTすることも可能です。
Sqlクラスのinsertメソッドにより、Zend\Db\Sql\Insertクラスのインスタンスが得られます。
InsertクラスはINSERT文を構築するための機能を持ちます。
基本的にはvaluesメソッドで値を配列の形式で指定するだけです。

use Zend\Db\TableGateway\TableGateway;

class ExampleTable extends TableGateway
{	

~~~(中略)~~~

    public function insertRecord($id)
    {
        $values = array(
            'user_id'  => $userId,
            'pay_type' => $payType,
            'date'     => date(),
        );
        $insert = $this->getSql()->insert();
        $insert->values($values);
        $result = $this->insertWith($insert);
        return $result;
    }
}

この方がオブジェクト指向っぽいですが、実質はTableGatewayクラスのinsertメソッドを利用する場合と違いはありません。

ちなみにInsertクラスのvaluesメソッドには2番目に指定できる引数があります。
それは、1番目の引数で指定する値が上書き型かマージ型かのどちらであるかを示す定数です。
上書き型とはつまり、既に一度valuesメソッドで値をセットしていた場合、それを取り消して今回指定した値で上書きする事で、
マージ型というのは、既に一度valuesメソッドで値をセットしていた場合、セットしていた物は取り消さずに新たにセットしたものとマージするということです。
もちろんマージ型でも、前回と今回で同じキーが存在すればそれについては上書きになりますが。
2番目の引数未指定の場合は上書き型となります。

まずは上書き型の例です。

use Zend\Db\Sql\Insert;

$insert = $this->getSql()->insert();
$insert->values(array(
    'user_id'  => 3,
    'pay_type' => 1,
    'date'     => date(),
), Insert::VALUES_SET); // Insert::VALUES_SETの場合は指定してもしなくてもどちらでもよい
$result = $this->insertWith($insert);
INSERT INTO "order_header" ("user_id", "pay_type", "date") VALUES ('3', '1', '1422715101')

マージ型にした場合は、一つ一つのフィールドの値の指定を別々に指定することも可能になります。

use Zend\Db\Sql\Insert;

$insert = $this->getSql()->insert();
$insert->values(array('user_id'  => 3),      Insert::VALUES_MERGE);
$insert->values(array('pay_type' => 1),      Insert::VALUES_MERGE);
$insert->values(array('date'     => date()), Insert::VALUES_MERGE);
$result = $this->insertWith($insert);
INSERT INTO "order_header" ("user_id", "pay_type", "date") VALUES ('3', '1', '1422715101')

ちなみに、上記のマージ型のようにバラバラで値を指定した場合で、Insert::VALUES_MERGEを指定し忘れたらどうなるか。
ご想像の通り、最後の指定だけが生きる事になります。

use Zend\Db\Sql\Insert;

$insert = $this->getSql()->insert();
$insert->values(array('user_id'  => 3));
$insert->values(array('pay_type' => 1));
$insert->values(array('date'     => date()));
$result = $this->insertWith($insert);
INSERT INTO "order_header" ("date") VALUES ('1422715101')

TableGatewayによる簡単なUPDATE

TableGatewayのupdateメソッドを利用したUPDATE文の実行です。

use Zend\Db\TableGateway\TableGateway;

class ExampleTable extends TableGateway
{	

~~~(中略)~~~

    public function updatePayType($id, $payType)
    {
        $values = array(
            'pay_type' => $payType,
            'date'     => time(),
        );
        $where= array(
            'id' => $id,
        );
        $result = $this->update($values, $where);
        return $result;
    }
}

insert同様、updateメソッドの戻り値は、UPDATE文の実行により、実際に更新対象となった行の数が返ってきます。
第2パラメーターの$whereを指定し忘れると全行更新されてしまうので注意が必要です。

Sqlクラスを利用したUPDATE

UPDATEもSqlクラスによる実行が可能です。
Sqlクラスのupdateメソッドにより、Zend\Db\Sql\Updateクラスのインスタンスが得られます。
UpdateクラスはUPDATE文を構築するための機能を持ちます。

use Zend\Db\TableGateway\TableGateway;

class ExampleTable extends TableGateway
{	

~~~(中略)~~~

    public function updatePayType($id, $payType)
    {
        $update= $this->getSql()->update();
        $update->set(array(
            'pay_type' => $payType,
            'date'     => time(),
        ));
        $update->where(array(
            'id' => $id,
        ));
        $result = $this->updateWith($update);
        return $result;
    }
}

setメソッドやwhereメソッドという形で指定するため、この方がよりSQLのUPDATE文を直接書くのに近い感覚でUpdate文の構築が可能になります。
SQLを想像しながら書けますね。

setメソッドについてはInsertクラスのvaluesメソッドと同様、2番目の引数で上書きかマージかの指定ができます。
詳しくはINSERTのところの説明を見ていただくとして、
上記のUPDATEの例をマージ型を利用して書くと以下のようにもかけるわけです。

use Zend\Db\Sql\Update;

$update= $this->getSql()->update();
$update->set(array('pay_type' => $payType, Update::VALUES_MERGE));
$update->set(array('date'     => time(),   Update::VALUES_MERGE));
$update->where(array('id' => $id));
$result = $this->updateWith($update);

TableGatewayによる簡単なDELETE

TableGatewayのdeleteメソッドにより、DELETEが実行できます。

use Zend\Db\TableGateway\TableGateway;

class ExampleTable extends TableGateway
{	

~~~(中略)~~~

    public function deleteRecord($id)
    {
        $where= array(
            'id' => $id,
        );
        $result = $this->delete($where);
        return $result;
    }
}

DELETE文なのでWHERE条件を指定するだけです。
deleteメソッドの戻り値は、DELETE文の実行により、実際に削除された行数が返ってきます。

Sqlクラスを利用したDELETE

DELETEもSqlクラスによる実行が可能です。
Sqlクラスのdeleteメソッドにより、Zend\Db\Sql\Deleteクラスのインスタンスが得られます。
DeleteクラスはDELETE文を構築するための機能を持ちます。

use Zend\Db\TableGateway\TableGateway;

class ExampleTable extends TableGateway
{	

~~~(中略)~~~

    public function deleteRecord($id)
    {
        $delete= $this->getSql()->delete();
        $delete->where($values);
        $result = $this->deleteWith($delete);
        return $result;
    }
}