symfony と 2038年問題

PHPの日付/時間操作で使用されるUNIXタイムスタンプは、皆さんご存知のとおり、
2038年1月19日 12:14:07 までしか扱えません。2038年問題ですね。
 この問題を知ってから、開発時はなるべくUNIXタイムスタンプを使用しないように しています。
PHP5.2以降は、この問題をクリアしているDateTimeオブジェクトが使用できるので、
symfonyでの開発時はこれを利用しています。

で、symfony自体はどうかというと・・・
対応してませんでしたorz

というか、symfony1.3から標準のORMとなったDoctrine自体がNG!

このままでは、2038年以降の日付を扱うシステムを開発する際に問題になるので
解決することに。

対象となるバージョン: symfony 1.4.2 + doctrine 1.2

まずはDoctrineの方から。

モデルを作成した際に作成されるBaseクラスは、sfDoctrineRecordという抽象クラスを継承します。
これは、Doctrine_Recordというクラスを継承していますが、このクラス内の_isValueModified()内で、
UNIXタイムスタンプを使用しています。

(1526行目)   return strtotime($old) !== strtotime($new);

1./libフォルダ以下に、sfDoctrineRecordを継承した抽象クラスを作成し上記関数を
overrideします。

abstract class myDoctrineRecord extends sfDoctrineRecord
{
  // override
  protected function _isValueModified($type, $old, $new)
  {
    if ($new instanceof Doctrine_Expression) {
        return true;
    }

    if ($type == 'timestamp' || $type == 'date')
    {
      // 2038年問題対応
     // 5/8修正
      //return date_create($old)->format(DATE_ISO8601)
     //                 !== date_create($new)->format(DATE_ISO8601);
     $old_val = null === $old ? false : date_create($old)->format(DATE_ISO8601);
    $new_val = null === $new ? false : date_create($new)->format(DATE_ISO8601);
     return $old_val !== $new_val; } else { return parent::_isValueModified($type, $old, $new); } } }

2./config/ProjectConfiguration内に、以下の関数を追加

  public function configureDoctrine(Doctrine_Manager $manager)
  {
    // 追加したクラス名
    $options = array('baseClassName'=>'myDoctrineRecord');
    sfConfig::set('doctrine_model_builder_options', $options);
  }

モデルの再作成すれば、Baseクラスの継承元が上記クラスに変更されます。

次に、widgetの対応をします。
(日付のwidgetにsfWidgetFormDateを利用している場合)

1./lib/以下に、sfWidgetFormDateを継承したクラスを作成し、render()を
そのままコピーしoverrideします。
class myWidgetFormDate extends sfWidgetFormDate
{
  // override
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
 ~省略
(71行目)
      // 2038年問題対応
      // $value = (string) $value == (string) (integer) $value ?
      //                             (integer) $value : strtotime($value);
     // 5/8 修正
     // $value = date_create($value);
     $value = null === $value ? false : date_create($value);
 ~省略

(78行目)
      // 2038年問題対応
        // $value = array('year' => date('Y', $value),
        //                'month' => date('n', $value),
        //                'day' => date('j', $value));
        $value = array('year' =>$value->format('Y'),
                       'month' => $value->format('n'),
                       'day' => $value->format('j'));

 ~省略

    return strtr($this->getOption('format'), $date);
  }
}

これで、2038年以降の日付も登録できるようになります。
(widgetが日付ではなく日時の場合は、sfWidgetFormTimeも同じようなクラスを作成しなければいけません。
その上で、sfWidgetFormDateTime内のgetTimeObject()で新クラスを返すようにすればOKだと思います。)

まだ他にもあるかもしれませんが、今回はここまで。
後は、気づいたときに修正していきたいと思います。

※ 5/8 追記 : DateTimeオブジェクトを作成し、処理する箇所にバグがあったので修正しました。
 myDoctrineRecord・myWidgetFormDateクラス内で「5/8修正」と記述している箇所です。
 原因: date_create($value) で、$valueがnullの場合、falseではなく現在日時が返る仕様
 でした・・・。

この投稿へのコメント

コメントはありません。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この投稿へのトラックバック

トラックバックはありません。

トラックバック URL