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