FuelPHPの「Observer_UpdatedAt」でupdated_atが更新されない場合がある

環境は、
FuelPHP:1.8
PHP:5.6.25
です。

FuelPHPでDBの更新処理を行う場合にOrm\Modelクラスを利用するのが一般的かと思いますが、
各テーブルのデータが作成・更新された段階で作成日や更新日を自動で挿入したいケースは多くあると思います。

そういった場合はObserverを利用するのが便利です。

Observerの設定について詳しくは公式サイトに記載があるので参照してください。
Included – Observers – Orm パッケージ – FuelPHP ドキュメント

各テーブルのフィールドに、

  • created_at
  • updated_at

を作成して、
Model内でたとえば下記のようにObserverを設定することで自動で挿入できます。

	protected static $_observers = array(
		'Orm\Observer_CreatedAt' => array(
			'events' => array(
				'before_insert'
			),
			'mysql_timestamp' => true
		),
		'Orm\Observer_UpdatedAt' => array(
			'events' => array(
				'before_save'
			),
			'mysql_timestamp' => true
		)
	);

検証

結論から書くと、
Modelからのsave()時に指定した更新対象の値とDBの対象カラムの値に変更点がない場合にupdated_atが更新されない。
(差分がないので更新していないと言えばそれまでですが。。。)

DB

hoges
ID PK
name 情報項目
created_at 作成日時
updated_at 更新日時

Model

class Model_Hoge extends \Orm\Model
{
	protected static $_properties = array(
		'id',
		'name',
		'created_at',
		'updated_at'
	);

	protected static $_observers = array(
		'Orm\Observer_CreatedAt' => array(
			'events' => array(
				'before_insert'
			),
			'mysql_timestamp' => true
		),
		'Orm\Observer_UpdatedAt' => array(
			'events' => array(
				'before_update'
			),
			'mysql_timestamp' => true
		)
	);
}

Insertを実行

	public function do_insert()
	{
		$model = \Model_Hoge::forge();
		$model->name = 'insert_test';
		$model->save();
	}

DBの値は下記の通り

ID name created_at updated_at
1 insert_test 2016-09-16 11:00:16 <null>

Updateを実行(値を変えて)

	public function do_update()
	{
		$model = \Model_Group::find(1);
		$model->name = 'save_test';
		$model->save();
	}

DBの値は下記の通り

ID name created_at updated_at
1 save_test 2016-09-16 11:00:16 2016-09-16 11:41:16

updated_atに値が挿入されていますね。

Updateを実行(値を変えない)

数分後に下記を実行

	public function do_update()
	{
		$model = \Model_Group::find(1);
		$model->name = 'save_test';
		$model->save();
	}

DBの値は下記の通り

ID name created_at updated_at
1 save_test 2016-09-16 11:00:16 2016-09-16 11:41:16

updated_atに変更がありません。

内部処理確認

Ormパッケージのupdate()を確認すると、
「Non changed objects don’t have to be saved, but return true anyway (no reason to fail)」
とコメントがあり、変更がなければそこで処理を終了している箇所がありました。

	/**
	 * Save using UPDATE
	 */
	protected function update()
	{
		// New objects can't be updated, neither can frozen
		if ($this->is_new())
		{
			return false;
		}

		// Non changed objects don't have to be saved, but return true anyway (no reason to fail)
		if ( ! $this->is_changed(array_keys(static::properties())))
		{
			return true;
		}

		$this->observe('before_update');

		// Create the query and limit to primary key(s)
		$query       = Query::forge(get_called_class(), static::connection(true));
		$primary_key = static::primary_key();
		$properties  = array_keys(static::properties());
		//Add the primary keys to the where
		$this->add_primary_keys_to_where($query);

		// Set all current values
		foreach ($properties as $p)
		{
			if ( ! in_array($p, $primary_key) )
			{
				if (array_key_exists($p, $this->_original))
				{
					$this->{$p} !== $this->_original[$p] and $query->set($p, isset($this->_data[$p]) ? $this->_data[$p] : null);
				}
				else
				{
					array_key_exists($p, $this->_data) and $query->set($p, $this->_data[$p]);
				}
			}
		}

		// Return false when update fails
		if ( ! $query->update())
		{
			return false;
		}

		$this->_original = $this->_data;
		static::$_cached_objects[get_class($this)][static::implode_pk($this)] = $this;

		// update the original property on success
		$this->observe('after_update');

		return true;
	}

更新日を更新したい場合は?

フィールドに更新がなかったけど更新日を更新したい場合もあるかもしれません。

表現が気持ち悪いですが、、、
複数テーブルや外部連携などの一連の処理の一部の場合にこの部分だけ更新日に変更がなくで不整合が生じる等
(設計が悪いというツッコミもありますが。。。)

保持しているフィールドのどこかに変更があればOKなので、
下記のように明示的に値を入れてあげればupdated_atは必ず更新されます。
(過去の日付とかを入れてもObserverで現在日時が挿入されるので日付がずれることはありません)

	public function do_update()
	{
		$model = \Model_Group::find(1);
		$model->name = 'save_test';
		$model->updated_at = date('Y/m/d H:i:s');
		$model->save();
	}

DBの値は下記の通り

ID name created_at updated_at
1 save_test 2016-09-16 11:00:16 2016-09-16 12:02:24

まとめ

通常created_atやupdated_atがビジネスロジックに依存することはあまり多くないですが、
updated_atでフィルタリングした後に処理を行いたいなどの機能があった場合に、
抽出漏れが発生する可能性があります。

知っていればそれを想定したプログラムの構築ができますが、
更新処理時にフィールドに変更がない場合というのは意外と意識しない部分かと思います。

「○○日に更新したはずなのに表示されている更新日と一致しない」
「○○日に更新したはずのデータが取得できていない」
のようなケースがあった場合は本仕様を疑ってみるといいかと思います。

参考サイト

FuelPHPのORM Observerはとても便利

  • このエントリーをはてなブックマークに追加
  • Pocket
  • LINEで送る