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;
88 * @param DBTable $table
89 * @param array|null $fields
90 * @param boolean $loadDefaults
92 public function __construct( DBTable
$table, $fields = null, $loadDefaults = false ) {
93 $this->table
= $table;
95 if ( !is_array( $fields ) ) {
99 if ( $loadDefaults ) {
100 $fields = array_merge( $this->table
->getDefaults(), $fields );
103 $this->setFields( $fields );
107 * Load the specified fields from the database.
111 * @param array|null $fields
112 * @param boolean $override
113 * @param boolean $skipLoaded
115 * @return bool Success indicator
117 public function loadFields( $fields = null, $override = true, $skipLoaded = false ) {
118 if ( is_null( $this->getId() ) ) {
122 if ( is_null( $fields ) ) {
123 $fields = array_keys( $this->table
->getFieldTypes() );
127 $fields = array_diff( $fields, array_keys( $this->fields
) );
130 if ( count( $fields ) > 0 ) {
131 $result = $this->table
->rawSelectRow(
132 $this->table
->getPrefixedFields( $fields ),
133 array( $this->table
->getPrefixedField( 'id' ) => $this->getId() ),
134 array( 'LIMIT' => 1 )
137 if ( $result !== false ) {
138 $this->setFields( $this->table
->getFieldsFromDBResult( $result ), $override );
149 * Gets the value of a field.
153 * @param string $name
154 * @param mixed $default
156 * @throws MWException
159 public function getField( $name, $default = null ) {
160 if ( $this->hasField( $name ) ) {
161 return $this->fields
[$name];
162 } elseif ( !is_null( $default ) ) {
165 throw new MWException( 'Attempted to get not-set field ' . $name );
170 * Gets the value of a field but first loads it if not done so already.
178 public function loadAndGetField( $name ) {
179 if ( !$this->hasField( $name ) ) {
180 $this->loadFields( array( $name ) );
183 return $this->getField( $name );
191 * @param string $name
193 public function removeField( $name ) {
194 unset( $this->fields
[$name] );
198 * Returns the objects database id.
202 * @return integer|null
204 public function getId() {
205 return $this->getField( 'id' );
209 * Sets the objects database id.
213 * @param integer|null $id
215 public function setId( $id ) {
216 return $this->setField( 'id', $id );
220 * Gets if a certain field is set.
224 * @param string $name
228 public function hasField( $name ) {
229 return array_key_exists( $name, $this->fields
);
233 * Gets if the id field is set.
239 public function hasIdField() {
240 return $this->hasField( 'id' )
241 && !is_null( $this->getField( 'id' ) );
245 * Sets multiple fields.
249 * @param array $fields The fields to set
250 * @param boolean $override Override already set fields with the provided values?
252 public function setFields( array $fields, $override = true ) {
253 foreach ( $fields as $name => $value ) {
254 if ( $override ||
!$this->hasField( $name ) ) {
255 $this->setField( $name, $value );
261 * Gets the fields => values to write to the table.
267 protected function getWriteValues() {
270 foreach ( $this->table
->getFieldTypes() as $name => $type ) {
271 if ( array_key_exists( $name, $this->fields
) ) {
272 $value = $this->fields
[$name];
276 $value = (array)$value;
278 $value = serialize( $value );
281 $values[$this->table
->getPrefixedField( $name )] = $value;
289 * Serializes the object to an associative array which
290 * can then easily be converted into JSON or similar.
294 * @param null|array $fields
295 * @param boolean $incNullId
299 public function toArray( $fields = null, $incNullId = false ) {
301 $setFields = array();
303 if ( !is_array( $fields ) ) {
304 $setFields = $this->getSetFieldNames();
306 foreach ( $fields as $field ) {
307 if ( $this->hasField( $field ) ) {
308 $setFields[] = $field;
313 foreach ( $setFields as $field ) {
314 if ( $incNullId ||
$field != 'id' ||
$this->hasIdField() ) {
315 $data[$field] = $this->getField( $field );
323 * Load the default values, via getDefaults.
327 * @param boolean $override
329 public function loadDefaults( $override = true ) {
330 $this->setFields( $this->table
->getDefaults(), $override );
334 * Writes the answer to the database, either updating it
335 * when it already exists, or inserting it when it doesn't.
339 * @return boolean Success indicator
341 public function save() {
342 if ( $this->hasIdField() ) {
343 return $this->saveExisting();
345 return $this->insert();
350 * Updates the object in the database.
354 * @return boolean Success indicator
356 protected function saveExisting() {
357 $dbw = wfGetDB( DB_MASTER
);
359 $success = $dbw->update(
360 $this->table
->getDBTable(),
361 $this->getWriteValues(),
362 array( $this->table
->getPrefixedField( 'id' ) => $this->getId() ),
370 * Inserts the object into the database.
374 * @return boolean Success indicator
376 protected function insert() {
377 $dbw = wfGetDB( DB_MASTER
);
379 $result = $dbw->insert(
380 $this->table
->getDBTable(),
381 $this->getWriteValues(),
387 $this->setField( 'id', $dbw->insertId() );
394 * Removes the object from the database.
398 * @return boolean Success indicator
400 public function remove() {
401 $this->beforeRemove();
403 $success = $this->table
->delete( array( 'id' => $this->getId() ) );
413 * Gets called before an object is removed from the database.
417 protected function beforeRemove() {
418 $this->loadFields( $this->getBeforeRemoveFields(), false, true );
422 * Before removal of an object happens, @see beforeRemove gets called.
423 * This method loads the fields of which the names have been returned by this one (or all fields if null is returned).
424 * This allows for loading info needed after removal to get rid of linked data and the like.
430 protected function getBeforeRemoveFields() {
435 * Gets called after successfull removal.
436 * Can be overriden to get rid of linked data.
440 protected function onRemoved() {
441 $this->setField( 'id', null );
445 * Return the names and values of the fields.
451 public function getFields() {
452 return $this->fields
;
456 * Return the names of the fields.
462 public function getSetFieldNames() {
463 return array_keys( $this->fields
);
467 * Sets the value of a field.
468 * Strings can be provided for other types,
469 * so this method can be called from unserialization handlers.
473 * @param string $name
474 * @param mixed $value
476 * @throws MWException
478 public function setField( $name, $value ) {
479 $fields = $this->table
->getFieldTypes();
481 if ( array_key_exists( $name, $fields ) ) {
482 switch ( $fields[$name] ) {
484 $value = (int)$value;
487 $value = (float)$value;
490 if ( is_string( $value ) ) {
491 $value = $value !== '0';
492 } elseif ( is_int( $value ) ) {
493 $value = $value !== 0;
497 if ( is_string( $value ) ) {
498 $value = unserialize( $value );
501 if ( !is_array( $value ) ) {
506 if ( is_string( $value ) ) {
507 $value = unserialize( $value );
511 if ( is_string( $value ) ) {
512 $value = (int)$value;
517 $this->fields
[$name] = $value;
519 throw new MWException( 'Attempted to set unknown field ' . $name );
524 * Add an amount (can be negative) to the specified field (needs to be numeric).
528 * @param string $field
529 * @param integer $amount
531 * @return boolean Success indicator
533 public function addToField( $field, $amount ) {
534 if ( $amount == 0 ) {
538 if ( !$this->hasIdField() ) {
542 $absoluteAmount = abs( $amount );
543 $isNegative = $amount < 0;
545 $dbw = wfGetDB( DB_MASTER
);
547 $fullField = $this->table
->getPrefixedField( $field );
549 $success = $dbw->update(
550 $this->table
->getDBTable(),
551 array( "$fullField=$fullField" . ( $isNegative ?
'-' : '+' ) . $absoluteAmount ),
552 array( $this->table
->getPrefixedField( 'id' ) => $this->getId() ),
556 if ( $success && static::hasField( $field ) ) {
557 static::setField( $field, static::getField( $field ) +
$amount );
564 * Return the names of the fields.
570 public function getFieldNames() {
571 return array_keys( $this->table
->getFieldTypes() );
575 * Computes and updates the values of the summary fields.
579 * @param array|string|null $summaryFields
581 public function loadSummaryFields( $summaryFields = null ) {
586 * Sets the value for the @see $updateSummaries field.
590 * @param boolean $update
592 public function setUpdateSummaries( $update ) {
593 $this->updateSummaries
= $update;
597 * Sets the value for the @see $inSummaryMode field.
601 * @param boolean $summaryMode
603 public function setSummaryMode( $summaryMode ) {
604 $this->inSummaryMode
= $summaryMode;
608 * Return if any fields got changed.
612 * @param DBDataObject $object
613 * @param boolean $excludeSummaryFields When set to true, summary field changes are ignored.
617 protected function fieldsChanged( DBDataObject
$object, $excludeSummaryFields = false ) {
618 foreach ( $this->fields
as $name => $value ) {
619 $excluded = $excludeSummaryFields && in_array( $name, $this->table
->getSummaryFields() );
621 if ( !$excluded && $object->getField( $name ) !== $value ) {
630 * Returns the table this DBDataObject is a row in.
636 public function getTable() {