カテゴリー別アーカイブ: プログラム小技

DoctrineでFatal error: Allowed memory size of ???? bytes exhausted を回避する

PHPでDoctrineを多用するようになったら、メモリが足りないというエラーが頻発してきました。

Doctrineそのものが3MBほど使うのは仕方ないとしても、クエリやレコード追加する度に使用メモリが増えつづけるのはいただけない。

不要メモリ削除にチャレンジしてみました。
まずは、一番簡単な方法。

例)
$query = Doctrine_Query::create()
->from(‘SomeModel’)
->where(…);
$rs = $query->execute();

// 使用後に、ここで参照を止めてみる
$rs = null;
$query = null;

通常はスコープをなくせばいいはずなのに、どんどん使用メモリが増えていきます。

さらに調べると、Doctrine_Recordが生成される際、Doctrine_Table_Repositoryに自分を登録しているではありませんか・・・

Doctrine_Query、Doctrine_Collection、Doctrine_Recordにはfreeメソッドがありました。特にDoctrine_Collectionでは、freeメソッドの引数deepをtrueにすることで、子のDoctrine_Recordのリレーション先のfreeまでやってくれそうです。

例)
$query = Doctrine_Query::create()
->from(‘SomeModel’)
->where(…);
$rs = $query->execute();

// 使用後に、Repositoryから削除し、参照も止めてみる
$rs->free(true);
$rs = null;
$query->free();
$query = null;

これで使用メモリは増えなくなり、スッキリしました。

ちなみに、そもそもDoctrine_Table_Repositoryに登録しない設定はないのかとしらべましたが、そんなものはありませんでした。

若干面倒ですが、freeしてnullするしかなさそうです。

(nullは、スコープが外れれば同じことなので、よほどシビアでなければやらなくても良い)

Zend_Formを使う時の注意

Zend_Formはとても便利です。

フォームに関するルーチンワーク(入力フォーム作成、バリデータ定義、確認フォーム作成)を定義ファイル1つに置き換えることができます。

ところが、定義ファイルを少し更新すると、動作がおかしくなることがちょくちょくあります。

1つ目はsetElementDecoratorsとsetElementFiltersの使い方。

このメソッドを呼び出すと、個別のエレメントで指定していたデコレータとフィルターが消されます。

よって、下記のような書き方は間違っています。

[test]
form.elementFilters.trim = “StringTrim”
form.elements.name.options.filters.lower.filter = “StringToLower”

この場合、StringTrimだけがフィルタとして登録されるので、エレメントごとにデコレータやフィルタが違う場合、すべて個別に指定しましょう。

2つ目は、カスタムフィルタなどフィルタを定義し、そのフィルタコンストラクタにオプションを定義したい時の制限です。

[test]
form.elements.name.options.filters.myfilter.filter = “MyFilter”
form.elements.name.options.filters.myfilter.filter.options.param2 = “second option”
form.elements.name.options.filters.myfilter.filter.options.param1 = “first option”

カスタムフィルタの定義は上のような形で良いのですが、MyFilterに渡すコンストラクタオプションが、optionsで指定した順番に依存し、

名称は無視されます。したがって、上記の定義例ではMyFilter::__construct($param2, $param1)が呼ばれることになります。

Zend_Form_Elementクラス内の_loadFilterメソッドで、実際にフィルタをロードしているのですが、

インスタンス作成は下記のコードを使用しています。

if (empty($filter['options'])) {
  $instance = new $name;
} else {
  $r = new ReflectionClass($name);
  if ($r->hasMethod('__construct')) {
    $instance = $r->newInstanceArgs((array) $filter['options']);
  } else {
    $instance = $r->newInstance();
  }
}

このRefrectionClassのnewInstanceArgsメソッドは、引数の配列の順番が、コンストラクタの引数の順番に該当するようになっています。

せっかくRefrectionしているのだから、順番ではなくて引数名をハッシュで指定できれば良いのですが・・・残念。

したがって、filterのオプション定義の場合は、コンストラクタの引数の順番と同じ順序で定義しましょう。

以下の書き方の方が、理解しやすいです。

[test]
form.elements.name.options.filters.myfilter.filter = “MyFilter”
form.elements.name.options.filters.myfilter.filter.options.1 = “first option”
form.elements.name.options.filters.myfilter.filter.options.2 = “second option”