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