Update formatting on database-related classes
[lhc/web/wiklou.git] / includes / db / ORMTable.php
1 <?php
2 /**
3 * Abstract base class for representing a single database table.
4 * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 * @since 1.20
22 * Non-abstract since 1.21
23 *
24 * @file ORMTable.php
25 * @ingroup ORM
26 *
27 * @license GNU GPL v2 or later
28 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
29 */
30
31 class ORMTable extends DBAccessBase implements IORMTable {
32 /**
33 * Cache for instances, used by the singleton method.
34 *
35 * @since 1.20
36 * @deprecated since 1.21
37 *
38 * @var ORMTable[]
39 */
40 protected static $instanceCache = array();
41
42 /**
43 * @since 1.21
44 *
45 * @var string
46 */
47 protected $tableName;
48
49 /**
50 * @since 1.21
51 *
52 * @var string[]
53 */
54 protected $fields = array();
55
56 /**
57 * @since 1.21
58 *
59 * @var string
60 */
61 protected $fieldPrefix = '';
62
63 /**
64 * @since 1.21
65 *
66 * @var string
67 */
68 protected $rowClass = 'ORMRow';
69
70 /**
71 * @since 1.21
72 *
73 * @var array
74 */
75 protected $defaults = array();
76
77 /**
78 * ID of the database connection to use for read operations.
79 * Can be changed via @see setReadDb.
80 *
81 * @since 1.20
82 *
83 * @var integer DB_ enum
84 */
85 protected $readDb = DB_SLAVE;
86
87 /**
88 * Constructor.
89 *
90 * @since 1.21
91 *
92 * @param string $tableName
93 * @param string[] $fields
94 * @param array $defaults
95 * @param string|null $rowClass
96 * @param string $fieldPrefix
97 */
98 public function __construct( $tableName = '', array $fields = array(), array $defaults = array(), $rowClass = null, $fieldPrefix = '' ) {
99 $this->tableName = $tableName;
100 $this->fields = $fields;
101 $this->defaults = $defaults;
102
103 if ( is_string( $rowClass ) ) {
104 $this->rowClass = $rowClass;
105 }
106
107 $this->fieldPrefix = $fieldPrefix;
108 }
109
110 /**
111 * @see IORMTable::getName
112 *
113 * @since 1.21
114 *
115 * @return string
116 * @throws MWException
117 */
118 public function getName() {
119 if ( $this->tableName === '' ) {
120 throw new MWException( 'The table name needs to be set' );
121 }
122
123 return $this->tableName;
124 }
125
126 /**
127 * Gets the db field prefix.
128 *
129 * @since 1.20
130 *
131 * @return string
132 */
133 protected function getFieldPrefix() {
134 return $this->fieldPrefix;
135 }
136
137 /**
138 * @see IORMTable::getRowClass
139 *
140 * @since 1.21
141 *
142 * @return string
143 */
144 public function getRowClass() {
145 return $this->rowClass;
146 }
147
148 /**
149 * @see ORMTable::getFields
150 *
151 * @since 1.21
152 *
153 * @return array
154 * @throws MWException
155 */
156 public function getFields() {
157 if ( $this->fields === array() ) {
158 throw new MWException( 'The table needs to have one or more fields' );
159 }
160
161 return $this->fields;
162 }
163
164 /**
165 * Returns a list of default field values.
166 * field name => field value
167 *
168 * @since 1.20
169 *
170 * @return array
171 */
172 public function getDefaults() {
173 return $this->defaults;
174 }
175
176 /**
177 * Returns a list of the summary fields.
178 * These are fields that cache computed values, such as the amount of linked objects of $type.
179 * This is relevant as one might not want to do actions such as log changes when these get updated.
180 *
181 * @since 1.20
182 *
183 * @return array
184 */
185 public function getSummaryFields() {
186 return array();
187 }
188
189 /**
190 * Selects the the specified fields of the records matching the provided
191 * conditions and returns them as DBDataObject. Field names get prefixed.
192 *
193 * @since 1.20
194 *
195 * @param array|string|null $fields
196 * @param array $conditions
197 * @param array $options
198 * @param string|null $functionName
199 *
200 * @return ORMResult
201 */
202 public function select( $fields = null, array $conditions = array(),
203 array $options = array(), $functionName = null
204 ) {
205 $res = $this->rawSelect( $fields, $conditions, $options, $functionName );
206
207 return new ORMResult( $this, $res );
208 }
209
210 /**
211 * Selects the the specified fields of the records matching the provided
212 * conditions and returns them as DBDataObject. Field names get prefixed.
213 *
214 * @since 1.20
215 *
216 * @param array|string|null $fields
217 * @param array $conditions
218 * @param array $options
219 * @param string|null $functionName
220 *
221 * @return array of row objects
222 * @throws DBQueryError if the query failed (even if the database was in ignoreErrors mode).
223 */
224 public function selectObjects( $fields = null, array $conditions = array(),
225 array $options = array(), $functionName = null
226 ) {
227 $result = $this->selectFields( $fields, $conditions, $options, false, $functionName );
228
229 $objects = array();
230
231 foreach ( $result as $record ) {
232 $objects[] = $this->newRow( $record );
233 }
234
235 return $objects;
236 }
237
238 /**
239 * Do the actual select.
240 *
241 * @since 1.20
242 *
243 * @param null|string|array $fields
244 * @param array $conditions
245 * @param array $options
246 * @param null|string $functionName
247 *
248 * @return ResultWrapper
249 * @throws DBQueryError if the quey failed (even if the database was in ignoreErrors mode).
250 */
251 public function rawSelect( $fields = null, array $conditions = array(),
252 array $options = array(), $functionName = null
253 ) {
254 if ( is_null( $fields ) ) {
255 $fields = array_keys( $this->getFields() );
256 } else {
257 $fields = (array)$fields;
258 }
259
260 $dbr = $this->getReadDbConnection();
261 $result = $dbr->select(
262 $this->getName(),
263 $this->getPrefixedFields( $fields ),
264 $this->getPrefixedValues( $conditions ),
265 is_null( $functionName ) ? __METHOD__ : $functionName,
266 $options
267 );
268
269 /* @var Exception $error */
270 $error = null;
271
272 if ( $result === false ) {
273 // Database connection was in "ignoreErrors" mode. We don't like that.
274 // So, we emulate the DBQueryError that should have been thrown.
275 $error = new DBQueryError(
276 $dbr,
277 $dbr->lastError(),
278 $dbr->lastErrno(),
279 $dbr->lastQuery(),
280 is_null( $functionName ) ? __METHOD__ : $functionName
281 );
282 }
283
284 $this->releaseConnection( $dbr );
285
286 if ( $error ) {
287 // Note: construct the error before releasing the connection,
288 // but throw it after.
289 throw $error;
290 }
291
292 return $result;
293 }
294
295 /**
296 * Selects the the specified fields of the records matching the provided
297 * conditions and returns them as associative arrays.
298 * Provided field names get prefixed.
299 * Returned field names will not have a prefix.
300 *
301 * When $collapse is true:
302 * If one field is selected, each item in the result array will be this field.
303 * If two fields are selected, each item in the result array will have as key
304 * the first field and as value the second field.
305 * If more then two fields are selected, each item will be an associative array.
306 *
307 * @since 1.20
308 *
309 * @param array|string|null $fields
310 * @param array $conditions
311 * @param array $options
312 * @param boolean $collapse Set to false to always return each result row as associative array.
313 * @param string|null $functionName
314 *
315 * @return array of array
316 */
317 public function selectFields( $fields = null, array $conditions = array(),
318 array $options = array(), $collapse = true, $functionName = null
319 ) {
320 $objects = array();
321
322 $result = $this->rawSelect( $fields, $conditions, $options, $functionName );
323
324 foreach ( $result as $record ) {
325 $objects[] = $this->getFieldsFromDBResult( $record );
326 }
327
328 if ( $collapse ) {
329 if ( count( $fields ) === 1 ) {
330 $objects = array_map( 'array_shift', $objects );
331 } elseif ( count( $fields ) === 2 ) {
332 $o = array();
333
334 foreach ( $objects as $object ) {
335 $o[array_shift( $object )] = array_shift( $object );
336 }
337
338 $objects = $o;
339 }
340 }
341
342 return $objects;
343 }
344
345 /**
346 * Selects the the specified fields of the first matching record.
347 * Field names get prefixed.
348 *
349 * @since 1.20
350 *
351 * @param array|string|null $fields
352 * @param array $conditions
353 * @param array $options
354 * @param string|null $functionName
355 *
356 * @return IORMRow|bool False on failure
357 */
358 public function selectRow( $fields = null, array $conditions = array(),
359 array $options = array(), $functionName = null
360 ) {
361 $options['LIMIT'] = 1;
362
363 $objects = $this->select( $fields, $conditions, $options, $functionName );
364
365 return ( !$objects || $objects->isEmpty() ) ? false : $objects->current();
366 }
367
368 /**
369 * Selects the the specified fields of the records matching the provided
370 * conditions. Field names do NOT get prefixed.
371 *
372 * @since 1.20
373 *
374 * @param array $fields
375 * @param array $conditions
376 * @param array $options
377 * @param string|null $functionName
378 *
379 * @return ResultWrapper
380 */
381 public function rawSelectRow( array $fields, array $conditions = array(),
382 array $options = array(), $functionName = null
383 ) {
384 $dbr = $this->getReadDbConnection();
385
386 $result = $dbr->selectRow(
387 $this->getName(),
388 $fields,
389 $conditions,
390 is_null( $functionName ) ? __METHOD__ : $functionName,
391 $options
392 );
393
394 $this->releaseConnection( $dbr );
395
396 return $result;
397 }
398
399 /**
400 * Selects the the specified fields of the first record matching the provided
401 * conditions and returns it as an associative array, or false when nothing matches.
402 * This method makes use of selectFields and expects the same parameters and
403 * returns the same results (if there are any, if there are none, this method returns false).
404 * @see ORMTable::selectFields
405 *
406 * @since 1.20
407 *
408 * @param array|string|null $fields
409 * @param array $conditions
410 * @param array $options
411 * @param boolean $collapse Set to false to always return each result row as associative array.
412 * @param string|null $functionName
413 *
414 * @return mixed|array|bool False on failure
415 */
416 public function selectFieldsRow( $fields = null, array $conditions = array(),
417 array $options = array(), $collapse = true, $functionName = null
418 ) {
419 $options['LIMIT'] = 1;
420
421 $objects = $this->selectFields( $fields, $conditions, $options, $collapse, $functionName );
422
423 return empty( $objects ) ? false : $objects[0];
424 }
425
426 /**
427 * Returns if there is at least one record matching the provided conditions.
428 * Condition field names get prefixed.
429 *
430 * @since 1.20
431 *
432 * @param array $conditions
433 *
434 * @return boolean
435 */
436 public function has( array $conditions = array() ) {
437 return $this->selectRow( array( 'id' ), $conditions ) !== false;
438 }
439
440 /**
441 * Checks if the table exists
442 *
443 * @since 1.21
444 *
445 * @return boolean
446 */
447 public function exists() {
448 $dbr = $this->getReadDbConnection();
449 $exists = $dbr->tableExists( $this->getName() );
450 $this->releaseConnection( $dbr );
451
452 return $exists;
453 }
454
455 /**
456 * Returns the amount of matching records.
457 * Condition field names get prefixed.
458 *
459 * Note that this can be expensive on large tables.
460 * In such cases you might want to use DatabaseBase::estimateRowCount instead.
461 *
462 * @since 1.20
463 *
464 * @param array $conditions
465 * @param array $options
466 *
467 * @return integer
468 */
469 public function count( array $conditions = array(), array $options = array() ) {
470 $res = $this->rawSelectRow(
471 array( 'rowcount' => 'COUNT(*)' ),
472 $this->getPrefixedValues( $conditions ),
473 $options,
474 __METHOD__
475 );
476
477 return $res->rowcount;
478 }
479
480 /**
481 * Removes the object from the database.
482 *
483 * @since 1.20
484 *
485 * @param array $conditions
486 * @param string|null $functionName
487 *
488 * @return boolean Success indicator
489 */
490 public function delete( array $conditions, $functionName = null ) {
491 $dbw = $this->getWriteDbConnection();
492
493 $result = $dbw->delete(
494 $this->getName(),
495 $conditions === array() ? '*' : $this->getPrefixedValues( $conditions ),
496 is_null( $functionName ) ? __METHOD__ : $functionName
497 ) !== false; // DatabaseBase::delete does not always return true for success as documented...
498
499 $this->releaseConnection( $dbw );
500
501 return $result;
502 }
503
504 /**
505 * Get API parameters for the fields supported by this object.
506 *
507 * @since 1.20
508 *
509 * @param boolean $requireParams
510 * @param boolean $setDefaults
511 *
512 * @return array
513 */
514 public function getAPIParams( $requireParams = false, $setDefaults = false ) {
515 $typeMap = array(
516 'id' => 'integer',
517 'int' => 'integer',
518 'float' => 'NULL',
519 'str' => 'string',
520 'bool' => 'integer',
521 'array' => 'string',
522 'blob' => 'string',
523 );
524
525 $params = array();
526 $defaults = $this->getDefaults();
527
528 foreach ( $this->getFields() as $field => $type ) {
529 if ( $field == 'id' ) {
530 continue;
531 }
532
533 $hasDefault = array_key_exists( $field, $defaults );
534
535 $params[$field] = array(
536 ApiBase::PARAM_TYPE => $typeMap[$type],
537 ApiBase::PARAM_REQUIRED => $requireParams && !$hasDefault
538 );
539
540 if ( $type == 'array' ) {
541 $params[$field][ApiBase::PARAM_ISMULTI] = true;
542 }
543
544 if ( $setDefaults && $hasDefault ) {
545 $default = is_array( $defaults[$field] ) ? implode( '|', $defaults[$field] ) : $defaults[$field];
546 $params[$field][ApiBase::PARAM_DFLT] = $default;
547 }
548 }
549
550 return $params;
551 }
552
553 /**
554 * Returns an array with the fields and their descriptions.
555 *
556 * field name => field description
557 *
558 * @since 1.20
559 *
560 * @return array
561 */
562 public function getFieldDescriptions() {
563 return array();
564 }
565
566 /**
567 * Get the database ID used for read operations.
568 *
569 * @since 1.20
570 *
571 * @return integer DB_ enum
572 */
573 public function getReadDb() {
574 return $this->readDb;
575 }
576
577 /**
578 * Set the database ID to use for read operations, use DB_XXX constants or an index to the load balancer setup.
579 *
580 * @param integer $db
581 *
582 * @since 1.20
583 */
584 public function setReadDb( $db ) {
585 $this->readDb = $db;
586 }
587
588 /**
589 * Get the ID of the any foreign wiki to use as a target for database operations
590 *
591 * @since 1.20
592 *
593 * @return String|bool The target wiki, in a form that LBFactory understands (or false if the local wiki is used)
594 */
595 public function getTargetWiki() {
596 return $this->wiki;
597 }
598
599 /**
600 * Set the ID of the any foreign wiki to use as a target for database operations
601 *
602 * @param string|bool $wiki The target wiki, in a form that LBFactory understands (or false if the local wiki shall be used)
603 *
604 * @since 1.20
605 */
606 public function setTargetWiki( $wiki ) {
607 $this->wiki = $wiki;
608 }
609
610 /**
611 * Get the database type used for read operations.
612 * This is to be used instead of wfGetDB.
613 *
614 * @see LoadBalancer::getConnection
615 *
616 * @since 1.20
617 *
618 * @return DatabaseBase The database object
619 */
620 public function getReadDbConnection() {
621 return $this->getConnection( $this->getReadDb(), array() );
622 }
623
624 /**
625 * Get the database type used for read operations.
626 * This is to be used instead of wfGetDB.
627 *
628 * @see LoadBalancer::getConnection
629 *
630 * @since 1.20
631 *
632 * @return DatabaseBase The database object
633 */
634 public function getWriteDbConnection() {
635 return $this->getConnection( DB_MASTER, array() );
636 }
637
638 /**
639 * Releases the lease on the given database connection. This is useful mainly
640 * for connections to a foreign wiki. It does nothing for connections to the local wiki.
641 *
642 * @see LoadBalancer::reuseConnection
643 *
644 * @param DatabaseBase $db the database
645 *
646 * @since 1.20
647 */
648 public function releaseConnection( DatabaseBase $db ) {
649 parent::releaseConnection( $db ); // just make it public
650 }
651
652 /**
653 * Update the records matching the provided conditions by
654 * setting the fields that are keys in the $values param to
655 * their corresponding values.
656 *
657 * @since 1.20
658 *
659 * @param array $values
660 * @param array $conditions
661 *
662 * @return boolean Success indicator
663 */
664 public function update( array $values, array $conditions = array() ) {
665 $dbw = $this->getWriteDbConnection();
666
667 $result = $dbw->update(
668 $this->getName(),
669 $this->getPrefixedValues( $values ),
670 $this->getPrefixedValues( $conditions ),
671 __METHOD__
672 ) !== false; // DatabaseBase::update does not always return true for success as documented...
673
674 $this->releaseConnection( $dbw );
675
676 return $result;
677 }
678
679 /**
680 * Computes the values of the summary fields of the objects matching the provided conditions.
681 *
682 * @since 1.20
683 *
684 * @param array|string|null $summaryFields
685 * @param array $conditions
686 */
687 public function updateSummaryFields( $summaryFields = null, array $conditions = array() ) {
688 $slave = $this->getReadDb();
689 $this->setReadDb( DB_MASTER );
690
691 /**
692 * @var IORMRow $item
693 */
694 foreach ( $this->select( null, $conditions ) as $item ) {
695 $item->loadSummaryFields( $summaryFields );
696 $item->setSummaryMode( true );
697 $item->save();
698 }
699
700 $this->setReadDb( $slave );
701 }
702
703 /**
704 * Takes in an associative array with field names as keys and
705 * their values as value. The field names are prefixed with the
706 * db field prefix.
707 *
708 * @since 1.20
709 *
710 * @param array $values
711 *
712 * @return array
713 */
714 public function getPrefixedValues( array $values ) {
715 $prefixedValues = array();
716
717 foreach ( $values as $field => $value ) {
718 if ( is_integer( $field ) ) {
719 if ( is_array( $value ) ) {
720 $field = $value[0];
721 $value = $value[1];
722 } else {
723 $value = explode( ' ', $value, 2 );
724 $value[0] = $this->getPrefixedField( $value[0] );
725 $prefixedValues[] = implode( ' ', $value );
726 continue;
727 }
728 }
729
730 $prefixedValues[$this->getPrefixedField( $field )] = $value;
731 }
732
733 return $prefixedValues;
734 }
735
736 /**
737 * Takes in a field or array of fields and returns an
738 * array with their prefixed versions, ready for db usage.
739 *
740 * @since 1.20
741 *
742 * @param array|string $fields
743 *
744 * @return array
745 */
746 public function getPrefixedFields( array $fields ) {
747 foreach ( $fields as &$field ) {
748 $field = $this->getPrefixedField( $field );
749 }
750
751 return $fields;
752 }
753
754 /**
755 * Takes in a field and returns an it's prefixed version, ready for db usage.
756 *
757 * @since 1.20
758 *
759 * @param string|array $field
760 *
761 * @return string
762 */
763 public function getPrefixedField( $field ) {
764 return $this->getFieldPrefix() . $field;
765 }
766
767 /**
768 * Takes an array of field names with prefix and returns the unprefixed equivalent.
769 *
770 * @since 1.20
771 *
772 * @param array $fieldNames
773 *
774 * @return array
775 */
776 public function unprefixFieldNames( array $fieldNames ) {
777 return array_map( array( $this, 'unprefixFieldName' ), $fieldNames );
778 }
779
780 /**
781 * Takes a field name with prefix and returns the unprefixed equivalent.
782 *
783 * @since 1.20
784 *
785 * @param string $fieldName
786 *
787 * @return string
788 */
789 public function unprefixFieldName( $fieldName ) {
790 return substr( $fieldName, strlen( $this->getFieldPrefix() ) );
791 }
792
793 /**
794 * Get an instance of this class.
795 *
796 * @since 1.20
797 * @deprecated since 1.21
798 *
799 * @return IORMTable
800 */
801 public static function singleton() {
802 $class = get_called_class();
803
804 if ( !array_key_exists( $class, self::$instanceCache ) ) {
805 self::$instanceCache[$class] = new $class;
806 }
807
808 return self::$instanceCache[$class];
809 }
810
811 /**
812 * Get an array with fields from a database result,
813 * that can be fed directly to the constructor or
814 * to setFields.
815 *
816 * @since 1.20
817 *
818 * @param stdClass $result
819 *
820 * @return array
821 */
822 public function getFieldsFromDBResult( stdClass $result ) {
823 $result = (array)$result;
824
825 $rawFields = array_combine(
826 $this->unprefixFieldNames( array_keys( $result ) ),
827 array_values( $result )
828 );
829
830 $fieldDefinitions = $this->getFields();
831 $fields = array();
832
833 foreach ( $rawFields as $name => $value ) {
834 if ( array_key_exists( $name, $fieldDefinitions ) ) {
835 switch ( $fieldDefinitions[$name] ) {
836 case 'int':
837 $value = (int)$value;
838 break;
839 case 'float':
840 $value = (float)$value;
841 break;
842 case 'bool':
843 if ( is_string( $value ) ) {
844 $value = $value !== '0';
845 } elseif ( is_int( $value ) ) {
846 $value = $value !== 0;
847 }
848 break;
849 case 'array':
850 if ( is_string( $value ) ) {
851 $value = unserialize( $value );
852 }
853
854 if ( !is_array( $value ) ) {
855 $value = array();
856 }
857 break;
858 case 'blob':
859 if ( is_string( $value ) ) {
860 $value = unserialize( $value );
861 }
862 break;
863 case 'id':
864 if ( is_string( $value ) ) {
865 $value = (int)$value;
866 }
867 break;
868 }
869
870 $fields[$name] = $value;
871 } else {
872 throw new MWException( 'Attempted to set unknown field ' . $name );
873 }
874 }
875
876 return $fields;
877 }
878
879 /**
880 * @see ORMTable::newRowFromFromDBResult
881 *
882 * @deprecated use newRowFromDBResult instead
883 * @since 1.20
884 *
885 * @param stdClass $result
886 *
887 * @return IORMRow
888 */
889 public function newFromDBResult( stdClass $result ) {
890 return self::newRowFromDBResult( $result );
891 }
892
893 /**
894 * Get a new instance of the class from a database result.
895 *
896 * @since 1.20
897 *
898 * @param stdClass $result
899 *
900 * @return IORMRow
901 */
902 public function newRowFromDBResult( stdClass $result ) {
903 return $this->newRow( $this->getFieldsFromDBResult( $result ) );
904 }
905
906 /**
907 * @see ORMTable::newRow
908 *
909 * @deprecated use newRow instead
910 * @since 1.20
911 *
912 * @param array $data
913 * @param boolean $loadDefaults
914 *
915 * @return IORMRow
916 */
917 public function newFromArray( array $data, $loadDefaults = false ) {
918 return static::newRow( $data, $loadDefaults );
919 }
920
921 /**
922 * Get a new instance of the class from an array.
923 *
924 * @since 1.20
925 *
926 * @param array $fields
927 * @param boolean $loadDefaults
928 *
929 * @return IORMRow
930 */
931 public function newRow( array $fields, $loadDefaults = false ) {
932 $class = $this->getRowClass();
933
934 return new $class( $this, $fields, $loadDefaults );
935 }
936
937 /**
938 * Return the names of the fields.
939 *
940 * @since 1.20
941 *
942 * @return array
943 */
944 public function getFieldNames() {
945 return array_keys( $this->getFields() );
946 }
947
948 /**
949 * Gets if the object can take a certain field.
950 *
951 * @since 1.20
952 *
953 * @param string $name
954 *
955 * @return boolean
956 */
957 public function canHaveField( $name ) {
958 return array_key_exists( $name, $this->getFields() );
959 }
960
961 /**
962 * Updates the provided row in the database.
963 *
964 * @since 1.22
965 *
966 * @param IORMRow $row The row to save
967 * @param string|null $functionName
968 *
969 * @return boolean Success indicator
970 */
971 public function updateRow( IORMRow $row, $functionName = null ) {
972 $dbw = $this->getWriteDbConnection();
973
974 $success = $dbw->update(
975 $this->getName(),
976 $this->getWriteValues( $row ),
977 $this->getPrefixedValues( array( 'id' => $row->getId() ) ),
978 is_null( $functionName ) ? __METHOD__ : $functionName
979 );
980
981 $this->releaseConnection( $dbw );
982
983 // DatabaseBase::update does not always return true for success as documented...
984 return $success !== false;
985 }
986
987 /**
988 * Inserts the provided row into the database.
989 *
990 * @since 1.22
991 *
992 * @param IORMRow $row
993 * @param string|null $functionName
994 * @param array|null $options
995 *
996 * @return boolean Success indicator
997 */
998 public function insertRow( IORMRow $row, $functionName = null, array $options = null ) {
999 $dbw = $this->getWriteDbConnection();
1000
1001 $success = $dbw->insert(
1002 $this->getName(),
1003 $this->getWriteValues( $row ),
1004 is_null( $functionName ) ? __METHOD__ : $functionName,
1005 $options
1006 );
1007
1008 // DatabaseBase::insert does not always return true for success as documented...
1009 $success = $success !== false;
1010
1011 if ( $success ) {
1012 $row->setField( 'id', $dbw->insertId() );
1013 }
1014
1015 $this->releaseConnection( $dbw );
1016
1017 return $success;
1018 }
1019
1020 /**
1021 * Gets the fields => values to write to the table.
1022 *
1023 * @since 1.22
1024 *
1025 * @param IORMRow $row
1026 *
1027 * @return array
1028 */
1029 protected function getWriteValues( IORMRow $row ) {
1030 $values = array();
1031
1032 $rowFields = $row->getFields();
1033
1034 foreach ( $this->getFields() as $name => $type ) {
1035 if ( array_key_exists( $name, $rowFields ) ) {
1036 $value = $rowFields[$name];
1037
1038 switch ( $type ) {
1039 case 'array':
1040 $value = (array)$value;
1041 // fall-through!
1042 case 'blob':
1043 $value = serialize( $value );
1044 // fall-through!
1045 }
1046
1047 $values[$this->getPrefixedField( $name )] = $value;
1048 }
1049 }
1050
1051 return $values;
1052 }
1053
1054 /**
1055 * Removes the provided row from the database.
1056 *
1057 * @since 1.22
1058 *
1059 * @param IORMRow $row
1060 * @param string|null $functionName
1061 *
1062 * @return boolean Success indicator
1063 */
1064 public function removeRow( IORMRow $row, $functionName = null ) {
1065 $success = $this->delete(
1066 array( 'id' => $row->getId() ),
1067 is_null( $functionName ) ? __METHOD__ : $functionName
1068 );
1069
1070 // DatabaseBase::delete does not always return true for success as documented...
1071 return $success !== false;
1072 }
1073
1074 /**
1075 * Add an amount (can be negative) to the specified field (needs to be numeric).
1076 *
1077 * @since 1.22
1078 *
1079 * @param array $conditions
1080 * @param string $field
1081 * @param integer $amount
1082 *
1083 * @return boolean Success indicator
1084 * @throws MWException
1085 */
1086 public function addToField( array $conditions, $field, $amount ) {
1087 if ( !array_key_exists( $field, $this->fields ) ) {
1088 throw new MWException( 'Unknown field "' . $field . '" provided' );
1089 }
1090
1091 if ( $amount == 0 ) {
1092 return true;
1093 }
1094
1095 $absoluteAmount = abs( $amount );
1096 $isNegative = $amount < 0;
1097
1098 $fullField = $this->getPrefixedField( $field );
1099
1100 $dbw = $this->getWriteDbConnection();
1101
1102 $success = $dbw->update(
1103 $this->getName(),
1104 array( "$fullField=$fullField" . ( $isNegative ? '-' : '+' ) . $absoluteAmount ),
1105 $this->getPrefixedValues( $conditions ),
1106 __METHOD__
1107 ) !== false; // DatabaseBase::update does not always return true for success as documented...
1108
1109 $this->releaseConnection( $dbw );
1110
1111 return $success;
1112 }
1113 }