Follow up to r111264; update @since tags
[lhc/web/wiklou.git] / includes / DBDataObject.php
1 <?php
2
3 /**
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.
8 *
9 * These methods must be implemented in deriving classes:
10 * * getFieldTypes
11 *
12 * These methods are likely candidates for overriding:
13 * * getDefaults
14 * * remove
15 * * insert
16 * * saveExisting
17 * * loadSummaryFields
18 * * getSummaryFields
19 *
20 * Deriving classes must register their table and field prefix in $wgDBDataObjects.
21 * Syntax: $wgDBDataObjects['DrivingClassName'] = array( 'table' => 'table_name', 'prefix' => 'fieldprefix_' );
22 * Example: $wgDBDataObjects['EPOrg'] = array( 'table' => 'ep_orgs', 'prefix' => 'org_' );
23 *
24 * Main instance methods:
25 * * getField(s)
26 * * setField(s)
27 * * save
28 * * remove
29 *
30 * Main static methods:
31 * * select
32 * * update
33 * * delete
34 * * count
35 * * has
36 * * selectRow
37 * * selectFields
38 * * selectFieldsRow
39 *
40 * @since 1.20
41 *
42 * @file DBDataObject.php
43 *
44 * @licence GNU GPL v3 or later
45 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
46 */
47 abstract class DBDataObject {
48
49 /**
50 * The fields of the object.
51 * field name (w/o prefix) => value
52 *
53 * @since 1.20
54 * @var array
55 */
56 protected $fields = array( 'id' => null );
57
58 /**
59 * If the object should update summaries of linked items when changed.
60 * For example, update the course_count field in universities when a course in courses is deleted.
61 * Settings this to false can prevent needless updating work in situations
62 * such as deleting a university, which will then delete all it's courses.
63 *
64 * @since 1.20
65 * @var bool
66 */
67 protected $updateSummaries = true;
68
69 /**
70 * Indicates if the object is in summary mode.
71 * This mode indicates that only summary fields got updated,
72 * which allows for optimizations.
73 *
74 * @since 1.20
75 * @var bool
76 */
77 protected $inSummaryMode = false;
78
79
80 /**
81 * The database connection to use for read operations.
82 * Can be changed via @see setReadDb.
83 *
84 * @since 0.2
85 * @var integer DB_ enum
86 */
87 protected static $readDb = DB_SLAVE;
88
89 /**
90 * Returns the name of the database table objects of this type are stored in.
91 *
92 * @since 1.20
93 *
94 * @throws MWException
95 * @return string
96 */
97 public static function getDBTable() {
98 global $wgDBDataObjects;
99 if ( array_key_exists( get_called_class(), $wgDBDataObjects ) ) {
100 return $wgDBDataObjects[get_called_class()]['table'];
101 }
102 else {
103 throw new MWException( 'Class "' . get_called_class() . '" not found in $wgDBDataObjects' );
104 }
105 }
106
107 /**
108 * Gets the db field prefix.
109 *
110 * @since 1.20
111 *
112 * @throws MWException
113 * @return string
114 */
115 protected static function getFieldPrefix() {
116 global $wgDBDataObjects;
117 if ( array_key_exists( get_called_class(), $wgDBDataObjects ) ) {
118 return $wgDBDataObjects[get_called_class()]['prefix'];
119 }
120 else {
121 throw new MWException( 'Class "' . get_called_class() . '" not found in $wgDBDataObjects' );
122 }
123 }
124
125 /**
126 * Returns an array with the fields and their types this object contains.
127 * This corresponds directly to the fields in the database, without prefix.
128 *
129 * field name => type
130 *
131 * Allowed types:
132 * * id
133 * * str
134 * * int
135 * * float
136 * * bool
137 * * array
138 *
139 * @since 1.20
140 *
141 * @throws MWException
142 * @return array
143 */
144 protected static function getFieldTypes() {
145 throw new MWException( 'Class did not implement getFieldTypes' );
146 }
147
148 /**
149 * Returns a list of default field values.
150 * field name => field value
151 *
152 * @since 1.20
153 *
154 * @return array
155 */
156 public static function getDefaults() {
157 return array();
158 }
159
160 /**
161 * Returns a list of the summary fields.
162 * These are fields that cache computed values, such as the amount of linked objects of $type.
163 * This is relevant as one might not want to do actions such as log changes when these get updated.
164 *
165 * @since 1.20
166 *
167 * @return array
168 */
169 public static function getSummaryFields() {
170 return array();
171 }
172
173 /**
174 * Constructor.
175 *
176 * @since 1.20
177 *
178 * @param array|null $fields
179 * @param boolean $loadDefaults
180 */
181 public function __construct( $fields = null, $loadDefaults = false ) {
182 if ( !is_array( $fields ) ) {
183 $fields = array();
184 }
185
186 if ( $loadDefaults ) {
187 $fields = array_merge( $this->getDefaults(), $fields );
188 }
189
190 $this->setFields( $fields );
191 }
192
193 /**
194 * Load the specified fields from the database.
195 *
196 * @since 1.20
197 *
198 * @param array|null $fields
199 * @param boolean $override
200 * @param boolean $skipLoaded
201 *
202 * @return Success indicator
203 */
204 public function loadFields( $fields = null, $override = true, $skipLoaded = false ) {
205 if ( is_null( $this->getId() ) ) {
206 return false;
207 }
208
209 if ( is_null( $fields ) ) {
210 $fields = array_keys( $this->getFieldTypes() );
211 }
212
213 if ( $skipLoaded ) {
214 $loadedFields = array_keys( $this->fields );
215 $fields = array_filter( $fields, function( $field ) use ( $loadedFields ) {
216 return !in_array( $field, $loadedFields );
217 } );
218 }
219
220 if ( count( $fields ) > 0 ) {
221 $results = $this->rawSelect(
222 $this->getPrefixedFields( $fields ),
223 array( $this->getPrefixedField( 'id' ) => $this->getId() ),
224 array( 'LIMIT' => 1 )
225 );
226
227 foreach ( $results as $result ) {
228 $this->setFields( $this->getFieldsFromDBResult( $result ), $override );
229 return true;
230 }
231
232 return false;
233 }
234
235 return true;
236 }
237
238 /**
239 * Gets the value of a field.
240 *
241 * @since 1.20
242 *
243 * @param string $name
244 * @param mixed $default
245 *
246 * @throws MWException
247 * @return mixed
248 */
249 public function getField( $name, $default = null ) {
250 if ( $this->hasField( $name ) ) {
251 return $this->fields[$name];
252 } elseif ( !is_null( $default ) ) {
253 return $default;
254 } else {
255 throw new MWException( 'Attempted to get not-set field ' . $name );
256 }
257 }
258
259 /**
260 * Gets the value of a field but first loads it if not done so already.
261 *
262 * @since 1.20
263 *
264 * @param string$name
265 *
266 * @return mixed
267 */
268 public function loadAndGetField( $name ) {
269 if ( !$this->hasField( $name ) ) {
270 $this->loadFields( array( $name ) );
271 }
272
273 return $this->getField( $name );
274 }
275
276 /**
277 * Remove a field.
278 *
279 * @since 1.20
280 *
281 * @param string $name
282 */
283 public function removeField( $name ) {
284 unset( $this->fields[$name] );
285 }
286
287 /**
288 * Returns the objects database id.
289 *
290 * @since 1.20
291 *
292 * @return integer|null
293 */
294 public function getId() {
295 return $this->getField( 'id' );
296 }
297
298 /**
299 * Sets the objects database id.
300 *
301 * @since 1.20
302 *
303 * @param integer|null $id
304 */
305 public function setId( $id ) {
306 return $this->setField( 'id', $id );
307 }
308
309 /**
310 * Gets if a certain field is set.
311 *
312 * @since 1.20
313 *
314 * @param string $name
315 *
316 * @return boolean
317 */
318 public function hasField( $name ) {
319 return array_key_exists( $name, $this->fields );
320 }
321
322 /**
323 * Gets if the id field is set.
324 *
325 * @since 1.20
326 *
327 * @return boolean
328 */
329 public function hasIdField() {
330 return $this->hasField( 'id' )
331 && !is_null( $this->getField( 'id' ) );
332 }
333
334 /**
335 * Sets multiple fields.
336 *
337 * @since 1.20
338 *
339 * @param array $fields The fields to set
340 * @param boolean $override Override already set fields with the provided values?
341 */
342 public function setFields( array $fields, $override = true ) {
343 foreach ( $fields as $name => $value ) {
344 if ( $override || !$this->hasField( $name ) ) {
345 $this->setField( $name, $value );
346 }
347 }
348 }
349
350 /**
351 * Gets the fields => values to write to the table.
352 *
353 * @since 1.20
354 *
355 * @return array
356 */
357 protected function getWriteValues() {
358 $values = array();
359
360 foreach ( $this->getFieldTypes() as $name => $type ) {
361 if ( array_key_exists( $name, $this->fields ) ) {
362 $value = $this->fields[$name];
363
364 switch ( $type ) {
365 case 'array':
366 $value = (array)$value;
367 case 'blob':
368 $value = serialize( $value );
369 }
370
371 $values[$this->getFieldPrefix() . $name] = $value;
372 }
373 }
374
375 return $values;
376 }
377
378 /**
379 * Serializes the object to an associative array which
380 * can then easily be converted into JSON or similar.
381 *
382 * @since 1.20
383 *
384 * @param null|array $fields
385 * @param boolean $incNullId
386 *
387 * @return array
388 */
389 public function toArray( $fields = null, $incNullId = false ) {
390 $data = array();
391 $setFields = array();
392
393 if ( !is_array( $fields ) ) {
394 $setFields = $this->getSetFieldNames();
395 } else {
396 foreach ( $fields as $field ) {
397 if ( $this->hasField( $field ) ) {
398 $setFields[] = $field;
399 }
400 }
401 }
402
403 foreach ( $setFields as $field ) {
404 if ( $incNullId || $field != 'id' || $this->hasIdField() ) {
405 $data[$field] = $this->getField( $field );
406 }
407 }
408
409 return $data;
410 }
411
412 /**
413 * Load the default values, via getDefaults.
414 *
415 * @since 1.20
416 *
417 * @param boolean $override
418 */
419 public function loadDefaults( $override = true ) {
420 $this->setFields( $this->getDefaults(), $override );
421 }
422
423 /**
424 * Writes the answer to the database, either updating it
425 * when it already exists, or inserting it when it doesn't.
426 *
427 * @since 1.20
428 *
429 * @return boolean Success indicator
430 */
431 public function save() {
432 if ( $this->hasIdField() ) {
433 return $this->saveExisting();
434 } else {
435 return $this->insert();
436 }
437 }
438
439 /**
440 * Updates the object in the database.
441 *
442 * @since 1.20
443 *
444 * @return boolean Success indicator
445 */
446 protected function saveExisting() {
447 $dbw = wfGetDB( DB_MASTER );
448
449 $success = $dbw->update(
450 $this->getDBTable(),
451 $this->getWriteValues(),
452 array( $this->getFieldPrefix() . 'id' => $this->getId() ),
453 __METHOD__
454 );
455
456 return $success;
457 }
458
459 /**
460 * Inserts the object into the database.
461 *
462 * @since 1.20
463 *
464 * @return boolean Success indicator
465 */
466 protected function insert() {
467 $dbw = wfGetDB( DB_MASTER );
468
469 $result = $dbw->insert(
470 $this->getDBTable(),
471 $this->getWriteValues(),
472 __METHOD__,
473 array( 'IGNORE' )
474 );
475
476 if ( $result ) {
477 $this->setField( 'id', $dbw->insertId() );
478 }
479
480 return $result;
481 }
482
483 /**
484 * Removes the object from the database.
485 *
486 * @since 1.20
487 *
488 * @return boolean Success indicator
489 */
490 public function remove() {
491 $this->beforeRemove();
492
493 $success = static::delete( array( 'id' => $this->getId() ) );
494
495 if ( $success ) {
496 $this->onRemoved();
497 }
498
499 return $success;
500 }
501
502 /**
503 * Gets called before an object is removed from the database.
504 *
505 * @since 1.20
506 */
507 protected function beforeRemove() {
508 $this->loadFields( $this->getBeforeRemoveFields(), false, true );
509 }
510
511 /**
512 * Before removal of an object happens, @see beforeRemove gets called.
513 * This method loads the fields of which the names have been returned by this one (or all fields if null is returned).
514 * This allows for loading info needed after removal to get rid of linked data and the like.
515 *
516 * @since 1.20
517 *
518 * @return array|null
519 */
520 protected function getBeforeRemoveFields() {
521 return array();
522 }
523
524 /**
525 * Gets called after successfull removal.
526 * Can be overriden to get rid of linked data.
527 *
528 * @since 1.20
529 */
530 protected function onRemoved() {
531 $this->setField( 'id', null );
532 }
533
534 /**
535 * Return the names and values of the fields.
536 *
537 * @since 1.20
538 *
539 * @return array
540 */
541 public function getFields() {
542 return $this->fields;
543 }
544
545 /**
546 * Return the names of the fields.
547 *
548 * @since 1.20
549 *
550 * @return array
551 */
552 public function getSetFieldNames() {
553 return array_keys( $this->fields );
554 }
555
556 /**
557 * Sets the value of a field.
558 * Strings can be provided for other types,
559 * so this method can be called from unserialization handlers.
560 *
561 * @since 1.20
562 *
563 * @param string $name
564 * @param mixed $value
565 *
566 * @throws MWException
567 */
568 public function setField( $name, $value ) {
569 $fields = $this->getFieldTypes();
570
571 if ( array_key_exists( $name, $fields ) ) {
572 switch ( $fields[$name] ) {
573 case 'int':
574 $value = (int)$value;
575 break;
576 case 'float':
577 $value = (float)$value;
578 break;
579 case 'bool':
580 if ( is_string( $value ) ) {
581 $value = $value !== '0';
582 } elseif ( is_int( $value ) ) {
583 $value = $value !== 0;
584 }
585 break;
586 case 'array':
587 if ( is_string( $value ) ) {
588 $value = unserialize( $value );
589 }
590
591 if ( !is_array( $value ) ) {
592 $value = array();
593 }
594 break;
595 case 'blob':
596 if ( is_string( $value ) ) {
597 $value = unserialize( $value );
598 }
599 break;
600 case 'id':
601 if ( is_string( $value ) ) {
602 $value = (int)$value;
603 }
604 break;
605 }
606
607 $this->fields[$name] = $value;
608 } else {
609 throw new MWException( 'Attempted to set unknown field ' . $name );
610 }
611 }
612
613 /**
614 * Get a new instance of the class from an array.
615 *
616 * @since 1.20
617 *
618 * @param array $data
619 * @param boolean $loadDefaults
620 *
621 * @return DBDataObject
622 */
623 public static function newFromArray( array $data, $loadDefaults = false ) {
624 return new static( $data, $loadDefaults );
625 }
626
627 /**
628 * Get the database type used for read operations.
629 *
630 * @since 0.2
631 * @return integer DB_ enum
632 */
633 public static function getReadDb() {
634 return self::$readDb;
635 }
636
637 /**
638 * Set the database type to use for read operations.
639 *
640 * @param integer $db
641 *
642 * @since 0.2
643 */
644 public static function setReadDb( $db ) {
645 self::$readDb = $db;
646 }
647
648 /**
649 * Gets if the object can take a certain field.
650 *
651 * @since 1.20
652 *
653 * @param string $name
654 *
655 * @return boolean
656 */
657 public static function canHasField( $name ) {
658 return array_key_exists( $name, static::getFieldTypes() );
659 }
660
661 /**
662 * Takes in a field or array of fields and returns an
663 * array with their prefixed versions, ready for db usage.
664 *
665 * @since 1.20
666 *
667 * @param array|string $fields
668 *
669 * @return array
670 */
671 public static function getPrefixedFields( array $fields ) {
672 foreach ( $fields as &$field ) {
673 $field = static::getPrefixedField( $field );
674 }
675
676 return $fields;
677 }
678
679 /**
680 * Takes in a field and returns an it's prefixed version, ready for db usage.
681 * If the field needs to be prefixed for another table, provide an array in the form
682 * array( 'tablename', 'fieldname' )
683 * Where table name is registered in $wgDBDataObjects.
684 *
685 * @since 1.20
686 *
687 * @param string|array $field
688 *
689 * @return string
690 * @throws MWException
691 */
692 public static function getPrefixedField( $field ) {
693 static $prefixes = false;
694
695 if ( $prefixes === false ) {
696 foreach ( $GLOBALS['wgDBDataObjects'] as $classInfo ) {
697 $prefixes[$classInfo['table']] = $classInfo['prefix'];
698 }
699 }
700
701 if ( is_array( $field ) && count( $field ) > 1 ) {
702 if ( array_key_exists( $field[0], $prefixes ) ) {
703 $prefix = $prefixes[$field[0]];
704 $field = $field[1];
705 }
706 else {
707 throw new MWException( 'Tried to prefix field with unknown table "' . $field[0] . '"' );
708 }
709 }
710 else {
711 $prefix = static::getFieldPrefix();
712 }
713
714 return $prefix . $field;
715 }
716
717 /**
718 * Takes in an associative array with field names as keys and
719 * their values as value. The field names are prefixed with the
720 * db field prefix.
721 *
722 * Field names can also be provided as an array with as first element a table name, such as
723 * $conditions = array(
724 * array( array( 'tablename', 'fieldname' ), $value ),
725 * );
726 *
727 * @since 1.20
728 *
729 * @param array $values
730 *
731 * @return array
732 */
733 public static function getPrefixedValues( array $values ) {
734 $prefixedValues = array();
735
736 foreach ( $values as $field => $value ) {
737 if ( is_integer( $field ) ) {
738 if ( is_array( $value ) ) {
739 $field = $value[0];
740 $value = $value[1];
741 }
742 else {
743 $value = explode( ' ', $value, 2 );
744 $value[0] = static::getPrefixedField( $value[0] );
745 $prefixedValues[] = implode( ' ', $value );
746 continue;
747 }
748 }
749
750 $prefixedValues[static::getPrefixedField( $field )] = $value;
751 }
752
753 return $prefixedValues;
754 }
755
756 /**
757 * Get an array with fields from a database result,
758 * that can be fed directly to the constructor or
759 * to setFields.
760 *
761 * @since 1.20
762 *
763 * @param object $result
764 *
765 * @return array
766 */
767 public static function getFieldsFromDBResult( $result ) {
768 $result = (array)$result;
769 return array_combine(
770 static::unprefixFieldNames( array_keys( $result ) ),
771 array_values( $result )
772 );
773 }
774
775 /**
776 * Takes a field name with prefix and returns the unprefixed equivalent.
777 *
778 * @since 1.20
779 *
780 * @param string $fieldName
781 *
782 * @return string
783 */
784 public static function unprefixFieldName( $fieldName ) {
785 return substr( $fieldName, strlen( static::getFieldPrefix() ) );
786 }
787
788 /**
789 * Takes an array of field names with prefix and returns the unprefixed equivalent.
790 *
791 * @since 1.20
792 *
793 * @param array $fieldNames
794 *
795 * @return array
796 */
797 public static function unprefixFieldNames( array $fieldNames ) {
798 return array_map( 'static::unprefixFieldName', $fieldNames );
799 }
800
801 /**
802 * Get a new instance of the class from a database result.
803 *
804 * @since 1.20
805 *
806 * @param stdClass $result
807 *
808 * @return DBDataObject
809 */
810 public static function newFromDBResult( stdClass $result ) {
811 return static::newFromArray( static::getFieldsFromDBResult( $result ) );
812 }
813
814 /**
815 * Removes the object from the database.
816 *
817 * @since 1.20
818 *
819 * @param array $conditions
820 *
821 * @return boolean Success indicator
822 */
823 public static function delete( array $conditions ) {
824 return wfGetDB( DB_MASTER )->delete(
825 static::getDBTable(),
826 static::getPrefixedValues( $conditions )
827 );
828 }
829
830 /**
831 * Add an amount (can be negative) to the specified field (needs to be numeric).
832 *
833 * @since 1.20
834 *
835 * @param string $field
836 * @param integer $amount
837 *
838 * @return boolean Success indicator
839 */
840 public static function addToField( $field, $amount ) {
841 if ( $amount == 0 ) {
842 return true;
843 }
844
845 if ( !static::hasIdField() ) {
846 return false;
847 }
848
849 $absoluteAmount = abs( $amount );
850 $isNegative = $amount < 0;
851
852 $dbw = wfGetDB( DB_MASTER );
853
854 $fullField = static::getPrefixedField( $field );
855
856 $success = $dbw->update(
857 static::getDBTable(),
858 array( "$fullField=$fullField" . ( $isNegative ? '-' : '+' ) . $absoluteAmount ),
859 array( static::getPrefixedField( 'id' ) => static::getId() ),
860 __METHOD__
861 );
862
863 if ( $success && static::hasField( $field ) ) {
864 static::setField( $field, static::getField( $field ) + $amount );
865 }
866
867 return $success;
868 }
869
870 /**
871 * Selects the the specified fields of the records matching the provided
872 * conditions and returns them as DBDataObject. Field names get prefixed.
873 *
874 * @since 1.20
875 *
876 * @param array|string|null $fields
877 * @param array $conditions
878 * @param array $options
879 * @param array $joinConds
880 *
881 * @return array of self
882 */
883 public static function select( $fields = null, array $conditions = array(), array $options = array(), array $joinConds = array() ) {
884 $result = static::selectFields( $fields, $conditions, $options, $joinConds, false );
885
886 $objects = array();
887
888 foreach ( $result as $record ) {
889 $objects[] = static::newFromArray( $record );
890 }
891
892 return $objects;
893 }
894
895 /**
896 * Selects the the specified fields of the records matching the provided
897 * conditions and returns them as associative arrays.
898 * Provided field names get prefixed.
899 * Returned field names will not have a prefix.
900 *
901 * When $collapse is true:
902 * If one field is selected, each item in the result array will be this field.
903 * If two fields are selected, each item in the result array will have as key
904 * the first field and as value the second field.
905 * If more then two fields are selected, each item will be an associative array.
906 *
907 * @since 1.20
908 *
909 * @param array|string|null $fields
910 * @param array $conditions
911 * @param array $options
912 * @param array $joinConds
913 * @param boolean $collapse Set to false to always return each result row as associative array.
914 *
915 * @return array of array
916 */
917 public static function selectFields( $fields = null, array $conditions = array(), array $options = array(), array $joinConds = array(), $collapse = true ) {
918 if ( is_null( $fields ) ) {
919 $fields = array_keys( static::getFieldTypes() );
920 }
921 else {
922 $fields = (array)$fields;
923 }
924
925 $tables = array( static::getDBTable() );
926 $joinConds = static::getProcessedJoinConds( $joinConds, $tables );
927
928 $result = static::rawSelect(
929 static::getPrefixedFields( $fields ),
930 static::getPrefixedValues( $conditions ),
931 $options,
932 $joinConds,
933 $tables
934 );
935
936 $objects = array();
937
938 foreach ( $result as $record ) {
939 $objects[] = static::getFieldsFromDBResult( $record );
940 }
941
942 if ( $collapse ) {
943 if ( count( $fields ) === 1 ) {
944 $objects = array_map( function( $object ) { return array_shift( $object ); } , $objects );
945 }
946 elseif ( count( $fields ) === 2 ) {
947 $o = array();
948
949 foreach ( $objects as $object ) {
950 $o[array_shift( $object )] = array_shift( $object );
951 }
952
953 $objects = $o;
954 }
955 }
956
957 return $objects;
958 }
959
960 /**
961 * Process the join conditions. This includes prefixing table and field names,
962 * and adding of needed tables.
963 *
964 * @since 1.20
965 *
966 * @param array $joinConds Join conditions without prefixes and fields in array rather then string with equals sign.
967 * @param array $tables List of tables to which the extra needed ones get added.
968 *
969 * @return array Join conditions ready to be fed to MediaWikis native select function.
970 */
971 protected static function getProcessedJoinConds( array $joinConds, array &$tables ) {
972 $conds = array();
973
974 foreach ( $joinConds as $table => $joinCond ) {
975 if ( !in_array( $table, $tables ) ) {
976 $tables[] = $table;
977 }
978
979 $cond = array( $joinCond[0], array() );
980
981 foreach ( $joinCond[1] as $joinCondPart ) {
982 $parts = array(
983 static::getPrefixedField( $joinCondPart[0] ),
984 static::getPrefixedField( $joinCondPart[1] ),
985 );
986
987 if ( !in_array( $joinCondPart[0][0], $tables ) ) {
988 $tables[] = $joinCondPart[0][0];
989 }
990
991 if ( !in_array( $joinCondPart[1][0], $tables ) ) {
992 $tables[] = $joinCondPart[1][0];
993 }
994
995 $cond[1][] = implode( '=', $parts );
996 }
997
998 $conds[$table] = $cond;
999 }
1000
1001 return $conds;
1002 }
1003
1004 /**
1005 * Selects the the specified fields of the first matching record.
1006 * Field names get prefixed.
1007 *
1008 * @since 1.20
1009 *
1010 * @param array|string|null $fields
1011 * @param array $conditions
1012 * @param array $options
1013 * @param array $joinConds
1014 *
1015 * @return EPBObject|false
1016 */
1017 public static function selectRow( $fields = null, array $conditions = array(), array $options = array(), array $joinConds = array() ) {
1018 $options['LIMIT'] = 1;
1019
1020 $objects = static::select( $fields, $conditions, $options, $joinConds );
1021
1022 return count( $objects ) > 0 ? $objects[0] : false;
1023 }
1024
1025 /**
1026 * Selects the the specified fields of the first record matching the provided
1027 * conditions and returns it as an associative array, or false when nothing matches.
1028 * This method makes use of selectFields and expects the same parameters and
1029 * returns the same results (if there are any, if there are none, this method returns false).
1030 * @see DBDataObject::selectFields
1031 *
1032 * @since 1.20
1033 *
1034 * @param array|string|null $fields
1035 * @param array $conditions
1036 * @param array $options
1037 * @param array $joinConds
1038 * @param boolean $collapse Set to false to always return each result row as associative array.
1039 *
1040 * @return mixed|array|false
1041 */
1042 public static function selectFieldsRow( $fields = null, array $conditions = array(), array $options = array(), array $joinConds = array(), $collapse = true ) {
1043 $options['LIMIT'] = 1;
1044
1045 $objects = static::selectFields( $fields, $conditions, $options, $joinConds, $collapse );
1046
1047 return count( $objects ) > 0 ? $objects[0] : false;
1048 }
1049
1050 /**
1051 * Returns if there is at least one record matching the provided conditions.
1052 * Condition field names get prefixed.
1053 *
1054 * @since 1.20
1055 *
1056 * @param array $conditions
1057 *
1058 * @return boolean
1059 */
1060 public static function has( array $conditions = array() ) {
1061 return static::selectRow( array( 'id' ), $conditions ) !== false;
1062 }
1063
1064 /**
1065 * Returns the amount of matching records.
1066 * Condition field names get prefixed.
1067 *
1068 * @since 1.20
1069 *
1070 * @param array $conditions
1071 * @param array $options
1072 *
1073 * @return integer
1074 */
1075 public static function count( array $conditions = array(), array $options = array() ) {
1076 $res = static::rawSelect(
1077 array( 'COUNT(*) AS rowcount' ),
1078 static::getPrefixedValues( $conditions ),
1079 $options
1080 )->fetchObject();
1081
1082 return $res->rowcount;
1083 }
1084
1085 /**
1086 * Selects the the specified fields of the records matching the provided
1087 * conditions. Field names do NOT get prefixed.
1088 *
1089 * @since 1.20
1090 *
1091 * @param array $fields
1092 * @param array $conditions
1093 * @param array $options
1094 * @param array $joinConds
1095 * @param array $tables
1096 *
1097 * @return ResultWrapper
1098 */
1099 public static function rawSelect( array $fields, array $conditions = array(), array $options = array(), array $joinConds = array(), array $tables = null ) {
1100 if ( is_null( $tables ) ) {
1101 $tables = static::getDBTable();
1102 }
1103
1104 $dbr = wfGetDB( static::getReadDb() );
1105
1106 return $dbr->select(
1107 $tables,
1108 $fields,
1109 count( $conditions ) == 0 ? '' : $conditions,
1110 __METHOD__,
1111 $options,
1112 $joinConds
1113 );
1114 }
1115
1116 /**
1117 * Update the records matching the provided conditions by
1118 * setting the fields that are keys in the $values param to
1119 * their corresponding values.
1120 *
1121 * @since 1.20
1122 *
1123 * @param array $values
1124 * @param array $conditions
1125 *
1126 * @return boolean Success indicator
1127 */
1128 public static function update( array $values, array $conditions = array() ) {
1129 $dbw = wfGetDB( DB_MASTER );
1130
1131 return $dbw->update(
1132 static::getDBTable(),
1133 static::getPrefixedValues( $values ),
1134 static::getPrefixedValues( $conditions ),
1135 __METHOD__
1136 );
1137 }
1138
1139 /**
1140 * Return the names of the fields.
1141 *
1142 * @since 1.20
1143 *
1144 * @return array
1145 */
1146 public static function getFieldNames() {
1147 return array_keys( static::getFieldTypes() );
1148 }
1149
1150 /**
1151 * Returns an array with the fields and their descriptions.
1152 *
1153 * field name => field description
1154 *
1155 * @since 1.20
1156 *
1157 * @return array
1158 */
1159 public static function getFieldDescriptions() {
1160 return array();
1161 }
1162
1163 /**
1164 * Get API parameters for the fields supported by this object.
1165 *
1166 * @since 1.20
1167 *
1168 * @param boolean $requireParams
1169 * @param boolean $setDefaults
1170 *
1171 * @return array
1172 */
1173 public static function getAPIParams( $requireParams = false, $setDefaults = false ) {
1174 $typeMap = array(
1175 'id' => 'integer',
1176 'int' => 'integer',
1177 'float' => 'NULL',
1178 'str' => 'string',
1179 'bool' => 'integer',
1180 'array' => 'string',
1181 'blob' => 'string',
1182 );
1183
1184 $params = array();
1185 $defaults = static::getDefaults();
1186
1187 foreach ( static::getFieldTypes() as $field => $type ) {
1188 if ( $field == 'id' ) {
1189 continue;
1190 }
1191
1192 $hasDefault = array_key_exists( $field, $defaults );
1193
1194 $params[$field] = array(
1195 ApiBase::PARAM_TYPE => $typeMap[$type],
1196 ApiBase::PARAM_REQUIRED => $requireParams && !$hasDefault
1197 );
1198
1199 if ( $type == 'array' ) {
1200 $params[$field][ApiBase::PARAM_ISMULTI] = true;
1201 }
1202
1203 if ( $setDefaults && $hasDefault ) {
1204 $default = is_array( $defaults[$field] ) ? implode( '|', $defaults[$field] ) : $defaults[$field];
1205 $params[$field][ApiBase::PARAM_DFLT] = $default;
1206 }
1207 }
1208
1209 return $params;
1210 }
1211
1212 /**
1213 * Computes and updates the values of the summary fields.
1214 *
1215 * @since 1.20
1216 *
1217 * @param array|string|null $summaryFields
1218 */
1219 public function loadSummaryFields( $summaryFields = null ) {
1220
1221 }
1222
1223 /**
1224 * Computes the values of the summary fields of the objects matching the provided conditions.
1225 *
1226 * @since 1.20
1227 *
1228 * @param array|string|null $summaryFields
1229 * @param array $conditions
1230 */
1231 public static function updateSummaryFields( $summaryFields = null, array $conditions = array() ) {
1232 self::setReadDb( DB_MASTER );
1233
1234 foreach ( self::select( null, $conditions ) as /* DBDataObject */ $item ) {
1235 $item->loadSummaryFields( $summaryFields );
1236 $item->setSummaryMode( true );
1237 $item->saveExisting();
1238 }
1239
1240 self::setReadDb( DB_SLAVE );
1241 }
1242
1243 /**
1244 * Sets the value for the @see $updateSummaries field.
1245 *
1246 * @since 1.20
1247 *
1248 * @param boolean $update
1249 */
1250 public function setUpdateSummaries( $update ) {
1251 $this->updateSummaries = $update;
1252 }
1253
1254 /**
1255 * Sets the value for the @see $inSummaryMode field.
1256 *
1257 * @since 1.20
1258 *
1259 * @param boolean $update
1260 */
1261 public function setSummaryMode( $summaryMode ) {
1262 $this->inSummaryMode = $summaryMode;
1263 }
1264
1265 /**
1266 * Return if any fields got changed.
1267 *
1268 * @since 1.20
1269 *
1270 * @param DBDataObject $object
1271 * @param boolean $excludeSummaryFields When set to true, summary field changes are ignored.
1272 *
1273 * @return boolean
1274 */
1275 protected function fieldsChanged( DBDataObject $object, $excludeSummaryFields = false ) {
1276 foreach ( $this->fields as $name => $value ) {
1277 $excluded = $excludeSummaryFields && in_array( $name, $this->getSummaryFields() );
1278
1279 if ( !$excluded && $object->getField( $name ) !== $value ) {
1280 return true;
1281 }
1282 }
1283
1284 return false;
1285 }
1286
1287 }