2 // vim: foldmethod=marker
6 * @author Masaki Fujimoto <fujimoto@php.net>
7 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
12 // {{{ Ethna_AppObject
14 * アプリケーションオブジェクトのベースクラス
16 * @author Masaki Fujimoto <fujimoto@php.net>
20 * @todo remove dependency on PEAR::DB
21 * @todo quoteidentifier は Ethna_AppSQL に持っていくべき
30 /** @var object Ethna_Backend backendオブジェクト */
33 /** @var object Ethna_Config 設定オブジェクト */
36 /** @var object Ethna_I18N i18nオブジェクト */
39 /** @var object Ethna_ActionForm アクションフォームオブジェクト */
42 /** @var object Ethna_ActionForm アクションフォームオブジェクト(省略形) */
45 /** @var object Ethna_Session セッションオブジェクト */
48 /** @var string DB定義プレフィクス */
49 var $db_prefix = null;
51 /** @var array テーブル定義。対応するDB上のテーブル名を指定します。*/
52 var $table_def = null;
54 /** @var array プロパティ定義。テーブルのカラム定義を記述します。 */
57 /** @var array プロパティ。各カラムに対応する実際の値です。 */
60 /** @var array プロパティ(バックアップ) */
61 var $prop_backup = null;
63 /** @var int プロパティ定義キャッシュ有効期間(sec) */
64 var $prop_def_cache_lifetime = 86400;
66 /** @var array プライマリキー定義 */
69 /** @var int オブジェクトID (プライマリーキーの値) */
75 // {{{ Ethna_AppObject
77 * Ethna_AppObjectクラスのコンストラクタ
80 * @param object Ethna_Backend &$backend Ethna_Backendオブジェクト
81 * @param mixed $key_type レコードを特定するためのカラム名
83 * @param mixed $key レコードを特定するためのカラム値
84 * @param array $prop プロパティ(レコードの値)一覧
85 * @return mixed 0:正常終了 -1:キー/プロパティ未指定 Ethna_Error:エラー
87 function Ethna_AppObject(&$backend, $key_type = null, $key = null, $prop = null)
89 $this->backend =& $backend;
90 $this->config =& $backend->getConfig();
91 $this->action_form =& $backend->getActionForm();
92 $this->af =& $this->action_form;
93 $this->session =& $backend->getSession();
94 $ctl =& $backend->getController();
97 $db_list = $this->_getDBList();
98 if (Ethna::isError($db_list)) {
100 } else if (is_null($db_list['rw'])) {
101 return Ethna::raiseError(
102 "Ethna_AppObjectを利用するにはデータベース設定が必要です",
105 $this->my_db_rw =& $db_list['rw'];
106 $this->my_db_ro =& $db_list['ro'];
107 // XXX: app objはdb typeを知らなくても動くべき
108 $this->my_db_type = $this->my_db_rw->getType();
111 // 現在、記述可能なテーブルは常に一つで、primaryはtrue
112 if (is_null($this->table_def)) {
113 $this->table_def = $this->_getTableDef();
115 if (is_string($this->table_def)) {
116 $this->table_def = array($this->table_def => array('primary' => true));
118 // プロパティ定義(テーブルのカラム定義)自動取得
119 // データベースから自動取得され、キャッシュされる
120 if (is_null($this->prop_def)) {
121 $this->prop_def = $this->_getPropDef();
125 foreach (array_keys($this->prop_def) as $k) {
126 if (isset($this->prop_def[$k]['primary']) == false) {
127 $this->prop_def[$k]['primary'] = false;
131 // オブジェクトのプライマリキー定義構築
132 foreach ($this->prop_def as $k => $v) {
133 if ($v['primary'] == false) {
136 if (is_null($this->id_def)) {
138 } else if (is_array($this->id_def)) {
139 $this->id_def[] = $k;
140 } else { // scalar の場合
141 $this->id_def = array($this->id_def, $k);
146 if (is_null($key_type) && is_null($key) && is_null($prop)) {
147 // perhaps for adding object
152 // $key_type, $key が指定されたらDBから値を取得し、設定する
153 // $prop が設定された場合はそれを設定する
154 if (is_null($prop)) {
155 $this->_setPropByDB($key_type, $key);
157 $this->_setPropByValue($prop);
160 $this->prop_backup = $this->prop;
163 if (is_array($this->id_def)) {
165 foreach ($this->id_def as $k) {
166 $this->id[] = $this->prop[$k];
169 $this->id = $this->prop[$this->id_def];
179 * プライマリーキーの値が設定されてなければ不正なオブジェクトです。
182 * @return bool true:有効 false:無効
186 if (is_array($this->id)) {
187 return is_null($this->id[0]) ? false : true;
189 return is_null($this->id) ? false : true;
196 * アクティブなオブジェクトかどうかを返す
198 * isValid()メソッドはオブジェクト自体が有効かどうかを判定するのに対し
199 * isActive()はオブジェクトがアプリケーションとして有効かどうかを返す
202 * @return bool true:アクティブ false:非アクティブ
206 if ($this->isValid() == false) {
209 return $this->prop['state'] == OBJECT_STATE_ACTIVE ? true : false;
215 * オブジェクトのプロパティ定義(カラム定義)を返す
218 * @return array オブジェクトのプロパティ定義
222 return $this->prop_def;
231 * @return mixed プライマリキーとなるプロパティ名
235 return $this->id_def;
241 * オブジェクトID(primary keyの値)を返す
244 * @return mixed オブジェクトID
254 * オブジェクトプロパティへのアクセサ(R)
257 * @param string $key プロパティ名(カラム名)
258 * @return mixed プロパティ(カラムの値)
262 if (isset($this->prop_def[$key]) == false) {
263 trigger_error(sprintf("Unknown property [%s]", $key), E_USER_ERROR);
266 if (isset($this->prop[$key])) {
267 return $this->prop[$key];
275 * オブジェクトプロパティ表示名へのアクセサ
276 * プロパティ値と、表示用の値が違う場合 (「県」等)に、
282 * @param string $key プロパティ(カラム)名
283 * @return string プロパティ(カラム)の表示名
285 function getName($key)
287 return $this->get($key);
292 * オブジェクトプロパティ表示名(詳細)へのアクセサ
293 * プロパティ値と、表示用の値が違う場合 (「県」等)に、
297 * @param string $key プロパティ(カラム)名
298 * @return string プロパティ(カラム)の表示名(詳細)
300 function getLongName($key)
302 return $this->get($key);
308 * プロパティ表示名を格納した連想配列を取得する
309 * すべての getName メソッドの戻り値を配列として返します。
312 * @return array プロパティ表示名を格納した連想配列
314 function getNameObject()
318 foreach ($this->prop_def as $key => $elt) {
319 $object[$elt['form_name']] = $this->getName($key);
328 * オブジェクトプロパティ(カラムに対応した値)を設定します。
331 * @param string $key プロパティ(カラム)名
332 * @param string $value プロパティ値
334 function set($key, $value)
336 if (isset($this->prop_def[$key]) == false) {
337 trigger_error(sprintf("Unknown property [%s]", $key), E_USER_ERROR);
340 $this->prop[$key] = $value;
346 * オブジェクトプロパティを指定の形式でダンプする(現在はCSV形式のみサポート)
349 * @param string $type ダンプ形式("csv"...)
350 * @return string ダンプ結果(エラーの場合はnull)
352 function dump($type = "csv")
354 $method = "_dump_$type";
355 if (method_exists($this, $method) == false) {
356 return Ethna::raiseError("Undefined Method [%s]", E_APP_NOMETHOD, $method);
359 return $this->$method();
365 * フォーム値からオブジェクトプロパティをインポートする
368 * @param int $option インポートオプション
369 * OBJECT_IMPORT_IGNORE_NULL: フォーム値が送信されていない場合はスキップ
370 * OBJECT_IMPORT_CONVERT_NULL: フォーム値が送信されていない場合、空文字列に変換
372 function importForm($option = null)
374 foreach ($this->getDef() as $k => $def) {
375 $value = $this->af->get($def['form_name']);
376 if (is_null($value)) {
377 // フォームから値が送信されていない場合の振舞い
378 if ($option == OBJECT_IMPORT_IGNORE_NULL) {
381 } else if ($option == OBJECT_IMPORT_CONVERT_NULL) {
386 $this->set($k, $value);
393 * オブジェクトプロパティをフォーム値にエクスポートする
397 function exportForm()
399 foreach ($this->getDef() as $k => $def) {
400 $this->af->set($def['form_name'], $this->get($k));
407 * オブジェクトを追加する(INSERT)
410 * @return mixed 0:正常終了 Ethna_Error:エラー
411 * @todo remove dependency on PEAR::DB
415 // primary key 定義が sequence の場合、
416 // next idの取得: (pgsqlの場合のみ)
418 foreach (to_array($this->id_def) as $id_def) {
419 if (isset($this->prop_def[$id_def]['seq'])
420 && $this->prop_def[$id_def]['seq']) {
421 // NOTE: このapp object以外からinsertがないことが前提
422 $next_id = $this->my_db_rw->getNextId(
423 $this->prop_def[$id_def]['table'], $id_def);
424 if ($next_id !== null && $next_id >= 0) {
425 $this->prop[$id_def] = $next_id;
432 $sql = $this->_getSQL_Add();
433 for ($i = 0; $i < 4; $i++) {
434 $r =& $this->my_db_rw->query($sql);
435 // エラーの場合 -> 重複キーエラーの場合はリトライ
436 if (Ethna::isError($r)) {
437 if ($r->getCode() == E_DB_DUPENT) {
439 $duplicate_key_list = $this->_getDuplicateKeyList();
440 if (Ethna::isError($duplicate_key_list)) {
441 return $duplicate_key_list;
443 if (is_array($duplicate_key_list)
444 && count($duplicate_key_list) > 0) {
445 foreach ($duplicate_key_list as $k) {
446 return Ethna::raiseNotice('Duplicate Key Error [%s]',
459 return Ethna::raiseError('Cannot detect Duplicate key Error', E_GENERAL);
462 // last insert idの取得: (mysql, sqliteのみ)
463 // primary key の 'seq' フラグがある(最初の)プロパティに入れる
464 $insert_id = $this->my_db_rw->getInsertId();
465 if ($insert_id !== null && $insert_id >= 0) {
466 foreach (to_array($this->id_def) as $id_def) {
467 if (isset($this->prop_def[$id_def]['seq'])
468 && $this->prop_def[$id_def]['seq']) {
469 $this->prop[$id_def] = $insert_id;
475 // ID(Primary Key)の値を設定
476 if (is_array($this->id_def)) {
478 foreach ($this->id_def as $k) {
479 $this->id[] = $this->prop[$k];
481 } else if (isset($this->prop[$this->id_def])) {
482 $this->id = $this->prop[$this->id_def];
484 trigger_error("primary key is missing", E_USER_ERROR);
488 $this->prop_backup = $this->prop;
489 $this->_clearPropCache();
497 * オブジェクトを更新する(UPDATE)
500 * @return mixed 0:正常終了 Ethna_Error:エラー
501 * @todo remove dependency on PEAR::DB
505 $sql = $this->_getSQL_Update();
506 // エラーの場合 -> 重複キーエラーの場合はリトライ(4回)
507 for ($i = 0; $i < 4; $i++) { // magic number
508 $r =& $this->my_db_rw->query($sql);
509 if (Ethna::isError($r)) {
510 if ($r->getCode() == E_DB_DUPENT) {
512 $duplicate_key_list = $this->_getDuplicateKeyList();
513 if (Ethna::isError($duplicate_key_list)) {
514 return $duplicate_key_list;
516 if (is_array($duplicate_key_list)
517 && count($duplicate_key_list) > 0) {
518 foreach ($duplicate_key_list as $k) {
519 return Ethna::raiseNotice('Duplicate Key Error [%s]',
532 return Ethna::raiseError('Cannot detect Duplicate key Error', E_GENERAL);
535 $affected_rows = $this->my_db_rw->affectedRows();
536 if ($affected_rows <= 0) {
537 $this->backend->log(LOG_DEBUG, "update query with 0 updated rows");
541 $this->prop_backup = $this->prop;
542 $this->_clearPropCache();
552 * MySQLのREPLACE文に相当する動作を行う(add()で重複エラーが発生したら
556 * @return mixed 0:正常終了 >0:オブジェクトID(追加時) Ethna_Error:エラー
557 * @todo remove dependency on PEAR::DB
561 $sql = $this->_getSQL_Select($this->getIdDef(), $this->getId());
563 // 重複機ーエラーの場合はリトライ(4回)
564 for ($i = 0; $i < 3; $i++) { // magic number
565 $r = $this->my_db_rw->query($sql);
566 if (Ethna::isError($r)) {
572 $r = $this->update();
576 if (Ethna::isError($r) == false) {
578 } else if ($r->getCode() != E_APP_DUPENT) {
593 * @return mixed 0:正常終了 Ethna_Error:エラー
594 * @todo remove dependency on PEAR::DB
598 $sql = $this->_getSQL_Remove();
599 $r =& $this->my_db_rw->query($sql);
600 if (Ethna::isError($r)) {
604 // プロパティ/バックアップ/キャッシュクリア
605 $this->id = $this->prop = $this->prop_backup = null;
606 $this->_clearPropCache();
614 * オブジェクトID(プライマリーキーの値)を検索する
617 * @param array $filter WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
618 * @param array $order 検索結果ソート条件
619 * (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
620 * @param int $offset 検索結果取得オフセット
621 * @param int $count 検索結果取得数
622 * @return mixed array(0 => 検索条件にマッチした件数,
623 * 1 => $offset, $countにより指定された件数のオブジェクトID一覧)
625 * TODO: remove dependency on PEAR::DB
627 function searchId($filter = null, $order = null, $offset = null, $count = null)
630 if (is_null($offset) == false || is_null($count) == false) {
631 $sql = $this->_getSQL_SearchLength($filter);
632 $r =& $this->my_db_ro->query($sql);
633 if (Ethna::isError($r)) {
636 $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
637 $length = $row['id_count'];
643 $sql = $this->_getSQL_SearchId($filter, $order, $offset, $count);
644 $r =& $this->my_db_ro->query($sql);
645 if (Ethna::isError($r)) {
649 for ($i = 0; $i < $n; $i++) {
650 $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
652 // プライマリキーが1カラムならスカラー値に変換
653 if (is_array($this->id_def) == false) {
654 $row = $row[$this->id_def];
658 if (is_null($length)) {
659 $length = count($id_list);
662 return array($length, $id_list);
668 * オブジェクトプロパティ(レコード)を検索する
671 * @param array $keys 取得するプロパティ(カラム名)
672 * @param array $filter WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
673 * @param array $order 検索結果ソート条件
674 * (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
675 * @param int $offset 検索結果取得オフセット
676 * @param int $count 検索結果取得数
677 * @return mixed array(0 => 検索条件にマッチした件数,
678 * 1 => $offset, $countにより指定された件数のオブジェクトプロパティ一覧)
680 * TODO: remove dependency on PEAR::DB
682 function searchProp($keys = null, $filter = null, $order = null,
683 $offset = null, $count = null)
686 if (is_null($offset) == false || is_null($count) == false) {
687 $sql = $this->_getSQL_SearchLength($filter);
688 $r =& $this->my_db_ro->query($sql);
689 if (Ethna::isError($r)) {
692 $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
693 $length = $row['id_count'];
698 $prop_list = array();
699 $sql = $this->_getSQL_SearchProp($keys, $filter, $order, $offset, $count);
700 $r =& $this->my_db_ro->query($sql);
701 if (Ethna::isError($r)) {
705 for ($i = 0; $i < $n; $i++) {
706 $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
709 if (is_null($length)) {
710 $length = count($prop_list);
713 return array($length, $prop_list);
719 * オブジェクトのアプリケーションデフォルトプロパティを設定する
721 * コンストラクタにより指定されたキーにマッチするエントリがなかった場合の
722 * デフォルトプロパティをここで設定することが出来る
725 * @param mixed $key_type 検索キー名
726 * @param mixed $key 検索キー
729 function _setDefault($key_type, $key)
737 * オブジェクトプロパティをDBから取得する
740 * @param mixed $key_type 検索キー名
741 * @param mixed $key 検索キー
742 * TODO: depend on PEAR::DB
744 function _setPropByDB($key_type, $key)
746 global $_ETHNA_APP_OBJECT_CACHE;
748 $key_type = to_array($key_type);
749 $key = to_array($key);
750 if (count($key_type) != count($key)) {
751 trigger_error(sprintf("Unmatched key_type & key length [%d-%d]",
752 count($key_type), count($key)), E_USER_ERROR);
755 foreach ($key_type as $elt) {
756 if (isset($this->prop_def[$elt]) == false) {
757 trigger_error("Invalid key_type [$elt]", E_USER_ERROR);
763 $class_name = strtolower(get_class($this));
764 if (is_array($_ETHNA_APP_OBJECT_CACHE) == false
765 || array_key_exists($class_name, $_ETHNA_APP_OBJECT_CACHE) == false) {
766 $_ETHNA_APP_OBJECT_CACHE[$class_name] = array();
768 $cache_key = serialize(array($key_type, $key));
769 if (array_key_exists($cache_key, $_ETHNA_APP_OBJECT_CACHE[$class_name])) {
770 $this->prop = $_ETHNA_APP_OBJECT_CACHE[$class_name][$cache_key];
775 $sql = $this->_getSQL_Select($key_type, $key);
778 $r =& $this->my_db_ro->query($sql);
779 if (Ethna::isError($r)) {
785 if ($this->_setDefault($key_type, $key) == false) {
790 trigger_error("Invalid key (multiple rows found) [$key]", E_USER_ERROR);
793 $this->prop = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
796 $_ETHNA_APP_OBJECT_CACHE[$class_name][$cache_key] = $this->prop;
800 // {{{ _setPropByValue
802 * コンストラクタで指定されたプロパティを設定する
805 * @param array $prop プロパティ一覧
807 function _setPropByValue($prop)
809 $def = $this->getDef();
810 foreach ($def as $key => $value) {
811 if ($value['primary'] && isset($prop[$key]) == false) {
813 trigger_error("primary key is not identical", E_USER_ERROR);
815 $this->prop[$key] = $prop[$key];
820 // {{{ _getPrimaryTable
822 * オブジェクトのプライマリテーブルを取得する
825 * @return string オブジェクトのプライマリテーブル名
827 function _getPrimaryTable()
829 $tables = array_keys($this->table_def);
836 // {{{ _getDuplicateKeyList
841 * @return mixed 0:重複なし Ethna_Error:エラー array:重複キーのプロパティ名一覧
842 * TODO: depend on PEAR::DB
844 function _getDuplicateKeyList()
846 $duplicate_key_list = array();
848 // 現在設定されているプライマリキーにNULLが含まれる場合は検索しない
850 foreach (to_array($this->id_def) as $k) {
851 if (isset($this->prop[$k]) == false || is_null($this->prop[$k])) {
857 // プライマリキーはmulti columnsになり得るので別扱い
859 $sql = $this->_getSQL_Duplicate($this->id_def);
860 $r =& $this->my_db_rw->query($sql);
861 if (Ethna::isError($r)) {
863 } else if ($r->numRows() > 0) {
864 // we can overwrite $key_list here
865 $duplicate_key_list = to_array($this->id_def);
870 foreach ($this->prop_def as $k => $v) {
871 if ($v['primary'] == true || $v['key'] == false) {
874 $sql = $this->_getSQL_Duplicate($k);
875 $r =& $this->my_db_rw->query($sql);
876 if (Ethna::isError($r)) {
878 } else if ($r->NumRows() > 0) {
879 $duplicate_key_list[] = $k;
883 if (count($duplicate_key_list) > 0) {
884 return $duplicate_key_list;
891 // {{{ _getSQL_Select
893 * オブジェクトプロパティを取得するSQL文を構築する
896 * @param array $key_type 検索キーとなるプロパティ(カラム)名一覧
897 * @param array $key $key_typeに対応するキー一覧
898 * @return string SELECT文
900 function _getSQL_Select($key_type, $key)
902 $key_type = to_array($key_type);
906 for ($i = 0; $i < count($key_type); $i++) {
910 $key = to_array($key);
914 Ethna_AppSQL::escapeSQL($key, $this->my_db_type);
916 $tables = implode(',',
917 $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
918 $columns = implode(',',
919 $this->my_db_ro->quoteIdentifier(array_keys($this->prop_def)));
923 for ($i = 0; $i < count($key_type); $i++) {
924 if (is_null($condition)) {
925 $condition = "WHERE ";
927 $condition .= " AND ";
929 $condition .= Ethna_AppSQL::getCondition(
930 $this->my_db_ro->quoteIdentifier($key_type[$i]), $key[$i]);
933 $sql = "SELECT $columns FROM $tables $condition";
941 * オブジェクトと追加するSQL文を構築する
944 * @return string オブジェクトを追加するためのINSERT文
946 function _getSQL_Add()
948 $tables = implode(',',
949 $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
953 $prop_arg_list = $this->prop;
955 Ethna_AppSQL::escapeSQL($prop_arg_list, $this->my_db_type);
956 foreach ($this->prop_def as $k => $v) {
957 if (isset($prop_arg_list[$k]) == false) {
960 $key_list[] = $this->my_db_rw->quoteIdentifier($k);
961 $set_list[] = $prop_arg_list[$k];
964 $key_list = implode(', ', $key_list);
965 $set_list = implode(', ', $set_list);
966 $sql = "INSERT INTO $tables ($key_list) VALUES ($set_list)";
972 // {{{ _getSQL_Update
974 * オブジェクトプロパティを更新するSQL文を構築する
977 * @return オブジェクトプロパティを更新するためのUPDATE文
979 function _getSQL_Update()
981 $tables = implode(',',
982 $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
986 $prop_arg_list = $this->prop;
987 Ethna_AppSQL::escapeSQL($prop_arg_list, $this->my_db_type);
988 foreach ($this->prop_def as $k => $v) {
989 if ($set_list != "") {
992 $set_list .= sprintf("%s=%s",
993 $this->my_db_rw->quoteIdentifier($k),
999 foreach (to_array($this->id_def) as $k) {
1000 if (is_null($condition)) {
1001 $condition = "WHERE ";
1003 $condition .= " AND ";
1005 $v = $this->prop_backup[$k]; // equals to $this->id
1006 Ethna_AppSQL::escapeSQL($v, $this->my_db_type);
1007 $condition .= Ethna_AppSQL::getCondition(
1008 $this->my_db_rw->quoteIdentifier($k), $v);
1011 $sql = "UPDATE $tables SET $set_list $condition";
1017 // {{{ _getSQL_Remove
1019 * オブジェクトを削除するSQL文を構築する
1022 * @return string オブジェクトを削除するためのDELETE文
1024 function _getSQL_Remove()
1026 $tables = implode(',',
1027 $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
1029 // 検索条件(primary key)
1031 foreach (to_array($this->id_def) as $k) {
1032 if (is_null($condition)) {
1033 $condition = "WHERE ";
1035 $condition .= " AND ";
1037 $v = $this->prop_backup[$k]; // equals to $this->id
1038 Ethna_AppSQL::escapeSQL($v, $this->my_db_type);
1039 $condition .= Ethna_AppSQL::getCondition(
1040 $this->my_db_rw->quoteIdentifier($k), $v);
1042 if (is_null($condition)) {
1043 trigger_error("DELETE with no conditon", E_USER_ERROR);
1047 $sql = "DELETE FROM $tables $condition";
1053 // {{{ _getSQL_Duplicate
1055 * オブジェクトプロパティのユニークチェックを行うSQL文を構築する
1058 * @param mixed $key ユニークチェックを行うプロパティ名
1059 * @return string ユニークチェックを行うためのSELECT文
1061 function _getSQL_Duplicate($key)
1063 $tables = implode(',',
1064 $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
1065 $columns = implode(',',
1066 $this->my_db_ro->quoteIdentifier(array_keys($this->prop_def)));
1069 // 検索条件(現在設定されているプライマリキーは検索対象から除く)
1070 if (is_null($this->id) == false) {
1071 $primary_value = to_array($this->getId());
1073 foreach (to_array($this->id_def) as $k) {
1074 if (is_null($condition)) {
1075 $condition = "WHERE ";
1077 $condition .= " AND ";
1079 $value = $primary_value[$n];
1080 Ethna_AppSQL::escapeSQL($value, $this->my_db_type);
1081 $condition .= Ethna_AppSQL::getCondition(
1082 $this->my_db_ro->quoteIdentifier($k), $value, OBJECT_CONDITION_NE);
1087 foreach (to_array($key) as $k) {
1088 if (is_null($condition)) {
1089 $condition = "WHERE ";
1091 $condition .= " AND ";
1093 $v = $this->prop[$k];
1094 Ethna_AppSQL::escapeSQL($v, $this->my_db_type);
1095 $condition .= Ethna_AppSQL::getCondition(
1096 $this->my_db_ro->quoteIdentifier($k), $v);
1099 $sql = "SELECT $columns FROM $tables $condition";
1105 // {{{ _getSQL_SearchLength
1107 * オブジェクト検索総数(offset, count除外)を取得するSQL文を構築する
1110 * @param array $filter WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
1111 * @return string 検索総数を取得するためのSELECT文
1112 * @todo my_db_typeの参照を廃止
1114 function _getSQL_SearchLength($filter)
1116 // テーブル名をクォートした上で連結。
1117 $tables = implode(',',
1118 $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
1120 // プライマリーキー以外の検索条件が含まれていた
1121 // 場合は、追加テーブルがあるとみなし、
1122 // その解釈は _SQLPlugin_SearchTable に任せる
1123 if ($this->_isAdditionalField($filter)) {
1124 $tables .= " " . $this->_SQLPlugin_SearchTable();
1127 $id_def = to_array($this->id_def);
1130 // 複数あった場合ははじめのものを使う
1131 $column_id = $this->my_db_ro->quoteIdentifier($this->_getPrimaryTable())
1132 . "." . $this->my_db_ro->quoteIdentifier($id_def[0]);
1133 $id_count = $this->my_db_ro->quoteIdentifier('id_count');
1134 $condition = $this->_getSQL_SearchCondition($filter);
1136 if ($this->my_db_type === 'sqlite') {
1137 $sql = "SELECT COUNT(*) AS $id_count FROM "
1138 . " (SELECT DISTINCT $column_id FROM $tables $condition)";
1140 $sql = "SELECT COUNT(DISTINCT $column_id) AS $id_count "
1141 . "FROM $tables $condition";
1148 // {{{ _getSQL_SearchId
1150 * オブジェクトID(プライマリーキー)検索を行うSQL文を構築する
1153 * @param array $filter WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
1154 * @param array $order 検索結果ソート条件
1155 * (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
1156 * @param int $offset 検索結果取得オフセット
1157 * @param int $count 検索結果取得数
1158 * @return string オブジェクト検索を行うSELECT文
1160 function _getSQL_SearchId($filter, $order, $offset, $count)
1163 $tables = implode(',',
1164 $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
1165 if ($this->_isAdditionalField($filter)
1166 || $this->_isAdditionalField($order)) {
1167 $tables .= " " . $this->_SQLPlugin_SearchTable();
1171 foreach (to_array($this->id_def) as $id) {
1172 if ($column_id != "") {
1175 $column_id .= $this->my_db_ro->quoteIdentifier($this->_getPrimaryTable())
1176 . "." . $this->my_db_ro->quoteIdentifier($id);
1178 $condition = $this->_getSQL_SearchCondition($filter);
1181 if (is_array($order)) {
1182 foreach ($order as $k => $v) {
1184 $sort = "ORDER BY ";
1188 $sort .= sprintf("%s %s", $this->my_db_ro->quoteIdentifier($k),
1189 $v == OBJECT_SORT_ASC ? "ASC" : "DESC");
1194 if (is_null($count) == false) {
1195 $limit = sprintf("LIMIT %d", $count);
1196 if (is_null($offset) == false) {
1197 $limit .= sprintf(" OFFSET %d", $offset);
1201 $sql = "SELECT DISTINCT $column_id FROM $tables $condition $sort $limit";
1207 // {{{ _getSQL_SearchProp
1209 * オブジェクトプロパティ検索を行うSQL文を構築する
1212 * @param array $keys 取得プロパティ(カラム名)一覧
1213 * @param array $filter WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
1214 * @param array $order 検索結果ソート条件
1215 * (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
1216 * @param int $offset 検索結果取得オフセット
1217 * @param int $count 検索結果取得数
1218 * @return string オブジェクト検索を行うSELECT文
1220 function _getSQL_SearchProp($keys, $filter, $order, $offset, $count)
1223 $tables = implode(',',
1224 $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
1225 if ($this->_isAdditionalField($filter)
1226 || $this->_isAdditionalField($order)) {
1227 $tables .= " " . $this->_SQLPlugin_SearchTable();
1229 $p_table = $this->_getPrimaryTable();
1232 // プライマリーキー以外の検索キーが含まれていた
1233 // 場合は、その解釈を _SQLPlugin_SearchPropDef に任せる
1235 // これによって、複数のテーブルの条件を指定することが
1237 if ($this->_isAdditionalField($filter)
1238 || $this->_isAdditionalField($order)) {
1239 $search_prop_def = $this->_SQLPlugin_SearchPropDef();
1241 $search_prop_def = array();
1243 $def = array_merge($this->getDef(), $search_prop_def);
1247 $keys = $keys === null ? array_keys($def) : to_array($keys);
1248 foreach ($keys as $key) {
1249 if ($column != "") {
1252 $t = isset($def[$key]['table']) ? $def[$key]['table'] : $p_table;
1254 $column .= sprintf("%s.%s",
1255 $this->my_db_ro->quoteIdentifier($t),
1256 $this->my_db_ro->quoteIdentifier($key));
1260 $condition = $this->_getSQL_SearchCondition($filter);
1264 if (is_array($order)) {
1265 foreach ($order as $k => $v) {
1267 $sort = "ORDER BY ";
1271 $sort .= sprintf("%s %s",
1272 $this->my_db_ro->quoteIdentifier($k),
1273 $v == OBJECT_SORT_ASC ? "ASC" : "DESC");
1279 if (is_null($count) == false) {
1280 $limit = sprintf("LIMIT %d", $count);
1281 if (is_null($offset) == false) {
1282 $limit .= sprintf(" OFFSET %d", $offset);
1286 $sql = "SELECT $column FROM $tables $condition $sort $limit";
1292 // {{{ _getSQL_SearchCondition
1294 * オブジェクト検索SQLの条件文を構築する
1297 * @param array $filter WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
1298 * @return string オブジェクト検索の条件文(エラーならnull)
1300 function _getSQL_SearchCondition($filter)
1302 if (is_array($filter) == false) {
1306 $p_table = $this->_getPrimaryTable();
1309 // プライマリーキー以外の検索キーが含まれていた
1310 // 場合は、その解釈を _SQLPlugin_SearchPropDef に任せる
1312 // これによって、複数のテーブルの条件を指定することが
1314 if ($this->_isAdditionalField($filter)) {
1315 $search_prop_def = $this->_SQLPlugin_SearchPropDef();
1317 $search_prop_def = array();
1319 $prop_def = array_merge($this->prop_def, $search_prop_def);
1322 foreach ($filter as $k => $v) {
1323 if (isset($prop_def[$k]) == false) {
1324 trigger_error(sprintf("Unknown property [%s]", $k), E_USER_ERROR);
1328 if (is_null($condition)) {
1329 $condition = "WHERE ";
1331 $condition .= " AND ";
1334 $t = isset($prop_def[$k]['table']) ? $prop_def[$k]['table'] : $p_table;
1336 // 細かい条件を指定するには、Ethna_AppSearchObject
1337 // を使う必要がある 文字列の場合は LIKE, 数値の場合
1338 // は = 条件しか指定できないからである。
1339 if (is_object($v)) {
1340 // Ethna_AppSearchObjectが指定されている場合
1341 $condition .= $v->toString(
1342 $this->my_db_ro->quoteIdentifier($t)
1343 .'.'. $this->my_db_ro->quoteIdentifier($k));
1344 } else if (is_array($v) && count($v) > 0 && is_object($v[0])) {
1345 // Ethna_AppSearchObjectが配列で指定されている場合
1347 foreach ($v as $so) {
1349 $condition .= " AND ";
1351 $condition .= $so->toString(
1352 $this->my_db_ro->quoteIdentifier($t)
1353 .'.'. $this->my_db_ro->quoteIdentifier($k));
1356 } else if ($prop_def[$k]['type'] == VAR_TYPE_STRING) {
1358 Ethna_AppSQL::escapeSQL($v, $this->my_db_type);
1359 $condition .= Ethna_AppSQL::getCondition(
1360 $this->my_db_ro->quoteIdentifier($t)
1361 .'.'. $this->my_db_ro->quoteIdentifier($k),
1362 $v, OBJECT_CONDITION_LIKE);
1365 Ethna_AppSQL::escapeSQL($v, $this->my_db_type);
1366 $condition .= Ethna_AppSQL::getCondition(
1367 $this->my_db_ro->quoteIdentifier($t)
1368 .'.'. $this->my_db_ro->quoteIdentifier($k),
1369 $v, OBJECT_CONDITION_EQ);
1377 // {{{ _SQLPlugin_SearchTable
1379 * オブジェクト検索SQLプラグイン(追加テーブル)
1383 * return " LEFT JOIN bar_tbl ON foo_tbl.user_id=bar_tbl.user_id";
1387 * @return string テーブルJOINのSQL文
1389 function _SQLPlugin_SearchTable()
1395 // {{{ _SQLPlugin_SearchPropDef
1397 * オブジェクト検索SQLプラグイン(追加条件定義)
1401 * $search_prop_def = array(
1402 * 'group_id' => array(
1403 * 'primary' => true, 'key' => true, 'type' => VAR_TYPE_INT,
1404 * 'form_name' => 'group_id', 'table' => 'group_user_tbl',
1407 * return $search_prop_def;
1411 * @return array 追加条件定義
1413 function _SQLPlugin_SearchPropDef()
1421 * オブジェクトプロパティをCSV形式でダンプする
1424 * @return string ダンプ結果
1426 function _dump_csv()
1431 foreach ($this->getDef() as $k => $def) {
1435 $dump .= Ethna_Util::escapeCSV($this->getName($k));
1443 // {{{ _isAdditionalField
1445 * (検索条件|ソート条件)フィールドにプライマリーキー以外
1446 * の追加フィールドが含まれるかどうかを返す
1449 * @param array $field (検索条件|ソート条件)定義
1450 * @return bool true:含まれる false:含まれない
1452 function _isAdditionalField($field)
1454 if (is_array($field) == false) {
1458 $def = $this->getDef();
1459 foreach ($field as $key => $value) {
1460 if (array_key_exists($key, $def) == false) {
1463 if (is_object($value)) {
1464 // Ethna_AppSearchObject
1465 if ($value->isTarget($key)) {
1474 // {{{ _clearPropCache
1480 function _clearPropCache()
1482 $class_name = strtolower(get_class($this));
1483 foreach (array('_ETHNA_APP_OBJECT_CACHE',
1484 '_ETHNA_APP_MANAGER_OL_CACHE',
1485 '_ETHNA_APP_MANAGER_OPL_CACHE',
1486 '_ETHNA_APP_MANAGER_OP_CACHE') as $key) {
1487 if (array_key_exists($key, $GLOBALS)
1488 && array_key_exists($class_name, $GLOBALS[$key])) {
1489 unset($GLOBALS[$key][$class_name]);
1497 * DBオブジェクト(read only/read-write)を取得する
1500 * @return array array('ro' => {read only db object}, 'rw' => {read-write db object})
1502 function _getDBList()
1504 $r = array('ro' => null, 'rw' => null);
1506 $db_list = $this->backend->getDBList();
1507 if (Ethna::isError($db_list)) {
1510 foreach ($db_list as $elt) {
1511 if ($this->db_prefix) {
1512 // 特定のプレフィクスが指定されたDB接続を利用
1513 // (テーブルごとにDBが異なる場合など)
1514 if (strncmp($this->db_prefix,
1516 strlen($this->db_prefix)) != 0) {
1521 $varname = $elt['varname'];
1524 $this->$varname =& $elt['db'];
1526 if ($elt['type'] == DB_TYPE_RW) {
1527 $r['rw'] =& $elt['db'];
1528 } else if ($elt['type'] == DB_TYPE_RO) {
1529 $r['ro'] =& $elt['db'];
1532 if ($r['ro'] == null && $r['rw'] != null) {
1533 $r['ro'] =& $r['rw'];
1544 * (クラス名→テーブル名のルールを変えたい場合は
1545 * このメソッドをオーバーライドします)
1548 * @return array テーブル定義
1550 function _getTableDef()
1552 $class_name = get_class($this);
1553 if (preg_match('/(\w+)_(.*)/', $class_name, $match) == 0) {
1558 // PHP 4は常に小文字を返す...のでPHP 5専用
1559 $table = preg_replace('/^([A-Z])/e', "strtolower('\$1')", $table);
1560 $table = preg_replace('/([A-Z])/e', "'_' . strtolower('\$1')", $table);
1562 // JOIN には対応していないので、記述可能なテーブルは
1563 // 常に一つ、かつ primary は trueになる
1564 return array($table => array('primary' => true));
1570 * プロパティ定義を取得します。キャッシュされている場合は、
1574 * @return array プロパティ定義
1576 function _getPropDef()
1578 if (is_null($this->table_def)) {
1581 foreach ($this->table_def as $table_name => $table_attr) {
1586 $cache_manager =& Ethna_CacheManager::getInstance('localfile');
1587 $cache_manager->setNamespace('ethna_app_object');
1588 $cache_key = md5($this->my_db_ro->getDSN() . '-' . $table_name);
1590 if ($cache_manager->isCached($cache_key, $this->prop_def_cache_lifetime)) {
1591 $prop_def = $cache_manager->get($cache_key,
1592 $this->prop_def_cache_lifetime);
1593 if (Ethna::isError($prop_def) == false) {
1598 $r = $this->my_db_ro->getMetaData($table_name);
1599 if(Ethna::isError($r)){
1603 $prop_def = array();
1604 foreach ($r as $i => $field_def) {
1605 $primary = in_array('primary_key', $field_def['flags']);
1606 $seq = in_array('sequence', $field_def['flags']);
1607 $required = in_array('not_null', $field_def['flags']);
1608 $key = in_array('primary_key', $field_def['flags'])
1609 || in_array('multiple_key', $field_def['flags'])
1610 || in_array('unique_key', $field_def['flags']);
1612 switch ($field_def['type']) {
1614 $type = VAR_TYPE_INT;
1617 $type = VAR_TYPE_BOOLEAN;
1620 $type = VAR_TYPE_DATETIME;
1623 $type = VAR_TYPE_STRING;
1627 $prop_def[$field_def['name']] = array(
1628 'primary' => $primary,
1632 'required' => $required,
1633 'length' => $field_def['len'],
1634 'form_name' => $this->_fieldNameToFormName($field_def),
1635 'table' => $table_name,
1639 $cache_manager->set($cache_key, $prop_def);
1645 // {{{ _fieldNameToFormName
1647 * データベースフィールド名に対応するフォーム名を取得する
1651 function _fieldNameToFormName($field_def)
1653 return $field_def['name'];