MediaWiki  REL1_24
ORMTable.php
Go to the documentation of this file.
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 }