MediaWiki
REL1_22
|
00001 <?php 00031 class ORMTable extends DBAccessBase implements IORMTable { 00032 00041 protected static $instanceCache = array(); 00042 00048 protected $tableName; 00049 00055 protected $fields = array(); 00056 00062 protected $fieldPrefix = ''; 00063 00069 protected $rowClass = 'ORMRow'; 00070 00076 protected $defaults = array(); 00077 00086 protected $readDb = DB_SLAVE; 00087 00099 public function __construct( $tableName = '', array $fields = array(), array $defaults = array(), $rowClass = null, $fieldPrefix = '' ) { 00100 $this->tableName = $tableName; 00101 $this->fields = $fields; 00102 $this->defaults = $defaults; 00103 00104 if ( is_string( $rowClass ) ) { 00105 $this->rowClass = $rowClass; 00106 } 00107 00108 $this->fieldPrefix = $fieldPrefix; 00109 } 00110 00119 public function getName() { 00120 if ( $this->tableName === '' ) { 00121 throw new MWException( 'The table name needs to be set' ); 00122 } 00123 00124 return $this->tableName; 00125 } 00126 00134 protected function getFieldPrefix() { 00135 return $this->fieldPrefix; 00136 } 00137 00145 public function getRowClass() { 00146 return $this->rowClass; 00147 } 00148 00157 public function getFields() { 00158 if ( $this->fields === array() ) { 00159 throw new MWException( 'The table needs to have one or more fields' ); 00160 } 00161 00162 return $this->fields; 00163 } 00164 00173 public function getDefaults() { 00174 return $this->defaults; 00175 } 00176 00186 public function getSummaryFields() { 00187 return array(); 00188 } 00189 00203 public function select( $fields = null, array $conditions = array(), 00204 array $options = array(), $functionName = null ) { 00205 $res = $this->rawSelect( $fields, $conditions, $options, $functionName ); 00206 return new ORMResult( $this, $res ); 00207 } 00208 00223 public function selectObjects( $fields = null, array $conditions = array(), 00224 array $options = array(), $functionName = null ) { 00225 $result = $this->selectFields( $fields, $conditions, $options, false, $functionName ); 00226 00227 $objects = array(); 00228 00229 foreach ( $result as $record ) { 00230 $objects[] = $this->newRow( $record ); 00231 } 00232 00233 return $objects; 00234 } 00235 00249 public function rawSelect( $fields = null, array $conditions = array(), 00250 array $options = array(), $functionName = null ) { 00251 if ( is_null( $fields ) ) { 00252 $fields = array_keys( $this->getFields() ); 00253 } 00254 else { 00255 $fields = (array)$fields; 00256 } 00257 00258 $dbr = $this->getReadDbConnection(); 00259 $result = $dbr->select( 00260 $this->getName(), 00261 $this->getPrefixedFields( $fields ), 00262 $this->getPrefixedValues( $conditions ), 00263 is_null( $functionName ) ? __METHOD__ : $functionName, 00264 $options 00265 ); 00266 00267 /* @var Exception $error */ 00268 $error = null; 00269 00270 if ( $result === false ) { 00271 // Database connection was in "ignoreErrors" mode. We don't like that. 00272 // So, we emulate the DBQueryError that should have been thrown. 00273 $error = new DBQueryError( 00274 $dbr, 00275 $dbr->lastError(), 00276 $dbr->lastErrno(), 00277 $dbr->lastQuery(), 00278 is_null( $functionName ) ? __METHOD__ : $functionName 00279 ); 00280 } 00281 00282 $this->releaseConnection( $dbr ); 00283 00284 if ( $error ) { 00285 // Note: construct the error before releasing the connection, 00286 // but throw it after. 00287 throw $error; 00288 } 00289 00290 return $result; 00291 } 00292 00315 public function selectFields( $fields = null, array $conditions = array(), 00316 array $options = array(), $collapse = true, $functionName = null ) { 00317 $objects = array(); 00318 00319 $result = $this->rawSelect( $fields, $conditions, $options, $functionName ); 00320 00321 foreach ( $result as $record ) { 00322 $objects[] = $this->getFieldsFromDBResult( $record ); 00323 } 00324 00325 if ( $collapse ) { 00326 if ( count( $fields ) === 1 ) { 00327 $objects = array_map( 'array_shift', $objects ); 00328 } 00329 elseif ( count( $fields ) === 2 ) { 00330 $o = array(); 00331 00332 foreach ( $objects as $object ) { 00333 $o[array_shift( $object )] = array_shift( $object ); 00334 } 00335 00336 $objects = $o; 00337 } 00338 } 00339 00340 return $objects; 00341 } 00342 00356 public function selectRow( $fields = null, array $conditions = array(), 00357 array $options = array(), $functionName = null ) { 00358 $options['LIMIT'] = 1; 00359 00360 $objects = $this->select( $fields, $conditions, $options, $functionName ); 00361 00362 return ( !$objects || $objects->isEmpty() ) ? false : $objects->current(); 00363 } 00364 00378 public function rawSelectRow( array $fields, array $conditions = array(), 00379 array $options = array(), $functionName = null ) { 00380 $dbr = $this->getReadDbConnection(); 00381 00382 $result = $dbr->selectRow( 00383 $this->getName(), 00384 $fields, 00385 $conditions, 00386 is_null( $functionName ) ? __METHOD__ : $functionName, 00387 $options 00388 ); 00389 00390 $this->releaseConnection( $dbr ); 00391 return $result; 00392 } 00393 00411 public function selectFieldsRow( $fields = null, array $conditions = array(), 00412 array $options = array(), $collapse = true, $functionName = null ) { 00413 $options['LIMIT'] = 1; 00414 00415 $objects = $this->selectFields( $fields, $conditions, $options, $collapse, $functionName ); 00416 00417 return empty( $objects ) ? false : $objects[0]; 00418 } 00419 00430 public function has( array $conditions = array() ) { 00431 return $this->selectRow( array( 'id' ), $conditions ) !== false; 00432 } 00433 00441 public function exists() { 00442 $dbr = $this->getReadDbConnection(); 00443 $exists = $dbr->tableExists( $this->getName() ); 00444 $this->releaseConnection( $dbr ); 00445 00446 return $exists; 00447 } 00448 00463 public function count( array $conditions = array(), array $options = array() ) { 00464 $res = $this->rawSelectRow( 00465 array( 'rowcount' => 'COUNT(*)' ), 00466 $this->getPrefixedValues( $conditions ), 00467 $options, 00468 __METHOD__ 00469 ); 00470 00471 return $res->rowcount; 00472 } 00473 00484 public function delete( array $conditions, $functionName = null ) { 00485 $dbw = $this->getWriteDbConnection(); 00486 00487 $result = $dbw->delete( 00488 $this->getName(), 00489 $conditions === array() ? '*' : $this->getPrefixedValues( $conditions ), 00490 is_null( $functionName ) ? __METHOD__ : $functionName 00491 ) !== false; // DatabaseBase::delete does not always return true for success as documented... 00492 00493 $this->releaseConnection( $dbw ); 00494 return $result; 00495 } 00496 00507 public function getAPIParams( $requireParams = false, $setDefaults = false ) { 00508 $typeMap = array( 00509 'id' => 'integer', 00510 'int' => 'integer', 00511 'float' => 'NULL', 00512 'str' => 'string', 00513 'bool' => 'integer', 00514 'array' => 'string', 00515 'blob' => 'string', 00516 ); 00517 00518 $params = array(); 00519 $defaults = $this->getDefaults(); 00520 00521 foreach ( $this->getFields() as $field => $type ) { 00522 if ( $field == 'id' ) { 00523 continue; 00524 } 00525 00526 $hasDefault = array_key_exists( $field, $defaults ); 00527 00528 $params[$field] = array( 00529 ApiBase::PARAM_TYPE => $typeMap[$type], 00530 ApiBase::PARAM_REQUIRED => $requireParams && !$hasDefault 00531 ); 00532 00533 if ( $type == 'array' ) { 00534 $params[$field][ApiBase::PARAM_ISMULTI] = true; 00535 } 00536 00537 if ( $setDefaults && $hasDefault ) { 00538 $default = is_array( $defaults[$field] ) ? implode( '|', $defaults[$field] ) : $defaults[$field]; 00539 $params[$field][ApiBase::PARAM_DFLT] = $default; 00540 } 00541 } 00542 00543 return $params; 00544 } 00545 00555 public function getFieldDescriptions() { 00556 return array(); 00557 } 00558 00566 public function getReadDb() { 00567 return $this->readDb; 00568 } 00569 00577 public function setReadDb( $db ) { 00578 $this->readDb = $db; 00579 } 00580 00588 public function getTargetWiki() { 00589 return $this->wiki; 00590 } 00591 00599 public function setTargetWiki( $wiki ) { 00600 $this->wiki = $wiki; 00601 } 00602 00613 public function getReadDbConnection() { 00614 return $this->getConnection( $this->getReadDb(), array() ); 00615 } 00616 00627 public function getWriteDbConnection() { 00628 return $this->getConnection( DB_MASTER, array() ); 00629 } 00630 00641 public function releaseConnection( DatabaseBase $db ) { 00642 parent::releaseConnection( $db ); // just make it public 00643 } 00644 00657 public function update( array $values, array $conditions = array() ) { 00658 $dbw = $this->getWriteDbConnection(); 00659 00660 $result = $dbw->update( 00661 $this->getName(), 00662 $this->getPrefixedValues( $values ), 00663 $this->getPrefixedValues( $conditions ), 00664 __METHOD__ 00665 ) !== false; // DatabaseBase::update does not always return true for success as documented... 00666 00667 $this->releaseConnection( $dbw ); 00668 return $result; 00669 } 00670 00679 public function updateSummaryFields( $summaryFields = null, array $conditions = array() ) { 00680 $slave = $this->getReadDb(); 00681 $this->setReadDb( DB_MASTER ); 00682 00686 foreach ( $this->select( null, $conditions ) as $item ) { 00687 $item->loadSummaryFields( $summaryFields ); 00688 $item->setSummaryMode( true ); 00689 $item->save(); 00690 } 00691 00692 $this->setReadDb( $slave ); 00693 } 00694 00706 public function getPrefixedValues( array $values ) { 00707 $prefixedValues = array(); 00708 00709 foreach ( $values as $field => $value ) { 00710 if ( is_integer( $field ) ) { 00711 if ( is_array( $value ) ) { 00712 $field = $value[0]; 00713 $value = $value[1]; 00714 } 00715 else { 00716 $value = explode( ' ', $value, 2 ); 00717 $value[0] = $this->getPrefixedField( $value[0] ); 00718 $prefixedValues[] = implode( ' ', $value ); 00719 continue; 00720 } 00721 } 00722 00723 $prefixedValues[$this->getPrefixedField( $field )] = $value; 00724 } 00725 00726 return $prefixedValues; 00727 } 00728 00739 public function getPrefixedFields( array $fields ) { 00740 foreach ( $fields as &$field ) { 00741 $field = $this->getPrefixedField( $field ); 00742 } 00743 00744 return $fields; 00745 } 00746 00756 public function getPrefixedField( $field ) { 00757 return $this->getFieldPrefix() . $field; 00758 } 00759 00769 public function unprefixFieldNames( array $fieldNames ) { 00770 return array_map( array( $this, 'unprefixFieldName' ), $fieldNames ); 00771 } 00772 00782 public function unprefixFieldName( $fieldName ) { 00783 return substr( $fieldName, strlen( $this->getFieldPrefix() ) ); 00784 } 00785 00794 public static function singleton() { 00795 $class = get_called_class(); 00796 00797 if ( !array_key_exists( $class, self::$instanceCache ) ) { 00798 self::$instanceCache[$class] = new $class; 00799 } 00800 00801 return self::$instanceCache[$class]; 00802 } 00803 00815 public function getFieldsFromDBResult( stdClass $result ) { 00816 $result = (array)$result; 00817 00818 $rawFields = array_combine( 00819 $this->unprefixFieldNames( array_keys( $result ) ), 00820 array_values( $result ) 00821 ); 00822 00823 $fieldDefinitions = $this->getFields(); 00824 $fields = array(); 00825 00826 foreach ( $rawFields as $name => $value ) { 00827 if ( array_key_exists( $name, $fieldDefinitions ) ) { 00828 switch ( $fieldDefinitions[$name] ) { 00829 case 'int': 00830 $value = (int)$value; 00831 break; 00832 case 'float': 00833 $value = (float)$value; 00834 break; 00835 case 'bool': 00836 if ( is_string( $value ) ) { 00837 $value = $value !== '0'; 00838 } elseif ( is_int( $value ) ) { 00839 $value = $value !== 0; 00840 } 00841 break; 00842 case 'array': 00843 if ( is_string( $value ) ) { 00844 $value = unserialize( $value ); 00845 } 00846 00847 if ( !is_array( $value ) ) { 00848 $value = array(); 00849 } 00850 break; 00851 case 'blob': 00852 if ( is_string( $value ) ) { 00853 $value = unserialize( $value ); 00854 } 00855 break; 00856 case 'id': 00857 if ( is_string( $value ) ) { 00858 $value = (int)$value; 00859 } 00860 break; 00861 } 00862 00863 $fields[$name] = $value; 00864 } else { 00865 throw new MWException( 'Attempted to set unknown field ' . $name ); 00866 } 00867 } 00868 00869 return $fields; 00870 } 00871 00882 public function newFromDBResult( stdClass $result ) { 00883 return self::newRowFromDBResult( $result ); 00884 } 00885 00895 public function newRowFromDBResult( stdClass $result ) { 00896 return $this->newRow( $this->getFieldsFromDBResult( $result ) ); 00897 } 00898 00910 public function newFromArray( array $data, $loadDefaults = false ) { 00911 return static::newRow( $data, $loadDefaults ); 00912 } 00913 00924 public function newRow( array $fields, $loadDefaults = false ) { 00925 $class = $this->getRowClass(); 00926 00927 return new $class( $this, $fields, $loadDefaults ); 00928 } 00929 00937 public function getFieldNames() { 00938 return array_keys( $this->getFields() ); 00939 } 00940 00950 public function canHaveField( $name ) { 00951 return array_key_exists( $name, $this->getFields() ); 00952 } 00953 00964 public function updateRow( IORMRow $row, $functionName = null ) { 00965 $dbw = $this->getWriteDbConnection(); 00966 00967 $success = $dbw->update( 00968 $this->getName(), 00969 $this->getWriteValues( $row ), 00970 $this->getPrefixedValues( array( 'id' => $row->getId() ) ), 00971 is_null( $functionName ) ? __METHOD__ : $functionName 00972 ); 00973 00974 $this->releaseConnection( $dbw ); 00975 00976 // DatabaseBase::update does not always return true for success as documented... 00977 return $success !== false; 00978 } 00979 00991 public function insertRow( IORMRow $row, $functionName = null, array $options = null ) { 00992 $dbw = $this->getWriteDbConnection(); 00993 00994 $success = $dbw->insert( 00995 $this->getName(), 00996 $this->getWriteValues( $row ), 00997 is_null( $functionName ) ? __METHOD__ : $functionName, 00998 $options 00999 ); 01000 01001 // DatabaseBase::insert does not always return true for success as documented... 01002 $success = $success !== false; 01003 01004 if ( $success ) { 01005 $row->setField( 'id', $dbw->insertId() ); 01006 } 01007 01008 $this->releaseConnection( $dbw ); 01009 01010 return $success; 01011 } 01012 01022 protected function getWriteValues( IORMRow $row ) { 01023 $values = array(); 01024 01025 $rowFields = $row->getFields(); 01026 01027 foreach ( $this->getFields() as $name => $type ) { 01028 if ( array_key_exists( $name, $rowFields ) ) { 01029 $value = $rowFields[$name]; 01030 01031 switch ( $type ) { 01032 case 'array': 01033 $value = (array)$value; 01034 // fall-through! 01035 case 'blob': 01036 $value = serialize( $value ); 01037 // fall-through! 01038 } 01039 01040 $values[$this->getPrefixedField( $name )] = $value; 01041 } 01042 } 01043 01044 return $values; 01045 } 01046 01057 public function removeRow( IORMRow $row, $functionName = null ) { 01058 $success = $this->delete( 01059 array( 'id' => $row->getId() ), 01060 is_null( $functionName ) ? __METHOD__ : $functionName 01061 ); 01062 01063 // DatabaseBase::delete does not always return true for success as documented... 01064 return $success !== false; 01065 } 01066 01079 public function addToField( array $conditions, $field, $amount ) { 01080 if ( !array_key_exists( $field, $this->fields ) ) { 01081 throw new MWException( 'Unknown field "' . $field . '" provided' ); 01082 } 01083 01084 if ( $amount == 0 ) { 01085 return true; 01086 } 01087 01088 $absoluteAmount = abs( $amount ); 01089 $isNegative = $amount < 0; 01090 01091 $fullField = $this->getPrefixedField( $field ); 01092 01093 $dbw = $this->getWriteDbConnection(); 01094 01095 $success = $dbw->update( 01096 $this->getName(), 01097 array( "$fullField=$fullField" . ( $isNegative ? '-' : '+' ) . $absoluteAmount ), 01098 $this->getPrefixedValues( $conditions ), 01099 __METHOD__ 01100 ) !== false; // DatabaseBase::update does not always return true for success as documented... 01101 01102 $this->releaseConnection( $dbw ); 01103 01104 return $success; 01105 } 01106 01107 }