MediaWiki
REL1_24
|
00001 <?php 00031 class ORMTable extends DBAccessBase implements IORMTable { 00040 protected static $instanceCache = array(); 00041 00047 protected $tableName; 00048 00054 protected $fields = array(); 00055 00061 protected $fieldPrefix = ''; 00062 00068 protected $rowClass = 'ORMRow'; 00069 00075 protected $defaults = array(); 00076 00085 protected $readDb = DB_SLAVE; 00086 00098 public function __construct( $tableName = '', array $fields = array(), 00099 array $defaults = array(), $rowClass = null, $fieldPrefix = '' 00100 ) { 00101 $this->tableName = $tableName; 00102 $this->fields = $fields; 00103 $this->defaults = $defaults; 00104 00105 if ( is_string( $rowClass ) ) { 00106 $this->rowClass = $rowClass; 00107 } 00108 00109 $this->fieldPrefix = $fieldPrefix; 00110 } 00111 00120 public function getName() { 00121 if ( $this->tableName === '' ) { 00122 throw new MWException( 'The table name needs to be set' ); 00123 } 00124 00125 return $this->tableName; 00126 } 00127 00135 protected function getFieldPrefix() { 00136 return $this->fieldPrefix; 00137 } 00138 00146 public function getRowClass() { 00147 return $this->rowClass; 00148 } 00149 00158 public function getFields() { 00159 if ( $this->fields === array() ) { 00160 throw new MWException( 'The table needs to have one or more fields' ); 00161 } 00162 00163 return $this->fields; 00164 } 00165 00174 public function getDefaults() { 00175 return $this->defaults; 00176 } 00177 00187 public function getSummaryFields() { 00188 return array(); 00189 } 00190 00204 public function select( $fields = null, array $conditions = array(), 00205 array $options = array(), $functionName = null 00206 ) { 00207 $res = $this->rawSelect( $fields, $conditions, $options, $functionName ); 00208 00209 return new ORMResult( $this, $res ); 00210 } 00211 00226 public function selectObjects( $fields = null, array $conditions = array(), 00227 array $options = array(), $functionName = null 00228 ) { 00229 $result = $this->selectFields( $fields, $conditions, $options, false, $functionName ); 00230 00231 $objects = array(); 00232 00233 foreach ( $result as $record ) { 00234 $objects[] = $this->newRow( $record ); 00235 } 00236 00237 return $objects; 00238 } 00239 00253 public function rawSelect( $fields = null, array $conditions = array(), 00254 array $options = array(), $functionName = null 00255 ) { 00256 if ( is_null( $fields ) ) { 00257 $fields = array_keys( $this->getFields() ); 00258 } else { 00259 $fields = (array)$fields; 00260 } 00261 00262 $dbr = $this->getReadDbConnection(); 00263 $result = $dbr->select( 00264 $this->getName(), 00265 $this->getPrefixedFields( $fields ), 00266 $this->getPrefixedValues( $conditions ), 00267 is_null( $functionName ) ? __METHOD__ : $functionName, 00268 $options 00269 ); 00270 00271 /* @var Exception $error */ 00272 $error = null; 00273 00274 if ( $result === false ) { 00275 // Database connection was in "ignoreErrors" mode. We don't like that. 00276 // So, we emulate the DBQueryError that should have been thrown. 00277 $error = new DBQueryError( 00278 $dbr, 00279 $dbr->lastError(), 00280 $dbr->lastErrno(), 00281 $dbr->lastQuery(), 00282 is_null( $functionName ) ? __METHOD__ : $functionName 00283 ); 00284 } 00285 00286 $this->releaseConnection( $dbr ); 00287 00288 if ( $error ) { 00289 // Note: construct the error before releasing the connection, 00290 // but throw it after. 00291 throw $error; 00292 } 00293 00294 return $result; 00295 } 00296 00319 public function selectFields( $fields = null, array $conditions = array(), 00320 array $options = array(), $collapse = true, $functionName = null 00321 ) { 00322 $objects = array(); 00323 00324 $result = $this->rawSelect( $fields, $conditions, $options, $functionName ); 00325 00326 foreach ( $result as $record ) { 00327 $objects[] = $this->getFieldsFromDBResult( $record ); 00328 } 00329 00330 if ( $collapse ) { 00331 if ( count( $fields ) === 1 ) { 00332 $objects = array_map( 'array_shift', $objects ); 00333 } elseif ( count( $fields ) === 2 ) { 00334 $o = array(); 00335 00336 foreach ( $objects as $object ) { 00337 $o[array_shift( $object )] = array_shift( $object ); 00338 } 00339 00340 $objects = $o; 00341 } 00342 } 00343 00344 return $objects; 00345 } 00346 00360 public function selectRow( $fields = null, array $conditions = array(), 00361 array $options = array(), $functionName = null 00362 ) { 00363 $options['LIMIT'] = 1; 00364 00365 $objects = $this->select( $fields, $conditions, $options, $functionName ); 00366 00367 return ( !$objects || $objects->isEmpty() ) ? false : $objects->current(); 00368 } 00369 00383 public function rawSelectRow( array $fields, array $conditions = array(), 00384 array $options = array(), $functionName = null 00385 ) { 00386 $dbr = $this->getReadDbConnection(); 00387 00388 $result = $dbr->selectRow( 00389 $this->getName(), 00390 $fields, 00391 $conditions, 00392 is_null( $functionName ) ? __METHOD__ : $functionName, 00393 $options 00394 ); 00395 00396 $this->releaseConnection( $dbr ); 00397 00398 return $result; 00399 } 00400 00418 public function selectFieldsRow( $fields = null, array $conditions = array(), 00419 array $options = array(), $collapse = true, $functionName = null 00420 ) { 00421 $options['LIMIT'] = 1; 00422 00423 $objects = $this->selectFields( $fields, $conditions, $options, $collapse, $functionName ); 00424 00425 return empty( $objects ) ? false : $objects[0]; 00426 } 00427 00438 public function has( array $conditions = array() ) { 00439 return $this->selectRow( array( 'id' ), $conditions ) !== false; 00440 } 00441 00449 public function exists() { 00450 $dbr = $this->getReadDbConnection(); 00451 $exists = $dbr->tableExists( $this->getName() ); 00452 $this->releaseConnection( $dbr ); 00453 00454 return $exists; 00455 } 00456 00471 public function count( array $conditions = array(), array $options = array() ) { 00472 $res = $this->rawSelectRow( 00473 array( 'rowcount' => 'COUNT(*)' ), 00474 $this->getPrefixedValues( $conditions ), 00475 $options, 00476 __METHOD__ 00477 ); 00478 00479 return $res->rowcount; 00480 } 00481 00492 public function delete( array $conditions, $functionName = null ) { 00493 $dbw = $this->getWriteDbConnection(); 00494 00495 $result = $dbw->delete( 00496 $this->getName(), 00497 $conditions === array() ? '*' : $this->getPrefixedValues( $conditions ), 00498 is_null( $functionName ) ? __METHOD__ : $functionName 00499 ) !== false; // DatabaseBase::delete does not always return true for success as documented... 00500 00501 $this->releaseConnection( $dbw ); 00502 00503 return $result; 00504 } 00505 00516 public function getAPIParams( $requireParams = false, $setDefaults = false ) { 00517 $typeMap = array( 00518 'id' => 'integer', 00519 'int' => 'integer', 00520 'float' => 'NULL', 00521 'str' => 'string', 00522 'bool' => 'integer', 00523 'array' => 'string', 00524 'blob' => 'string', 00525 ); 00526 00527 $params = array(); 00528 $defaults = $this->getDefaults(); 00529 00530 foreach ( $this->getFields() as $field => $type ) { 00531 if ( $field == 'id' ) { 00532 continue; 00533 } 00534 00535 $hasDefault = array_key_exists( $field, $defaults ); 00536 00537 $params[$field] = array( 00538 ApiBase::PARAM_TYPE => $typeMap[$type], 00539 ApiBase::PARAM_REQUIRED => $requireParams && !$hasDefault 00540 ); 00541 00542 if ( $type == 'array' ) { 00543 $params[$field][ApiBase::PARAM_ISMULTI] = true; 00544 } 00545 00546 if ( $setDefaults && $hasDefault ) { 00547 $default = is_array( $defaults[$field] ) 00548 ? implode( '|', $defaults[$field] ) 00549 : $defaults[$field]; 00550 $params[$field][ApiBase::PARAM_DFLT] = $default; 00551 } 00552 } 00553 00554 return $params; 00555 } 00556 00566 public function getFieldDescriptions() { 00567 return array(); 00568 } 00569 00577 public function getReadDb() { 00578 return $this->readDb; 00579 } 00580 00589 public function setReadDb( $db ) { 00590 $this->readDb = $db; 00591 } 00592 00601 public function getTargetWiki() { 00602 return $this->wiki; 00603 } 00604 00613 public function setTargetWiki( $wiki ) { 00614 $this->wiki = $wiki; 00615 } 00616 00627 public function getReadDbConnection() { 00628 return $this->getConnection( $this->getReadDb(), array() ); 00629 } 00630 00641 public function getWriteDbConnection() { 00642 return $this->getConnection( DB_MASTER, array() ); 00643 } 00644 00655 // @codingStandardsIgnoreStart Suppress "useless method overriding" sniffer warning 00656 public function releaseConnection( DatabaseBase $db ) { 00657 parent::releaseConnection( $db ); // just make it public 00658 } 00659 // @codingStandardsIgnoreEnd 00660 00673 public function update( array $values, array $conditions = array() ) { 00674 $dbw = $this->getWriteDbConnection(); 00675 00676 $result = $dbw->update( 00677 $this->getName(), 00678 $this->getPrefixedValues( $values ), 00679 $this->getPrefixedValues( $conditions ), 00680 __METHOD__ 00681 ) !== false; // DatabaseBase::update does not always return true for success as documented... 00682 00683 $this->releaseConnection( $dbw ); 00684 00685 return $result; 00686 } 00687 00696 public function updateSummaryFields( $summaryFields = null, array $conditions = array() ) { 00697 $slave = $this->getReadDb(); 00698 $this->setReadDb( DB_MASTER ); 00699 00703 foreach ( $this->select( null, $conditions ) as $item ) { 00704 $item->loadSummaryFields( $summaryFields ); 00705 $item->setSummaryMode( true ); 00706 $item->save(); 00707 } 00708 00709 $this->setReadDb( $slave ); 00710 } 00711 00723 public function getPrefixedValues( array $values ) { 00724 $prefixedValues = array(); 00725 00726 foreach ( $values as $field => $value ) { 00727 if ( is_integer( $field ) ) { 00728 if ( is_array( $value ) ) { 00729 $field = $value[0]; 00730 $value = $value[1]; 00731 } else { 00732 $value = explode( ' ', $value, 2 ); 00733 $value[0] = $this->getPrefixedField( $value[0] ); 00734 $prefixedValues[] = implode( ' ', $value ); 00735 continue; 00736 } 00737 } 00738 00739 $prefixedValues[$this->getPrefixedField( $field )] = $value; 00740 } 00741 00742 return $prefixedValues; 00743 } 00744 00755 public function getPrefixedFields( array $fields ) { 00756 foreach ( $fields as &$field ) { 00757 $field = $this->getPrefixedField( $field ); 00758 } 00759 00760 return $fields; 00761 } 00762 00772 public function getPrefixedField( $field ) { 00773 return $this->getFieldPrefix() . $field; 00774 } 00775 00785 public function unprefixFieldNames( array $fieldNames ) { 00786 return array_map( array( $this, 'unprefixFieldName' ), $fieldNames ); 00787 } 00788 00798 public function unprefixFieldName( $fieldName ) { 00799 return substr( $fieldName, strlen( $this->getFieldPrefix() ) ); 00800 } 00801 00810 public static function singleton() { 00811 $class = get_called_class(); 00812 00813 if ( !array_key_exists( $class, self::$instanceCache ) ) { 00814 self::$instanceCache[$class] = new $class; 00815 } 00816 00817 return self::$instanceCache[$class]; 00818 } 00819 00831 public function getFieldsFromDBResult( stdClass $result ) { 00832 $result = (array)$result; 00833 00834 $rawFields = array_combine( 00835 $this->unprefixFieldNames( array_keys( $result ) ), 00836 array_values( $result ) 00837 ); 00838 00839 $fieldDefinitions = $this->getFields(); 00840 $fields = array(); 00841 00842 foreach ( $rawFields as $name => $value ) { 00843 if ( array_key_exists( $name, $fieldDefinitions ) ) { 00844 switch ( $fieldDefinitions[$name] ) { 00845 case 'int': 00846 $value = (int)$value; 00847 break; 00848 case 'float': 00849 $value = (float)$value; 00850 break; 00851 case 'bool': 00852 if ( is_string( $value ) ) { 00853 $value = $value !== '0'; 00854 } elseif ( is_int( $value ) ) { 00855 $value = $value !== 0; 00856 } 00857 break; 00858 case 'array': 00859 if ( is_string( $value ) ) { 00860 $value = unserialize( $value ); 00861 } 00862 00863 if ( !is_array( $value ) ) { 00864 $value = array(); 00865 } 00866 break; 00867 case 'blob': 00868 if ( is_string( $value ) ) { 00869 $value = unserialize( $value ); 00870 } 00871 break; 00872 case 'id': 00873 if ( is_string( $value ) ) { 00874 $value = (int)$value; 00875 } 00876 break; 00877 } 00878 00879 $fields[$name] = $value; 00880 } else { 00881 throw new MWException( 'Attempted to set unknown field ' . $name ); 00882 } 00883 } 00884 00885 return $fields; 00886 } 00887 00898 public function newFromDBResult( stdClass $result ) { 00899 return self::newRowFromDBResult( $result ); 00900 } 00901 00911 public function newRowFromDBResult( stdClass $result ) { 00912 return $this->newRow( $this->getFieldsFromDBResult( $result ) ); 00913 } 00914 00926 public function newFromArray( array $data, $loadDefaults = false ) { 00927 return static::newRow( $data, $loadDefaults ); 00928 } 00929 00940 public function newRow( array $fields, $loadDefaults = false ) { 00941 $class = $this->getRowClass(); 00942 00943 return new $class( $this, $fields, $loadDefaults ); 00944 } 00945 00953 public function getFieldNames() { 00954 return array_keys( $this->getFields() ); 00955 } 00956 00966 public function canHaveField( $name ) { 00967 return array_key_exists( $name, $this->getFields() ); 00968 } 00969 00980 public function updateRow( IORMRow $row, $functionName = null ) { 00981 $dbw = $this->getWriteDbConnection(); 00982 00983 $success = $dbw->update( 00984 $this->getName(), 00985 $this->getWriteValues( $row ), 00986 $this->getPrefixedValues( array( 'id' => $row->getId() ) ), 00987 is_null( $functionName ) ? __METHOD__ : $functionName 00988 ); 00989 00990 $this->releaseConnection( $dbw ); 00991 00992 // DatabaseBase::update does not always return true for success as documented... 00993 return $success !== false; 00994 } 00995 01007 public function insertRow( IORMRow $row, $functionName = null, array $options = null ) { 01008 $dbw = $this->getWriteDbConnection(); 01009 01010 $success = $dbw->insert( 01011 $this->getName(), 01012 $this->getWriteValues( $row ), 01013 is_null( $functionName ) ? __METHOD__ : $functionName, 01014 $options 01015 ); 01016 01017 // DatabaseBase::insert does not always return true for success as documented... 01018 $success = $success !== false; 01019 01020 if ( $success ) { 01021 $row->setField( 'id', $dbw->insertId() ); 01022 } 01023 01024 $this->releaseConnection( $dbw ); 01025 01026 return $success; 01027 } 01028 01038 protected function getWriteValues( IORMRow $row ) { 01039 $values = array(); 01040 01041 $rowFields = $row->getFields(); 01042 01043 foreach ( $this->getFields() as $name => $type ) { 01044 if ( array_key_exists( $name, $rowFields ) ) { 01045 $value = $rowFields[$name]; 01046 01047 switch ( $type ) { 01048 case 'array': 01049 $value = (array)$value; 01050 // fall-through! 01051 case 'blob': 01052 $value = serialize( $value ); 01053 // fall-through! 01054 } 01055 01056 $values[$this->getPrefixedField( $name )] = $value; 01057 } 01058 } 01059 01060 return $values; 01061 } 01062 01073 public function removeRow( IORMRow $row, $functionName = null ) { 01074 $success = $this->delete( 01075 array( 'id' => $row->getId() ), 01076 is_null( $functionName ) ? __METHOD__ : $functionName 01077 ); 01078 01079 // DatabaseBase::delete does not always return true for success as documented... 01080 return $success !== false; 01081 } 01082 01095 public function addToField( array $conditions, $field, $amount ) { 01096 if ( !array_key_exists( $field, $this->fields ) ) { 01097 throw new MWException( 'Unknown field "' . $field . '" provided' ); 01098 } 01099 01100 if ( $amount == 0 ) { 01101 return true; 01102 } 01103 01104 $absoluteAmount = abs( $amount ); 01105 $isNegative = $amount < 0; 01106 01107 $fullField = $this->getPrefixedField( $field ); 01108 01109 $dbw = $this->getWriteDbConnection(); 01110 01111 $success = $dbw->update( 01112 $this->getName(), 01113 array( "$fullField=$fullField" . ( $isNegative ? '-' : '+' ) . $absoluteAmount ), 01114 $this->getPrefixedValues( $conditions ), 01115 __METHOD__ 01116 ) !== false; // DatabaseBase::update does not always return true for success as documented... 01117 01118 $this->releaseConnection( $dbw ); 01119 01120 return $success; 01121 } 01122 }