Source for file AppObject.php

Documentation is available at AppObject.php

  1. <?php
  2. // vim: foldmethod=marker
  3. /**
  4.  *  AppObject.php
  5.  *
  6.  *  @author     Masaki Fujimoto <fujimoto@php.net>
  7.  *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
  8.  *  @package    Ethna
  9.  *  @version    $Id: 34811a223f106bb88158be4783391e12bdc8b8f2 $
  10.  */
  11.  
  12. // {{{ Ethna_AppObject
  13. /**
  14.  *  アプリケーションオブジェクトのベースクラス
  15.  *
  16.  *  @author     Masaki Fujimoto <fujimoto@php.net>
  17.  *  @access     public
  18.  *  @package    Ethna
  19.  *  @todo       複数テーブルの対応
  20.  *  @todo       remove dependency on PEAR::DB
  21.  *  @todo       quoteidentifier は Ethna_AppSQL に持っていくべき
  22.  */
  23. {
  24.     // {{{ properties
  25.     /**#@+
  26.      *  @access private
  27.      */
  28.  
  29.     /** @protected    object  Ethna_Backend       backendオブジェクト */
  30.     protected $backend;
  31.  
  32.     /** @protected    object  Ethna_Config        設定オブジェクト */
  33.     protected $config;
  34.  
  35.     /** @protected    object  Ethna_I18N          i18nオブジェクト */
  36.     protected $i18n;
  37.  
  38.     /** @protected    object  Ethna_ActionForm    アクションフォームオブジェクト */
  39.     protected $action_form;
  40.  
  41.     /** @protected    object  Ethna_ActionForm    アクションフォームオブジェクト(省略形) */
  42.     protected $af;
  43.  
  44.     /** @protected    object  Ethna_Session       セッションオブジェクト */
  45.     protected $session;
  46.  
  47.     /** @protected    string  DB定義プレフィクス */
  48.     protected $db_prefix = null;
  49.  
  50.     /** @protected    array   テーブル定義。対応するDB上のテーブル名を指定します。*/
  51.     protected $table_def = null;
  52.  
  53.     /** @protected    array   プロパティ定義。テーブルのカラム定義を記述します。 */
  54.     protected $prop_def = null;
  55.  
  56.     /** @protected    array   プロパティ。各カラムに対応する実際の値です。 */
  57.     protected $prop = null;
  58.  
  59.     /** @protected    array   プロパティ(バックアップ) */
  60.     protected $prop_backup = null;
  61.  
  62.     /** @protected    int     プロパティ定義キャッシュ有効期間(sec) */
  63.     protected $prop_def_cache_lifetime = 86400;
  64.  
  65.     /** @protected    array   プライマリキー定義 */
  66.     protected $id_def = null;
  67.  
  68.     /** @protected    int     オブジェクトID (プライマリーキーの値) */
  69.     protected $id = null;
  70.  
  71.     /**#@-*/
  72.     // }}}
  73.  
  74.     // {{{ Ethna_AppObject
  75.     /**
  76.      *  Ethna_AppObjectクラスのコンストラクタ
  77.      *
  78.      *  @access public
  79.      *  @param  object  Ethna_Backend   $backend   Ethna_Backendオブジェクト
  80.      *  @param  mixed   $key_type   レコードを特定するためのカラム名
  81.      *                               (通常はプライマリーキーのフィールド)
  82.      *  @param  mixed   $key        レコードを特定するためのカラム値
  83.      *  @param  array   $prop       プロパティ(レコードの値)一覧
  84.      *  @return mixed   0:正常終了 -1:キー/プロパティ未指定 Ethna_Error:エラー
  85.      */
  86.     public function __construct($backend$key_type null$key null$prop null)
  87.     {
  88.         $this->backend = $backend;
  89.         $this->config = $backend->getConfig();
  90.         $this->action_form = $backend->getActionForm();
  91.         $this->af = $this->action_form;
  92.         $this->session = $backend->getSession();
  93.         $ctl $backend->getController();
  94.  
  95.         // DBオブジェクトの設定
  96.         $db_list $this->_getDBList();
  97.         if (Ethna::isError($db_list)) {
  98.             return $db_list;
  99.         else if (is_null($db_list['rw'])) {
  100.             return Ethna::raiseError(
  101.                 "Ethna_AppObjectを利用するにはデータベース設定が必要です",
  102.                 E_DB_NODSN);
  103.         }
  104.         $this->my_db_rw $db_list['rw'];
  105.         $this->my_db_ro $db_list['ro'];
  106.         // XXX: app objはdb typeを知らなくても動くべき
  107.         $this->my_db_type $this->my_db_rw->getType();
  108.  
  109.         // テーブル定義自動取得
  110.         // 現在、記述可能なテーブルは常に一つで、primaryはtrue
  111.         if (is_null($this->table_def)) {
  112.             $this->table_def = $this->_getTableDef();
  113.         }
  114.         if (is_string($this->table_def)) {
  115.             $this->table_def = array($this->table_def => array('primary' => true));
  116.         }
  117.         // プロパティ定義(テーブルのカラム定義)自動取得
  118.         // データベースから自動取得され、キャッシュされる
  119.         if (is_null($this->prop_def)) {
  120.             $this->prop_def = $this->_getPropDef();
  121.         }
  122.  
  123.         // プロパティ定義の必須キーを補完
  124.         foreach (array_keys($this->prop_defas $k{
  125.             if (isset($this->prop_def[$k]['primary']== false{
  126.                 $this->prop_def[$k]['primary'false;
  127.             }
  128.         }
  129.  
  130.         // オブジェクトのプライマリキー定義構築
  131.         foreach ($this->prop_def as $k => $v{
  132.             if ($v['primary'== false{
  133.                 continue;
  134.             }
  135.             if (is_null($this->id_def)) {
  136.                 $this->id_def = $k;
  137.             else if (is_array($this->id_def)) {
  138.                 $this->id_def[$k;
  139.             else {  // scalar の場合
  140.                 $this->id_def = array($this->id_def$k);
  141.             }
  142.         }
  143.         
  144.         // キー妥当性チェック
  145.         if (is_null($key_type&& is_null($key&& is_null($prop)) {
  146.             // perhaps for adding object
  147.             return 0;
  148.         }
  149.  
  150.         // プロパティ設定
  151.         // $key_type, $key が指定されたらDBから値を取得し、設定する
  152.         // $prop が設定された場合はそれを設定する
  153.         if (is_null($prop)) {
  154.             $this->_setPropByDB($key_type$key);
  155.         else {
  156.             $this->_setPropByValue($prop);
  157.         }
  158.  
  159.         $this->prop_backup = $this->prop;
  160.  
  161.         //   プライマリーキーの値を設定
  162.         if (is_array($this->id_def)) {
  163.             $this->id = array();
  164.             foreach ($this->id_def as $k{
  165.                 $this->id[$this->prop[$k];
  166.             }
  167.         else {
  168.             $this->id = $this->prop[$this->id_def];
  169.         }
  170.  
  171.         return 0;
  172.     }
  173.     // }}}
  174.  
  175.     // {{{ isValid
  176.     /**
  177.      *  有効なオブジェクトかどうかを返す
  178.      *  プライマリーキーの値が設定されてなければ不正なオブジェクトです。
  179.      *
  180.      *  @access public
  181.      *  @return bool    true:有効 false:無効
  182.      */
  183.     function isValid()
  184.     {
  185.         if (is_array($this->id)) {
  186.             return is_null($this->id[0]false true;
  187.         else {
  188.             return is_null($this->idfalse true;
  189.         }
  190.     }
  191.     // }}}
  192.  
  193.     // {{{ isActive
  194.     /**
  195.      *  アクティブなオブジェクトかどうかを返す
  196.      *
  197.      *  isValid()メソッドはオブジェクト自体が有効かどうかを判定するのに対し
  198.      *  isActive()はオブジェクトがアプリケーションとして有効かどうかを返す
  199.      *
  200.      *  @access public
  201.      *  @return bool    true:アクティブ false:非アクティブ
  202.      */
  203.     function isActive()
  204.     {
  205.         if ($this->isValid(== false{
  206.             return false;
  207.         }
  208.         return $this->prop['state'== OBJECT_STATE_ACTIVE true false;
  209.     }
  210.     // }}}
  211.  
  212.     // {{{ getDef
  213.     /**
  214.      *  オブジェクトのプロパティ定義(カラム定義)を返す
  215.      *
  216.      *  @access public
  217.      *  @return array   オブジェクトのプロパティ定義
  218.      */
  219.     function getDef()
  220.     {
  221.         return $this->prop_def;
  222.     }
  223.     // }}}
  224.  
  225.     // {{{ getIdDef
  226.     /**
  227.      *  プライマリキー定義を返す
  228.      *
  229.      *  @access public
  230.      *  @return mixed   プライマリキーとなるプロパティ名
  231.      */
  232.     function getIdDef()
  233.     {
  234.         return $this->id_def;
  235.     }
  236.     // }}}
  237.  
  238.     // {{{ getId
  239.     /**
  240.      *  オブジェクトID(primary keyの値)を返す
  241.      *
  242.      *  @access public
  243.      *  @return mixed   オブジェクトID
  244.      */
  245.     function getId()
  246.     {
  247.         return $this->id;
  248.     }
  249.     // }}}
  250.  
  251.     // {{{ get
  252.     /**
  253.      *  オブジェクトプロパティへのアクセサ(R)
  254.      *
  255.      *  @access public
  256.      *  @param  string  $key    プロパティ名(カラム名)
  257.      *  @return mixed   プロパティ(カラムの値)
  258.      */
  259.     function get($key)
  260.     {
  261.         if (isset($this->prop_def[$key]== false{
  262.             trigger_error(sprintf("Unknown property [%s]"$key)E_USER_ERROR);
  263.             return null;
  264.         }
  265.         if (isset($this->prop[$key])) {
  266.             return $this->prop[$key];
  267.         }
  268.         return null;
  269.     }
  270.     // }}}
  271.  
  272.     // {{{ getName
  273.     /**
  274.      *  オブジェクトプロパティ表示名へのアクセサ
  275.      *  プロパティ値と、表示用の値が違う場合 (「県」等)に、
  276.      *  オーバーライドして下さい。
  277.      *
  278.      *  表示用の値を返す形で実装します。
  279.      *
  280.      *  @access public
  281.      *  @param  string  $key    プロパティ(カラム)名
  282.      *  @return string  プロパティ(カラム)の表示名
  283.      */
  284.     function getName($key)
  285.     {
  286.         return $this->get($key);
  287.     }
  288.     // }}}
  289.  
  290.     /**
  291.      *  オブジェクトプロパティ表示名(詳細)へのアクセサ
  292.      *  プロパティ値と、表示用の値が違う場合 (「県」等)に、
  293.      *  オーバーライドして下さい。
  294.      *
  295.      *  @access public
  296.      *  @param  string  $key    プロパティ(カラム)名
  297.      *  @return string  プロパティ(カラム)の表示名(詳細)
  298.      */
  299.     function getLongName($key)
  300.     {
  301.         return $this->get($key);
  302.     }
  303.     // }}}
  304.  
  305.     // {{{ getNameObject
  306.     /**
  307.      *  プロパティ表示名を格納した連想配列を取得する
  308.      *  すべての getName メソッドの戻り値を配列として返します。
  309.      *
  310.      *  @access public
  311.      *  @return array   プロパティ表示名を格納した連想配列
  312.      */
  313.     function getNameObject()
  314.     {
  315.         $object array();
  316.  
  317.         foreach ($this->prop_def as $key => $elt{
  318.             $object[$elt['form_name']] $this->getName($key);
  319.         }
  320.  
  321.         return $object;
  322.     }
  323.     // }}}
  324.  
  325.     // {{{ set
  326.     /**
  327.      *  オブジェクトプロパティ(カラムに対応した値)を設定します。
  328.      *
  329.      *  @access public
  330.      *  @param  string  $key    プロパティ(カラム)名
  331.      *  @param  string  $value  プロパティ値
  332.      */
  333.     function set($key$value)
  334.     {
  335.         if (isset($this->prop_def[$key]== false{
  336.             trigger_error(sprintf("Unknown property [%s]"$key)E_USER_ERROR);
  337.             return null;
  338.         }
  339.         $this->prop[$key$value;
  340.     }
  341.     // }}}
  342.  
  343.     // {{{ dump
  344.     /**
  345.      *  オブジェクトプロパティを指定の形式でダンプする(現在はCSV形式のみサポート)
  346.      *
  347.      *  @access public
  348.      *  @param  string  $type   ダンプ形式("csv"...)
  349.      *  @return string  ダンプ結果(エラーの場合はnull)
  350.      */
  351.     function dump($type "csv")
  352.     {
  353.         $method "_dump_$type";
  354.         if (method_exists($this$method== false{
  355.             return Ethna::raiseError("Undefined Method [%s]"E_APP_NOMETHOD$method);
  356.         }
  357.  
  358.         return $this->$method();
  359.     }
  360.     // }}}
  361.  
  362.     // {{{ importForm
  363.     /**
  364.      *  フォーム値からオブジェクトプロパティをインポートする
  365.      *
  366.      *  @access public
  367.      *  @param  int     $option インポートオプション
  368.      *                   OBJECT_IMPORT_IGNORE_NULL: フォーム値が送信されていない場合はスキップ
  369.      *                   OBJECT_IMPORT_CONVERT_NULL: フォーム値が送信されていない場合、空文字列に変換
  370.      */
  371.     function importForm($option null)
  372.     {
  373.         foreach ($this->getDef(as $k => $def{
  374.             $value $this->af->get($def['form_name']);
  375.             if (is_null($value)) {
  376.                 // フォームから値が送信されていない場合の振舞い
  377.                 if ($option == OBJECT_IMPORT_IGNORE_NULL{
  378.                     // nullはスキップ
  379.                     continue;
  380.                 else if ($option == OBJECT_IMPORT_CONVERT_NULL{
  381.                     // 空文字列に変換
  382.                     $value '';
  383.                 }
  384.             }
  385.             $this->set($k$value);
  386.         }
  387.     }
  388.     // }}}
  389.  
  390.     // {{{ exportForm
  391.     /**
  392.      *  オブジェクトプロパティをフォーム値にエクスポートする
  393.      *
  394.      *  @access public
  395.      */
  396.     function exportForm()
  397.     {
  398.         foreach ($this->getDef(as $k => $def{
  399.             $this->af->set($def['form_name']$this->get($k));
  400.         }
  401.     }
  402.     // }}}
  403.  
  404.     // {{{ add
  405.     /**
  406.      *  オブジェクトを追加する(INSERT)
  407.      *
  408.      *  @access public
  409.      *  @return mixed   0:正常終了 Ethna_Error:エラー
  410.      *  @todo remove dependency on PEAR::DB
  411.      */
  412.     function add()
  413.     {
  414.         // primary key 定義が sequence の場合、
  415.         // next idの取得: (pgsqlの場合のみ)
  416.         // 取得できた場合はこのidを使う
  417.         foreach (to_array($this->id_defas $id_def{
  418.             if (isset($this->prop_def[$id_def]['seq'])
  419.                 && $this->prop_def[$id_def]['seq']{
  420.                 // NOTE: このapp object以外からinsertがないことが前提
  421.                 $next_id $this->my_db_rw->getNextId(
  422.                     $this->prop_def[$id_def]['table']$id_def);
  423.                 if ($next_id !== null && $next_id >= 0{
  424.                     $this->prop[$id_def$next_id;
  425.                 }
  426.                 break;
  427.             }
  428.         }
  429.  
  430.         //    INSERT 文を取得し、実行
  431.         $sql $this->_getSQL_Add();
  432.         for ($i 0$i 4$i++{
  433.             $r $this->my_db_rw->query($sql);
  434.             //   エラーの場合 -> 重複キーエラーの場合はリトライ
  435.             if (Ethna::isError($r)) {
  436.                 if ($r->getCode(== E_DB_DUPENT{
  437.                     // 重複エラーキーの判別
  438.                     $duplicate_key_list $this->_getDuplicateKeyList();
  439.                     if (Ethna::isError($duplicate_key_list)) {
  440.                         return $duplicate_key_list;
  441.                     }
  442.                     if (is_array($duplicate_key_list)
  443.                         && count($duplicate_key_list0{
  444.                         foreach ($duplicate_key_list as $k{
  445.                             return Ethna::raiseNotice('Duplicate Key Error [%s]',
  446.                                                       E_APP_DUPENT$k);
  447.                         }
  448.                     }
  449.                 else {
  450.                     return $r;
  451.                 }
  452.             else {
  453.                 break;
  454.             }
  455.         }
  456.         if ($i == 4{
  457.             // cannot be reached
  458.             return Ethna::raiseError('Cannot detect Duplicate key Error'E_GENERAL);
  459.         }
  460.  
  461.         // last insert idの取得: (mysql, sqliteのみ)
  462.         // primary key の 'seq' フラグがある(最初の)プロパティに入れる
  463.         $insert_id $this->my_db_rw->getInsertId()
  464.         if ($insert_id !== null && $insert_id >= 0{
  465.             foreach (to_array($this->id_defas $id_def{
  466.                 if (isset($this->prop_def[$id_def]['seq'])
  467.                     && $this->prop_def[$id_def]['seq']{
  468.                     $this->prop[$id_def$insert_id;
  469.                     break;
  470.                 }
  471.             }
  472.         }
  473.  
  474.         // ID(Primary Key)の値を設定
  475.         if (is_array($this->id_def)) {
  476.             $this->id = array();
  477.             foreach ($this->id_def as $k{
  478.                 $this->id[$this->prop[$k];
  479.             }
  480.         else if (isset($this->prop[$this->id_def])) {
  481.             $this->id = $this->prop[$this->id_def];
  482.         else {
  483.             trigger_error("primary key is missing"E_USER_ERROR);
  484.         }
  485.  
  486.         // バックアップ/キャッシュ更新
  487.         $this->prop_backup = $this->prop;
  488.         $this->_clearPropCache();
  489.  
  490.         return 0;
  491.     }
  492.     // }}}
  493.  
  494.     // {{{ update
  495.     /**
  496.      *  オブジェクトを更新する(UPDATE)
  497.      *
  498.      *  @access public
  499.      *  @return mixed   0:正常終了 Ethna_Error:エラー
  500.      *  @todo remove dependency on PEAR::DB
  501.      */
  502.     function update()
  503.     {
  504.         $sql $this->_getSQL_Update();
  505.         //   エラーの場合 -> 重複キーエラーの場合はリトライ(4回)
  506.         for ($i 0$i 4$i++{  //  magic number
  507.             $r $this->my_db_rw->query($sql);
  508.             if (Ethna::isError($r)) {
  509.                 if ($r->getCode(== E_DB_DUPENT{
  510.                     // 重複エラーキーの判別
  511.                     $duplicate_key_list $this->_getDuplicateKeyList();
  512.                     if (Ethna::isError($duplicate_key_list)) {
  513.                         return $duplicate_key_list;
  514.                     }
  515.                     if (is_array($duplicate_key_list)
  516.                         && count($duplicate_key_list0{
  517.                         foreach ($duplicate_key_list as $k{
  518.                             return Ethna::raiseNotice('Duplicate Key Error [%s]',
  519.                                                       E_APP_DUPENT$k);
  520.                         }
  521.                     }
  522.                 else {
  523.                     return $r;
  524.                 }
  525.             else {
  526.                 break;
  527.             }
  528.         }
  529.         if ($i == 4{
  530.             // cannot be reached
  531.             return Ethna::raiseError('Cannot detect Duplicate key Error'E_GENERAL);
  532.         }
  533.  
  534.         $affected_rows $this->my_db_rw->affectedRows();
  535.         if ($affected_rows <= 0{
  536.             $this->backend->log(LOG_DEBUG"update query with 0 updated rows");
  537.         }
  538.  
  539.         // バックアップ/キャッシュ更新
  540.         $this->prop_backup = $this->prop;
  541.         $this->_clearPropCache();
  542.  
  543.         return 0;
  544.     }
  545.     // }}}
  546.  
  547.     // {{{ replace
  548.     /**
  549.      *  オブジェクトを置換する
  550.      *
  551.      *  MySQLのREPLACE文に相当する動作を行う(add()で重複エラーが発生したら
  552.      *  update()を行う)
  553.      *
  554.      *  @access public
  555.      *  @return mixed   0:正常終了 >0:オブジェクトID(追加時) Ethna_Error:エラー
  556.      *  @todo remove dependency on PEAR::DB
  557.      */
  558.     function replace()
  559.     {
  560.         $sql $this->_getSQL_Select($this->getIdDef()$this->getId());
  561.  
  562.         //   重複機ーエラーの場合はリトライ(4回) 
  563.         for ($i 0$i 3$i++{  // magic number
  564.             $r $this->my_db_rw->query($sql);
  565.             if (Ethna::isError($r)) {
  566.                 return $r;
  567.             }
  568.             $n $r->numRows();
  569.  
  570.             if ($n 0{
  571.                 $r $this->update();
  572.                 return $r;
  573.             else {
  574.                 $r $this->add();
  575.                 if (Ethna::isError($r== false{
  576.                     return $r;
  577.                 else if ($r->getCode(!= E_APP_DUPENT{
  578.                     return $r;
  579.                 }
  580.             }
  581.         }
  582.         
  583.         return $r;
  584.     }
  585.     // }}}
  586.  
  587.     // {{{ remove
  588.     /**
  589.      *  オブジェクト(レコード)を削除する
  590.      *
  591.      *  @access public
  592.      *  @return mixed   0:正常終了 Ethna_Error:エラー
  593.      *  @todo remove dependency on PEAR::DB
  594.      */
  595.     function remove()
  596.     {
  597.         $sql $this->_getSQL_Remove();
  598.         $r $this->my_db_rw->query($sql);
  599.         if (Ethna::isError($r)) {
  600.             return $r;
  601.         }
  602.  
  603.         // プロパティ/バックアップ/キャッシュクリア
  604.         $this->id = $this->prop = $this->prop_backup = null;
  605.         $this->_clearPropCache();
  606.  
  607.         return 0;
  608.     }
  609.     // }}}
  610.  
  611.     // {{{ searchId
  612.     /**
  613.      *  オブジェクトID(プライマリーキーの値)を検索する
  614.      *
  615.      *  @access public
  616.      *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
  617.      *  @param  array   $order      検索結果ソート条件
  618.      *                               (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
  619.      *  @param  int     $offset     検索結果取得オフセット
  620.      *  @param  int     $count      検索結果取得数
  621.      *  @return mixed   array(0 => 検索条件にマッチした件数,
  622.      *                   1 => $offset, $countにより指定された件数のオブジェクトID一覧)
  623.      *                   Ethna_Error:エラー
  624.      *   TODO: remove dependency on PEAR::DB
  625.      */
  626.     function searchId($filter null$order null$offset null$count null)
  627.     {
  628.        //   プライマリーキー件数検索
  629.        if (is_null($offset== false || is_null($count== false{
  630.             $sql $this->_getSQL_SearchLength($filter);
  631.             $r $this->my_db_ro->query($sql);
  632.             if (Ethna::isError($r)) {
  633.                 return $r;
  634.             }
  635.             $row $this->my_db_ro->fetchRow($rDB_FETCHMODE_ASSOC);
  636.             $length $row['id_count'];
  637.         else {
  638.             $length null;
  639.         }
  640.  
  641.         $id_list array();
  642.         $sql $this->_getSQL_SearchId($filter$order$offset$count);
  643.         $r $this->my_db_ro->query($sql);
  644.         if (Ethna::isError($r)) {
  645.             return $r;
  646.         }
  647.         $n $r->numRows();
  648.         for ($i 0$i $n$i++{
  649.             $row $this->my_db_ro->fetchRow($rDB_FETCHMODE_ASSOC);
  650.  
  651.             // プライマリキーが1カラムならスカラー値に変換
  652.             if (is_array($this->id_def== false{
  653.                 $row $row[$this->id_def];
  654.             }
  655.             $id_list[$row;
  656.         }
  657.         if (is_null($length)) {
  658.             $length count($id_list);
  659.         }
  660.  
  661.         return array($length$id_list);
  662.     }
  663.     // }}}
  664.  
  665.     // {{{ searchProp
  666.     /**
  667.      *  オブジェクトプロパティ(レコード)を検索する
  668.      *
  669.      *  @access public
  670.      *  @param  array   $keys       取得するプロパティ(カラム名)
  671.      *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
  672.      *  @param  array   $order      検索結果ソート条件
  673.      *                               (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
  674.      *  @param  int     $offset     検索結果取得オフセット
  675.      *  @param  int     $count      検索結果取得数
  676.      *  @return mixed   array(0 => 検索条件にマッチした件数,
  677.      *                   1 => $offset, $countにより指定された件数のオブジェクトプロパティ一覧)
  678.      *                   Ethna_Error:エラー
  679.      *   TODO: remove dependency on PEAR::DB
  680.      */
  681.     function searchProp($keys null$filter null$order null,
  682.                         $offset null$count null)
  683.     {
  684.         //   プライマリーキー件数検索
  685.         if (is_null($offset== false || is_null($count== false{
  686.             $sql $this->_getSQL_SearchLength($filter);
  687.             $r $this->my_db_ro->query($sql);
  688.             if (Ethna::isError($r)) {
  689.                 return $r;
  690.             }
  691.             $row $this->my_db_ro->fetchRow($rDB_FETCHMODE_ASSOC);
  692.             $length $row['id_count'];
  693.         else {
  694.             $length null;
  695.         }
  696.  
  697.         $prop_list array();
  698.         $sql $this->_getSQL_SearchProp($keys$filter$order$offset$count);
  699.         $r $this->my_db_ro->query($sql);
  700.         if (Ethna::isError($r)) {
  701.             return $r;
  702.         }
  703.         $n $r->numRows();
  704.         for ($i 0$i $n$i++{
  705.             $row $this->my_db_ro->fetchRow($rDB_FETCHMODE_ASSOC);
  706.             $prop_list[$row;
  707.         }
  708.         if (is_null($length)) {
  709.             $length count($prop_list);
  710.         }
  711.  
  712.         return array($length$prop_list);
  713.     }
  714.     // }}}
  715.  
  716.     // {{{ _setDefault
  717.     /**
  718.      *  オブジェクトのアプリケーションデフォルトプロパティを設定する
  719.      *
  720.      *  コンストラクタにより指定されたキーにマッチするエントリがなかった場合の
  721.      *  デフォルトプロパティをここで設定することが出来る
  722.      *
  723.      *  @access protected
  724.      *  @param  mixed   $key_type   検索キー名
  725.      *  @param  mixed   $key        検索キー
  726.      *  @return int     0:正常終了
  727.      */
  728.     function _setDefault($key_type$key)
  729.     {
  730.         return 0;
  731.     }
  732.     // }}}
  733.  
  734.     // {{{ _setPropByDB
  735.     /**
  736.      *  オブジェクトプロパティをDBから取得する
  737.      *
  738.      *  @access private
  739.      *  @param  mixed   $key_type   検索キー名
  740.      *  @param  mixed   $key        検索キー
  741.      *   TODO: depend on PEAR::DB
  742.      */
  743.     function _setPropByDB($key_type$key)
  744.     {
  745.         global $_ETHNA_APP_OBJECT_CACHE;
  746.  
  747.         $key_type to_array($key_type);
  748.         $key to_array($key);
  749.         if (count($key_type!= count($key)) {
  750.             trigger_error(sprintf("Unmatched key_type & key length [%d-%d]",
  751.                           count($key_type)count($key))E_USER_ERROR);
  752.             return;
  753.         }
  754.         foreach ($key_type as $elt{
  755.             if (isset($this->prop_def[$elt]== false{
  756.                 trigger_error("Invalid key_type [$elt]"E_USER_ERROR);
  757.                 return;
  758.             }
  759.         }
  760.  
  761.         // キャッシュチェック
  762.         $class_name strtolower(get_class($this));
  763.         if (is_array($_ETHNA_APP_OBJECT_CACHE== false
  764.             || array_key_exists($class_name$_ETHNA_APP_OBJECT_CACHE== false{
  765.             $_ETHNA_APP_OBJECT_CACHE[$class_namearray();
  766.         }
  767.         $cache_key serialize(array($key_type$key));
  768.         if (array_key_exists($cache_key$_ETHNA_APP_OBJECT_CACHE[$class_name])) {
  769.             $this->prop = $_ETHNA_APP_OBJECT_CACHE[$class_name][$cache_key];
  770.             return;
  771.         }
  772.  
  773.         // SQL文構築
  774.         $sql $this->_getSQL_Select($key_type$key);
  775.  
  776.         // プロパティ取得
  777.         $r $this->my_db_ro->query($sql);
  778.         if (Ethna::isError($r)) {
  779.             return;
  780.         }
  781.         $n $r->numRows();
  782.         if ($n == 0{
  783.             // try default
  784.             if ($this->_setDefault($key_type$key== false{
  785.                 // nop
  786.             }
  787.             return;
  788.         else if ($n 1{
  789.             trigger_error("Invalid key (multiple rows found) [$key]"E_USER_ERROR);
  790.             return;
  791.         }
  792.         $this->prop = $this->my_db_ro->fetchRow($rDB_FETCHMODE_ASSOC);
  793.  
  794.         // キャッシュアップデート
  795.         $_ETHNA_APP_OBJECT_CACHE[$class_name][$cache_key$this->prop;
  796.     }
  797.     // }}}
  798.  
  799.     // {{{ _setPropByValue
  800.     /**
  801.      *  コンストラクタで指定されたプロパティを設定する
  802.      *
  803.      *  @access private
  804.      *  @param  array   $prop   プロパティ一覧
  805.      */
  806.     function _setPropByValue($prop)
  807.     {
  808.         $def $this->getDef();
  809.         foreach ($def as $key => $value{
  810.             if ($value['primary'&& isset($prop[$key]== false{
  811.                 // プライマリキーは省略不可
  812.                 trigger_error("primary key is not identical"E_USER_ERROR);
  813.             }
  814.             $this->prop[$key$prop[$key];
  815.         }
  816.     }
  817.     // }}}
  818.  
  819.     // {{{ _getPrimaryTable
  820.     /**
  821.      *  オブジェクトのプライマリテーブルを取得する
  822.      *
  823.      *  @access private
  824.      *  @return string  オブジェクトのプライマリテーブル名
  825.      */
  826.     function _getPrimaryTable()
  827.     {
  828.         $tables array_keys($this->table_def);
  829.         $table $tables[0];
  830.         
  831.         return $table;
  832.     }
  833.     // }}}
  834.  
  835.     // {{{ _getDuplicateKeyList
  836.     /**
  837.      *  重複キーを取得する
  838.      *
  839.      *  @access private
  840.      *  @return mixed   0:重複なし Ethna_Error:エラー array:重複キーのプロパティ名一覧
  841.      *   TODO: depend on PEAR::DB
  842.      */
  843.     function _getDuplicateKeyList()
  844.     {
  845.         $duplicate_key_list array();
  846.  
  847.         // 現在設定されているプライマリキーにNULLが含まれる場合は検索しない
  848.         $check_pkey true;
  849.         foreach (to_array($this->id_defas $k{
  850.             if (isset($this->prop[$k]== false || is_null($this->prop[$k])) {
  851.                 $check_pkey false;
  852.                 break;
  853.             }
  854.         }
  855.  
  856.         // プライマリキーはmulti columnsになり得るので別扱い
  857.         if ($check_pkey{
  858.             $sql $this->_getSQL_Duplicate($this->id_def);
  859.             $r $this->my_db_rw->query($sql);
  860.             if (Ethna::isError($r)) {
  861.                 return $r;
  862.             else if ($r->numRows(0{
  863.                 // we can overwrite $key_list here
  864.                 $duplicate_key_list to_array($this->id_def);
  865.             }
  866.         }
  867.  
  868.         // ユニークキー
  869.         foreach ($this->prop_def as $k => $v{
  870.             if ($v['primary'== true || $v['key'== false{
  871.                 continue;
  872.             }
  873.             $sql $this->_getSQL_Duplicate($k);
  874.             $r $this->my_db_rw->query($sql);
  875.             if (Ethna::isError($r)) {
  876.                 return $r;
  877.             else if ($r->NumRows(0{
  878.                 $duplicate_key_list[$k;
  879.             }
  880.         }
  881.  
  882.         if (count($duplicate_key_list0{
  883.             return $duplicate_key_list;
  884.         else {
  885.             return 0;
  886.         }
  887.     }
  888.     // }}}
  889.  
  890.     // {{{ _getSQL_Select
  891.     /**
  892.      *  オブジェクトプロパティを取得するSQL文を構築する
  893.      *
  894.      *  @access private
  895.      *  @param  array   $key_type   検索キーとなるプロパティ(カラム)名一覧
  896.      *  @param  array   $key        $key_typeに対応するキー一覧
  897.      *  @return string  SELECT文
  898.      */
  899.     function _getSQL_Select($key_type$key)
  900.     {
  901.         $key_type to_array($key_type);
  902.         if (is_null($key)) {
  903.             // add()前
  904.             $key array();
  905.             for ($i 0$i count($key_type)$i++{
  906.                 $key[$inull;
  907.             }
  908.         else {
  909.             $key to_array($key);
  910.         }
  911.  
  912.         // SQLエスケープ
  913.         Ethna_AppSQL::escapeSQL($key$this->my_db_type);
  914.  
  915.         $tables implode(',',
  916.             $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
  917.         $columns implode(',',
  918.             $this->my_db_ro->quoteIdentifier(array_keys($this->prop_def)));
  919.  
  920.         // 検索条件
  921.         $condition null;
  922.         for ($i 0$i count($key_type)$i++{
  923.             if (is_null($condition)) {
  924.                 $condition "WHERE ";
  925.             else {
  926.                 $condition .= " AND ";
  927.             }
  928.             $condition .= Ethna_AppSQL::getCondition(
  929.                 $this->my_db_ro->quoteIdentifier($key_type[$i])$key[$i]);
  930.         }
  931.  
  932.         $sql "SELECT $columns FROM $tables $condition";
  933.  
  934.         return $sql;
  935.     }
  936.     // }}}
  937.  
  938.     // {{{ _getSQL_Add
  939.     /**
  940.      *  オブジェクトと追加するSQL文を構築する
  941.      *
  942.      *  @access private
  943.      *  @return string  オブジェクトを追加するためのINSERT文
  944.      */
  945.     function _getSQL_Add()
  946.     {
  947.         $tables implode(',',
  948.             $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
  949.  
  950.         $key_list array();
  951.         $set_list array();
  952.         $prop_arg_list $this->prop;
  953.  
  954.         Ethna_AppSQL::escapeSQL($prop_arg_list$this->my_db_type);
  955.         foreach ($this->prop_def as $k => $v{
  956.             if (isset($prop_arg_list[$k]== false{
  957.                 continue;
  958.             }
  959.             $key_list[$this->my_db_rw->quoteIdentifier($k);
  960.             $set_list[$prop_arg_list[$k];
  961.         }
  962.  
  963.         $key_list implode(', '$key_list);
  964.         $set_list implode(', '$set_list);
  965.         $sql "INSERT INTO $tables ($key_list) VALUES ($set_list)";
  966.  
  967.         return $sql;
  968.     }
  969.     // }}}
  970.  
  971.     // {{{ _getSQL_Update
  972.     /**
  973.      *  オブジェクトプロパティを更新するSQL文を構築する
  974.      *
  975.      *  @access private
  976.      *  @return オブジェクトプロパティを更新するためのUPDATE文 
  977.      */
  978.     function _getSQL_Update()
  979.     {
  980.         $tables implode(',',
  981.             $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
  982.  
  983.         // SET句構築
  984.         $set_list "";
  985.         $prop_arg_list $this->prop;
  986.         Ethna_AppSQL::escapeSQL($prop_arg_list$this->my_db_type);
  987.         foreach ($this->prop_def as $k => $v{
  988.             if ($set_list != ""{
  989.                 $set_list .= ",";
  990.             }
  991.             $set_list .= sprintf("%s=%s",
  992.                                  $this->my_db_rw->quoteIdentifier($k),
  993.                                  $prop_arg_list[$k]);
  994.         }
  995.  
  996.         // 検索条件(primary key)
  997.         $condition null;
  998.         foreach (to_array($this->id_defas $k{
  999.             if (is_null($condition)) {
  1000.                 $condition "WHERE ";
  1001.             else {
  1002.                 $condition .= " AND ";
  1003.             }
  1004.             $v $this->prop_backup[$k];    // equals to $this->id
  1005.             Ethna_AppSQL::escapeSQL($v$this->my_db_type);
  1006.             $condition .= Ethna_AppSQL::getCondition(
  1007.                 $this->my_db_rw->quoteIdentifier($k)$v);
  1008.         }
  1009.  
  1010.         $sql "UPDATE $tables SET $set_list $condition";
  1011.  
  1012.         return $sql;
  1013.     }
  1014.     // }}}
  1015.  
  1016.     // {{{ _getSQL_Remove
  1017.     /**
  1018.      *  オブジェクトを削除するSQL文を構築する
  1019.      *
  1020.      *  @access private
  1021.      *  @return string  オブジェクトを削除するためのDELETE文
  1022.      */
  1023.     function _getSQL_Remove()
  1024.     {
  1025.         $tables implode(',',
  1026.             $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
  1027.  
  1028.         // 検索条件(primary key)
  1029.         $condition null;
  1030.         foreach (to_array($this->id_defas $k{
  1031.             if (is_null($condition)) {
  1032.                 $condition "WHERE ";
  1033.             else {
  1034.                 $condition .= " AND ";
  1035.             }
  1036.             $v $this->prop_backup[$k];    // equals to $this->id
  1037.             Ethna_AppSQL::escapeSQL($v$this->my_db_type);
  1038.             $condition .= Ethna_AppSQL::getCondition(
  1039.                 $this->my_db_rw->quoteIdentifier($k)$v);
  1040.         }
  1041.         if (is_null($condition)) {
  1042.             trigger_error("DELETE with no conditon"E_USER_ERROR);
  1043.             return null;
  1044.         }
  1045.  
  1046.         $sql "DELETE FROM $tables $condition";
  1047.  
  1048.         return $sql;
  1049.     }
  1050.     // }}}
  1051.  
  1052.     // {{{ _getSQL_Duplicate
  1053.     /**
  1054.      *  オブジェクトプロパティのユニークチェックを行うSQL文を構築する
  1055.      *
  1056.      *  @access private
  1057.      *  @param  mixed   $key    ユニークチェックを行うプロパティ名
  1058.      *  @return string  ユニークチェックを行うためのSELECT文
  1059.      */
  1060.     function _getSQL_Duplicate($key)
  1061.     {
  1062.         $tables implode(',',
  1063.             $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
  1064.         $columns implode(',',
  1065.             $this->my_db_ro->quoteIdentifier(array_keys($this->prop_def)));
  1066.  
  1067.         $condition null;
  1068.         // 検索条件(現在設定されているプライマリキーは検索対象から除く)
  1069.         if (is_null($this->id== false{
  1070.             $primary_value to_array($this->getId());
  1071.             $n 0;
  1072.             foreach (to_array($this->id_defas $k{
  1073.                 if (is_null($condition)) {
  1074.                     $condition "WHERE ";
  1075.                 else {
  1076.                     $condition .= " AND ";
  1077.                 }
  1078.                 $value $primary_value[$n];
  1079.                 Ethna_AppSQL::escapeSQL($value$this->my_db_type);
  1080.                 $condition .= Ethna_AppSQL::getCondition(
  1081.                     $this->my_db_ro->quoteIdentifier($k)$valueOBJECT_CONDITION_NE);
  1082.                 $n++;
  1083.             }
  1084.         }
  1085.  
  1086.         foreach (to_array($keyas $k{
  1087.             if (is_null($condition)) {
  1088.                 $condition "WHERE ";
  1089.             else {
  1090.                 $condition .= " AND ";
  1091.             }
  1092.             $v $this->prop[$k];
  1093.             Ethna_AppSQL::escapeSQL($v$this->my_db_type);
  1094.             $condition .= Ethna_AppSQL::getCondition(
  1095.                 $this->my_db_ro->quoteIdentifier($k)$v);
  1096.         }
  1097.  
  1098.         $sql "SELECT $columns FROM $tables $condition";
  1099.  
  1100.         return $sql;
  1101.     }
  1102.     // }}}
  1103.  
  1104.     // {{{ _getSQL_SearchLength
  1105.     /**
  1106.      *  オブジェクト検索総数(offset, count除外)を取得するSQL文を構築する
  1107.      *
  1108.      *  @access private
  1109.      *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
  1110.      *  @return string  検索総数を取得するためのSELECT文
  1111.      *  @todo   my_db_typeの参照を廃止
  1112.      */
  1113.     function _getSQL_SearchLength($filter)
  1114.     {
  1115.         // テーブル名をクォートした上で連結。
  1116.         $tables implode(',',
  1117.             $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
  1118.  
  1119.         // プライマリーキー以外の検索条件が含まれていた
  1120.         // 場合は、追加テーブルがあるとみなし、
  1121.         // その解釈は _SQLPlugin_SearchTable に任せる
  1122.         if ($this->_isAdditionalField($filter)) {
  1123.             $tables .= " " $this->_SQLPlugin_SearchTable();
  1124.         }
  1125.  
  1126.         $id_def to_array($this->id_def);
  1127.  
  1128.         //  テーブル名.プライマリーキー名
  1129.         //  複数あった場合ははじめのものを使う
  1130.         $column_id $this->my_db_ro->quoteIdentifier($this->_getPrimaryTable())
  1131.              . "." $this->my_db_ro->quoteIdentifier($id_def[0]);
  1132.         $id_count $this->my_db_ro->quoteIdentifier('id_count');
  1133.         $condition $this->_getSQL_SearchCondition($filter);
  1134.  
  1135.         if ($this->my_db_type === 'sqlite'{
  1136.             $sql "SELECT COUNT(*) AS $id_count FROM "
  1137.                 . " (SELECT DISTINCT $column_id FROM $tables $condition)";
  1138.         else {
  1139.             $sql "SELECT COUNT(DISTINCT $column_id) AS $id_count "
  1140.                 . "FROM $tables $condition";
  1141.         }
  1142.  
  1143.         return $sql;
  1144.     }
  1145.     // }}}
  1146.  
  1147.     // {{{ _getSQL_SearchId
  1148.     /**
  1149.      *  オブジェクトID(プライマリーキー)検索を行うSQL文を構築する
  1150.      *
  1151.      *  @access private
  1152.      *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
  1153.      *  @param  array   $order      検索結果ソート条件
  1154.      *                               (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
  1155.      *  @param  int     $offset     検索結果取得オフセット
  1156.      *  @param  int     $count      検索結果取得数
  1157.      *  @return string  オブジェクト検索を行うSELECT文
  1158.      */
  1159.     function _getSQL_SearchId($filter$order$offset$count)
  1160.     {
  1161.         // テーブル
  1162.         $tables implode(',',
  1163.             $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
  1164.         if ($this->_isAdditionalField($filter)
  1165.             || $this->_isAdditionalField($order)) {
  1166.             $tables .= " " $this->_SQLPlugin_SearchTable();
  1167.         }
  1168.  
  1169.         $column_id "";
  1170.         foreach (to_array($this->id_defas $id{
  1171.             if ($column_id != ""{
  1172.                 $column_id .= ",";
  1173.             }
  1174.             $column_id .= $this->my_db_ro->quoteIdentifier($this->_getPrimaryTable())
  1175.                 . "." $this->my_db_ro->quoteIdentifier($id);
  1176.         }
  1177.         $condition $this->_getSQL_SearchCondition($filter);
  1178.  
  1179.         $sort "";
  1180.         if (is_array($order)) {
  1181.             foreach ($order as $k => $v{
  1182.                 if ($sort == ""{
  1183.                     $sort "ORDER BY ";
  1184.                 else {
  1185.                     $sort .= ", ";
  1186.                 }
  1187.                 $sort .= sprintf("%s %s"$this->my_db_ro->quoteIdentifier($k),
  1188.                                  $v == OBJECT_SORT_ASC "ASC" "DESC");
  1189.             }
  1190.         }
  1191.  
  1192.         $limit "";
  1193.         if (is_null($count== false{
  1194.             $limit sprintf("LIMIT %d"$count);
  1195.             if (is_null($offset== false{
  1196.                 $limit .= sprintf(" OFFSET %d"$offset);
  1197.             }
  1198.         }
  1199.  
  1200.         $sql "SELECT DISTINCT $column_id FROM $tables $condition $sort $limit";
  1201.  
  1202.         return $sql;
  1203.     }
  1204.     // }}}
  1205.  
  1206.     // {{{ _getSQL_SearchProp
  1207.     /**
  1208.      *  オブジェクトプロパティ検索を行うSQL文を構築する
  1209.      *
  1210.      *  @access private
  1211.      *  @param  array   $keys       取得プロパティ(カラム名)一覧
  1212.      *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
  1213.      *  @param  array   $order      検索結果ソート条件
  1214.      *                               (カラム名をキー。値には、昇順の場合は OBJECT_SORT_ASC, 降順の場合は OBJECT_SORT_DESC)
  1215.      *  @param  int     $offset     検索結果取得オフセット
  1216.      *  @param  int     $count      検索結果取得数
  1217.      *  @return string  オブジェクト検索を行うSELECT文
  1218.      */
  1219.     function _getSQL_SearchProp($keys$filter$order$offset$count)
  1220.     {
  1221.         // テーブル
  1222.         $tables implode(',',
  1223.             $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
  1224.         if ($this->_isAdditionalField($filter)
  1225.             || $this->_isAdditionalField($order)) {
  1226.             $tables .= " " $this->_SQLPlugin_SearchTable();
  1227.         }
  1228.         $p_table $this->_getPrimaryTable();
  1229.  
  1230.         //  検索用追加プロパティ
  1231.         //  プライマリーキー以外の検索キーが含まれていた
  1232.         //  場合は、その解釈を _SQLPlugin_SearchPropDef に任せる
  1233.         //
  1234.         //  これによって、複数のテーブルの条件を指定することが
  1235.         //  できる(一応. ダサいけど)
  1236.         if ($this->_isAdditionalField($filter)
  1237.             || $this->_isAdditionalField($order)) {
  1238.             $search_prop_def $this->_SQLPlugin_SearchPropDef();
  1239.         else {
  1240.             $search_prop_def array();
  1241.         }
  1242.         $def array_merge($this->getDef()$search_prop_def);
  1243.  
  1244.         // カラム
  1245.         $column "";
  1246.         $keys $keys === null array_keys($defto_array($keys);
  1247.         foreach ($keys as $key{
  1248.             if ($column != ""{
  1249.                 $column .= ", ";
  1250.             }
  1251.             $t = isset($def[$key]['table']$def[$key]['table'$p_table;
  1252.             //   テーブル名.カラム名
  1253.             $column .= sprintf("%s.%s",
  1254.                                $this->my_db_ro->quoteIdentifier($t),
  1255.                                $this->my_db_ro->quoteIdentifier($key));
  1256.         }
  1257.  
  1258.         // WHERE の条件
  1259.         $condition $this->_getSQL_SearchCondition($filter);
  1260.  
  1261.         // ORDER BY
  1262.         $sort "";
  1263.         if (is_array($order)) {
  1264.             foreach ($order as $k => $v{
  1265.                 if ($sort == ""{
  1266.                     $sort "ORDER BY ";
  1267.                 else {
  1268.                     $sort .= ", ";
  1269.                 }
  1270.                 $sort .= sprintf("%s %s",
  1271.                                  $this->my_db_ro->quoteIdentifier($k),
  1272.                                  $v == OBJECT_SORT_ASC "ASC" "DESC");
  1273.             }
  1274.         }
  1275.  
  1276.         // LIMIT, OFFSET
  1277.         $limit "";
  1278.         if (is_null($count== false{
  1279.             $limit sprintf("LIMIT %d"$count);
  1280.             if (is_null($offset== false{
  1281.                 $limit .= sprintf(" OFFSET %d"$offset);
  1282.             }
  1283.         }
  1284.  
  1285.         $sql "SELECT $column FROM $tables $condition $sort $limit";
  1286.  
  1287.         return $sql;
  1288.     }
  1289.     // }}}
  1290.  
  1291.     // {{{ _getSQL_SearchCondition
  1292.     /**
  1293.      *  オブジェクト検索SQLの条件文を構築する
  1294.      *
  1295.      *  @access private
  1296.      *  @param  array   $filter     WHERE検索条件(カラム名をキー、値には実際の条件値か、Ethna_AppSearchObjectを指定)
  1297.      *  @return string  オブジェクト検索の条件文(エラーならnull)
  1298.      */
  1299.     function _getSQL_SearchCondition($filter)
  1300.     {
  1301.         if (is_array($filter== false{
  1302.             return "";
  1303.         }
  1304.  
  1305.         $p_table $this->_getPrimaryTable();
  1306.  
  1307.         //  検索用追加プロパティ
  1308.         //  プライマリーキー以外の検索キーが含まれていた
  1309.         //  場合は、その解釈を _SQLPlugin_SearchPropDef に任せる
  1310.         //
  1311.         //  これによって、複数のテーブルの条件を指定することが
  1312.         //  できる(一応. ダサいけど)
  1313.         if ($this->_isAdditionalField($filter)) {
  1314.             $search_prop_def $this->_SQLPlugin_SearchPropDef();
  1315.         else {
  1316.             $search_prop_def array();
  1317.         }
  1318.         $prop_def array_merge($this->prop_def$search_prop_def);
  1319.  
  1320.         $condition null;
  1321.         foreach ($filter as $k => $v{
  1322.             if (isset($prop_def[$k]== false{
  1323.                 trigger_error(sprintf("Unknown property [%s]"$k)E_USER_ERROR);
  1324.                 return null;
  1325.             }
  1326.  
  1327.             if (is_null($condition)) {
  1328.                 $condition "WHERE ";
  1329.             else {
  1330.                 $condition .= " AND ";
  1331.             }
  1332.  
  1333.             $t = isset($prop_def[$k]['table']$prop_def[$k]['table'$p_table;
  1334.  
  1335.             // 細かい条件を指定するには、Ethna_AppSearchObject
  1336.             // を使う必要がある  文字列の場合は LIKE, 数値の場合
  1337.             // は = 条件しか指定できないからである。
  1338.             if (is_object($v)) {
  1339.                 // Ethna_AppSearchObjectが指定されている場合
  1340.                 $condition .= $v->toString(
  1341.                     $this->my_db_ro->quoteIdentifier($t)
  1342.                     .'.'$this->my_db_ro->quoteIdentifier($k));
  1343.             else if (is_array($v&& count($v&& is_object($v[0])) {
  1344.                 // Ethna_AppSearchObjectが配列で指定されている場合
  1345.                 $n 0;
  1346.                 foreach ($v as $so{
  1347.                     if ($n 0{
  1348.                         $condition .= " AND ";
  1349.                     }
  1350.                     $condition .= $so->toString(
  1351.                         $this->my_db_ro->quoteIdentifier($t)
  1352.                         .'.'$this->my_db_ro->quoteIdentifier($k));
  1353.                     $n++;
  1354.                 }
  1355.             else if ($prop_def[$k]['type'== VAR_TYPE_STRING{
  1356.                 // 省略形(文字列)
  1357.                 Ethna_AppSQL::escapeSQL($v$this->my_db_type);
  1358.                 $condition .= Ethna_AppSQL::getCondition(
  1359.                     $this->my_db_ro->quoteIdentifier($t)
  1360.                     .'.'$this->my_db_ro->quoteIdentifier($k),
  1361.                     $vOBJECT_CONDITION_LIKE);
  1362.             else {
  1363.                 // 省略形(数値)
  1364.                 Ethna_AppSQL::escapeSQL($v$this->my_db_type);
  1365.                 $condition .= Ethna_AppSQL::getCondition(
  1366.                     $this->my_db_ro->quoteIdentifier($t)
  1367.                     .'.'$this->my_db_ro->quoteIdentifier($k),
  1368.                     $vOBJECT_CONDITION_EQ);
  1369.             }
  1370.         }
  1371.  
  1372.         return $condition;
  1373.     }
  1374.     // }}}
  1375.  
  1376.     // {{{ _SQLPlugin_SearchTable
  1377.     /**
  1378.      *  オブジェクト検索SQLプラグイン(追加テーブル)
  1379.      *
  1380.      *  sample:
  1381.      *  <code>
  1382.      *  return " LEFT JOIN bar_tbl ON foo_tbl.user_id=bar_tbl.user_id";
  1383.      *  </code>
  1384.      *
  1385.      *  @access protected
  1386.      *  @return string  テーブルJOINのSQL文
  1387.      */
  1388.     function _SQLPlugin_SearchTable()
  1389.     {
  1390.         return "";
  1391.     }
  1392.     // }}}
  1393.  
  1394.     // {{{ _SQLPlugin_SearchPropDef
  1395.     /**
  1396.      *  オブジェクト検索SQLプラグイン(追加条件定義)
  1397.      *
  1398.      *  sample:
  1399.      *  <code>
  1400.      *  $search_prop_def = array(
  1401.      *    'group_id' => array(
  1402.      *      'primary' => true, 'key' => true, 'type' => VAR_TYPE_INT,
  1403.      *      'form_name' => 'group_id', 'table' => 'group_user_tbl',
  1404.      *    ),
  1405.      *  );
  1406.      *  return $search_prop_def;
  1407.      *  </code>
  1408.      *
  1409.      *  @access protected
  1410.      *  @return array   追加条件定義
  1411.      */
  1412.     function _SQLPlugin_SearchPropDef()
  1413.     {
  1414.         return array();
  1415.     }
  1416.     // }}}
  1417.  
  1418.     // {{{ _dump_csv
  1419.     /**
  1420.      *  オブジェクトプロパティをCSV形式でダンプする
  1421.      *
  1422.      *  @access protected
  1423.      *  @return string  ダンプ結果
  1424.      */
  1425.     function _dump_csv()
  1426.     {
  1427.         $dump "";
  1428.  
  1429.         $n 0;
  1430.         foreach ($this->getDef(as $k => $def{
  1431.             if ($n 0{
  1432.                 $dump .= ",";
  1433.             }
  1434.             $dump .= Ethna_Util::escapeCSV($this->getName($k));
  1435.             $n++;
  1436.         }
  1437.  
  1438.         return $dump;
  1439.     }
  1440.     // }}}
  1441.  
  1442.     // {{{ _isAdditionalField
  1443.     /**
  1444.      *  (検索条件|ソート条件)フィールドにプライマリーキー以外
  1445.      *  の追加フィールドが含まれるかどうかを返す
  1446.      *
  1447.      *  @access private
  1448.      *  @param  array   $field  (検索条件|ソート条件)定義
  1449.      *  @return bool    true:含まれる false:含まれない
  1450.      */
  1451.     function _isAdditionalField($field)
  1452.     {
  1453.         if (is_array($field== false{
  1454.             return false;
  1455.         }
  1456.  
  1457.         $def $this->getDef();
  1458.         foreach ($field as $key => $value{
  1459.             if (array_key_exists($key$def== false{
  1460.                 return true;
  1461.             }
  1462.             if (is_object($value)) {
  1463.                 // Ethna_AppSearchObject
  1464.                 if ($value->isTarget($key)) {
  1465.                     return true;
  1466.                 }
  1467.             }
  1468.         }
  1469.         return false;
  1470.     }
  1471.     // }}}
  1472.  
  1473.     // {{{ _clearPropCache
  1474.     /**
  1475.      *  キャッシュデータを削除する
  1476.      *
  1477.      *  @access private
  1478.      */
  1479.     function _clearPropCache()
  1480.     {
  1481.         $class_name strtolower(get_class($this));
  1482.         foreach (array('_ETHNA_APP_OBJECT_CACHE',
  1483.                        '_ETHNA_APP_MANAGER_OL_CACHE',
  1484.                        '_ETHNA_APP_MANAGER_OPL_CACHE',
  1485.                        '_ETHNA_APP_MANAGER_OP_CACHE'as $key{
  1486.             if (array_key_exists($key$GLOBALS)
  1487.                 && array_key_exists($class_name$GLOBALS[$key])) {
  1488.                 unset($GLOBALS[$key][$class_name]);
  1489.             }
  1490.         }
  1491.     }
  1492.     // }}}
  1493.  
  1494.     // {{{ _getDBList
  1495.     /**
  1496.      *  DBオブジェクト(read only/read-write)を取得する
  1497.      *
  1498.      *  @access protected
  1499.      *  @return array   array('ro' => {read only db object}, 'rw' => {read-write db object})
  1500.      */
  1501.     function _getDBList()
  1502.     {
  1503.         $r array('ro' => null'rw' => null);
  1504.  
  1505.         $db_list $this->backend->getDBList();
  1506.         if (Ethna::isError($db_list)) {
  1507.             return $r;
  1508.         }
  1509.         foreach ($db_list as $elt{
  1510.             if ($this->db_prefix{
  1511.                 // 特定のプレフィクスが指定されたDB接続を利用
  1512.                 // (テーブルごとにDBが異なる場合など)
  1513.                 if (strncmp($this->db_prefix,
  1514.                             $elt['key'],
  1515.                             strlen($this->db_prefix)) != 0{
  1516.                     continue;
  1517.                 }
  1518.             }
  1519.  
  1520.             $varname $elt['varname'];
  1521.  
  1522.             // for B.C.
  1523.             $this->$varname $elt['db'];
  1524.  
  1525.             if ($elt['type'== DB_TYPE_RW{
  1526.                 $r['rw'$elt['db'];
  1527.             else if ($elt['type'== DB_TYPE_RO{
  1528.                 $r['ro'$elt['db'];
  1529.             }
  1530.         }
  1531.         if ($r['ro'== null && $r['rw'!= null{
  1532.             $r['ro'$r['rw'];
  1533.         }
  1534.  
  1535.         return $r;
  1536.     }
  1537.     // }}}
  1538.  
  1539.     // {{{ _getTableDef
  1540.     /**
  1541.      *  テーブル定義を取得する
  1542.      *
  1543.      *  (クラス名→テーブル名のルールを変えたい場合は
  1544.      *  このメソッドをオーバーライドします)
  1545.      *
  1546.      *  @access protected
  1547.      *  @return array   テーブル定義
  1548.      */
  1549.     function _getTableDef()
  1550.     {
  1551.         $class_name get_class($this);
  1552.         if (preg_match('/(\w+)_(.*)/'$class_name$match== 0{
  1553.             return null;
  1554.         }
  1555.         $table $match[2];
  1556.  
  1557.         // PHP 4は常に小文字を返す...のでPHP 5専用
  1558.         $table preg_replace('/^([A-Z])/e'"strtolower('\$1')"$table);
  1559.         $table preg_replace('/([A-Z])/e'"'_' . strtolower('\$1')"$table);
  1560.  
  1561.         //   JOIN には対応していないので、記述可能なテーブルは
  1562.         //   常に一つ、かつ primary は trueになる
  1563.         return array($table => array('primary' => true));
  1564.     }
  1565.     // }}}
  1566.  
  1567.     // {{{ _getPropDef
  1568.     /**
  1569.      *  プロパティ定義を取得します。キャッシュされている場合は、
  1570.      *  そこから取得します。
  1571.      *
  1572.      *  @access protected
  1573.      *  @return array   プロパティ定義
  1574.      */
  1575.     function _getPropDef()
  1576.     {
  1577.         if (is_null($this->table_def)) {
  1578.             return null;
  1579.         }
  1580.         foreach ($this->table_def as $table_name => $table_attr{
  1581.             // use 1st one
  1582.             break;
  1583.         }
  1584.  
  1585.         $cache_manager Ethna_CacheManager::getInstance('localfile');
  1586.         $cache_manager->setNamespace('ethna_app_object');
  1587.         $cache_key md5($this->my_db_ro->getDSN('-' $table_name);
  1588.  
  1589.         if ($cache_manager->isCached($cache_key$this->prop_def_cache_lifetime)) {
  1590.             $prop_def $cache_manager->get($cache_key,
  1591.                                             $this->prop_def_cache_lifetime);
  1592.             if (Ethna::isError($prop_def== false{
  1593.                 return $prop_def;
  1594.             }
  1595.         }
  1596.  
  1597.         $r $this->my_db_ro->getMetaData($table_name);
  1598.         if(Ethna::isError($r)){
  1599.             return null;
  1600.         }
  1601.  
  1602.         $prop_def array();
  1603.         foreach ($r as $i => $field_def{
  1604.             $primary  in_array('primary_key'$field_def['flags']);
  1605.             $seq      in_array('sequence',    $field_def['flags']);
  1606.             $required in_array('not_null',    $field_def['flags']);
  1607.             $key      in_array('primary_key'$field_def['flags'])
  1608.                         || in_array('multiple_key'$field_def['flags'])
  1609.                         || in_array('unique_key'$field_def['flags']);
  1610.  
  1611.             switch ($field_def['type']{
  1612.             case 'int':
  1613.                 $type VAR_TYPE_INT;
  1614.                 break;
  1615.             case 'boolean':
  1616.                 $type VAR_TYPE_BOOLEAN;
  1617.                 break;
  1618.             case 'datetime':
  1619.                 $type VAR_TYPE_DATETIME;
  1620.                 break;
  1621.             default:
  1622.                 $type VAR_TYPE_STRING;
  1623.                 break;
  1624.             }
  1625.  
  1626.             $prop_def[$field_def['name']] array(
  1627.                 'primary'   => $primary,
  1628.                 'seq'       => $seq,
  1629.                 'key'       => $key,
  1630.                 'type'      => $type,
  1631.                 'required'  => $required,
  1632.                 'length'    => $field_def['len'],
  1633.                 'form_name' => $this->_fieldNameToFormName($field_def),
  1634.                 'table'     => $table_name,
  1635.             );
  1636.         }
  1637.         
  1638.         $cache_manager->set($cache_key$prop_def);
  1639.  
  1640.         return $prop_def;
  1641.     }
  1642.     // }}}
  1643.  
  1644.     // {{{ _fieldNameToFormName
  1645.     /**
  1646.      *  データベースフィールド名に対応するフォーム名を取得する
  1647.      *
  1648.      *  @access protected
  1649.      */
  1650.     function _fieldNameToFormName($field_def)
  1651.     {
  1652.         return $field_def['name'];
  1653.     }
  1654.     // }}}
  1655. }
  1656. // }}}

Documentation generated on Fri, 11 Nov 2011 03:57:40 +0900 by phpDocumentor 1.4.3