symfony DoctrineのTIPS その2

こんばんは。牧野です。

最近、会社で使っているPCも自分用のノートPCもハードディスク容量が少ないせいか、重く感じるようになっていました。
そんな時に発表されたsonyの新しいVAIO Z。シミュレータで出てきた価格にびっくりしつつも、心ひかれています。。。

さて、今日はsymfonyと使うDoctrineのTIPSその2です。ここのところ、またsymfonyを触る機会が増えていました。

1.生SQLを使う
schema.ymlをちゃんと書けばDQLでたいていのことはできるのですが、そのままSQLを実行してデータを取りたい時にどうぞ。
Doctrine_Connectionオブジェクトがあれば、とりあえず何でもできます。


$con = Doctrine::getTable(適当なテーブル)->getConnection();
$con = Doctrine_Manager::getInstance()->getConnection(コネクション名);
$sql = "select * from hogehgoe";
$results = $con->fetchAll($sql);

複数のデータを取得するfetchAll(=fetchAssoc)メソッド、一つのデータを連想配列で取得するfetchRowメソッドあたりが便利だと思います。

2.データ更新時の注意
Doctrineは関連するデータを簡単に引っ張ってこられるところが便利なのですが、データ更新をする際には気をつけたいことがあります。
あるモデルオブジェクトのデータ変更内容を確定させる前に、同じモデルオブジェクトを取得するようなことをすると、確定前のデータ変更内容はリセットされます。

具体例な例を見てみましょう。

config/schema.yml


DvdSeries:
  tableName: dvd_series
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    series_title:
      type: string(255)
      notnull: true
DvdPackage:
  tableName: dvd_package
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    dvd_series_id:
      type: integer(4)
    number:
      type: integer(4)
    package_title:
      type: string(255)
    price:
      type: integer(11)
    release_date:
      type: date
  relations:
    DvdSeries:
      local: dvd_series_id
      foreign: id
      class: DvdSeries
      foreignAlias: DvdPackages

DBの中身


mysql> select * from dvd_series;
+----+-----------------+
| id | series_title    |
+----+-----------------+
|  1 | シーズン1      | 
+----+-----------------+
1 row in set (0.00 sec)
mysql> select * from dvd_package\G
*************************** 1. row ***************************
           id: 1
dvd_series_id: 1
       number: 1
package_title: テストタイトルその1
        price: 3000
 release_date: 2010-03-01
*************************** 2. row ***************************
           id: 2
dvd_series_id: 1
       number: 2
package_title: テストタイトルその2
        price: 4000
 release_date: 2010-04-02
*************************** 3. row ***************************
           id: 3
dvd_series_id: 1
       number: 3
package_title: テストタイトルその3
        price: 4000
 release_date: 2010-05-03
*************************** 4. row ***************************
           id: 4
dvd_series_id: NULL
       number: NULL
package_title: テストタイトル
        price: 3000
 release_date: 2010-06-04
4 rows in set (0.00 sec)

lib/model/doctrine/DvdPackage.class.php


class DvdPackage extends BaseDvdPackage
{
  /**
   * 同一シリーズのDVD発売日をまとめてずらす。
   *
   */
  public function changeAllSeriesReleaseDate($date_string)
  {
    $old_timestamp = strtotime($this->release_date);
    $new_timestamp = strtotime($date_string);
    $today_timestamp = strtotime(date('Y-m-d'));
    $this->release_date = $date_string;
//変更前、または変更後の発売日が以前の場合は、他のDVDの発売日は変更しない。
    if ($today_timestamp > $old_timestamp || $today_timestamp > $new_timestamp) {
      return $this->save();
    }
    $change_timestamp = $new_timestamp - $old_timestamp;
    if ($this->DvdSeries) {
      foreach ($this->DvdSeries->DvdPackages as $key => $dvd) {
        if ($dvd->id == $this->id) {
          continue;
        }
        $this->DvdSeries->DvdPackages[$key]->release_date = date('Y-m-d', strtotime($dvd->release_date) + $change_timestamp);
      }
    }
    return $this->save();
  }
}

テスト用のアクションクラス


class testActions extends sfActions{
  public function executeIndex(sfWebRequest $request)
  {
    echo "<pre>";
    $con = Doctrine::getTable('DvdPackage')->getConnection();
    //dvd_seriesをもつデータ
    $test_dvd = Doctrine::getTable('DvdPackage')->find(1);
    var_dump($test_dvd->toArray());
    $test_dvd->changeAllSeriesReleaseDate('2010-04-01');
    var_dump($test_dvd->toArray());
    echo "</pre>";
    exit;
  }
}

上のアクションでは、idが1のdvd_packageデータの発売日(release_date)を1ヶ月遅らせて、
関連するdvd_packageデータの発売日も1ヶ月遅らせようとしています。
結果をみると、、

肝心のidが1のdvd_packageデータが2010-03-03のままで、更新できていません。
これは、changeAllSeriesReleaseDateメソッドのforeachの部分が原因です。
$dvdとして自分自身を取得した際に、保存前のrelease_dateが消えてしまったのです。

保存前、というのが問題なので、
$this->release_date = $date_string;
の後に
$this->save();
を入れれば大丈夫です。

今まで2回ほどこのようなケースに出会ったことがありましたので、どこかで役立つことがあるかもしれません。