BEAR.Sunday 学習記録(2) データベース接続

[「BEAR.Sunday 学習記録（1）DI」](http://qiita.com/kilica/items/7a717cd4d1c6abb9a45d)の続きです。

なんちゃってエンジニアが BEAR.Sunday であれこれ作ってみたい、という勉強の記録です。怪しい記述があったり、微妙に間違っていたり、盛大に間違っていたりすると思いますので参考にされる場合はご注意ください。
また、試行錯誤しながら書き進めているため、理路整然とした記事にはなっておりません。すみません。
PHP のオブジェクト指向はかろうじて理解していますが、PHP5.4 以降の機能や BEAR.Sunday でも使われている DI や AOP はぼんやりと概念は理解した、というレベルです。

# 改めて、データベース接続したい

## 前準備

続きです。
ようやく、DI のことが分かってきました。BEAR.Sunday でデータベース接続をしようと思うと、Provider Binding を使うとスッキリしたコードになりそうです。

と、その前に、まだ確認しなければならないことがいくつかあります。

### データベース関係のライブラリ
Zend\Db を使うには、そのライブラリをインストールして置かなければなりません。通常であれば Composer を使うところですが、BEAR.Package には元々 Zend\Db のライブラリが入っていました（vendor/zendframework/zend-db）ので、この作業は不要でした。

### データベース接続情報
データベースに接続するには、使うDBの指定（MySQL, SQLiteなど）、接続ユーザ、パスワード、DB名などが必要になり、どのフレームワークでも、どこかで設定しています。

#### DB選択の設定ファイル
BEAR.Sunday では、まず以下に「どのデータベースを使うか」が書かれています。

```php:{bear}/apps/{Vendor.Application}/var/conf/constants.php
$masterDb = $slaveDb = require __DIR__  .'/db/sqlite.php';
```

bear/skeleton で作ったアプリでは、このように sqlite がデフォルトになっていて、あと mysql の設定がコメントアウト状態で用意されています。なお、以下では MySQL を使っています。

#### 接続情報の設定ファイル
var/conf/db 以下に mysql と sqlite 用の設定ファイルが用意されています。
DB接続情報などは、ここに書けばよろしくやってくれるようです。dbname, user, password などは各自の MySQL の設定に合わせて指定してください。

```php:{bear}/apps/{Vendor.Application}/var/conf/db/mysql.php
$mysql = [
    'driver' => 'pdo_mysql',
    'host' => 'localhost',
    'dbname' => 'bear',
    'charset' => 'UTF8'
];
$master = [
    'user' => '{masteruser}', // $_SERVER['APP_MASTER_ID'],
    'password' => '{masterpassword}', // $_SERVER['APP_MASTER_PASSWORD']
];
$slave = [
    'user' => '{slaveuser}', // $_SERVER['APP_SLAVE_ID'],
    'password' => '{slavepassword}', // $_SERVER['APPs_SLAVE_PASSWORD']
];
```

#### インスタンスの取得
Zend_Db では、データベースへの接続を Zend\Db\Adaptor\Adaptor が取り仕切るので、通常でしたらこのインスタンスを、

```php
$adapter = new Zend\Db\Adapter\Adapter(array(
   'driver' => 'Mysqli',
   'database' => 'zend_db_example',
   'username' => 'developer',
   'password' => 'developer-password'
));
```

のように作ります。
が、BEAR.Sunday では、DB接続情報を var/conf/db に書いているので、せっかくだからそれを使いたいところです。さてどうすればいいのでしょう。

config の値の取得方法がわからなかったりするのですが、参考ページでは、@Named アノテーションを使って入れていました。

```php:{bear}/apps/{Vendor.Application}/Module/Provider/ZendDbProvider
/**
 * @param  array $masterDb
 *
 * @Inject
 * @Named("masterDb=master_db")
 */
public function __construct($masterDb)
{
    $this->masterDb = $masterDb;
}
```

え、これでコンストラクタの引数 `$masterDb` にコンフィグの master_db の値が入るの？ッて感じですが、入るみたいです。この仕組はまた今度調べるとして、とりあえず先に進みます。

## Provider を作る
ではいよいよ、Provider を作ってデータベースに接続し、onGet() 用のデータを取得してみます。クラス名は ZendDbProvider にしました。

### Module に追記
先にモジュールに Binding を追記してみます。簡単そうなので。

```php:apps/{Vendor.Application}/src/Module/AppModule.php
protected function configure()
{
    $this->install(new StandardPackageModule('Kilica\Cubecms', $this->context, dirname(dirname(__DIR__))));
    // 追記
    $this->bind('Zend\Db\Adapter\AdapterInterface')->toProvider('{Vendor}\{Application}\Module\Provider\ZendDbProvider');
}
```

### ZendDbProvider
次に Provider。
[BEAR.SundayでDoctrine2のORMを使ってみた（改）](http://blog.a-way-out.net/blog/2014/07/11/bear-sunday-doctrine/)のコピペ ^ ^;;

```php:apps/{Vendor.Application}/src/Module/Provider/ZendDbProvider.php
<?php

namespace {Vendor}\{Application}\Module\Provider;

use Zend\Db\Adapter;
use Ray\Di\ProviderInterface;
use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;

class DoctrineORMProvider implements ProviderInterface
{
    /**
     * @param array $masterDb
     *
     * @Inject
     * @Named("masterDb=master_db")
     */
    public function __construct(array $masterDb)
    {
        $this->masterDb = $masterDb;
    }

    public function get()
    {
        $paths = [__DIR__.'/../../Entity'];
        $isDevMode = false;

        $config = Setup::createAnnotationMetadataConfiguration(
            $paths, $isDevMode
        );
        $conn = $this->masterDb;

        return EntityManager::create($conn, $config);
    }
}
```

### リソースファイル
とりあえずミニマムに、テーブルからカテゴリを全件取得して配列で返すだけのリソースです。

```php:apps/{Vendor.Application}/src/Resource/App/Category/Post.php
<?php
namespace Kilica\Cubecms\Resource\App\Category;

use Zend\Db\Adapter\Adapter;
use Kilica\Cubecms\Model\CategoryTable;
use BEAR\Sunday\Annotation\Db;
use BEAR\Resource\ResourceObject;
use BEAR\Package\Module\Database\Dbal\Setter\DbSetterTrait;
use Ray\Di\Di\Inject;
use PDO;
use Zend\Db\TableGateway\TableGateway;

/**
 * @Db
 */
class Post extends ResourceObject
{
    use DbSetterTrait;

    /**
     * @var CategoryTable
     */
    public $table;

    /**
     * @var string
     */
    protected $name = 'category';

    /**
     * @param Adapter $adapter
     * @Inject
     */
    public function __construct(Adapter $adapter)
    {
        $tableGateway = new TableGateway('category', $adapter);
        $this->table = new CategoryTable($tableGateway);
    }

    public function onGet()
    {
        $results = $this->table->fetchAll();
        $this->body = $results->toArray();
        return $this;
    }
}
```

### Category モデル
おまけで、Category Model と Category Table を載せておきます。BEAR に特有の記述は特に無いと思います。

#### Category モデル
```php:apps/{Vendor.Application}/src/Model/Category/Category.php
<?php
namespace Kilica\Cubecms\Model;

class Category
{
    public $id;
    public $title;
    public $description;
    public $parent_id;

    public function exchangeArray($data)
    {
        $this->id = (!empty($data['id'])) ? $data['id'] : null;
        $this->title = (!empty($data['title'])) ? $data['title'] : null;
        $this->description = (!empty($data['description'])) ? $data['description'] : null;
        $this->parent_id = (!empty($data['parent_id'])) ? $data['parent_id'] : null;
    }
}
```

#### CategoryTable
```php:apps/{Vendor.Application}/src/Model/Category/CategoryTable.php
<?php
namespace Kilica\Cubecms\Model;

use Zend\Db\TableGateway\TableGateway;

class CategoryTable
{
    protected $tableGateway;

    public function __construct(TableGateway $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }

    public function fetchAll()
    {
        $resultSet = $this->tableGateway->select();
        return $resultSet;
    }
} 
```

## コンソールから実行
いよいよ、コンソールから以下のコマンドでリソースを取得してみます。

`php bootstrap/contexts/api.php get app://self/category/post`

しかし、次のようなエラーが出ます >_<
```exception 'Ray\Di\Exception\NotBound' with message 'Valid interface is not found. (array ?) Injection requested at argument #0 $driver in Zend\Db\Adapter\Adapter constructor.' in /var/www/bear/vendor/ray/di/src/Binder.php:378
```
これは困ってしまいました。原因がつかめない。
あちこち止めてみて値を見るもよくわからず。最終的には試行錯誤して、次のように変更すると動きました。

```php:apps/{Vendor.Application}/src/Module/AppModule.php
protected function configure()
{
    // 略
    $this->bind('Zend\Db\Adapter\Adapter')->toProvider('{Vendor}\{Application}\Module\Provider\ZendDbProvider');
}
```
最初、`'Zend\Db\Adapter\AdapterInterface'` にバインドしていたんだけど、`'Zend\Db\Adapter\Adapter'` にすると動きました。

あれれ？　インタフェースじゃなかった？　イマイチ理解できていないことが判明（笑）
とりあえず、これで動いたんだけど……と見なおしていて、分かりました。

本当の原因はこっちでした。`configure()` の指定はやっぱり `AdapterInterface` で良かった。

```php:apps/{Vendor.Application}/src/Resource/App/Category/Post.php
<?php
namespace Kilica\Cubecms\Resource\App\Category;

use Zend\Db\Adapter\AdapterInterface;
// 略

/**
 * @Db
 */
class Post extends ResourceObject
{
// 略

    /**
     * @param AdapterInterface $adapter
     * @Inject
     */
    public function __construct(AdapterInterface $adapter)
    {
        $tableGateway = new TableGateway('category', $adapter);
        $this->table = new CategoryTable($tableGateway);
    }

// 略
```
こっちで、`AdapterInterface` を指定しないといけないのでした。

これで無事、MySQL の Category テーブルから Zendframework2 の DB\Adapter を使ってデータの一覧を取得することが出来ました。

今回はここまで。続く？