process S+D

developing process with symfony and doctrine

ふつうにSQLでUPDATE文を書くときは、複数のレコードに対して変更が適用されますが、Doctrine_Recordクラスを使って変更をしようとすると、一つ一つレコードを取得して、save()メソッドを呼ぶということをしなければなりません。
これはリソースの無駄遣いなので、複数のレコードを一括して変更する場合は、Doctrine_Queryのupdate()メソッドを使います。

$q = Doctrine::getTable('SomeTable')->createQuery();
$q->update('SomeTable');
$q->set('field_name', 'new_field_value');
$q->where('field_name > ?', 'some_value');
$q->execute();
タグ:

関連する投稿

Doctrine_Queryクラスにはdistinct()というメソッドがあるのですが、引数はブール値だけで、フィールドを指定することが出来ません。
さらに、selectメソッドでid以外のフィールドにDISTINCT指定をしても、常にテーブルのidが含まれてしまうので、全レコードが返されます。
手動でフィールドにエイリアスをつけてやると、idフィールドをSELECTからはずすことが出来るのですが、idが含まれていないと、複数の結果が返ってくるべきところが、一件しか返ってきません。

にっちもさっちもいかないので、doctrineのQueryクラスを経由せずに、直にPDOへクエリを発行してやります。

$query = 'SELECT DISTINCT t.field_name FROM table_foo t WHERE t.another_field = ?'
$con = $this->getConnection()->prepare($query);
$con->execute(array('another_field_value'));
return $con->fetchAll(PDO::FETCH_CLASS);

結果セットの値を参照するには

$result->field_name

というようにします。
フィールド名は AS で書き換えられますが、プロパティを直接参照しているので、

  • getが付かない
  • ()が付かない

という違いが有り、通常の処理と比べると、少し違和感があります。

タグ:

関連する投稿

lib/model/doctrine以下に生成される、レコードの親クラスを変更したいときは、
/plugins/sfDoctrinePlugin/lib/task/sfDoctrineBuildModelTask.class.php のオプション指定を変更します。

このファイルにはtableClassNameという設定もありますが、これは設定しても有効になりません。
テーブルクラスの親クラスを変更したい場合は、以下のパッケージ指定で生成される中間クラスをスキップする方法と同じやり方で、直接doctrineのライブラリを書き換えます。

パッケージ指定をすると、
Doctrine_Record/Doctrine_Table -> PackageHoge/PackageHogeTable -> Hoge/HogeTable
というように、間にパッケージ用の抽象クラスが自動で作られるのですが、直接Doctrine_Tableを継承したい場合は、
/plugins/sfDoctrinePlugin/lib/doctrine/Doctrine/Import/Builder.phpファイルの、buildRecordメソッドの中身を三箇所変更します。

//一箇所目
//            $topLevel['inheritance']['extends'] = (isset($topLevel['package']) && $topLevel['package']) ? $this->_packagesPrefix . $topLevel['className']:$this->_baseClassPrefix . $topLevel['className'];
$topLevel['inheritance']['extends'] = $this->_baseClassPrefix . $topLevel['className'];
//二箇所目
/*            
                $topLevel['tableClassName'] = $topLevel['topLevelClassName'] . 'Table';
                $topLevel['inheritance']['tableExtends'] = $packageLevel['className'] . 'Table';
            } else {
                $topLevel['tableClassName'] = $topLevel['className'] . 'Table';
                $topLevel['inheritance']['tableExtends'] = isset($definition['inheritance']['extends']) ? $definition['inheritance']['extends'] . 'Table':'Doctrine_Table';
            }
*/
$topLevel['tableClassName'] = $topLevel['className'] . 'Table';
$topLevel['inheritance']['tableExtends'] = isset($definition['inheritance']['extends']) ? $definition['inheritance']['extends'] . 'Table':'Doctrine_Table';
            if ( ! empty($packageLevel)) {
//三箇所目 コメントアウト
//                $this->writeDefinition($packageLevel);
            }
タグ: ,

関連する投稿

Doctrine使い方まとめ - indexesについて

Posted by shuwat on 9 月-11-2008

doctrineのマニュアルでは、インデックスを張る場合、
Doctrine - 1.0 Manual - Basic schema mapping
と書いてありますが、これだと

symfony doctrine:build-model

をすると、yamlのパースでエラーが出ます。

以下のようにfields:の値を配列にしてやれば、エラーは出なくなります。

      fields: [name]
タグ: ,

関連する投稿

スキーマファイルを複数に分けることが出来るのと同じように、doctrineが自動生成するクラスも、ディレクトリ分けが出来ます。
スキーマファイルの先頭に、

---
package: Foo

と書くだけです。

サブディレクトリの指定は、スラッシュで区切ります。

---
package: Foo/Bar

これで、lib/model/doctrine以下に、指定されたディレクトリが作成され、その中にファイルが書き込まれます。

しかし、plugins/ 以下に、パッケージ名/PluginHoge.class.php という謎のファイルが出来てしまいます。
これは/plugins/sfDoctrinePlugin/lib/task/sfDoctrineBuildModelTask.class.php のオプション指定がおかしいせいで、
sfDoctrineBuildModelTask.class.phpでは、作成先がプラグインディレクトリに、接頭辞がPluginになってしまっています。
以下のように変更すると、lib/ディレクトリ以下に中間ファイルが生成されるようになります。

//    $import->setOption('packagesPath', sfConfig::get('sf_plugins_dir'));
//    $import->setOption('packagesPrefix', 'Plugin');
    $import->setOption('packagesPath', '');
    $import->setOption('packagesPrefix', 'Package');
タグ: ,

関連する投稿

スキーマファイルを書き換えて、

symfony doctrine:build-model

をすると、Missing class name.というエラーに遭遇することがあります。
これはrelations:に書かれているクラス名が、定義されていないときに起こるようなのですが、エラーの内容も、どこでエラーが起こっているのかもわかりません。

クラスが少ないうちは、目視で確認して修正することも出来ますが、多くなってくると大変です。
そこで、スキーマのエラーと、relations:の未定義クラスを検出する小さなスクリプトを書きました。

以下のコードをファイルに保存して、プロジェクトディレクトリ直下に置き、php filename.phpで実行してください。

<?php
require_once(dirname(__FILE__).'/config/ProjectConfiguration.class.php');
$yaml = new sfYaml();
$files = scandir(dirname(__FILE__).'/config/doctrine');
 
$schema = array();
foreach($files as $file){
	$fullpath_filename = dirname(__FILE__).'/config/doctrine/'.$file;
	if(is_file($fullpath_filename)){
		$schema = array_merge($schema, sfYaml::load($fullpath_filename));
	}
}
 
$class = array();
$relation = array();
foreach($schema as $key=>$value){
	if(is_array($value) === true){
		$class[] = $key;
		if(array_key_exists('relations', $value)){
			foreach($value['relations'] as $_key => $_value){
				if(array_key_exists('class', $_value)){
					$relation[] = $_value['class'];
				}
				else{
					$relation[] = $_key;
				}
 
			}
		}
	}
}
 
$relation = array_unique($relation);
 
foreach($relation as $value){
	if(array_search($value, $class) === false){
		echo 'リレーション定義の'.$value.'クラスが見つかりません';
	}
}
?>

yamlのシンタックスエラーはsfYamlがエラーメッセージをはいてくれるので、どこがおかしいのかすぐにわかります。
relations:の未定義クラスがあれば、最後にまとめて表示されます。
問題がなければ、何も表示せずに終了します。

タグ: ,

関連する投稿

Doctrine使い方まとめ - ページ処理

Posted by shuwat on 9 月-8-2008

propelではsymfonyのプラグインでページ処理をしていましたが、doctrineにはDoctrine_Pagerというクラスがあるので、これを使ってページ処理をします。
最近登録されたsfDoctrineSuperPagerPluginというのもありますが、
jsライブラリはprototypeではなくjQueryを使いたいので、今回は採用しません。

Doctrine_Pagerには、Doctrine_Pager_Layoutというクラスもあって、表示に関する部分を受け持っているようなのですが、ドキュメントが無くて使い方がいまいちわからないので、独自のクラスを作って、ページ処理を行ってみます。

myPager.phpという名前で以下の内容のファイルを作り、/libディレクトリ以下におきます。

class myPager extends Doctrine_Pager
{
	private $pageIdentifier = 'page';
	private $itemsPerPageIdentifier = 'perpage';
	private $defaultPage = 1;
	private $defaultItemsPerPage = 10;
	private $slideChunk = 10;
 
	function __construct($query_object, $page=null, $perpage=null)
	{
		$page = ($page == null ? $this->defaultPage : $page);
		$perpage = ($perpage == null ? $this->defaultItemsPerPage : $perpage);
		parent::__construct($query_object, $page, $perpage);	
	}
 
	/**
	 * 全ページ中、現在リンクを表示すべきページを取得する
	 */
	private function getPageRange()
	{
	    $pager_range = $this->getRange('Sliding', array('chunk' => $this->slideChunk ));
    return $pager_range->rangeAroundPage();
	}
 
	/**
	 * ページリンクを取得する
	 */
	public function getNavigation($uri = null)
	{
		sfLoader::loadHelpers('Url');
		$navi = '';
 
		if($this->haveToPaginate()){
		    $uri = $uri.(preg_match('/\?/', $uri) ? '&' : '?').$this->pageIdentifier.'=';
 
		    if($this->getPage() != 1){
                        $navi .= '<span class="prev_page">'.link_to('前へ', $uri.$this->getPreviousPage()).'</span>';
                        $navi .= '<span class="first_page">'.link_to('[1]', $uri.'1').'</span>';
		    }
		    else{
		    	$navi .= '<span class="prev_page">前へ</span>';
		    	$navi .= '<span class="current_page">[1]</span>';
		    }
 
			$pages = $this->getPageRange();
 
			if($pages[0] > 2) $navi .= '・・';
			$range_last_page = 0;
			foreach($pages as $value){
				if($value != $this->getFirstpage() && $value != $this->getLastPage()){
					if($value != $this->getPage()){
						$navi .= '<span class="page">'.link_to('['.$value.']', $uri.$value).'</span>';
					}
					else{
						$navi .= '<span class="current_page">'.'['.$value.']</span>';
					}
				}
				$range_last_page = $value;
			}
 
			if($range_last_page < $this->getLastPage() - 1) $navi .= '・・';
			if($this->getPage() != $this->getLastPage()){
		    	    $navi .= '<span class="last_page">'.link_to('['.$this->getLastPage().']', $uri.$this->getLastPage()).'</span>';
		    	    $navi .= '<span class="next_page">'.link_to('次へ', $uri.$this->getNextPage()).'</span>';
			}
			else{
		    	    $navi .= '<span class="current_page">['.$this->getLastPage().']</span>';
		    	    $navi .= '<span class="next_page">次へ</span>';
			}
		}
 
		return $navi;
	}
 
	/**
	 * データ件数、表示中のページを返す
	 */
	public function getInformation()
	{
		$info ='';
		if($this->getNumResults()>0){
			$info = '全'.$this->getNumResults().'件中 ';
			$info .= ($this->getPage() == 1 ? 1 : ($this->getPage()-1) * $this->getMaxPerPage() +1)  . '件から';
			if($this->getPage() != $this->getLastPage()){
				$info .= $this->getPage() * $this->getMaxPerPage() . '件を表示中';
			}
			else{
				$info .= $this->getNumResults() . '件を表示中';
			}
		}
 
		return $info;
	}
}

ページ処理をしたいところでは以下のようにします。

    $query = Doctrine::getTable('User')->createQuery('t');
    $query->addWhere('t.email IS NOT NULL');
    $pager = new myPager($query, 1, 10); //クエリと、最初に表示するページ、1ページの表示件数
    $this->data= $pager->execute();
    $this->navi= $pager->getNavigation();
    $this->info= $pager->getInformation();

ビューでは、$dataにレコードオブジェクトの配列が、$naviにページナビゲーションが、$infoにページ情報が格納されるので、これらをページ内で表示します。

タグ: ,

関連する投稿

  • レコードのインスタンスを作製する

    $record = new ClassName();
  • レコードに値をセットする

    $record->set('field_name', value);
  • レコードから値を取得する

    $record->getFieldName();
  • 配列からレコードに値をセットする

    $record->fromArray($array_value);

    配列の中で、$recordのプロパティと一致するキーを持つ値だけがセットされます。

  • レコードを保存する

    $record->save();
  • レコードを削除する

    $record->delete();
タグ:

関連する投稿

  • クエリオブジェクトの取得

    $query = Doctrine::getTable('ClassName')->createQuery('t');

    createQueryの引数はテーブルのエイリアスです。

  • テーブルのJOIN

    $query->leftJoin('t.JoinClassName p');

    JoinClassNameに指定するのは、スキーマでrelations: に記述したリレーション名です。
    最後についているpは、joinしたテーブルのエイリアスになります。

  • where句の追加 数値と文字列

    $query->addWhere('t.field_name = ?', $value);
  • where句の追加 ブール値

    $query->addWhere('t.field_name = ?', intval($value));
  • where句の追加 文字列の部分一致

    $query->addWhere('t.field_name LIKE ?', '%'.$value.'%');
  • where句の追加 IN指定

    $query->whereIn('t.field_name', $array_value);
  • where句の追加 NOT IN指定

    $query->whereNotIn('t.field_name', $array_value);
  • select句の追加

    $query->addSelect('t.field_name1, t.field_name2');

    取得したいフィールドをカンマ区切りで指定します。
    addSelectもしくはselectメソッドでフィールドを指定しない場合、すべてのフィールドが取得されます。

  • Order By句の追加(降順)

    $query->addOrderBy('t.field_name desc');

    昇順の場合には desc をはずします。

  • limit, offset句の追加

    $query->limit(10);
    $query->offset(0);

    クエリ結果の最初の10件を返します。

  • クエリの実行

    $query->execute();

    戻り値はレコードオブジェクトの配列です。レコードが見つからない場合はfalseが返ります。
    テーブルのfind系メソッドと同じように、戻り値にはtoArray()メソッドが使えます。
    ただし、toArray()すると、JOINしたテーブルのデータが消えるので注意が必要です。
    JOIN先のデータを保持したまま配列にするには、toArray(true)とします。

  • クエリ結果から最初の一件を取得

    $query->fetchOne();

    戻り値はレコードオブジェクトです。

  • レコード数を数える

    $query->count();
タグ:

関連する投稿

テーブルを作成したので、実際にデータにアクセスする方法を説明していきます。まずはテーブルから。

  • テーブルオブジェクトを取得する
    $table = Doctrine::getTable('ClassName');

    戻り値はテーブルオブジェクトです。

  • テーブルから全レコードを取得する

    $result = Doctrine::getTable('ClassName')->findAll();

    戻り値はレコードオブジェクトの配列です。レコードが見つからない場合はfalseが返ります。

  • テーブルからプライマリキーでレコードを取得する

    $result = Doctrine::getTable('ClassName')->find($id);

    戻り値はレコードオブジェクトです。レコードが見つからない場合はfalseが返ります。

  • テーブルからフィールドの値でレコードを取得する

    $result = Doctrine::getTable('ClassName')->findByFieldName($value);

    戻り値はレコードオブジェクトの配列です。レコードが見つからない場合はfalseが返ります。

  • テーブルからフィールドの値で一つのレコードを取得する

    $result = Doctrine::getTable('ClassName')->findOneByFieldName($value);

    戻り値はレコードオブジェクトです。レコードが見つからない場合はfalseが返ります。

  • レコードの数を数える
    $result = Doctrine::getTable('ClassName')->count();

    戻り値は数値です。

  • クエリ結果を配列に変換する

    $result = $result->toArray();

    オブジェクトやオブジェクトの配列が、配列に変換されます。

テーブルオブジェクトにはごく基本的なメソッドしかないので、開発の過程でより複雑なクエリを発行したい場合には、
自分でメソッドを追加し、その中でクエリオブジェクトを使うことになります。

タグ:

関連する投稿