3fa528516f8dbacdd46799ffcb230b299281f6c4
4 * Abstract base class for representing objects that are stored in some DB table.
5 * This is basically an ORM-like wrapper around rows in database tables that
6 * aims to be both simple and very flexible. It is centered around an associative
7 * array of fields and various methods to do common interaction with the database.
9 * These methods must be implemented in deriving classes:
14 * These methods are likely candidates for overriding:
22 * Main instance methods:
28 * Main static methods:
40 * @file DBDataObject.php
42 * @licence GNU GPL v2 or later
43 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
45 abstract class DBDataObject
{
48 * The fields of the object.
49 * field name (w/o prefix) => value
54 protected $fields = array( 'id' => null );
63 * If the object should update summaries of linked items when changed.
64 * For example, update the course_count field in universities when a course in courses is deleted.
65 * Settings this to false can prevent needless updating work in situations
66 * such as deleting a university, which will then delete all it's courses.
71 protected $updateSummaries = true;
74 * Indicates if the object is in summary mode.
75 * This mode indicates that only summary fields got updated,
76 * which allows for optimizations.
81 protected $inSummaryMode = false;
84 * The database connection to use for read operations.
85 * Can be changed via @see setReadDb.
88 * @var integer DB_ enum
90 protected $readDb = DB_SLAVE
;
97 * @param DBTable $table
98 * @param array|null $fields
99 * @param boolean $loadDefaults
101 public function __construct( DBTable
$table, $fields = null, $loadDefaults = false ) {
102 $this->table
= $table;
104 if ( !is_array( $fields ) ) {
108 if ( $loadDefaults ) {
109 $fields = array_merge( $this->table
->getDefaults(), $fields );
112 $this->setFields( $fields );
116 * Load the specified fields from the database.
120 * @param array|null $fields
121 * @param boolean $override
122 * @param boolean $skipLoaded
124 * @return bool Success indicator
126 public function loadFields( $fields = null, $override = true, $skipLoaded = false ) {
127 if ( is_null( $this->getId() ) ) {
131 if ( is_null( $fields ) ) {
132 $fields = array_keys( $this->table
->getFieldTypes() );
136 $fields = array_diff( $fields, array_keys( $this->fields
) );
139 if ( count( $fields ) > 0 ) {
140 $result = $this->table
->rawSelectRow(
141 $this->table
->getPrefixedFields( $fields ),
142 array( $this->table
->getPrefixedField( 'id' ) => $this->getId() ),
143 array( 'LIMIT' => 1 )
146 if ( $result !== false ) {
147 $this->setFields( $this->table
->getFieldsFromDBResult( $result ), $override );
158 * Gets the value of a field.
162 * @param string $name
163 * @param mixed $default
165 * @throws MWException
168 public function getField( $name, $default = null ) {
169 if ( $this->hasField( $name ) ) {
170 return $this->fields
[$name];
171 } elseif ( !is_null( $default ) ) {
174 throw new MWException( 'Attempted to get not-set field ' . $name );
179 * Gets the value of a field but first loads it if not done so already.
187 public function loadAndGetField( $name ) {
188 if ( !$this->hasField( $name ) ) {
189 $this->loadFields( array( $name ) );
192 return $this->getField( $name );
200 * @param string $name
202 public function removeField( $name ) {
203 unset( $this->fields
[$name] );
207 * Returns the objects database id.
211 * @return integer|null
213 public function getId() {
214 return $this->getField( 'id' );
218 * Sets the objects database id.
222 * @param integer|null $id
224 public function setId( $id ) {
225 return $this->setField( 'id', $id );
229 * Gets if a certain field is set.
233 * @param string $name
237 public function hasField( $name ) {
238 return array_key_exists( $name, $this->fields
);
242 * Gets if the id field is set.
248 public function hasIdField() {
249 return $this->hasField( 'id' )
250 && !is_null( $this->getField( 'id' ) );
254 * Sets multiple fields.
258 * @param array $fields The fields to set
259 * @param boolean $override Override already set fields with the provided values?
261 public function setFields( array $fields, $override = true ) {
262 foreach ( $fields as $name => $value ) {
263 if ( $override ||
!$this->hasField( $name ) ) {
264 $this->setField( $name, $value );
270 * Gets the fields => values to write to the table.
276 protected function getWriteValues() {
279 foreach ( $this->table
->getFieldTypes() as $name => $type ) {
280 if ( array_key_exists( $name, $this->fields
) ) {
281 $value = $this->fields
[$name];
285 $value = (array)$value;
287 $value = serialize( $value );
290 $values[$this->table
->getPrefixedField( $name )] = $value;
298 * Serializes the object to an associative array which
299 * can then easily be converted into JSON or similar.
303 * @param null|array $fields
304 * @param boolean $incNullId
308 public function toArray( $fields = null, $incNullId = false ) {
310 $setFields = array();
312 if ( !is_array( $fields ) ) {
313 $setFields = $this->getSetFieldNames();
315 foreach ( $fields as $field ) {
316 if ( $this->hasField( $field ) ) {
317 $setFields[] = $field;
322 foreach ( $setFields as $field ) {
323 if ( $incNullId ||
$field != 'id' ||
$this->hasIdField() ) {
324 $data[$field] = $this->getField( $field );
332 * Load the default values, via getDefaults.
336 * @param boolean $override
338 public function loadDefaults( $override = true ) {
339 $this->setFields( $this->table
->getDefaults(), $override );
343 * Writes the answer to the database, either updating it
344 * when it already exists, or inserting it when it doesn't.
348 * @return boolean Success indicator
350 public function save() {
351 if ( $this->hasIdField() ) {
352 return $this->saveExisting();
354 return $this->insert();
359 * Updates the object in the database.
363 * @return boolean Success indicator
365 protected function saveExisting() {
366 $dbw = wfGetDB( DB_MASTER
);
368 $success = $dbw->update(
369 $this->table
->getDBTable(),
370 $this->getWriteValues(),
371 array( $this->table
->getPrefixedField( 'id' ) => $this->getId() ),
379 * Inserts the object into the database.
383 * @return boolean Success indicator
385 protected function insert() {
386 $dbw = wfGetDB( DB_MASTER
);
388 $result = $dbw->insert(
389 $this->table
->getDBTable(),
390 $this->getWriteValues(),
396 $this->setField( 'id', $dbw->insertId() );
403 * Removes the object from the database.
407 * @return boolean Success indicator
409 public function remove() {
410 $this->beforeRemove();
412 $success = $this->table
->delete( array( 'id' => $this->getId() ) );
422 * Gets called before an object is removed from the database.
426 protected function beforeRemove() {
427 $this->loadFields( $this->getBeforeRemoveFields(), false, true );
431 * Before removal of an object happens, @see beforeRemove gets called.
432 * This method loads the fields of which the names have been returned by this one (or all fields if null is returned).
433 * This allows for loading info needed after removal to get rid of linked data and the like.
439 protected function getBeforeRemoveFields() {
444 * Gets called after successfull removal.
445 * Can be overriden to get rid of linked data.
449 protected function onRemoved() {
450 $this->setField( 'id', null );
454 * Return the names and values of the fields.
460 public function getFields() {
461 return $this->fields
;
465 * Return the names of the fields.
471 public function getSetFieldNames() {
472 return array_keys( $this->fields
);
476 * Sets the value of a field.
477 * Strings can be provided for other types,
478 * so this method can be called from unserialization handlers.
482 * @param string $name
483 * @param mixed $value
485 * @throws MWException
487 public function setField( $name, $value ) {
488 $fields = $this->table
->getFieldTypes();
490 if ( array_key_exists( $name, $fields ) ) {
491 switch ( $fields[$name] ) {
493 $value = (int)$value;
496 $value = (float)$value;
499 if ( is_string( $value ) ) {
500 $value = $value !== '0';
501 } elseif ( is_int( $value ) ) {
502 $value = $value !== 0;
506 if ( is_string( $value ) ) {
507 $value = unserialize( $value );
510 if ( !is_array( $value ) ) {
515 if ( is_string( $value ) ) {
516 $value = unserialize( $value );
520 if ( is_string( $value ) ) {
521 $value = (int)$value;
526 $this->fields
[$name] = $value;
528 throw new MWException( 'Attempted to set unknown field ' . $name );
533 * Get the database type used for read operations.
537 * @return integer DB_ enum
539 public function getReadDb() {
540 return $this->readDb
;
544 * Set the database type to use for read operations.
550 public function setReadDb( $db ) {
555 * Add an amount (can be negative) to the specified field (needs to be numeric).
559 * @param string $field
560 * @param integer $amount
562 * @return boolean Success indicator
564 public function addToField( $field, $amount ) {
565 if ( $amount == 0 ) {
569 if ( !$this->hasIdField() ) {
573 $absoluteAmount = abs( $amount );
574 $isNegative = $amount < 0;
576 $dbw = wfGetDB( DB_MASTER
);
578 $fullField = $this->table
->getPrefixedField( $field );
580 $success = $dbw->update(
581 $this->table
->getDBTable(),
582 array( "$fullField=$fullField" . ( $isNegative ?
'-' : '+' ) . $absoluteAmount ),
583 array( $this->table
->getPrefixedField( 'id' ) => $this->getId() ),
587 if ( $success && static::hasField( $field ) ) {
588 static::setField( $field, static::getField( $field ) +
$amount );
595 * Return the names of the fields.
601 public function getFieldNames() {
602 return array_keys( $this->table
->getFieldTypes() );
606 * Computes and updates the values of the summary fields.
610 * @param array|string|null $summaryFields
612 public function loadSummaryFields( $summaryFields = null ) {
617 * Sets the value for the @see $updateSummaries field.
621 * @param boolean $update
623 public function setUpdateSummaries( $update ) {
624 $this->updateSummaries
= $update;
628 * Sets the value for the @see $inSummaryMode field.
632 * @param boolean $summaryMode
634 public function setSummaryMode( $summaryMode ) {
635 $this->inSummaryMode
= $summaryMode;
639 * Return if any fields got changed.
643 * @param DBDataObject $object
644 * @param boolean $excludeSummaryFields When set to true, summary field changes are ignored.
648 protected function fieldsChanged( DBDataObject
$object, $excludeSummaryFields = false ) {
649 foreach ( $this->fields
as $name => $value ) {
650 $excluded = $excludeSummaryFields && in_array( $name, $this->table
->getSummaryFields() );
652 if ( !$excluded && $object->getField( $name ) !== $value ) {
661 * Returns the table this DBDataObject is a row in.
667 public function getTable() {