LionHeart SD BLOG

株式会社ライオンハート システムデザインの技術ブログ

インストールしたPropelを実際に利用してみる(その1:データ取得・検索編)

こんにちは、株式会社ライオンハートの鵜飼です。

前回Zend Framework2にPropelをインストールしてみてみましたので、実際にController側から使ってみた時のメモその1です。

その1では、Propelを利用したDBからのデータ取得まわりの処理を残していきます。

インストールをした環境は前回の例の様に、Application\Model\***にモデルが追加されている状態を仮定します。

サンプルのデータベースを用意

今回は下記のようなデータベース構造で用意してみました。

f:id:lh-sukai:20151127210850p:plain

schema.xmlで書いてみると下記のような記述になります。

<database name="default" defaultIdMethod="native"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="http://xsd.propelorm.org/1.6/database.xsd"
          namespace="Application\Model"
        >
    <table name="sample_tbl" phpName="Sample">
        <column name="id" type="bigint" required="true" primaryKey="true" autoIncrement="true"/>
        <column name="name" type="varchar" size="255" required="true"/>
        <column name="a_category_id" type="integer" required="true"/>

        <foreign-key foreignTable="a_category_tbl" phpName="ACategory" refPhpName="Sample">
            <reference local="a_category_id" foreign="id"/>
        </foreign-key>
    </table>

    <table name="a_category_tbl" phpName="ACategory">
        <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
        <column name="name" type="varchar" size="128" required="true"/>
    </table>

    <table name="b_category_tbl" phpName="BCategory">
        <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
        <column name="name" type="varchar" size="128" required="true"/>
    </table>

    <table name="sample_b_category_rel" phpName="SampleBCategoryRel" isCrossRef="true">
        <column name="id" type="bigint" required="true" primaryKey="true" autoIncrement="true"/>
        <column name="sample_id" type="bigint" required="true"/>
        <column name="b_category_id" type="integer" required="true"/>

        <foreign-key foreignTable="sample_tbl" skipSql="true">
            <reference local="sample_id" foreign="id"/>
        </foreign-key>
        <foreign-key foreignTable="b_category_tbl" skipSql="true">
            <reference local="b_category_id" foreign="id"/>
        </foreign-key>
    </table>
</database>

<table isCrossRef="true">が抜けていたので追記しました。コレにより編集処理もシンプルになっています。

一覧を取得する

一覧を取得する場合は、モデルを生成した際に***Queryというクラスが同時に生成されていますので、そちらを利用します。

何も制限せずに単純に取得する場合は下記の様になります。

<?php

namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        $params = [];

        // sample_tbl から全件取得
        $params['samples'] = \Application\Model\SampleQuery::create()->find();

        return new ViewModel($params);
    }
}

取得した値はforeachでループすることが可能ですので、ビュー側では下記のように記述できます。

<table>
    <thead>
        <tr>
            <th>ID</th>
            <th>名前</th>
            <th>カテゴリA</th>
            <th>カテゴリB</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach($samples as $sample): ?>
        <tr>
            <td><?php $this->escapeHtml($sample->getId()) ?></td>
            <td><?php $this->escapeHtml($sample->getName()) ?></td>
            <td><?php $this->escapeHtml($sample->getACategory()->getName()) ?></td>
            <td>
                <ul>
                    <?php foreach($sample->getSampleBCategoryRels() as $rel): ?>
                    <li><?php $this->escapeHtml($rel->getBCategory()->getName()) ?></li>
                    <?php endforeach; ?>
                </ul>
            </td>
        </tr>
        <?php endforeach; ?>
    </tbody>
</table>

また、リレーションしているカラムは、自動的にそのモデルクラスのオブジェクトが返ってきますので、そのまま繋げて該当のモデルの値を取得しに行くことが可能です。
上記の例で言う所の、

$sample->getAcategory()->getName()

が該当します。

また、sample_b_category_relの様なリレーションを行っている場合は少しややこしいですが、再度foreachで回すようにすることで値を表示する事が出来ます。
上記の例で言う所の、

<?php foreach($sample->getSampleBCategoryRels() as $rel): ?>
<li><?php $this->escapeHtml($rel->getBCategory()->getName()) ?></li>
<?php endforeach; ?>

が該当します。

検索条件を追加してみる

続いて、検索条件を追加してみます。通常のカラムで絞り込む場合は、filterBy[カラム名]というメソッドが生成サれていますので、そちらを利用することが出来ます。

<?php
// 省略

// sample_tbl から取得開始
$query = \Application\Model\SampleQuery::create();

// a_category_id で絞り込む
$query->filterByACategoryId($a_category_id);

// 名称で絞り込む
// %を付けると自動的にLIKE句になります
$query->filterByName('%' . $name . '%');

$params['samples'] = $query->find();

この辺りは覚えてしまえば簡単ですね。続いてリレーション先のカラムで絞り込む場合の記述です。

ちょっとこの書き方が最適なのかについては自信が無いのですが、一度リレーション先で絞り込んだ結果を渡す方法で考えてみました。
こういったケースで良く使われるであろう、Bカテゴリからの絞り込みのケースの記述です。

<?php
// 省略

// sample_tbl から取得開始
$query = \Application\Model\SampleQuery::create();

// b_category_tbl で一旦絞り込む
$rels = \Application\Model\SampleBCategoryRelQuery::create()
            ->filterByBCategoryId($b_category_id)->find();

// sample_tbl の検索条件に追加する
$query->filterBySampleBCategoryRel($rels);

$params['samples'] = $query->find();

SQL文は恐らく二回発行されると思いますが、中途半端にJOINするよりは良いかなぁ…と認識しています。(JOINしたらGROUP BYとかDISTINCTとかしないといけないし…)

別の記法(追記)

上記の様に順々に絞り込んでいく方法でも良さそうですが、一度に実行出来る方法も見つかりましたので追記しておきます。

<?php
// 省略

// sample_tbl から取得開始
$query = \Application\Model\SampleQuery::create();

// b_category_tbl で一旦絞り込む
$query->useSampleBCategoryRelQuery()
      ->filterByBCategoryId($b_category_id)
      ->endUse()
      ->distinct();

$params['samples'] = $query->find();

コチラの記法ですと、JOINを掛けてくれるようですので、重複レコードが返ってこないようにdistinctを呼んでおく必要があります。

その他

その他、filterを利用せずに、where句を追加するメソッドも用意されているようです。

<?php
// 省略

// sample_tbl から取得開始
$query = \Application\Model\SampleQuery::create();

// filterByACategoryId と同等
$query->where('a_category_id = ?', $a_category_id);

// 通常続けるとAND検索、OR検索にも変更は可能
$query->_or();

// filterByName と同等
$query->where('name LIKE ?', '%' . $name . '%');

$params['samples'] = $query->find();

また、conditioncombineというメソッドを利用することで、検索条件のグループ化も出来るようです。

<?php
// 省略

// sample_tbl から取得開始
$query = \Application\Model\SampleQuery::create();

// cond1 という名称のconditionを生成
$query->condition('cond1', 'a_category_id = ?', 1);
// cond2 という名称のconditionを生成
$query->condition('cond2', 'name = ?', '名称1');
// cond1 AND cond2 という クエリを cond12 という名称で生成
$query->combine(['cond1', 'cond2'], 'and', 'cond12');

// cond3 という名称のconditionを生成
$query->condition('cond3', 'a_category_id = ?', 2);
// cond4 という名称のconditionを生成
$query->condition('cond4', 'name = ?', '名称2');
// cond3 OR cond4 という クエリを cond34 という名称で生成
$query->combine(['cond3', 'cond4'], 'or', 'cond34');

// 実際にWHERE句にOR検索で追加する
$query->where(['cond12', 'cond34'], 'or');

$params['samples'] = $query->find();

/*
生成されるクエリ文
SELECT * FROM `sample_tbl` WHERE (
  ( a_category_id = 1 AND name = "名称1" )
  OR
  ( a_category_id = 2 OR name = "名称2" )
);
*/

ちょっとややこしいですが、使いどころが出てくると便利そうな機能ですね。

雑感

機能が豊富でまだ洗い出しきれていない機能もありそうですが、現時点だけでも便利な機能がたくさんありました。
$sample->idというカラム名そのままプロパティになっていると嬉しかったですが、まだ目をつむれるところだと思います。

ただ、リレーションの検索方法はもっと他にいい方法がありそうな予感がします…。またちょこちょこと調べてみます。

次回は挿入、更新まわりの処理についても見ていこうかと思います。