PageRenderTime 58ms CodeModel.GetById 16ms app.highlight 32ms RepoModel.GetById 1ms app.codeStats 0ms

/class/AppObject.php

http://github.com/ethna/ethna
PHP | 1657 lines | 925 code | 161 blank | 571 comment | 242 complexity | db831ea14439b0b6117998303249718e MD5 | raw file
   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 */
  23class Ethna_AppObject
  24{
  25    // {{{ properties
  26    /**#@+
  27     *  @access private
  28     */
  29
  30    /** @protected    object  Ethna_Backend       backend?????? */
  31    protected $backend;
  32
  33    /** @protected    object  Ethna_Config        ???????? */
  34    protected $config;
  35
  36    /** @protected    object  Ethna_I18N          i18n?????? */
  37    protected $i18n;
  38
  39    /** @protected    object  Ethna_ActionForm    ??????????????? */
  40    protected $action_form;
  41
  42    /** @protected    object  Ethna_ActionForm    ???????????????(???) */
  43    protected $af;
  44
  45    /** @protected    object  Ethna_Session       ??????????? */
  46    protected $session;
  47
  48    /** @protected    string  DB???????? */
  49    protected $db_prefix = null;
  50
  51    /** @protected    array   ???????????DB??????????????*/
  52    protected $table_def = null;
  53
  54    /** @protected    array   ????????????????????????? */
  55    protected $prop_def = null;
  56
  57    /** @protected    array   ?????????????????????? */
  58    protected $prop = null;
  59
  60    /** @protected    array   ?????(??????) */
  61    protected $prop_backup = null;
  62
  63    /** @protected    int     ????????????????(sec) */
  64    protected $prop_def_cache_lifetime = 86400;
  65
  66    /** @protected    array   ????????? */
  67    protected $id_def = null;
  68
  69    /** @protected    int     ??????ID (??????????) */
  70    protected $id = null;
  71
  72    /**#@-*/
  73    // }}}
  74
  75    // {{{ Ethna_AppObject
  76    /**
  77     *  Ethna_AppObject???????????
  78     *
  79     *  @access public
  80     *  @param  object  Ethna_Backend   $backend   Ethna_Backend??????
  81     *  @param  mixed   $key_type   ????????????????
  82     *                              (?????????????????)
  83     *  @param  mixed   $key        ????????????????
  84     *  @param  array   $prop       ?????(??????)??
  85     *  @return mixed   0:???? -1:??/???????? Ethna_Error:???
  86     */
  87    public function __construct($backend, $key_type = null, $key = null, $prop = null)
  88    {
  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();
  95
  96        // DB?????????
  97        $db_list = $this->_getDBList();
  98        if (Ethna::isError($db_list)) {
  99            return $db_list;
 100        } else if (is_null($db_list['rw'])) {
 101            return Ethna::raiseError(
 102                "Ethna_AppObject????????????????????",
 103                E_DB_NODSN);
 104        }
 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();
 109
 110        // ??????????
 111        // ???????????????????primary?true
 112        if (is_null($this->table_def)) {
 113            $this->table_def = $this->_getTableDef();
 114        }
 115        if (is_string($this->table_def)) {
 116            $this->table_def = array($this->table_def => array('primary' => true));
 117        }
 118        // ???????(??????????)????
 119        // ???????????????????????
 120        if (is_null($this->prop_def)) {
 121            $this->prop_def = $this->_getPropDef();
 122        }
 123
 124        // ???????????????
 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;
 128            }
 129        }
 130
 131        // ??????????????????
 132        foreach ($this->prop_def as $k => $v) {
 133            if ($v['primary'] == false) {
 134                continue;
 135            }
 136            if (is_null($this->id_def)) {
 137                $this->id_def = $k;
 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);
 142            }
 143        }
 144        
 145        // ?????????
 146        if (is_null($key_type) && is_null($key) && is_null($prop)) {
 147            // perhaps for adding object
 148            return 0;
 149        }
 150
 151        // ???????
 152        // $key_type, $key ???????DB????????????
 153        // $prop ????????????????
 154        if (is_null($prop)) {
 155            $this->_setPropByDB($key_type, $key);
 156        } else {
 157            $this->_setPropByValue($prop);
 158        }
 159
 160        $this->prop_backup = $this->prop;
 161
 162        //   ?????????????
 163        if (is_array($this->id_def)) {
 164            $this->id = array();
 165            foreach ($this->id_def as $k) {
 166                $this->id[] = $this->prop[$k];
 167            }
 168        } else {
 169            $this->id = $this->prop[$this->id_def];
 170        }
 171
 172        return 0;
 173    }
 174    // }}}
 175
 176    // {{{ isValid
 177    /**
 178     *  ????????????????
 179     *  ????????????????????????????????
 180     *
 181     *  @access public
 182     *  @return bool    true:?? false:??
 183     */
 184    function isValid()
 185    {
 186        if (is_array($this->id)) {
 187            return is_null($this->id[0]) ? false : true;
 188        } else {
 189            return is_null($this->id) ? false : true;
 190        }
 191    }
 192    // }}}
 193
 194    // {{{ isActive
 195    /**
 196     *  ???????????????????
 197     *
 198     *  isValid()?????????????????????????????
 199     *  isActive()????????????????????????????
 200     *
 201     *  @access public
 202     *  @return bool    true:????? false:??????
 203     */
 204    function isActive()
 205    {
 206        if ($this->isValid() == false) {
 207            return false;
 208        }
 209        return $this->prop['state'] == OBJECT_STATE_ACTIVE ? true : false;
 210    }
 211    // }}}
 212
 213    // {{{ getDef
 214    /**
 215     *  ??????????????(?????)???
 216     *
 217     *  @access public
 218     *  @return array   ??????????????
 219     */
 220    function getDef()
 221    {
 222        return $this->prop_def;
 223    }
 224    // }}}
 225
 226    // {{{ getIdDef
 227    /**
 228     *  ????????????
 229     *
 230     *  @access public
 231     *  @return mixed   ????????????????
 232     */
 233    function getIdDef()
 234    {
 235        return $this->id_def;
 236    }
 237    // }}}
 238
 239    // {{{ getId
 240    /**
 241     *  ??????ID(primary key??)???
 242     *
 243     *  @access public
 244     *  @return mixed   ??????ID
 245     */
 246    function getId()
 247    {
 248        return $this->id;
 249    }
 250    // }}}
 251
 252    // {{{ get
 253    /**
 254     *  ?????????????????(R)
 255     *
 256     *  @access public
 257     *  @param  string  $key    ??????(????)
 258     *  @return mixed   ?????(?????)
 259     */
 260    function get($key)
 261    {
 262        if (isset($this->prop_def[$key]) == false) {
 263            trigger_error(sprintf("Unknown property [%s]", $key), E_USER_ERROR);
 264            return null;
 265        }
 266        if (isset($this->prop[$key])) {
 267            return $this->prop[$key];
 268        }
 269        return null;
 270    }
 271    // }}}
 272
 273    // {{{ getName
 274    /**
 275     *  ????????????????????
 276     *  ?????????????????? (???????
 277     *  ?????????????
 278     *
 279     *  ???????????????? 
 280     *
 281     *  @access public
 282     *  @param  string  $key    ?????(???)?
 283     *  @return string  ?????(???)????
 284     */
 285    function getName($key)
 286    {
 287        return $this->get($key);
 288    }
 289    // }}}
 290
 291    /**
 292     *  ??????????????(??)??????
 293     *  ?????????????????? (???????
 294     *  ?????????????
 295     *
 296     *  @access public
 297     *  @param  string  $key    ?????(???)?
 298     *  @return string  ?????(???)????(??)
 299     */
 300    function getLongName($key)
 301    {
 302        return $this->get($key);
 303    }
 304    // }}}
 305
 306    // {{{ getNameObject
 307    /**
 308     *  ??????????????????????
 309     *  ???? getName ???????????????????
 310     *
 311     *  @access public
 312     *  @return array   ?????????????????
 313     */
 314    function getNameObject()
 315    {
 316        $object = array();
 317
 318        foreach ($this->prop_def as $key => $elt) {
 319            $object[$elt['form_name']] = $this->getName($key);
 320        }
 321
 322        return $object;
 323    }
 324    // }}}
 325
 326    // {{{ set
 327    /**
 328     *  ???????????(?????????)???????
 329     *
 330     *  @access public
 331     *  @param  string  $key    ?????(???)?
 332     *  @param  string  $value  ??????
 333     */
 334    function set($key, $value)
 335    {
 336        if (isset($this->prop_def[$key]) == false) {
 337            trigger_error(sprintf("Unknown property [%s]", $key), E_USER_ERROR);
 338            return null;
 339        }
 340        $this->prop[$key] = $value;
 341    }
 342    // }}}
 343
 344    // {{{ dump
 345    /**
 346     *  ???????????????????????(???CSV????????)
 347     *
 348     *  @access public
 349     *  @param  string  $type   ?????("csv"...)
 350     *  @return string  ?????(???????null)
 351     */
 352    function dump($type = "csv")
 353    {
 354        $method = "_dump_$type";
 355        if (method_exists($this, $method) == false) {
 356            return Ethna::raiseError("Undefined Method [%s]", E_APP_NOMETHOD, $method);
 357        }
 358
 359        return $this->$method();
 360    }
 361    // }}}
 362
 363    // {{{ importForm
 364    /**
 365     *  ??????????????????????????
 366     *
 367     *  @access public
 368     *  @param  int     $option ??????????
 369     *                  OBJECT_IMPORT_IGNORE_NULL: ?????????????????????
 370     *                  OBJECT_IMPORT_CONVERT_NULL: ????????????????????????
 371     */
 372    function importForm($option = null)
 373    {
 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) {
 379                    // null?????
 380                    continue;
 381                } else if ($option == OBJECT_IMPORT_CONVERT_NULL) {
 382                    // ???????
 383                    $value = '';
 384                }
 385            }
 386            $this->set($k, $value);
 387        }
 388    }
 389    // }}}
 390
 391    // {{{ exportForm
 392    /**
 393     *  ??????????????????????????
 394     *
 395     *  @access public
 396     */
 397    function exportForm()
 398    {
 399        foreach ($this->getDef() as $k => $def) {
 400            $this->af->set($def['form_name'], $this->get($k));
 401        }
 402    }
 403    // }}}
 404
 405    // {{{ add
 406    /**
 407     *  ???????????(INSERT)
 408     *
 409     *  @access public
 410     *  @return mixed   0:???? Ethna_Error:???
 411     *  @todo remove dependency on PEAR::DB
 412     */
 413    function add()
 414    {
 415        // primary key ??? sequence ????
 416        // next id???: (pgsql?????)
 417        // ??????????id???
 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;
 426                }
 427                break;
 428            }
 429        }
 430
 431        //    INSERT ????????
 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) {
 438                    // ??????????
 439                    $duplicate_key_list = $this->_getDuplicateKeyList();
 440                    if (Ethna::isError($duplicate_key_list)) {
 441                        return $duplicate_key_list;
 442                    }
 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]',
 447                                                      E_APP_DUPENT, $k);
 448                        }
 449                    }
 450                } else {
 451                    return $r;
 452                }
 453            } else {
 454                break;
 455            }
 456        }
 457        if ($i == 4) {
 458            // cannot be reached
 459            return Ethna::raiseError('Cannot detect Duplicate key Error', E_GENERAL);
 460        }
 461
 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;
 470                    break;
 471                }
 472            }
 473        }
 474
 475        // ID(Primary Key)?????
 476        if (is_array($this->id_def)) {
 477            $this->id = array();
 478            foreach ($this->id_def as $k) {
 479                $this->id[] = $this->prop[$k];
 480            }
 481        } else if (isset($this->prop[$this->id_def])) {
 482            $this->id = $this->prop[$this->id_def];
 483        } else {
 484            trigger_error("primary key is missing", E_USER_ERROR);
 485        }
 486
 487        // ??????/???????
 488        $this->prop_backup = $this->prop;
 489        $this->_clearPropCache();
 490
 491        return 0;
 492    }
 493    // }}}
 494
 495    // {{{ update
 496    /**
 497     *  ???????????(UPDATE)
 498     *
 499     *  @access public
 500     *  @return mixed   0:???? Ethna_Error:???
 501     *  @todo remove dependency on PEAR::DB
 502     */
 503    function update()
 504    {
 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) {
 511                    // ??????????
 512                    $duplicate_key_list = $this->_getDuplicateKeyList();
 513                    if (Ethna::isError($duplicate_key_list)) {
 514                        return $duplicate_key_list;
 515                    }
 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]',
 520                                                      E_APP_DUPENT, $k);
 521                        }
 522                    }
 523                } else {
 524                    return $r;
 525                }
 526            } else {
 527                break;
 528            }
 529        }
 530        if ($i == 4) {
 531            // cannot be reached
 532            return Ethna::raiseError('Cannot detect Duplicate key Error', E_GENERAL);
 533        }
 534
 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");
 538        }
 539
 540        // ??????/???????
 541        $this->prop_backup = $this->prop;
 542        $this->_clearPropCache();
 543
 544        return 0;
 545    }
 546    // }}}
 547
 548    // {{{ replace
 549    /**
 550     *  ???????????
 551     *
 552     *  MySQL?REPLACE???????????(add()????????????
 553     *  update()???)
 554     *
 555     *  @access public
 556     *  @return mixed   0:???? >0:??????ID(???) Ethna_Error:???
 557     *  @todo remove dependency on PEAR::DB
 558     */
 559    function replace()
 560    {
 561        $sql = $this->_getSQL_Select($this->getIdDef(), $this->getId());
 562
 563        //   ???????????????(4?) 
 564        for ($i = 0; $i < 3; $i++) {  // magic number
 565            $r = $this->my_db_rw->query($sql);
 566            if (Ethna::isError($r)) {
 567                return $r;
 568            }
 569            $n = $r->numRows();
 570
 571            if ($n > 0) {
 572                $r = $this->update();
 573                return $r;
 574            } else {
 575                $r = $this->add();
 576                if (Ethna::isError($r) == false) {
 577                    return $r;
 578                } else if ($r->getCode() != E_APP_DUPENT) {
 579                    return $r;
 580                }
 581            }
 582        }
 583        
 584        return $r;
 585    }
 586    // }}}
 587
 588    // {{{ remove
 589    /**
 590     *  ??????(????)?????
 591     *
 592     *  @access public
 593     *  @return mixed   0:???? Ethna_Error:???
 594     *  @todo remove dependency on PEAR::DB
 595     */
 596    function remove()
 597    {
 598        $sql = $this->_getSQL_Remove();
 599        $r = $this->my_db_rw->query($sql);
 600        if (Ethna::isError($r)) {
 601            return $r;
 602        }
 603
 604        // ?????/??????/????????
 605        $this->id = $this->prop = $this->prop_backup = null;
 606        $this->_clearPropCache();
 607
 608        return 0;
 609    }
 610    // }}}
 611
 612    // {{{ searchId
 613    /**
 614     *  ??????ID(??????????)?????
 615     *
 616     *  @access public
 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??)
 624     *                  Ethna_Error:???
 625     *  TODO: remove dependency on PEAR::DB
 626     */
 627    function searchId($filter = null, $order = null, $offset = null, $count = null)
 628    {
 629       //   ????????????
 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)) {
 634                return $r;
 635            }
 636            $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
 637            $length = $row['id_count'];
 638        } else {
 639            $length = null;
 640        }
 641
 642        $id_list = array();
 643        $sql = $this->_getSQL_SearchId($filter, $order, $offset, $count);
 644        $r = $this->my_db_ro->query($sql);
 645        if (Ethna::isError($r)) {
 646            return $r;
 647        }
 648        $n = $r->numRows();
 649        for ($i = 0; $i < $n; $i++) {
 650            $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
 651
 652            // ????????1?????????????
 653            if (is_array($this->id_def) == false) {
 654                $row = $row[$this->id_def];
 655            }
 656            $id_list[] = $row;
 657        }
 658        if (is_null($length)) {
 659            $length = count($id_list);
 660        }
 661
 662        return array($length, $id_list);
 663    }
 664    // }}}
 665
 666    // {{{ searchProp
 667    /**
 668     *  ???????????(????)?????
 669     *
 670     *  @access public
 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????????????????????????)
 679     *                  Ethna_Error:???
 680     *  TODO: remove dependency on PEAR::DB
 681     */
 682    function searchProp($keys = null, $filter = null, $order = null,
 683                        $offset = null, $count = null)
 684    {
 685        //   ????????????
 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)) {
 690                return $r;
 691            }
 692            $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
 693            $length = $row['id_count'];
 694        } else {
 695            $length = null;
 696        }
 697
 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)) {
 702            return $r;
 703        }
 704        $n = $r->numRows();
 705        for ($i = 0; $i < $n; $i++) {
 706            $row = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
 707            $prop_list[] = $row;
 708        }
 709        if (is_null($length)) {
 710            $length = count($prop_list);
 711        }
 712
 713        return array($length, $prop_list);
 714    }
 715    // }}}
 716
 717    // {{{ _setDefault
 718    /**
 719     *  ??????????????????????????????
 720     *
 721     *  ???????????????????????????????????
 722     *  ????????????????????????
 723     *
 724     *  @access protected
 725     *  @param  mixed   $key_type   ?????
 726     *  @param  mixed   $key        ????
 727     *  @return int     0:????
 728     */
 729    function _setDefault($key_type, $key)
 730    {
 731        return 0;
 732    }
 733    // }}}
 734
 735    // {{{ _setPropByDB
 736    /**
 737     *  ????????????DB??????
 738     *
 739     *  @access private
 740     *  @param  mixed   $key_type   ?????
 741     *  @param  mixed   $key        ????
 742     *  TODO: depend on PEAR::DB
 743     */
 744    function _setPropByDB($key_type, $key)
 745    {
 746        global $_ETHNA_APP_OBJECT_CACHE;
 747
 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);
 753            return;
 754        }
 755        foreach ($key_type as $elt) {
 756            if (isset($this->prop_def[$elt]) == false) {
 757                trigger_error("Invalid key_type [$elt]", E_USER_ERROR);
 758                return;
 759            }
 760        }
 761
 762        // ?????????
 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();
 767        }
 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];
 771            return;
 772        }
 773
 774        // SQL???
 775        $sql = $this->_getSQL_Select($key_type, $key);
 776
 777        // ???????
 778        $r = $this->my_db_ro->query($sql);
 779        if (Ethna::isError($r)) {
 780            return;
 781        }
 782        $n = $r->numRows();
 783        if ($n == 0) {
 784            // try default
 785            if ($this->_setDefault($key_type, $key) == false) {
 786                // nop
 787            }
 788            return;
 789        } else if ($n > 1) {
 790            trigger_error("Invalid key (multiple rows found) [$key]", E_USER_ERROR);
 791            return;
 792        }
 793        $this->prop = $this->my_db_ro->fetchRow($r, DB_FETCHMODE_ASSOC);
 794
 795        // ???????????
 796        $_ETHNA_APP_OBJECT_CACHE[$class_name][$cache_key] = $this->prop;
 797    }
 798    // }}}
 799
 800    // {{{ _setPropByValue
 801    /**
 802     *  ???????????????????????
 803     *
 804     *  @access private
 805     *  @param  array   $prop   ???????
 806     */
 807    function _setPropByValue($prop)
 808    {
 809        $def = $this->getDef();
 810        foreach ($def as $key => $value) {
 811            if ($value['primary'] && isset($prop[$key]) == false) {
 812                // ????????????
 813                trigger_error("primary key is not identical", E_USER_ERROR);
 814            }
 815            $this->prop[$key] = $prop[$key];
 816        }
 817    }
 818    // }}}
 819
 820    // {{{ _getPrimaryTable
 821    /**
 822     *  ?????????????????????
 823     *
 824     *  @access private
 825     *  @return string  ?????????????????
 826     */
 827    function _getPrimaryTable()
 828    {
 829        $tables = array_keys($this->table_def);
 830        $table = $tables[0];
 831        
 832        return $table;
 833    }
 834    // }}}
 835
 836    // {{{ _getDuplicateKeyList
 837    /**
 838     *  ?????????
 839     *
 840     *  @access private
 841     *  @return mixed   0:???? Ethna_Error:??? array:?????????????
 842     *  TODO: depend on PEAR::DB
 843     */
 844    function _getDuplicateKeyList()
 845    {
 846        $duplicate_key_list = array();
 847
 848        // ?????????????????NULL?????????????
 849        $check_pkey = true;
 850        foreach (to_array($this->id_def) as $k) {
 851            if (isset($this->prop[$k]) == false || is_null($this->prop[$k])) {
 852                $check_pkey = false;
 853                break;
 854            }
 855        }
 856
 857        // ????????multi columns??????????
 858        if ($check_pkey) {
 859            $sql = $this->_getSQL_Duplicate($this->id_def);
 860            $r = $this->my_db_rw->query($sql);
 861            if (Ethna::isError($r)) {
 862                return $r;
 863            } else if ($r->numRows() > 0) {
 864                // we can overwrite $key_list here
 865                $duplicate_key_list = to_array($this->id_def);
 866            }
 867        }
 868
 869        // ??????
 870        foreach ($this->prop_def as $k => $v) {
 871            if ($v['primary'] == true || $v['key'] == false) {
 872                continue;
 873            }
 874            $sql = $this->_getSQL_Duplicate($k);
 875            $r = $this->my_db_rw->query($sql);
 876            if (Ethna::isError($r)) {
 877                return $r;
 878            } else if ($r->NumRows() > 0) {
 879                $duplicate_key_list[] = $k;
 880            }
 881        }
 882
 883        if (count($duplicate_key_list) > 0) {
 884            return $duplicate_key_list;
 885        } else {
 886            return 0;
 887        }
 888    }
 889    // }}}
 890
 891    // {{{ _getSQL_Select
 892    /**
 893     *  ????????????????SQL??????
 894     *
 895     *  @access private
 896     *  @param  array   $key_type   ????????????(???)???
 897     *  @param  array   $key        $key_type?????????
 898     *  @return string  SELECT?
 899     */
 900    function _getSQL_Select($key_type, $key)
 901    {
 902        $key_type = to_array($key_type);
 903        if (is_null($key)) {
 904            // add()?
 905            $key = array();
 906            for ($i = 0; $i < count($key_type); $i++) {
 907                $key[$i] = null;
 908            }
 909        } else {
 910            $key = to_array($key);
 911        }
 912
 913        // SQL?????
 914        Ethna_AppSQL::escapeSQL($key, $this->my_db_type);
 915
 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)));
 920
 921        // ????
 922        $condition = null;
 923        for ($i = 0; $i < count($key_type); $i++) {
 924            if (is_null($condition)) {
 925                $condition = "WHERE ";
 926            } else {
 927                $condition .= " AND ";
 928            }
 929            $condition .= Ethna_AppSQL::getCondition(
 930                $this->my_db_ro->quoteIdentifier($key_type[$i]), $key[$i]);
 931        }
 932
 933        $sql = "SELECT $columns FROM $tables $condition";
 934
 935        return $sql;
 936    }
 937    // }}}
 938
 939    // {{{ _getSQL_Add
 940    /**
 941     *  ???????????SQL??????
 942     *
 943     *  @access private
 944     *  @return string  ??????????????INSERT?
 945     */
 946    function _getSQL_Add()
 947    {
 948        $tables = implode(',',
 949            $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
 950
 951        $key_list = array();
 952        $set_list = array();
 953        $prop_arg_list = $this->prop;
 954
 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) {
 958                continue;
 959            }
 960            $key_list[] = $this->my_db_rw->quoteIdentifier($k);
 961            $set_list[] = $prop_arg_list[$k];
 962        }
 963
 964        $key_list = implode(', ', $key_list);
 965        $set_list = implode(', ', $set_list);
 966        $sql = "INSERT INTO $tables ($key_list) VALUES ($set_list)";
 967
 968        return $sql;
 969    }
 970    // }}}
 971
 972    // {{{ _getSQL_Update
 973    /**
 974     *  ????????????????SQL??????
 975     *
 976     *  @access private
 977     *  @return ???????????????????UPDATE?
 978     */
 979    function _getSQL_Update()
 980    {
 981        $tables = implode(',',
 982            $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
 983
 984        // SET???
 985        $set_list = "";
 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 != "") {
 990                $set_list .= ",";
 991            }
 992            $set_list .= sprintf("%s=%s",
 993                                 $this->my_db_rw->quoteIdentifier($k),
 994                                 $prop_arg_list[$k]);
 995        }
 996
 997        // ????(primary key)
 998        $condition = null;
 999        foreach (to_array($this->id_def) as $k) {
1000            if (is_null($condition)) {
1001                $condition = "WHERE ";
1002            } else {
1003                $condition .= " AND ";
1004            }
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);
1009        }
1010
1011        $sql = "UPDATE $tables SET $set_list $condition";
1012
1013        return $sql;
1014    }
1015    // }}}
1016
1017    // {{{ _getSQL_Remove
1018    /**
1019     *  ???????????SQL??????
1020     *
1021     *  @access private
1022     *  @return string  ??????????????DELETE?
1023     */
1024    function _getSQL_Remove()
1025    {
1026        $tables = implode(',',
1027            $this->my_db_rw->quoteIdentifier(array_keys($this->table_def)));
1028
1029        // ????(primary key)
1030        $condition = null;
1031        foreach (to_array($this->id_def) as $k) {
1032            if (is_null($condition)) {
1033                $condition = "WHERE ";
1034            } else {
1035                $condition .= " AND ";
1036            }
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);
1041        }
1042        if (is_null($condition)) {
1043            trigger_error("DELETE with no conditon", E_USER_ERROR);
1044            return null;
1045        }
1046
1047        $sql = "DELETE FROM $tables $condition";
1048
1049        return $sql;
1050    }
1051    // }}}
1052
1053    // {{{ _getSQL_Duplicate
1054    /**
1055     *  ???????????????????????SQL??????
1056     *
1057     *  @access private
1058     *  @param  mixed   $key    ?????????????????
1059     *  @return string  ??????????????SELECT?
1060     */
1061    function _getSQL_Duplicate($key)
1062    {
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)));
1067
1068        $condition = null;
1069        // ????(?????????????????????????)
1070        if (is_null($this->id) == false) {
1071            $primary_value = to_array($this->getId());
1072            $n = 0;
1073            foreach (to_array($this->id_def) as $k) {
1074                if (is_null($condition)) {
1075                    $condition = "WHERE ";
1076                } else {
1077                    $condition .= " AND ";
1078                }
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);
1083                $n++;
1084            }
1085        }
1086
1087        foreach (to_array($key) as $k) {
1088            if (is_null($condition)) {
1089                $condition = "WHERE ";
1090            } else {
1091                $condition .= " AND ";
1092            }
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);
1097        }
1098
1099        $sql = "SELECT $columns FROM $tables $condition";
1100
1101        return $sql;
1102    }
1103    // }}}
1104
1105    // {{{ _getSQL_SearchLength
1106    /**
1107     *  ??????????(offset, count??)?????SQL??????
1108     *
1109     *  @access private
1110     *  @param  array   $filter     WHERE????(???????????????????Ethna_AppSearchObject???)
1111     *  @return string  ????????????SELECT?
1112     *  @todo   my_db_type??????
1113     */
1114    function _getSQL_SearchLength($filter)
1115    {
1116        // ?????????????????
1117        $tables = implode(',',
1118            $this->my_db_ro->quoteIdentifier(array_keys($this->table_def)));
1119
1120        // ??????????????????????
1121        // ??????????????????
1122        // ????? _SQLPlugin_SearchTable ????
1123        if ($this->_isAdditionalField($filter)) {
1124            $tables .= " " . $this->_SQLPlugin_SearchTable();
1125        }
1126
1127        $id_def = to_array($this->id_def);
1128
1129        //  ?????.?????????
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);
1135
1136        if ($this->my_db_type === 'sqlite') {
1137            $sql = "SELECT COUNT(*) AS $id_count FROM "
1138                . " (SELECT DISTINCT $column_id FROM $tables $condition)";
1139        } else {
1140            $sql = "SELECT COUNT(DISTINCT $column_id) AS $id_count "
1141                . "FROM $tables $condition";
1142        }
1143
1144        return $sql;
1145    }
1146    // }}}
1147
1148    // {{{ _getSQL_SearchId
1149    /**
1150     *  ??????ID(????????)?????SQL??????
1151     *
1152     *  @access private
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?
1159     */
1160    function _getSQL_SearchId($filter, $order, $offset, $count)
1161    {
1162        // ????
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();
1168        }
1169
1170        $column_id = "";
1171        foreach (to_array($this->id_def) as $id) {
1172            if ($column_id != "") {
1173                $column_id .= ",";
1174            }
1175            $column_id .= $this->my_db_ro->quoteIdentifier($this->_getPrimaryTable())
1176                . "." . $this->my_db_ro->quoteIdentifier($id);
1177        }
1178        $condition = $this->_getSQL_SearchCondition($filter);
1179
1180        $sort = "";
1181        if (is_array($order)) {
1182            foreach ($order as $k => $v) {
1183                if ($sort == "") {
1184                    $sort = "ORDER BY ";
1185                } else {
1186                    $sort .= ", ";
1187                }
1188                $sort .= sprintf("%s %s", $this->my_db_ro->quoteIdentifier($k),
1189                                 $v == OBJECT_SORT_ASC ? "ASC" : "DESC");
1190            }
1191        }
1192
1193        $limit = "";
1194        if (is_null($count) == false) {
1195            $limit = sprintf("LIMIT %d", $count);
1196            if (is_null($offset) == false) {
1197                $limit .= sprintf(" OFFSET %d", $offset);
1198            }
1199        }
1200
1201        $sql = "SELECT DISTINCT $column_id FROM $tables $condition $sort $limit";
1202
1203        return $sql;
1204    }
1205    // }}}
1206
1207    // {{{ _getSQL_SearchProp
1208    /**
1209     *  ????????????????SQL??????
1210     *
1211     *  @access private
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?
1219     */
1220    function _getSQL_SearchProp($keys, $filter, $order, $offset, $count)
1221    {
1222        // ????
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();
1228        }
1229        $p_table = $this->_getPrimaryTable();
1230
1231        //  ??????????
1232        //  ??????????????????????
1233        //  ????????? _SQLPlugin_SearchPropDef ????
1234        //
1235        //  ?????????????????????????
1236        //  ???(??. ?????)
1237        if ($this->_isAdditionalField($filter)
1238            || $this->_isAdditionalField($order)) {
1239            $search_prop_def = $this->_SQLPlugin_SearchPropDef();
1240        } else {
1241            $search_prop_def = array();
1242        }
1243        $def = array_merge($this->getDef(), $search_prop_def);
1244
1245        // ???
1246        $column = "";
1247        $keys = $keys === null ? array_keys($def) : to_array($keys);
1248        foreach ($keys as $key) {
1249            if ($column != "") {
1250                $column .= ", ";
1251            }
1252            $t = isset($def[$key]['table']) ? $def[$key]['table'] : $p_table;
1253            //   ?????.????
1254            $column .= sprintf("%s.%s",
1255                               $this->my_db_ro->quoteIdentifier($t),
1256                               $this->my_db_ro->quoteIdentifier($key));
1257        }
1258
1259        // WHERE ???
1260        $condition = $this->_getSQL_SearchCondition($filter);
1261
1262        // ORDER BY
1263        $sort = "";
1264        if (is_array($order)) {
1265            foreach ($order as $k => $v) {
1266                if ($sort == "") {
1267                    $sort = "ORDER BY ";
1268                } else {
1269                    $sort .= ", ";
1270                }
1271                $sort .= sprintf("%s %s",
1272                                 $this->my_db_ro->quoteIdentifier($k),
1273                                 $v == OBJECT_SORT_ASC ? "ASC" : "DESC");
1274            }
1275        }
1276
1277        // LIMIT, OFFSET
1278        $limit = "";
1279        if (is_null($count) == false) {
1280            $limit = sprintf("LIMIT %d", $count);
1281            if (is_null($offset) == false) {
1282                $limit .= sprintf(" OFFSET %d", $offset);
1283            }
1284        }
1285
1286        $sql = "SELECT $column FROM $tables $condition $sort $limit";
1287
1288        return $sql;
1289    }
1290    // }}}
1291
1292    // {{{ _getSQL_SearchCondition
1293    /**
1294     *  ????????SQL?????????
1295     *
1296     *  @access private
1297     *  @param  array   $filter     WHERE????(???????????????????Ethna_AppSearchObject???)
1298     *  @return string  ????????????(?????null)
1299     */
1300    function _getSQL_SearchCondition($filter)
1301    {
1302        if (is_array($filter) == false) {
1303            return "";
1304        }
1305
1306        $p_table = $this->_getPrimaryTable();
1307
1308        //  ??????????
1309        //  ??????????????????????
1310        //  ????????? _SQLPlugin_SearchPropDef ????
1311        //
1312        //  ?????????????????????????
1313        //  ???(??. ?????)
1314        if ($this->_isAdditionalField($filter)) {
1315            $search_prop_def = $this->_SQLPlugin_SearchPropDef();
1316        } else {
1317            $search_prop_def = array();
1318        }
1319        $prop_def = array_merge($this->prop_def, $search_prop_def);
1320
1321        $condition = null;
1322        foreach ($filter as $k => $v) {
1323            if (isset($prop_def[$k]) == false) {
1324                trigger_error(sprintf("Unknown property [%s]", $k), E_USER_ERROR);
1325                return null;
1326            }
1327
1328            if (is_null($condition)) {
1329                $condition = "WHERE ";
1330            } else {
1331                $condition .= " AND ";
1332            }
1333
1334            $t = isset($prop_def[$k]['table']) ? $prop_def[$k]['table'] : $p_table;
1335
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?????????????
1346                $n = 0;
1347                foreach ($v as $so) {
1348                    if ($n > 0) {
1349                        $condition .= " AND ";
1350                    }
1351                    $condition .= $so->toString(
1352                        $this->my_db_ro->quoteIdentifier($t)
1353                        .'.'. $this->my_db_ro->quoteIdentifier($k));
1354                    $n++;
1355                }
1356            } else if ($prop_def[$k]['type'] == VAR_TYPE_STRING) {
1357                // ???(???)
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);
1363            } else {
1364                // ???(??)
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);
1370            }
1371        }
1372
1373        return $condition;
1374    }
1375    // }}}
1376
1377    // {{{ _SQLPlugin_SearchTable
1378    /**
1379     *  ????????SQL?????(??????)
1380     *
1381     *  sample:
1382     *  <code>
1383     *  return " LEFT JOIN bar_tbl ON foo_tbl.user_id=bar_tbl.user_id";
1384     *  </code>
1385     *
1386     *  @access protected
1387     *  @return string  ????JOIN?SQL?
1388     */
1389    function _SQLPlugin_SearchTable()
1390    {
1391        return "";
1392    }
1393    // }}}
1394
1395    // {{{ _SQLPlugin_SearchPropDef
1396    /**
1397     *  ????????SQL?????(??????)
1398     *
1399     *  sample:
1400     *  <code>
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',
1405     *    ),
1406     *  );
1407     *  return $search_prop_def;
1408     *  </code>
1409     *
1410     *  @access protected
1411     *  @return array   ??????
1412     */
1413    function _SQLPlugin_SearchPropDef()
1414    {
1415        return array();
1416    }
1417    // }}}
1418
1419    // {{{ _dump_csv
1420    /**
1421     *  ????????????CSV????????
1422     *
1423     *  @access protected
1424     *  @return string  ?????
1425     */
1426    function _dump_csv()
1427    {
1428        $dump = "";
1429
1430        $n = 0;
1431        foreach ($this->getDef() as $k => $def) {
1432            if ($n > 0) {
1433                $dump .= ",";
1434            }
1435            $dump .= Ethna_Util::escapeCSV($this->getName($k));
1436            $n++;
1437        }
1438
1439        return $dump;
1440    }
1441    // }}}
1442
1443    // {{{ _isAdditionalField
1444    /**
1445     *  (????|?????)????????????????
1446     *  ????????????????????
1447     *
1448     *  @access private
1449     *  @param  array   $field  (????|?????)??
1450     *  @return bool    true:???? false:?????
1451     */
1452    function _isAdditionalField($field)
1453    {
1454        if (is_array($field) == false) {
1455            return false;
1456        }
1457
1458        $def = $this->getDef();
1459        foreach ($field as $key => $value) {
1460            if (array_key_exists($key, $def) == false) {
1461                return true;
1462            }
1463            if (is_object($value)) {
1464                // Ethna_AppSearchObject
1465                if ($value->isTarget($key)) {
1466                    return true;
1467                }
1468            }
1469        }
1470        return false;
1471    }
1472    // }}}
1473
1474    // {{{ _clearPropCache
1475    /**
1476     *  ?????????????
1477     *
1478     *  @access private
1479     */
1480    function _clearPropCache()
1481    {
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]);
1490            }
1491        }
1492    }
1493    // }}}
1494
1495    // {{{ _getDBList
1496    /**
1497     *  DB??????(read only/read-write)?????
1498     *
1499     *  @access protected
1500     *  @return array   array('ro' => {read only db object}, 'rw' => {read-write db object})
1501     */
1502    function _getDBList()
1503    {
1504        $r = array('ro' => null, 'rw' => null);
1505
1506        $db_list = $this->backend->getDBList();
1507        if (Ethna::isError($db_list)) {
1508            return $r;
1509        }
1510        foreach ($db_list as $elt) {
1511            if ($this->db_prefix) {
1512                // ???????????????DB?????
1513                // (???????DB????????)
1514                if (strncmp($this->db_prefix,
1515                            $elt['key'],
1516                            strlen($this->db_prefix)) != 0) {
1517                    continue;
1518                }
1519            }
1520
1521            $varname = $elt['varname'];
1522
1523            // for B.C.
1524            $this->$varname = $elt['db'];
1525
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'];
1530            }
1531        }
1532        if ($r['ro'] == null && $r['rw'] != null) {
1533            $r['ro'] = $r['rw'];
1534        }
1535
1536        return $r;
1537    }
1538    // }}}
1539
1540    // {{{ _getTableDef
1541    /**
1542     *  ???????????
1543     *
1544     *  (??????????????????????
1545     *  ?????????????????)
1546     *
1547     *  @access protected
1548     *  @return array   ??????
1549     */
1550    function _getTableDef()
1551    {
1552        $class_name = get_class($this);
1553        if (preg_match('/(\w+)_(.*)/', $class_name, $match) == 0) {
1554            return null;
1555        }
1556        $table = $match[2];
1557
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);
1561
1562        //   JOIN ??????????????????????
1563        //   ??????? primary ? true???
1564        return array($table => array('primary' => true));
1565    }
1566    // }}}
1567
1568    // {{{ _getPropDef
1569    /**
1570     *  ????????????????????????????
1571     *  ??????????
1572     *
1573     *  @access protected
1574     *  @return array   ???????
1575     */
1576    function _getPropDef()
1577    {
1578        if (is_null($this->table_def)) {
1579            return null;
1580        }
1581        foreach ($this->table_def as $table_name => $table_attr) {
1582            // use 1st one
1583            break;
1584        }
1585
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);
1589
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) {
1594                return $prop_def;
1595            }
1596        }
1597
1598        $r = $this->my_db_ro->getMetaData($table_name);
1599        if(Ethna::isError($r)){
1600            return null;
1601        }
1602
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']);
1611
1612            switch ($field_def['type']) {
1613            case 'int':
1614                $type = VAR_TYPE_INT;
1615                break;
1616            case 'boolean':
1617                $type = VAR_TYPE_BOOLEAN;
1618                break;
1619            case 'datetime':
1620                $type = VAR_TYPE_DATETIME;
1621                break;
1622            default:
1623                $type = VAR_TYPE_STRING;
1624                break;
1625            }
1626
1627            $prop_def[$field_def['name']] = array(
1628                'primary'   => $primary,
1629                'seq'       => $seq,
1630                'key'       => $key,
1631                'type'      => $type,
1632                'required'  => $required,
1633                'length'    => $field_def['len'],
1634                'form_name' => $this->_fieldNameToFormName($field_def),
1635                'table'     => $table_name,
1636            );
1637        }
1638        
1639        $cache_manager->set($cache_key, $prop_def);
1640
1641        return $prop_def;
1642    }
1643    // }}}
1644
1645    // {{{ _fieldNameToFormName
1646    /**
1647     *  ???????????????????????????
1648     *
1649     *  @access protected
1650     */
1651    function _fieldNameToFormName($field_def)
1652    {
1653        return $field_def['name'];
1654    }
1655    // }}}
1656}
1657// }}}