Reedy has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/246718

Change subject: Move ORM classes to EducationProgram extension
......................................................................

Move ORM classes to EducationProgram extension

Change-Id: Id223222aff7d375687bbf0126f3514d248660bab
---
M EducationProgram.php
A includes/ORMIterator.php
A includes/ORMResult.php
M includes/RoleObject.php
M includes/pagers/CoursePager.php
M includes/rows/EPRevision.php
A includes/rows/IORMRow.php
A includes/rows/ORMRow.php
M includes/rows/RevisionedObject.php
M includes/tables/CAs.php
M includes/tables/Events.php
A includes/tables/IORMTable.php
M includes/tables/Instructors.php
M includes/tables/OAs.php
A includes/tables/ORMTable.php
M includes/tables/PageTable.php
M includes/tables/Revisions.php
M includes/tables/Students.php
A tests/phpunit/db/ORMRowTest.php
A tests/phpunit/db/ORMTableTest.php
A tests/phpunit/db/TestORMRowTest.php
M tests/phpunit/rows/CourseTest.php
M tests/phpunit/rows/OrgTest.php
M tests/phpunit/rows/PageObjectTest.php
24 files changed, 3,288 insertions(+), 19 deletions(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/EducationProgram 
refs/changes/18/246718/1

diff --git a/EducationProgram.php b/EducationProgram.php
index 058c2bc..93291d2 100644
--- a/EducationProgram.php
+++ b/EducationProgram.php
@@ -134,7 +134,9 @@
 $wgAutoloadClasses['EducationProgram\EPArticle']                               
        = $dir . '/includes/rows/EPArticle.php';
 $wgAutoloadClasses['EducationProgram\EPRevision']                              
        = $dir . '/includes/rows/EPRevision.php';
 $wgAutoloadClasses['EducationProgram\Instructor']                              
        = $dir . '/includes/rows/Instructor.php';
+$wgAutoloadClasses['EducationProgram\IORMRow']                                 
            = $dir . '/includes/rows/IORMRow.php';
 $wgAutoloadClasses['EducationProgram\OA']                                      
                = $dir . '/includes/rows/OA.php';
+$wgAutoloadClasses['EducationProgram\ORMRow']                                  
    = $dir . '/includes/rows/ORMRow.php';
 $wgAutoloadClasses['EducationProgram\Org']                                     
                = $dir . '/includes/rows/Org.php';
 $wgAutoloadClasses['EducationProgram\PageObject']                              
        = $dir . '/includes/rows/PageObject.php';
 $wgAutoloadClasses['EducationProgram\RevisionedObject']                        
= $dir . '/includes/rows/RevisionedObject.php';
@@ -165,7 +167,9 @@
 $wgAutoloadClasses['EducationProgram\Courses']                                 
                = $dir . '/includes/tables/Courses.php';
 $wgAutoloadClasses['EducationProgram\Events']                                  
        = $dir . '/includes/tables/Events.php';
 $wgAutoloadClasses['EducationProgram\Instructors']                             
        = $dir . '/includes/tables/Instructors.php';
+$wgAutoloadClasses['EducationProgram\IORMTable']                               
    = $dir . '/includes/rows/IORMTable.php';
 $wgAutoloadClasses['EducationProgram\OAs']                                     
                = $dir . '/includes/tables/OAs.php';
+$wgAutoloadClasses['EducationProgram\ORMTable']                                
    = $dir . '/includes/rows/ORMTable.php';
 $wgAutoloadClasses['EducationProgram\Orgs']                                    
        = $dir . '/includes/tables/Orgs.php';
 $wgAutoloadClasses['EducationProgram\PageTable']                               
        = $dir . '/includes/tables/PageTable.php';
 $wgAutoloadClasses['EducationProgram\Revisions']                               
        = $dir . '/includes/tables/Revisions.php';
@@ -210,6 +214,8 @@
 $wgAutoloadClasses['EducationProgram\UPCUserCourseFinder']                     
= $dir . '/includes/UPCUserCourseFinder.php';
 $wgAutoloadClasses['EducationProgram\UserCourseFinder']                        
= $dir . '/includes/UserCourseFinder.php';
 $wgAutoloadClasses['EducationProgram\UserRolesMessage']                        
= $dir . '/includes/UserRolesMessage.php';
+$wgAutoloadClasses['EducationProgram\ORMIterator']                             
    = $dir . '/includes/ORMIterator.php';
+$wgAutoloadClasses['EducationProgram\ORMResult']                               
    = $dir . '/includes/ORMResult.php';
 $wgAutoloadClasses['EducationProgram\OrgDeletionHelper']                       
= $dir . '/includes/OrgDeletionHelper.php';
 $wgAutoloadClasses['EducationProgram\OrgDelCheck']                             
        = $dir . '/includes/OrgDeletionHelper.php';
 $wgAutoloadClasses['EducationProgram\Utils']                                   
        = $dir . '/includes/Utils.php';
diff --git a/includes/ORMIterator.php b/includes/ORMIterator.php
new file mode 100644
index 0000000..b30caa3
--- /dev/null
+++ b/includes/ORMIterator.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace EducationProgram;
+
+/**
+ * Interface for Iterators containing IORMRows.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ *
+ * @file
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+interface ORMIterator extends \Iterator {
+}
diff --git a/includes/ORMResult.php b/includes/ORMResult.php
new file mode 100644
index 0000000..0cb3a5f
--- /dev/null
+++ b/includes/ORMResult.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace EducationProgram;
+
+/**
+ * ORMIterator that takes a ResultWrapper object returned from
+ * a select operation returning IORMRow objects (ie IORMTable::select).
+ *
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ *
+ * @file ORMResult.php
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+
+class ORMResult implements ORMIterator {
+       /**
+        * @var \ResultWrapper
+        */
+       protected $res;
+
+       /**
+        * @var int
+        */
+       protected $key;
+
+       /**
+        * @var IORMRow
+        */
+       protected $current;
+
+       /**
+        * @var IORMTable
+        */
+       protected $table;
+
+       /**
+        * @param IORMTable $table
+        * @param \ResultWrapper $res
+        */
+       public function __construct( IORMTable $table, \ResultWrapper $res ) {
+               $this->table = $table;
+               $this->res = $res;
+               $this->key = 0;
+               $this->setCurrent( $this->res->current() );
+       }
+
+       /**
+        * @param bool|object $row
+        */
+       protected function setCurrent( $row ) {
+               if ( $row === false ) {
+                       $this->current = false;
+               } else {
+                       $this->current = $this->table->newRowFromDBResult( $row 
);
+               }
+       }
+
+       /**
+        * @return int
+        */
+       public function count() {
+               return $this->res->numRows();
+       }
+
+       /**
+        * @return bool
+        */
+       public function isEmpty() {
+               return $this->res->numRows() === 0;
+       }
+
+       /**
+        * @return IORMRow
+        */
+       public function current() {
+               return $this->current;
+       }
+
+       /**
+        * @return int
+        */
+       public function key() {
+               return $this->key;
+       }
+
+       public function next() {
+               $row = $this->res->next();
+               $this->setCurrent( $row );
+               $this->key++;
+       }
+
+       public function rewind() {
+               $this->res->rewind();
+               $this->key = 0;
+               $this->setCurrent( $this->res->current() );
+       }
+
+       /**
+        * @return bool
+        */
+       public function valid() {
+               return $this->current !== false;
+       }
+}
diff --git a/includes/RoleObject.php b/includes/RoleObject.php
index b9a5b03..4a233c5 100644
--- a/includes/RoleObject.php
+++ b/includes/RoleObject.php
@@ -13,7 +13,7 @@
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
  */
-abstract class RoleObject extends \ORMRow implements IRole {
+abstract class RoleObject extends ORMRow implements IRole {
 
        /**
         * Field for caching the linked user.
diff --git a/includes/pagers/CoursePager.php b/includes/pagers/CoursePager.php
index 0ad95b4..4eb58f0 100644
--- a/includes/pagers/CoursePager.php
+++ b/includes/pagers/CoursePager.php
@@ -238,7 +238,7 @@
        /**
         * @see EPPager::getControlLinks()
         */
-       protected function getControlLinks( \IORMRow $item ) {
+       protected function getControlLinks( ORMRow $item ) {
                $links = parent::getControlLinks( $item );
 
                $links[] = $item->getLink( 'view', $this->msg( 'view' 
)->escaped() );
diff --git a/includes/rows/EPRevision.php b/includes/rows/EPRevision.php
index 24819fd..f7966c6 100644
--- a/includes/rows/EPRevision.php
+++ b/includes/rows/EPRevision.php
@@ -13,7 +13,7 @@
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
  */
-class EPRevision extends \ORMRow {
+class EPRevision extends ORMRow {
 
        /**
         * Cached user object for this revision.
diff --git a/includes/rows/IORMRow.php b/includes/rows/IORMRow.php
new file mode 100644
index 0000000..039172c
--- /dev/null
+++ b/includes/rows/IORMRow.php
@@ -0,0 +1,272 @@
+<?php
+namespace EducationProgram;
+
+/**
+ * Interface for representing objects that are stored in some DB table.
+ * This is basically an ORM-like wrapper around rows in database tables that
+ * aims to be both simple and very flexible. It is centered around an 
associative
+ * array of fields and various methods to do common interaction with the 
database.
+ *
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ *
+ * @file
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+
+interface IORMRow {
+       /**
+        * Load the specified fields from the database.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param array|null $fields
+        * @param bool $override
+        * @param bool $skipLoaded
+        *
+        * @return bool Success indicator
+        */
+       public function loadFields( $fields = null, $override = true, 
$skipLoaded = false );
+
+       /**
+        * Gets the value of a field.
+        *
+        * @since 1.20
+        *
+        * @param string $name
+        * @param mixed $default
+        *
+        * @throws \MWException
+        * @return mixed
+        */
+       public function getField( $name, $default = null );
+
+       /**
+        * Gets the value of a field but first loads it if not done so already.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param string $name
+        *
+        * @return mixed
+        */
+       public function loadAndGetField( $name );
+
+       /**
+        * Remove a field.
+        *
+        * @since 1.20
+        *
+        * @param string $name
+        */
+       public function removeField( $name );
+
+       /**
+        * Returns the objects database id.
+        *
+        * @since 1.20
+        *
+        * @return int|null
+        */
+       public function getId();
+
+       /**
+        * Sets the objects database id.
+        *
+        * @since 1.20
+        *
+        * @param int|null $id
+        */
+       public function setId( $id );
+
+       /**
+        * Gets if a certain field is set.
+        *
+        * @since 1.20
+        *
+        * @param string $name
+        *
+        * @return bool
+        */
+       public function hasField( $name );
+
+       /**
+        * Gets if the id field is set.
+        *
+        * @since 1.20
+        *
+        * @return bool
+        */
+       public function hasIdField();
+
+       /**
+        * Sets multiple fields.
+        *
+        * @since 1.20
+        *
+        * @param array $fields The fields to set
+        * @param bool $override Override already set fields with the provided 
values?
+        */
+       public function setFields( array $fields, $override = true );
+
+       /**
+        * Serializes the object to an associative array which
+        * can then easily be converted into JSON or similar.
+        *
+        * @since 1.20
+        *
+        * @param null|array $fields
+        * @param bool $incNullId
+        *
+        * @return array
+        */
+       public function toArray( $fields = null, $incNullId = false );
+
+       /**
+        * Load the default values, via getDefaults.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param bool $override
+        */
+       public function loadDefaults( $override = true );
+
+       /**
+        * Writes the answer to the database, either updating it
+        * when it already exists, or inserting it when it doesn't.
+        *
+        * @since 1.20
+        *
+        * @param string|null $functionName
+        * @deprecated since 1.22
+        *
+        * @return bool Success indicator
+        */
+       public function save( $functionName = null );
+
+       /**
+        * Removes the object from the database.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @return bool Success indicator
+        */
+       public function remove();
+
+       /**
+        * Return the names and values of the fields.
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getFields();
+
+       /**
+        * Return the names of the fields.
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getSetFieldNames();
+
+       /**
+        * Sets the value of a field.
+        * Strings can be provided for other types,
+        * so this method can be called from unserialization handlers.
+        *
+        * @since 1.20
+        *
+        * @param string $name
+        * @param mixed $value
+        *
+        * @throws \MWException
+        */
+       public function setField( $name, $value );
+
+       /**
+        * Add an amount (can be negative) to the specified field (needs to be 
numeric).
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param string $field
+        * @param int $amount
+        *
+        * @return bool Success indicator
+        */
+       public function addToField( $field, $amount );
+
+       /**
+        * Return the names of the fields.
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getFieldNames();
+
+       /**
+        * Computes and updates the values of the summary fields.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param array|string|null $summaryFields
+        */
+       public function loadSummaryFields( $summaryFields = null );
+
+       /**
+        * Sets the value for the @see $updateSummaries field.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param bool $update
+        */
+       public function setUpdateSummaries( $update );
+
+       /**
+        * Sets the value for the @see $inSummaryMode field.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param bool $summaryMode
+        */
+       public function setSummaryMode( $summaryMode );
+
+       /**
+        * Returns the table this IORMRow is a row in.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @return IORMTable
+        */
+       public function getTable();
+}
diff --git a/includes/rows/ORMRow.php b/includes/rows/ORMRow.php
new file mode 100644
index 0000000..e3a0d6c
--- /dev/null
+++ b/includes/rows/ORMRow.php
@@ -0,0 +1,595 @@
+<?php
+namespace EducationProgram;
+
+/**
+ * Abstract base class for representing objects that are stored in some DB 
table.
+ * This is basically an ORM-like wrapper around rows in database tables that
+ * aims to be both simple and very flexible. It is centered around an 
associative
+ * array of fields and various methods to do common interaction with the 
database.
+ *
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ *
+ * @file ORMRow.php
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+
+class ORMRow implements IORMRow {
+       /**
+        * The fields of the object.
+        * field name (w/o prefix) => value
+        *
+        * @since 1.20
+        * @var array
+        */
+       protected $fields = array( 'id' => null );
+
+       /**
+        * If the object should update summaries of linked items when changed.
+        * For example, update the course_count field in universities when a 
course in courses is deleted.
+        * Settings this to false can prevent needless updating work in 
situations
+        * such as deleting a university, which will then delete all it's 
courses.
+        *
+        * @deprecated since 1.22
+        * @since 1.20
+        * @var bool
+        */
+       protected $updateSummaries = true;
+
+       /**
+        * Indicates if the object is in summary mode.
+        * This mode indicates that only summary fields got updated,
+        * which allows for optimizations.
+        *
+        * @deprecated since 1.22
+        * @since 1.20
+        * @var bool
+        */
+       protected $inSummaryMode = false;
+
+       /**
+        * @deprecated since 1.22
+        * @since 1.20
+        * @var ORMTable|null
+        */
+       protected $table;
+
+       /**
+        * Constructor.
+        *
+        * @since 1.20
+        *
+        * @param IORMTable|null $table Deprecated since 1.22
+        * @param array|null $fields
+        * @param bool $loadDefaults Deprecated since 1.22
+        */
+       public function __construct( IORMTable $table = null, $fields = null, 
$loadDefaults = false ) {
+               $this->table = $table;
+
+               if ( !is_array( $fields ) ) {
+                       $fields = array();
+               }
+
+               if ( $loadDefaults ) {
+                       $fields = array_merge( $this->table->getDefaults(), 
$fields );
+               }
+
+               $this->setFields( $fields );
+       }
+
+       /**
+        * Load the specified fields from the database.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param array|null $fields
+        * @param bool $override
+        * @param bool $skipLoaded
+        *
+        * @return bool Success indicator
+        */
+       public function loadFields( $fields = null, $override = true, 
$skipLoaded = false ) {
+               if ( is_null( $this->getId() ) ) {
+                       return false;
+               }
+
+               if ( is_null( $fields ) ) {
+                       $fields = array_keys( $this->table->getFields() );
+               }
+
+               if ( $skipLoaded ) {
+                       $fields = array_diff( $fields, array_keys( 
$this->fields ) );
+               }
+
+               if ( !empty( $fields ) ) {
+                       $result = $this->table->rawSelectRow(
+                               $this->table->getPrefixedFields( $fields ),
+                               array( $this->table->getPrefixedField( 'id' ) 
=> $this->getId() ),
+                               array( 'LIMIT' => 1 ),
+                               __METHOD__
+                       );
+
+                       if ( $result !== false ) {
+                               $this->setFields( 
$this->table->getFieldsFromDBResult( $result ), $override );
+
+                               return true;
+                       }
+
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
+        * Gets the value of a field.
+        *
+        * @since 1.20
+        *
+        * @param string $name Field name
+        * @param mixed $default Default value to return when none is found
+        * (default: null)
+        *
+        * @throws \MWException
+        * @return mixed
+        */
+       public function getField( $name, $default = null ) {
+               if ( $this->hasField( $name ) ) {
+                       return $this->fields[$name];
+               } elseif ( !is_null( $default ) ) {
+                       return $default;
+               } else {
+                       throw new \MWException( 'Attempted to get not-set field 
' . $name );
+               }
+       }
+
+       /**
+        * Gets the value of a field but first loads it if not done so already.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param string $name
+        *
+        * @return mixed
+        */
+       public function loadAndGetField( $name ) {
+               if ( !$this->hasField( $name ) ) {
+                       $this->loadFields( array( $name ) );
+               }
+
+               return $this->getField( $name );
+       }
+
+       /**
+        * Remove a field.
+        *
+        * @since 1.20
+        *
+        * @param string $name
+        */
+       public function removeField( $name ) {
+               unset( $this->fields[$name] );
+       }
+
+       /**
+        * Returns the objects database id.
+        *
+        * @since 1.20
+        *
+        * @return int|null
+        */
+       public function getId() {
+               return $this->getField( 'id' );
+       }
+
+       /**
+        * Sets the objects database id.
+        *
+        * @since 1.20
+        *
+        * @param int|null $id
+        */
+       public function setId( $id ) {
+               $this->setField( 'id', $id );
+       }
+
+       /**
+        * Gets if a certain field is set.
+        *
+        * @since 1.20
+        *
+        * @param string $name
+        *
+        * @return bool
+        */
+       public function hasField( $name ) {
+               return array_key_exists( $name, $this->fields );
+       }
+
+       /**
+        * Gets if the id field is set.
+        *
+        * @since 1.20
+        *
+        * @return bool
+        */
+       public function hasIdField() {
+               return $this->hasField( 'id' ) && !is_null( $this->getField( 
'id' ) );
+       }
+
+       /**
+        * Gets the fields => values to write to the table.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @return array
+        */
+       protected function getWriteValues() {
+               $values = array();
+
+               foreach ( $this->table->getFields() as $name => $type ) {
+                       if ( array_key_exists( $name, $this->fields ) ) {
+                               $value = $this->fields[$name];
+
+                               // Skip null id fields so that the DBMS can set 
the default.
+                               if ( $name === 'id' && is_null( $value ) ) {
+                                       continue;
+                               }
+
+                               switch ( $type ) {
+                                       case 'array':
+                                               $value = (array)$value;
+                                       // fall-through!
+                                       case 'blob':
+                                               $value = serialize( $value );
+                                       // fall-through!
+                               }
+
+                               $values[$this->table->getPrefixedField( $name 
)] = $value;
+                       }
+               }
+
+               return $values;
+       }
+
+       /**
+        * Sets multiple fields.
+        *
+        * @since 1.20
+        *
+        * @param array $fields The fields to set
+        * @param bool $override Override already set fields with the provided 
values?
+        */
+       public function setFields( array $fields, $override = true ) {
+               foreach ( $fields as $name => $value ) {
+                       if ( $override || !$this->hasField( $name ) ) {
+                               $this->setField( $name, $value );
+                       }
+               }
+       }
+
+       /**
+        * Serializes the object to an associative array which
+        * can then easily be converted into JSON or similar.
+        *
+        * @since 1.20
+        *
+        * @param null|array $fields
+        * @param bool $incNullId
+        *
+        * @return array
+        */
+       public function toArray( $fields = null, $incNullId = false ) {
+               $data = array();
+               $setFields = array();
+
+               if ( !is_array( $fields ) ) {
+                       $setFields = $this->getSetFieldNames();
+               } else {
+                       foreach ( $fields as $field ) {
+                               if ( $this->hasField( $field ) ) {
+                                       $setFields[] = $field;
+                               }
+                       }
+               }
+
+               foreach ( $setFields as $field ) {
+                       if ( $incNullId || $field != 'id' || 
$this->hasIdField() ) {
+                               $data[$field] = $this->getField( $field );
+                       }
+               }
+
+               return $data;
+       }
+
+       /**
+        * Load the default values, via getDefaults.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param bool $override
+        */
+       public function loadDefaults( $override = true ) {
+               $this->setFields( $this->table->getDefaults(), $override );
+       }
+
+       /**
+        * Writes the answer to the database, either updating it
+        * when it already exists, or inserting it when it doesn't.
+        *
+        * @since 1.20
+        * @deprecated since 1.22 Use IORMTable->updateRow or ->insertRow
+        *
+        * @param string|null $functionName
+        *
+        * @return bool Success indicator
+        */
+       public function save( $functionName = null ) {
+               if ( $this->hasIdField() ) {
+                       return $this->table->updateRow( $this, $functionName );
+               } else {
+                       return $this->table->insertRow( $this, $functionName );
+               }
+       }
+
+       /**
+        * Updates the object in the database.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param string|null $functionName
+        *
+        * @return bool Success indicator
+        */
+       protected function saveExisting( $functionName = null ) {
+               $dbw = $this->table->getWriteDbConnection();
+
+               $success = $dbw->update(
+                       $this->table->getName(),
+                       $this->getWriteValues(),
+                       $this->table->getPrefixedValues( 
$this->getUpdateConditions() ),
+                       is_null( $functionName ) ? __METHOD__ : $functionName
+               );
+
+               $this->table->releaseConnection( $dbw );
+
+               // DatabaseBase::update does not always return true for success 
as documented...
+               return $success !== false;
+       }
+
+       /**
+        * Returns the WHERE considtions needed to identify this object so
+        * it can be updated.
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       protected function getUpdateConditions() {
+               return array( 'id' => $this->getId() );
+       }
+
+       /**
+        * Inserts the object into the database.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param string|null $functionName
+        * @param array|null $options
+        *
+        * @return bool Success indicator
+        */
+       protected function insert( $functionName = null, array $options = null 
) {
+               $dbw = $this->table->getWriteDbConnection();
+
+               $success = $dbw->insert(
+                       $this->table->getName(),
+                       $this->getWriteValues(),
+                       is_null( $functionName ) ? __METHOD__ : $functionName,
+                       $options
+               );
+
+               // DatabaseBase::insert does not always return true for success 
as documented...
+               $success = $success !== false;
+
+               if ( $success ) {
+                       $this->setField( 'id', $dbw->insertId() );
+               }
+
+               $this->table->releaseConnection( $dbw );
+
+               return $success;
+       }
+
+       /**
+        * Removes the object from the database.
+        *
+        * @since 1.20
+        * @deprecated since 1.22, use IORMTable->removeRow
+        *
+        * @return bool Success indicator
+        */
+       public function remove() {
+               $this->beforeRemove();
+
+               $success = $this->table->removeRow( $this, __METHOD__ );
+
+               if ( $success ) {
+                       $this->onRemoved();
+               }
+
+               return $success;
+       }
+
+       /**
+        * Gets called before an object is removed from the database.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        */
+       protected function beforeRemove() {
+               $this->loadFields( $this->getBeforeRemoveFields(), false, true 
);
+       }
+
+       /**
+        * Before removal of an object happens, @see beforeRemove gets called.
+        * This method loads the fields of which the names have been returned by
+        * this one (or all fields if null is returned). This allows for loading
+        * info needed after removal to get rid of linked data and the like.
+        *
+        * @since 1.20
+        *
+        * @return array|null
+        */
+       protected function getBeforeRemoveFields() {
+               return array();
+       }
+
+       /**
+        * Gets called after successful removal.
+        * Can be overridden to get rid of linked data.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        */
+       protected function onRemoved() {
+               $this->setField( 'id', null );
+       }
+
+       /**
+        * Return the names and values of the fields.
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getFields() {
+               return $this->fields;
+       }
+
+       /**
+        * Return the names of the fields.
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getSetFieldNames() {
+               return array_keys( $this->fields );
+       }
+
+       /**
+        * Sets the value of a field.
+        * Strings can be provided for other types,
+        * so this method can be called from unserialization handlers.
+        *
+        * @since 1.20
+        *
+        * @param string $name
+        * @param mixed $value
+        *
+        * @throws \MWException
+        */
+       public function setField( $name, $value ) {
+               $this->fields[$name] = $value;
+       }
+
+       /**
+        * Add an amount (can be negative) to the specified field (needs to be 
numeric).
+        *
+        * @since 1.20
+        * @deprecated since 1.22, use IORMTable->addToField
+        *
+        * @param string $field
+        * @param int $amount
+        *
+        * @return bool Success indicator
+        */
+       public function addToField( $field, $amount ) {
+               return $this->table->addToField( $this->getUpdateConditions(), 
$field, $amount );
+       }
+
+       /**
+        * Return the names of the fields.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @return array
+        */
+       public function getFieldNames() {
+               return array_keys( $this->table->getFields() );
+       }
+
+       /**
+        * Computes and updates the values of the summary fields.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param array|string|null $summaryFields
+        */
+       public function loadSummaryFields( $summaryFields = null ) {
+       }
+
+       /**
+        * Sets the value for the @see $updateSummaries field.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param bool $update
+        */
+       public function setUpdateSummaries( $update ) {
+               $this->updateSummaries = $update;
+       }
+
+       /**
+        * Sets the value for the @see $inSummaryMode field.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @param bool $summaryMode
+        */
+       public function setSummaryMode( $summaryMode ) {
+               $this->inSummaryMode = $summaryMode;
+       }
+
+       /**
+        * Returns the table this IORMRow is a row in.
+        *
+        * @since 1.20
+        * @deprecated since 1.22
+        *
+        * @return IORMTable
+        */
+       public function getTable() {
+               return $this->table;
+       }
+}
diff --git a/includes/rows/RevisionedObject.php 
b/includes/rows/RevisionedObject.php
index df9b5fa..36e7ddc 100644
--- a/includes/rows/RevisionedObject.php
+++ b/includes/rows/RevisionedObject.php
@@ -14,7 +14,7 @@
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
  */
-abstract class RevisionedObject extends \ORMRow {
+abstract class RevisionedObject extends ORMRow {
        /**
         * If the object should log changes.
         * Can be changed via disableLogging and enableLogging.
diff --git a/includes/tables/CAs.php b/includes/tables/CAs.php
index 9ea3292..56f6ac2 100644
--- a/includes/tables/CAs.php
+++ b/includes/tables/CAs.php
@@ -12,7 +12,7 @@
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
  */
-class CAs extends \ORMTable {
+class CAs extends ORMTable {
 
        public function __construct() {
                $this->fieldPrefix = 'ca_';
diff --git a/includes/tables/Events.php b/includes/tables/Events.php
index e770cef..9b489b9 100644
--- a/includes/tables/Events.php
+++ b/includes/tables/Events.php
@@ -12,7 +12,7 @@
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
  */
-class Events extends \ORMTable {
+class Events extends ORMTable {
 
        public function __construct() {
                $this->fieldPrefix = 'event_';
diff --git a/includes/tables/IORMTable.php b/includes/tables/IORMTable.php
new file mode 100644
index 0000000..be6d69b
--- /dev/null
+++ b/includes/tables/IORMTable.php
@@ -0,0 +1,522 @@
+<?php
+namespace EducationProgram;
+
+/**
+ * Interface for objects representing a single database table.
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ *
+ * @file
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+
+interface IORMTable {
+       /**
+        * Returns the name of the database table objects of this type are 
stored in.
+        *
+        * @since 1.20
+        *
+        * @return string
+        */
+       public function getName();
+
+       /**
+        * Returns the name of a IORMRow implementing class that
+        * represents single rows in this table.
+        *
+        * @since 1.20
+        *
+        * @return string
+        */
+       public function getRowClass();
+
+       /**
+        * Returns an array with the fields and their types this object 
contains.
+        * This corresponds directly to the fields in the database, without 
prefix.
+        *
+        * field name => type
+        *
+        * Allowed types:
+        * * id
+        * * str
+        * * int
+        * * float
+        * * bool
+        * * array
+        * * blob
+        *
+        * @todo Get rid of the id field. Every row instance needs to have one 
so
+        *   this is just causing hassle at various locations by requiring an 
extra
+        *   check for field name.
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getFields();
+
+       /**
+        * Returns a list of default field values.
+        * field name => field value
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getDefaults();
+
+       /**
+        * Returns a list of the summary fields.
+        * These are fields that cache computed values, such as the amount of 
linked objects of $type.
+        * This is relevant as one might not want to do actions such as log 
changes when these get updated.
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getSummaryFields();
+
+       /**
+        * Selects the specified fields of the records matching the provided
+        * conditions and returns them as DBDataObject. Field names get 
prefixed.
+        *
+        * @see DatabaseBase::select()
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $fields
+        * @param array $conditions
+        * @param array $options
+        * @param string|null $functionName
+        *
+        * @return ORMResult The result set
+        * @throws \DBQueryError If the query failed (even if the database was 
in ignoreErrors mode)
+        */
+       public function select( $fields = null, array $conditions = array(),
+               array $options = array(), $functionName = null );
+
+       /**
+        * Selects the specified fields of the records matching the provided
+        * conditions and returns them as DBDataObject. Field names get 
prefixed.
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $fields
+        * @param array $conditions
+        * @param array $options
+        * @param string|null $functionName
+        *
+        * @return array Array of self
+        */
+       public function selectObjects( $fields = null, array $conditions = 
array(),
+               array $options = array(), $functionName = null );
+
+       /**
+        * Do the actual select.
+        *
+        * @since 1.20
+        *
+        * @param null|string|array $fields
+        * @param array $conditions
+        * @param array $options
+        * @param null|string $functionName
+        *
+        * @return \ResultWrapper
+        * @throws \DBQueryError If the query failed (even if the database was 
in ignoreErrors mode)
+        */
+       public function rawSelect( $fields = null, array $conditions = array(),
+               array $options = array(), $functionName = null );
+
+       /**
+        * Selects the specified fields of the records matching the provided
+        * conditions and returns them as associative arrays.
+        * Provided field names get prefixed.
+        * Returned field names will not have a prefix.
+        *
+        * When $collapse is true:
+        * If one field is selected, each item in the result array will be this 
field.
+        * If two fields are selected, each item in the result array will have 
as key
+        * the first field and as value the second field.
+        * If more then two fields are selected, each item will be an 
associative array.
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $fields
+        * @param array $conditions
+        * @param array $options
+        * @param bool $collapse Set to false to always return each result row 
as associative array.
+        * @param string|null $functionName
+        *
+        * @return array Array of array
+        */
+       public function selectFields( $fields = null, array $conditions = 
array(),
+               array $options = array(), $collapse = true, $functionName = 
null );
+
+       /**
+        * Selects the specified fields of the first matching record.
+        * Field names get prefixed.
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $fields
+        * @param array $conditions
+        * @param array $options
+        * @param string|null $functionName
+        *
+        * @return IORMRow|bool False on failure
+        */
+       public function selectRow( $fields = null, array $conditions = array(),
+               array $options = array(), $functionName = null );
+
+       /**
+        * Selects the specified fields of the records matching the provided
+        * conditions. Field names do NOT get prefixed.
+        *
+        * @since 1.20
+        *
+        * @param array $fields
+        * @param array $conditions
+        * @param array $options
+        * @param string|null $functionName
+        *
+        * @return \ResultWrapper
+        */
+       public function rawSelectRow( array $fields, array $conditions = 
array(),
+               array $options = array(), $functionName = null );
+
+       /**
+        * Selects the specified fields of the first record matching the 
provided
+        * conditions and returns it as an associative array, or false when 
nothing matches.
+        * This method makes use of selectFields and expects the same 
parameters and
+        * returns the same results (if there are any, if there are none, this 
method returns false).
+        * @see IORMTable::selectFields
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $fields
+        * @param array $conditions
+        * @param array $options
+        * @param bool $collapse Set to false to always return each result row 
as associative array.
+        * @param string|null $functionName
+        *
+        * @return mixed|array|bool False on failure
+        */
+       public function selectFieldsRow( $fields = null, array $conditions = 
array(),
+               array $options = array(), $collapse = true, $functionName = 
null );
+
+       /**
+        * Returns if there is at least one record matching the provided 
conditions.
+        * Condition field names get prefixed.
+        *
+        * @since 1.20
+        *
+        * @param array $conditions
+        *
+        * @return bool
+        */
+       public function has( array $conditions = array() );
+
+       /**
+        * Checks if the table exists
+        *
+        * @since 1.21
+        *
+        * @return bool
+        */
+       public function exists();
+
+       /**
+        * Returns the amount of matching records.
+        * Condition field names get prefixed.
+        *
+        * Note that this can be expensive on large tables.
+        * In such cases you might want to use DatabaseBase::estimateRowCount 
instead.
+        *
+        * @since 1.20
+        *
+        * @param array $conditions
+        * @param array $options
+        *
+        * @return int
+        */
+       public function count( array $conditions = array(), array $options = 
array() );
+
+       /**
+        * Removes the object from the database.
+        *
+        * @since 1.20
+        *
+        * @param array $conditions
+        * @param string|null $functionName
+        *
+        * @return bool Success indicator
+        */
+       public function delete( array $conditions, $functionName = null );
+
+       /**
+        * Get API parameters for the fields supported by this object.
+        *
+        * @since 1.20
+        *
+        * @param bool $requireParams
+        * @param bool $setDefaults
+        *
+        * @return array
+        */
+       public function getAPIParams( $requireParams = false, $setDefaults = 
false );
+
+       /**
+        * Returns an array with the fields and their descriptions.
+        *
+        * field name => field description
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getFieldDescriptions();
+
+       /**
+        * Get the database type used for read operations.
+        *
+        * @since 1.20
+        *
+        * @return int DB_ enum
+        */
+       public function getReadDb();
+
+       /**
+        * Set the database type to use for read operations.
+        *
+        * @param int $db
+        *
+        * @since 1.20
+        */
+       public function setReadDb( $db );
+
+       /**
+        * Get the ID of the any foreign wiki to use as a target for database 
operations
+        *
+        * @since 1.20
+        *
+        * @return string|bool The target wiki, in a form that LBFactory
+        *   understands (or false if the local wiki is used)
+        */
+       public function getTargetWiki();
+
+       /**
+        * Set the ID of the any foreign wiki to use as a target for database 
operations
+        *
+        * @param string|bool $wiki The target wiki, in a form that LBFactory
+        *   understands (or false if the local wiki shall be used)
+        *
+        * @since 1.20
+        */
+       public function setTargetWiki( $wiki );
+
+       /**
+        * Get the database type used for read operations.
+        * This is to be used instead of wfGetDB.
+        *
+        * @see LoadBalancer::getConnection
+        *
+        * @since 1.20
+        *
+        * @return \DatabaseBase The database object
+        */
+       public function getReadDbConnection();
+
+       /**
+        * Get the database type used for read operations.
+        * This is to be used instead of wfGetDB.
+        *
+        * @see LoadBalancer::getConnection
+        *
+        * @since 1.20
+        *
+        * @return \DatabaseBase The database object
+        */
+       public function getWriteDbConnection();
+
+       /**
+        * Get the database type used for read operations.
+        *
+        * @see wfGetLB
+        *
+        * @since 1.20
+        *
+        * @return \LoadBalancer The database load balancer object
+        */
+       public function getLoadBalancer();
+
+       /**
+        * Releases the lease on the given database connection. This is useful 
mainly
+        * for connections to a foreign wiki. It does nothing for connections 
to the local wiki.
+        *
+        * @see LoadBalancer::reuseConnection
+        *
+        * @param \DatabaseBase $db The database
+        *
+        * @since 1.20
+        */
+       public function releaseConnection( \DatabaseBase $db );
+
+       /**
+        * Update the records matching the provided conditions by
+        * setting the fields that are keys in the $values param to
+        * their corresponding values.
+        *
+        * @since 1.20
+        *
+        * @param array $values
+        * @param array $conditions
+        *
+        * @return bool Success indicator
+        */
+       public function update( array $values, array $conditions = array() );
+
+       /**
+        * Computes the values of the summary fields of the objects matching 
the provided conditions.
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $summaryFields
+        * @param array $conditions
+        */
+       public function updateSummaryFields( $summaryFields = null, array 
$conditions = array() );
+
+       /**
+        * Takes in an associative array with field names as keys and
+        * their values as value. The field names are prefixed with the
+        * db field prefix.
+        *
+        * @since 1.20
+        *
+        * @param array $values
+        *
+        * @return array
+        */
+       public function getPrefixedValues( array $values );
+
+       /**
+        * Takes in a field or array of fields and returns an
+        * array with their prefixed versions, ready for db usage.
+        *
+        * @since 1.20
+        *
+        * @param array $fields
+        *
+        * @return array
+        */
+       public function getPrefixedFields( array $fields );
+
+       /**
+        * Takes in a field and returns an it's prefixed version, ready for db 
usage.
+        *
+        * @since 1.20
+        *
+        * @param string|array $field
+        *
+        * @return string
+        */
+       public function getPrefixedField( $field );
+
+       /**
+        * Takes an array of field names with prefix and returns the unprefixed 
equivalent.
+        *
+        * @since 1.20
+        * @deprecated since 1.25, will be removed
+        *
+        * @param string[] $fieldNames
+        *
+        * @return string[]
+        */
+       public function unprefixFieldNames( array $fieldNames );
+
+       /**
+        * Takes a field name with prefix and returns the unprefixed equivalent.
+        *
+        * @since 1.20
+        * @deprecated since 1.25, will be removed
+        *
+        * @param string $fieldName
+        *
+        * @return string
+        */
+       public function unprefixFieldName( $fieldName );
+
+       /**
+        * Get an array with fields from a database result,
+        * that can be fed directly to the constructor or
+        * to setFields.
+        *
+        * @since 1.20
+        *
+        * @param stdClass $result
+        *
+        * @return array
+        */
+       public function getFieldsFromDBResult( stdClass $result );
+
+       /**
+        * Get a new instance of the class from a database result.
+        *
+        * @since 1.20
+        *
+        * @param stdClass $result
+        *
+        * @return IORMRow
+        */
+       public function newRowFromDBResult( stdClass $result );
+
+       /**
+        * Get a new instance of the class from an array.
+        *
+        * @since 1.20
+        *
+        * @param array $data
+        * @param bool $loadDefaults
+        *
+        * @return IORMRow
+        */
+       public function newRow( array $data, $loadDefaults = false );
+
+       /**
+        * Return the names of the fields.
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getFieldNames();
+
+       /**
+        * Gets if the object can take a certain field.
+        *
+        * @since 1.20
+        *
+        * @param string $name
+        *
+        * @return bool
+        */
+       public function canHaveField( $name );
+}
diff --git a/includes/tables/Instructors.php b/includes/tables/Instructors.php
index 2d5e18e..dbc9c3b 100644
--- a/includes/tables/Instructors.php
+++ b/includes/tables/Instructors.php
@@ -12,7 +12,7 @@
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
  */
-class Instructors extends \ORMTable {
+class Instructors extends ORMTable {
 
        public function __construct() {
                $this->fieldPrefix = 'instructor_';
diff --git a/includes/tables/OAs.php b/includes/tables/OAs.php
index 88796f6..02a5d04 100644
--- a/includes/tables/OAs.php
+++ b/includes/tables/OAs.php
@@ -12,7 +12,7 @@
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
  */
-class OAs extends \ORMTable {
+class OAs extends ORMTable {
 
        public function __construct() {
                $this->fieldPrefix = 'oa_';
diff --git a/includes/tables/ORMTable.php b/includes/tables/ORMTable.php
new file mode 100644
index 0000000..a58bd67
--- /dev/null
+++ b/includes/tables/ORMTable.php
@@ -0,0 +1,1147 @@
+<?php
+
+namespace EducationProgram;
+
+/**
+ * Abstract base class for representing a single database table.
+ * Documentation inline and at https://www.mediawiki.org/wiki/Manual:ORMTable
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.20
+ * Non-abstract since 1.21
+ *
+ * @file ORMTable.php
+ * @ingroup ORM
+ *
+ * @license GNU GPL v2 or later
+ * @author Jeroen De Dauw < [email protected] >
+ */
+
+class ORMTable extends \DBAccessBase implements IORMTable {
+       /**
+        * Cache for instances, used by the singleton method.
+        *
+        * @since 1.20
+        * @deprecated since 1.21
+        *
+        * @var ORMTable[]
+        */
+       protected static $instanceCache = array();
+
+       /**
+        * @since 1.21
+        *
+        * @var string
+        */
+       protected $tableName;
+
+       /**
+        * @since 1.21
+        *
+        * @var string[]
+        */
+       protected $fields = array();
+
+       /**
+        * @since 1.21
+        *
+        * @var string
+        */
+       protected $fieldPrefix = '';
+
+       /**
+        * @since 1.21
+        *
+        * @var string
+        */
+       protected $rowClass = 'ORMRow';
+
+       /**
+        * @since 1.21
+        *
+        * @var array
+        */
+       protected $defaults = array();
+
+       /**
+        * ID of the database connection to use for read operations.
+        * Can be changed via @see setReadDb.
+        *
+        * @since 1.20
+        *
+        * @var int DB_ enum
+        */
+       protected $readDb = DB_SLAVE;
+
+       /**
+        * Constructor.
+        *
+        * @since 1.21
+        *
+        * @param string $tableName
+        * @param string[] $fields
+        * @param array $defaults
+        * @param string|null $rowClass
+        * @param string $fieldPrefix
+        */
+       public function __construct( $tableName = '', array $fields = array(),
+               array $defaults = array(), $rowClass = null, $fieldPrefix = ''
+       ) {
+               $this->tableName = $tableName;
+               $this->fields = $fields;
+               $this->defaults = $defaults;
+
+               if ( is_string( $rowClass ) ) {
+                       $this->rowClass = $rowClass;
+               }
+
+               $this->fieldPrefix = $fieldPrefix;
+       }
+
+       /**
+        * @see IORMTable::getName
+        *
+        * @since 1.21
+        *
+        * @return string
+        * @throws \MWException
+        */
+       public function getName() {
+               if ( $this->tableName === '' ) {
+                       throw new \MWException( 'The table name needs to be 
set' );
+               }
+
+               return $this->tableName;
+       }
+
+       /**
+        * Gets the db field prefix.
+        *
+        * @since 1.20
+        * @deprecated since 1.25, use the $this->fieldPrefix property instead
+        *
+        * @return string
+        */
+       protected function getFieldPrefix() {
+               return $this->fieldPrefix;
+       }
+
+       /**
+        * @see IORMTable::getRowClass
+        *
+        * @since 1.21
+        *
+        * @return string
+        */
+       public function getRowClass() {
+               return $this->rowClass;
+       }
+
+       /**
+        * @see ORMTable::getFields
+        *
+        * @since 1.21
+        *
+        * @return array
+        * @throws \MWException
+        */
+       public function getFields() {
+               if ( $this->fields === array() ) {
+                       throw new \MWException( 'The table needs to have one or 
more fields' );
+               }
+
+               return $this->fields;
+       }
+
+       /**
+        * Returns a list of default field values.
+        * field name => field value
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getDefaults() {
+               return $this->defaults;
+       }
+
+       /**
+        * Returns a list of the summary fields.
+        * These are fields that cache computed values, such as the amount of 
linked objects of $type.
+        * This is relevant as one might not want to do actions such as log 
changes when these get updated.
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getSummaryFields() {
+               return array();
+       }
+
+       /**
+        * Selects the specified fields of the records matching the provided
+        * conditions and returns them as DBDataObject. Field names get 
prefixed.
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $fields
+        * @param array $conditions
+        * @param array $options
+        * @param string|null $functionName
+        *
+        * @return ORMResult
+        */
+       public function select( $fields = null, array $conditions = array(),
+               array $options = array(), $functionName = null
+       ) {
+               $res = $this->rawSelect( $fields, $conditions, $options, 
$functionName );
+
+               return new ORMResult( $this, $res );
+       }
+
+       /**
+        * Selects the specified fields of the records matching the provided
+        * conditions and returns them as DBDataObject. Field names get 
prefixed.
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $fields
+        * @param array $conditions
+        * @param array $options
+        * @param string|null $functionName
+        *
+        * @return array Array of row objects
+        * @throws \DBQueryError If the query failed (even if the database was 
in ignoreErrors mode).
+        */
+       public function selectObjects( $fields = null, array $conditions = 
array(),
+               array $options = array(), $functionName = null
+       ) {
+               $result = $this->selectFields( $fields, $conditions, $options, 
false, $functionName );
+
+               $objects = array();
+
+               foreach ( $result as $record ) {
+                       $objects[] = $this->newRow( $record );
+               }
+
+               return $objects;
+       }
+
+       /**
+        * Do the actual select.
+        *
+        * @since 1.20
+        *
+        * @param null|string|array $fields
+        * @param array $conditions
+        * @param array $options
+        * @param null|string $functionName
+        * @return \ResultWrapper
+        * @throws \Exception
+        * @throws \MWException
+        */
+       public function rawSelect( $fields = null, array $conditions = array(),
+               array $options = array(), $functionName = null
+       ) {
+               if ( is_null( $fields ) ) {
+                       $fields = array_keys( $this->getFields() );
+               } else {
+                       $fields = (array)$fields;
+               }
+
+               $dbr = $this->getReadDbConnection();
+               $result = $dbr->select(
+                       $this->getName(),
+                       $this->getPrefixedFields( $fields ),
+                       $this->getPrefixedValues( $conditions ),
+                       is_null( $functionName ) ? __METHOD__ : $functionName,
+                       $options
+               );
+
+               /* @var \Exception $error */
+               $error = null;
+
+               if ( $result === false ) {
+                       // Database connection was in "ignoreErrors" mode. We 
don't like that.
+                       // So, we emulate the DBQueryError that should have 
been thrown.
+                       $error = new \DBQueryError(
+                               $dbr,
+                               $dbr->lastError(),
+                               $dbr->lastErrno(),
+                               $dbr->lastQuery(),
+                               is_null( $functionName ) ? __METHOD__ : 
$functionName
+                       );
+               }
+
+               $this->releaseConnection( $dbr );
+
+               if ( $error ) {
+                       // Note: construct the error before releasing the 
connection,
+                       // but throw it after.
+                       throw $error;
+               }
+
+               return $result;
+       }
+
+       /**
+        * Selects the specified fields of the records matching the provided
+        * conditions and returns them as associative arrays.
+        * Provided field names get prefixed.
+        * Returned field names will not have a prefix.
+        *
+        * When $collapse is true:
+        * If one field is selected, each item in the result array will be this 
field.
+        * If two fields are selected, each item in the result array will have 
as key
+        * the first field and as value the second field.
+        * If more then two fields are selected, each item will be an 
associative array.
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $fields
+        * @param array $conditions
+        * @param array $options
+        * @param bool $collapse Set to false to always return each result row 
as associative array.
+        * @param string|null $functionName
+        *
+        * @return array Array of array
+        */
+       public function selectFields( $fields = null, array $conditions = 
array(),
+               array $options = array(), $collapse = true, $functionName = null
+       ) {
+               $objects = array();
+
+               $result = $this->rawSelect( $fields, $conditions, $options, 
$functionName );
+
+               foreach ( $result as $record ) {
+                       $objects[] = $this->getFieldsFromDBResult( $record );
+               }
+
+               if ( $collapse ) {
+                       if ( count( $fields ) === 1 ) {
+                               $objects = array_map( 'array_shift', $objects );
+                       } elseif ( count( $fields ) === 2 ) {
+                               $o = array();
+
+                               foreach ( $objects as $object ) {
+                                       $o[array_shift( $object )] = 
array_shift( $object );
+                               }
+
+                               $objects = $o;
+                       }
+               }
+
+               return $objects;
+       }
+
+       /**
+        * Selects the specified fields of the first matching record.
+        * Field names get prefixed.
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $fields
+        * @param array $conditions
+        * @param array $options
+        * @param string|null $functionName
+        *
+        * @return IORMRow|bool False on failure
+        */
+       public function selectRow( $fields = null, array $conditions = array(),
+               array $options = array(), $functionName = null
+       ) {
+               $options['LIMIT'] = 1;
+
+               $objects = $this->select( $fields, $conditions, $options, 
$functionName );
+
+               return ( !$objects || $objects->isEmpty() ) ? false : 
$objects->current();
+       }
+
+       /**
+        * Selects the specified fields of the records matching the provided
+        * conditions. Field names do NOT get prefixed.
+        *
+        * @since 1.20
+        *
+        * @param array $fields
+        * @param array $conditions
+        * @param array $options
+        * @param string|null $functionName
+        *
+        * @return stdClass
+        */
+       public function rawSelectRow( array $fields, array $conditions = 
array(),
+               array $options = array(), $functionName = null
+       ) {
+               $dbr = $this->getReadDbConnection();
+
+               $result = $dbr->selectRow(
+                       $this->getName(),
+                       $fields,
+                       $conditions,
+                       is_null( $functionName ) ? __METHOD__ : $functionName,
+                       $options
+               );
+
+               $this->releaseConnection( $dbr );
+
+               return $result;
+       }
+
+       /**
+        * Selects the specified fields of the first record matching the 
provided
+        * conditions and returns it as an associative array, or false when 
nothing matches.
+        * This method makes use of selectFields and expects the same 
parameters and
+        * returns the same results (if there are any, if there are none, this 
method returns false).
+        * @see ORMTable::selectFields
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $fields
+        * @param array $conditions
+        * @param array $options
+        * @param bool $collapse Set to false to always return each result row 
as associative array.
+        * @param string|null $functionName
+        *
+        * @return mixed|array|bool False on failure
+        */
+       public function selectFieldsRow( $fields = null, array $conditions = 
array(),
+               array $options = array(), $collapse = true, $functionName = null
+       ) {
+               $options['LIMIT'] = 1;
+
+               $objects = $this->selectFields( $fields, $conditions, $options, 
$collapse, $functionName );
+
+               return empty( $objects ) ? false : $objects[0];
+       }
+
+       /**
+        * Returns if there is at least one record matching the provided 
conditions.
+        * Condition field names get prefixed.
+        *
+        * @since 1.20
+        *
+        * @param array $conditions
+        *
+        * @return bool
+        */
+       public function has( array $conditions = array() ) {
+               return $this->selectRow( array( 'id' ), $conditions ) !== false;
+       }
+
+       /**
+        * Checks if the table exists
+        *
+        * @since 1.21
+        *
+        * @return bool
+        */
+       public function exists() {
+               $dbr = $this->getReadDbConnection();
+               $exists = $dbr->tableExists( $this->getName() );
+               $this->releaseConnection( $dbr );
+
+               return $exists;
+       }
+
+       /**
+        * Returns the amount of matching records.
+        * Condition field names get prefixed.
+        *
+        * Note that this can be expensive on large tables.
+        * In such cases you might want to use DatabaseBase::estimateRowCount 
instead.
+        *
+        * @since 1.20
+        *
+        * @param array $conditions
+        * @param array $options
+        *
+        * @return int
+        */
+       public function count( array $conditions = array(), array $options = 
array() ) {
+               $res = $this->rawSelectRow(
+                       array( 'rowcount' => 'COUNT(*)' ),
+                       $this->getPrefixedValues( $conditions ),
+                       $options,
+                       __METHOD__
+               );
+
+               return $res->rowcount;
+       }
+
+       /**
+        * Removes the object from the database.
+        *
+        * @since 1.20
+        *
+        * @param array $conditions
+        * @param string|null $functionName
+        *
+        * @return bool Success indicator
+        */
+       public function delete( array $conditions, $functionName = null ) {
+               $dbw = $this->getWriteDbConnection();
+
+               $result = $dbw->delete(
+                       $this->getName(),
+                       $conditions === array() ? '*' : 
$this->getPrefixedValues( $conditions ),
+                       is_null( $functionName ) ? __METHOD__ : $functionName
+               ) !== false; // DatabaseBase::delete does not always return 
true for success as documented...
+
+               $this->releaseConnection( $dbw );
+
+               return $result;
+       }
+
+       /**
+        * Get API parameters for the fields supported by this object.
+        *
+        * @since 1.20
+        *
+        * @param bool $requireParams
+        * @param bool $setDefaults
+        *
+        * @return array
+        */
+       public function getAPIParams( $requireParams = false, $setDefaults = 
false ) {
+               $typeMap = array(
+                       'id' => 'integer',
+                       'int' => 'integer',
+                       'float' => 'NULL',
+                       'str' => 'string',
+                       'bool' => 'integer',
+                       'array' => 'string',
+                       'blob' => 'string',
+               );
+
+               $params = array();
+               $defaults = $this->getDefaults();
+
+               foreach ( $this->getFields() as $field => $type ) {
+                       if ( $field == 'id' ) {
+                               continue;
+                       }
+
+                       $hasDefault = array_key_exists( $field, $defaults );
+
+                       $params[$field] = array(
+                               \ApiBase::PARAM_TYPE => $typeMap[$type],
+                               \ApiBase::PARAM_REQUIRED => $requireParams && 
!$hasDefault
+                       );
+
+                       if ( $type == 'array' ) {
+                               $params[$field][\ApiBase::PARAM_ISMULTI] = true;
+                       }
+
+                       if ( $setDefaults && $hasDefault ) {
+                               $default = is_array( $defaults[$field] )
+                                       ? implode( '|', $defaults[$field] )
+                                       : $defaults[$field];
+                               $params[$field][\ApiBase::PARAM_DFLT] = 
$default;
+                       }
+               }
+
+               return $params;
+       }
+
+       /**
+        * Returns an array with the fields and their descriptions.
+        *
+        * field name => field description
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getFieldDescriptions() {
+               return array();
+       }
+
+       /**
+        * Get the database ID used for read operations.
+        *
+        * @since 1.20
+        *
+        * @return int DB_ enum
+        */
+       public function getReadDb() {
+               return $this->readDb;
+       }
+
+       /**
+        * Set the database ID to use for read operations, use DB_XXX constants 
or
+        *   an index to the load balancer setup.
+        *
+        * @param int $db
+        *
+        * @since 1.20
+        */
+       public function setReadDb( $db ) {
+               $this->readDb = $db;
+       }
+
+       /**
+        * Get the ID of the any foreign wiki to use as a target for database 
operations
+        *
+        * @since 1.20
+        *
+        * @return string|bool The target wiki, in a form that LBFactory 
understands
+        *   (or false if the local wiki is used)
+        */
+       public function getTargetWiki() {
+               return $this->wiki;
+       }
+
+       /**
+        * Set the ID of the any foreign wiki to use as a target for database 
operations
+        *
+        * @param string|bool $wiki The target wiki, in a form that  LBFactory
+        *   understands (or false if the local wiki shall be used)
+        *
+        * @since 1.20
+        */
+       public function setTargetWiki( $wiki ) {
+               $this->wiki = $wiki;
+       }
+
+       /**
+        * Get the database type used for read operations.
+        * This is to be used instead of wfGetDB.
+        *
+        * @see LoadBalancer::getConnection
+        *
+        * @since 1.20
+        *
+        * @return \DatabaseBase The database object
+        */
+       public function getReadDbConnection() {
+               return $this->getConnection( $this->getReadDb(), array() );
+       }
+
+       /**
+        * Get the database type used for read operations.
+        * This is to be used instead of wfGetDB.
+        *
+        * @see LoadBalancer::getConnection
+        *
+        * @since 1.20
+        *
+        * @return \DatabaseBase The database object
+        */
+       public function getWriteDbConnection() {
+               return $this->getConnection( DB_MASTER, array() );
+       }
+
+       /**
+        * Releases the lease on the given database connection. This is useful 
mainly
+        * for connections to a foreign wiki. It does nothing for connections 
to the local wiki.
+        *
+        * @see LoadBalancer::reuseConnection
+        *
+        * @param \DatabaseBase $db
+        *
+        * @since 1.20
+        */
+       // @codingStandardsIgnoreStart Suppress "useless method overriding" 
sniffer warning
+       public function releaseConnection( \DatabaseBase $db ) {
+               parent::releaseConnection( $db ); // just make it public
+       }
+       // @codingStandardsIgnoreEnd
+
+       /**
+        * Update the records matching the provided conditions by
+        * setting the fields that are keys in the $values param to
+        * their corresponding values.
+        *
+        * @since 1.20
+        *
+        * @param array $values
+        * @param array $conditions
+        *
+        * @return bool Success indicator
+        */
+       public function update( array $values, array $conditions = array() ) {
+               $dbw = $this->getWriteDbConnection();
+
+               $result = $dbw->update(
+                       $this->getName(),
+                       $this->getPrefixedValues( $values ),
+                       $this->getPrefixedValues( $conditions ),
+                       __METHOD__
+               ) !== false; // DatabaseBase::update does not always return 
true for success as documented...
+
+               $this->releaseConnection( $dbw );
+
+               return $result;
+       }
+
+       /**
+        * Computes the values of the summary fields of the objects matching 
the provided conditions.
+        *
+        * @since 1.20
+        *
+        * @param array|string|null $summaryFields
+        * @param array $conditions
+        */
+       public function updateSummaryFields( $summaryFields = null, array 
$conditions = array() ) {
+               $slave = $this->getReadDb();
+               $this->setReadDb( DB_MASTER );
+
+               /**
+                * @var IORMRow $item
+                */
+               foreach ( $this->select( null, $conditions ) as $item ) {
+                       $item->loadSummaryFields( $summaryFields );
+                       $item->setSummaryMode( true );
+                       $item->save();
+               }
+
+               $this->setReadDb( $slave );
+       }
+
+       /**
+        * Takes in an associative array with field names as keys and
+        * their values as value. The field names are prefixed with the
+        * db field prefix.
+        *
+        * @since 1.20
+        *
+        * @param array $values
+        *
+        * @return array
+        */
+       public function getPrefixedValues( array $values ) {
+               $prefixedValues = array();
+
+               foreach ( $values as $field => $value ) {
+                       if ( is_integer( $field ) ) {
+                               if ( is_array( $value ) ) {
+                                       $field = $value[0];
+                                       $value = $value[1];
+                               } else {
+                                       $value = explode( ' ', $value, 2 );
+                                       $value[0] = $this->getPrefixedField( 
$value[0] );
+                                       $prefixedValues[] = implode( ' ', 
$value );
+                                       continue;
+                               }
+                       }
+
+                       $prefixedValues[$this->getPrefixedField( $field )] = 
$value;
+               }
+
+               return $prefixedValues;
+       }
+
+       /**
+        * Takes in a field or array of fields and returns an
+        * array with their prefixed versions, ready for db usage.
+        *
+        * @since 1.20
+        *
+        * @param array $fields
+        *
+        * @return array
+        */
+       public function getPrefixedFields( array $fields ) {
+               foreach ( $fields as &$field ) {
+                       $field = $this->getPrefixedField( $field );
+               }
+
+               return $fields;
+       }
+
+       /**
+        * Takes in a field and returns an it's prefixed version, ready for db 
usage.
+        *
+        * @since 1.20
+        *
+        * @param string|array $field
+        *
+        * @return string
+        */
+       public function getPrefixedField( $field ) {
+               return $this->fieldPrefix . $field;
+       }
+
+       /**
+        * Takes an array of field names with prefix and returns the unprefixed 
equivalent.
+        *
+        * @since 1.20
+        * @deprecated since 1.25, will be removed
+        *
+        * @param string[] $fieldNames
+        *
+        * @return string[]
+        */
+       public function unprefixFieldNames( array $fieldNames ) {
+               wfDeprecated( __METHOD__, '1.25' );
+
+               return $this->stripFieldPrefix( $fieldNames );
+       }
+
+       /**
+        * Takes an array of field names with prefix and returns the unprefixed 
equivalent.
+        *
+        * @param string[] $fieldNames
+        *
+        * @return string[]
+        */
+       private function stripFieldPrefix( array $fieldNames ) {
+               $start = strlen( $this->fieldPrefix );
+
+               return array_map( function ( $fieldName ) use ( $start ) {
+                       return substr( $fieldName, $start );
+               }, $fieldNames );
+       }
+
+       /**
+        * Takes a field name with prefix and returns the unprefixed equivalent.
+        *
+        * @since 1.20
+        * @deprecated since 1.25, will be removed
+        *
+        * @param string $fieldName
+        *
+        * @return string
+        */
+       public function unprefixFieldName( $fieldName ) {
+               wfDeprecated( __METHOD__, '1.25' );
+
+               return substr( $fieldName, strlen( $this->fieldPrefix ) );
+       }
+
+       /**
+        * Get an instance of this class.
+        *
+        * @since 1.20
+        * @deprecated since 1.21
+        *
+        * @return IORMTable
+        */
+       public static function singleton() {
+               $class = get_called_class();
+
+               if ( !array_key_exists( $class, self::$instanceCache ) ) {
+                       self::$instanceCache[$class] = new $class;
+               }
+
+               return self::$instanceCache[$class];
+       }
+
+       /**
+        * Get an array with fields from a database result,
+        * that can be fed directly to the constructor or
+        * to setFields.
+        *
+        * @since 1.20
+        *
+        * @param stdClass $result
+        * @throws \MWException
+        * @return array
+        */
+       public function getFieldsFromDBResult( stdClass $result ) {
+               $result = (array)$result;
+
+               $rawFields = array_combine(
+                       $this->stripFieldPrefix( array_keys( $result ) ),
+                       array_values( $result )
+               );
+
+               $fieldDefinitions = $this->getFields();
+               $fields = array();
+
+               foreach ( $rawFields as $name => $value ) {
+                       if ( array_key_exists( $name, $fieldDefinitions ) ) {
+                               switch ( $fieldDefinitions[$name] ) {
+                                       case 'int':
+                                               $value = (int)$value;
+                                               break;
+                                       case 'float':
+                                               $value = (float)$value;
+                                               break;
+                                       case 'bool':
+                                               if ( is_string( $value ) ) {
+                                                       $value = $value !== '0';
+                                               } elseif ( is_int( $value ) ) {
+                                                       $value = $value !== 0;
+                                               }
+                                               break;
+                                       case 'array':
+                                               if ( is_string( $value ) ) {
+                                                       $value = unserialize( 
$value );
+                                               }
+
+                                               if ( !is_array( $value ) ) {
+                                                       $value = array();
+                                               }
+                                               break;
+                                       case 'blob':
+                                               if ( is_string( $value ) ) {
+                                                       $value = unserialize( 
$value );
+                                               }
+                                               break;
+                                       case 'id':
+                                               if ( is_string( $value ) ) {
+                                                       $value = (int)$value;
+                                               }
+                                               break;
+                               }
+
+                               $fields[$name] = $value;
+                       } else {
+                               throw new \MWException( 'Attempted to set 
unknown field ' . $name );
+                       }
+               }
+
+               return $fields;
+       }
+
+       /**
+        * @see ORMTable::newRowFromFromDBResult
+        *
+        * @deprecated since 1.20 use newRowFromDBResult instead
+        * @since 1.20
+        *
+        * @param stdClass $result
+        *
+        * @return IORMRow
+        */
+       public function newFromDBResult( stdClass $result ) {
+               return self::newRowFromDBResult( $result );
+       }
+
+       /**
+        * Get a new instance of the class from a database result.
+        *
+        * @since 1.20
+        *
+        * @param stdClass $result
+        *
+        * @return IORMRow
+        */
+       public function newRowFromDBResult( stdClass $result ) {
+               return $this->newRow( $this->getFieldsFromDBResult( $result ) );
+       }
+
+       /**
+        * @see ORMTable::newRow
+        *
+        * @deprecated since 1.20 use newRow instead
+        * @since 1.20
+        *
+        * @param array $data
+        * @param bool $loadDefaults
+        *
+        * @return IORMRow
+        */
+       public function newFromArray( array $data, $loadDefaults = false ) {
+               return static::newRow( $data, $loadDefaults );
+       }
+
+       /**
+        * Get a new instance of the class from an array.
+        *
+        * @since 1.20
+        *
+        * @param array $fields
+        * @param bool $loadDefaults
+        *
+        * @return IORMRow
+        */
+       public function newRow( array $fields, $loadDefaults = false ) {
+               $class = $this->getRowClass();
+
+               return new $class( $this, $fields, $loadDefaults );
+       }
+
+       /**
+        * Return the names of the fields.
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getFieldNames() {
+               return array_keys( $this->getFields() );
+       }
+
+       /**
+        * Gets if the object can take a certain field.
+        *
+        * @since 1.20
+        *
+        * @param string $name
+        *
+        * @return bool
+        */
+       public function canHaveField( $name ) {
+               return array_key_exists( $name, $this->getFields() );
+       }
+
+       /**
+        * Updates the provided row in the database.
+        *
+        * @since 1.22
+        *
+        * @param IORMRow $row The row to save
+        * @param string|null $functionName
+        *
+        * @return bool Success indicator
+        */
+       public function updateRow( IORMRow $row, $functionName = null ) {
+               $dbw = $this->getWriteDbConnection();
+
+               $success = $dbw->update(
+                       $this->getName(),
+                       $this->getWriteValues( $row ),
+                       $this->getPrefixedValues( array( 'id' => $row->getId() 
) ),
+                       is_null( $functionName ) ? __METHOD__ : $functionName
+               );
+
+               $this->releaseConnection( $dbw );
+
+               // DatabaseBase::update does not always return true for success 
as documented...
+               return $success !== false;
+       }
+
+       /**
+        * Inserts the provided row into the database.
+        *
+        * @since 1.22
+        *
+        * @param IORMRow $row
+        * @param string|null $functionName
+        * @param array|null $options
+        *
+        * @return bool Success indicator
+        */
+       public function insertRow( IORMRow $row, $functionName = null, array 
$options = null ) {
+               $dbw = $this->getWriteDbConnection();
+
+               $success = $dbw->insert(
+                       $this->getName(),
+                       $this->getWriteValues( $row ),
+                       is_null( $functionName ) ? __METHOD__ : $functionName,
+                       $options
+               );
+
+               // DatabaseBase::insert does not always return true for success 
as documented...
+               $success = $success !== false;
+
+               if ( $success ) {
+                       $row->setField( 'id', $dbw->insertId() );
+               }
+
+               $this->releaseConnection( $dbw );
+
+               return $success;
+       }
+
+       /**
+        * Gets the fields => values to write to the table.
+        *
+        * @since 1.22
+        *
+        * @param IORMRow $row
+        *
+        * @return array
+        */
+       protected function getWriteValues( IORMRow $row ) {
+               $values = array();
+
+               $rowFields = $row->getFields();
+
+               foreach ( $this->getFields() as $name => $type ) {
+                       if ( array_key_exists( $name, $rowFields ) ) {
+                               $value = $rowFields[$name];
+
+                               switch ( $type ) {
+                                       case 'array':
+                                               $value = (array)$value;
+                                       // fall-through!
+                                       case 'blob':
+                                               $value = serialize( $value );
+                                       // fall-through!
+                               }
+
+                               $values[$this->getPrefixedField( $name )] = 
$value;
+                       }
+               }
+
+               return $values;
+       }
+
+       /**
+        * Removes the provided row from the database.
+        *
+        * @since 1.22
+        *
+        * @param IORMRow $row
+        * @param string|null $functionName
+        *
+        * @return bool Success indicator
+        */
+       public function removeRow( IORMRow $row, $functionName = null ) {
+               $success = $this->delete(
+                       array( 'id' => $row->getId() ),
+                       is_null( $functionName ) ? __METHOD__ : $functionName
+               );
+
+               // DatabaseBase::delete does not always return true for success 
as documented...
+               return $success !== false;
+       }
+
+       /**
+        * Add an amount (can be negative) to the specified field (needs to be 
numeric).
+        *
+        * @since 1.22
+        *
+        * @param array $conditions
+        * @param string $field
+        * @param int $amount
+        *
+        * @return bool Success indicator
+        * @throws \MWException
+        */
+       public function addToField( array $conditions, $field, $amount ) {
+               if ( !array_key_exists( $field, $this->fields ) ) {
+                       throw new \MWException( 'Unknown field "' . $field . '" 
provided' );
+               }
+
+               if ( $amount == 0 ) {
+                       return true;
+               }
+
+               $absoluteAmount = abs( $amount );
+               $isNegative = $amount < 0;
+
+               $fullField = $this->getPrefixedField( $field );
+
+               $dbw = $this->getWriteDbConnection();
+
+               $success = $dbw->update(
+                       $this->getName(),
+                       array( "$fullField=$fullField" . ( $isNegative ? '-' : 
'+' ) . $absoluteAmount ),
+                       $this->getPrefixedValues( $conditions ),
+                       __METHOD__
+               ) !== false; // DatabaseBase::update does not always return 
true for success as documented...
+
+               $this->releaseConnection( $dbw );
+
+               return $success;
+       }
+}
diff --git a/includes/tables/PageTable.php b/includes/tables/PageTable.php
index 8ef917a..546e741 100644
--- a/includes/tables/PageTable.php
+++ b/includes/tables/PageTable.php
@@ -15,7 +15,7 @@
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
  */
-abstract class PageTable extends \ORMTable {
+abstract class PageTable extends ORMTable {
 
        /**
         * Returns the field use to identify this object, ie the part used as 
page title.
diff --git a/includes/tables/Revisions.php b/includes/tables/Revisions.php
index 5f647f5..e5461e6 100644
--- a/includes/tables/Revisions.php
+++ b/includes/tables/Revisions.php
@@ -12,7 +12,7 @@
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
  */
-class Revisions extends \ORMTable {
+class Revisions extends ORMTable {
 
        public function __construct() {
                $this->fieldPrefix = 'rev_';
diff --git a/includes/tables/Students.php b/includes/tables/Students.php
index 91b3819..38567d4 100644
--- a/includes/tables/Students.php
+++ b/includes/tables/Students.php
@@ -12,7 +12,7 @@
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
  */
-class Students extends \ORMTable {
+class Students extends ORMTable {
 
        public function __construct() {
                $this->fieldPrefix = 'student_';
diff --git a/tests/phpunit/db/ORMRowTest.php b/tests/phpunit/db/ORMRowTest.php
new file mode 100644
index 0000000..db65342
--- /dev/null
+++ b/tests/phpunit/db/ORMRowTest.php
@@ -0,0 +1,229 @@
+<?php
+
+namespace EducationProgram\Tests;
+use EducationProgram\IORMRow;
+use EducationProgram\IORMTable;
+
+/**
+ * Abstract class to construct tests for ORMRow deriving classes.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.20
+ *
+ * @ingroup Test
+ *
+ * @group ORM
+ *
+ * The database group has as a side effect that temporal database tables are 
created. This makes
+ * it possible to test without poisoning a production database.
+ * @group Database
+ *
+ * Some of the tests takes more time, and needs therefor longer time before 
they can be aborted
+ * as non-functional. The reason why tests are aborted is assumed to be set up 
of temporal databases
+ * that hold the first tests in a pending state awaiting access to the 
database.
+ * @group medium
+ *
+ * @author Jeroen De Dauw < [email protected] >
+ */
+abstract class ORMRowTest extends \MediaWikiTestCase {
+
+       /**
+        * @since 1.20
+        * @return string
+        */
+       abstract protected function getRowClass();
+
+       /**
+        * @since 1.20
+        * @return IORMTable
+        */
+       abstract protected function getTableInstance();
+
+       /**
+        * @since 1.20
+        * @return array
+        */
+       abstract public function constructorTestProvider();
+
+       /**
+        * @since 1.20
+        * @param IORMRow $row
+        * @param array $data
+        */
+       protected function verifyFields( IORMRow $row, array $data ) {
+               foreach ( array_keys( $data ) as $fieldName ) {
+                       $this->assertEquals( $data[$fieldName], $row->getField( 
$fieldName ) );
+               }
+       }
+
+       /**
+        * @since 1.20
+        * @param array $data
+        * @param bool $loadDefaults
+        * @return IORMRow
+        */
+       protected function getRowInstance( array $data, $loadDefaults ) {
+               $class = $this->getRowClass();
+
+               return new $class( $this->getTableInstance(), $data, 
$loadDefaults );
+       }
+
+       /**
+        * @since 1.20
+        * @return array
+        */
+       protected function getMockValues() {
+               return array(
+                       'id' => 1,
+                       'str' => 'foobar4645645',
+                       'int' => 42,
+                       'float' => 4.2,
+                       'bool' => true,
+                       'array' => array( 42, 'foobar' ),
+                       'blob' => new stdClass()
+               );
+       }
+
+       /**
+        * @since 1.20
+        * @return array
+        */
+       protected function getMockFields() {
+               $mockValues = $this->getMockValues();
+               $mockFields = array();
+
+               foreach ( $this->getTableInstance()->getFields() as $name => 
$type ) {
+                       if ( $name !== 'id' ) {
+                               $mockFields[$name] = $mockValues[$type];
+                       }
+               }
+
+               return $mockFields;
+       }
+
+       /**
+        * @since 1.20
+        * @return array Array of IORMRow
+        */
+       public function instanceProvider() {
+               $instances = array();
+
+               foreach ( $this->constructorTestProvider() as $arguments ) {
+                       $instances[] = array( call_user_func_array( array( 
$this, 'getRowInstance' ), $arguments ) );
+               }
+
+               return $instances;
+       }
+
+       /**
+        * @dataProvider constructorTestProvider
+        */
+       public function testConstructor( array $data, $loadDefaults ) {
+               $this->verifyFields( $this->getRowInstance( $data, 
$loadDefaults ), $data );
+       }
+
+       /**
+        * @dataProvider constructorTestProvider
+        */
+       public function testSaveAndRemove( array $data, $loadDefaults ) {
+               $item = $this->getRowInstance( $data, $loadDefaults );
+
+               $this->assertTrue( $item->save() );
+
+               $this->assertTrue( $item->hasIdField() );
+               $this->assertTrue( is_integer( $item->getId() ) );
+
+               $id = $item->getId();
+
+               $this->assertTrue( $item->save() );
+
+               $this->assertEquals( $id, $item->getId() );
+
+               $this->verifyFields( $item, $data );
+
+               $this->assertTrue( $item->remove() );
+
+               $this->assertFalse( $item->hasIdField() );
+
+               $this->assertTrue( $item->save() );
+
+               $this->verifyFields( $item, $data );
+
+               $this->assertTrue( $item->remove() );
+
+               $this->assertFalse( $item->hasIdField() );
+
+               $this->verifyFields( $item, $data );
+       }
+
+       /**
+        * @dataProvider instanceProvider
+        */
+       public function testSetField( IORMRow $item ) {
+               foreach ( $this->getMockFields() as $name => $value ) {
+                       $item->setField( $name, $value );
+                       $this->assertEquals( $value, $item->getField( $name ) );
+               }
+       }
+
+       /**
+        * @since 1.20
+        * @param array $expected
+        * @param IORMRow $item
+        */
+       protected function assertFieldValues( array $expected, IORMRow $item ) {
+               foreach ( $expected as $name => $type ) {
+                       if ( $name !== 'id' ) {
+                               $this->assertEquals( $expected[$name], 
$item->getField( $name ) );
+                       }
+               }
+       }
+
+       /**
+        * @dataProvider instanceProvider
+        */
+       public function testSetFields( IORMRow $item ) {
+               $originalValues = $item->getFields();
+
+               $item->setFields( array(), false );
+
+               foreach ( $item->getTable()->getFields() as $name => $type ) {
+                       $originalHas = array_key_exists( $name, $originalValues 
);
+                       $newHas = $item->hasField( $name );
+
+                       $this->assertEquals( $originalHas, $newHas );
+
+                       if ( $originalHas && $newHas ) {
+                               $this->assertEquals( $originalValues[$name], 
$item->getField( $name ) );
+                       }
+               }
+
+               $mockFields = $this->getMockFields();
+
+               $item->setFields( $mockFields, false );
+
+               $this->assertFieldValues( $originalValues, $item );
+
+               $item->setFields( $mockFields, true );
+
+               $this->assertFieldValues( $mockFields, $item );
+       }
+
+       // TODO: test all of the methods!
+
+}
diff --git a/tests/phpunit/db/ORMTableTest.php 
b/tests/phpunit/db/ORMTableTest.php
new file mode 100644
index 0000000..60a55f2
--- /dev/null
+++ b/tests/phpunit/db/ORMTableTest.php
@@ -0,0 +1,130 @@
+<?php
+
+namespace EducationProgram\Tests;
+use EducationProgram\IORMRow;
+use EducationProgram\IORMTable;
+use EducationProgram\ORMTable;
+
+/**
+ * Abstract class to construct tests for ORMTable deriving classes.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.21
+ *
+ * @ingroup Test
+ *
+ * @group ORM
+ * @group Database
+ *
+ * @covers PageORMTableForTesting
+ *
+ * @author Jeroen De Dauw < [email protected] >
+ * @author Daniel Kinzler
+ */
+
+class ORMTableTest extends \MediaWikiTestCase {
+
+       /**
+        * @since 1.21
+        * @return string
+        */
+       protected function getTableClass() {
+               return 'PageORMTableForTesting';
+       }
+
+       /**
+        * @since 1.21
+        * @return IORMTable
+        */
+       public function getTable() {
+               $class = $this->getTableClass();
+
+               return $class::singleton();
+       }
+
+       /**
+        * @since 1.21
+        * @return string
+        */
+       public function getRowClass() {
+               return $this->getTable()->getRowClass();
+       }
+
+       /**
+        * @since 1.21
+        */
+       public function testSingleton() {
+               $class = $this->getTableClass();
+
+               $this->assertInstanceOf( $class, $class::singleton() );
+               $this->assertTrue( $class::singleton() === $class::singleton() 
);
+       }
+}
+
+/**
+ * Dummy ORM table for testing, reading Title objects from the page table.
+ *
+ * @since 1.21
+ */
+
+class PageORMTableForTesting extends ORMTable {
+
+       public function __construct() {
+               $this->fieldPrefix = 'page_';
+       }
+
+       /**
+        * @see ORMTable::getName
+        *
+        * @return string
+        */
+       public function getName() {
+               return 'page';
+       }
+
+       /**
+        * @see ORMTable::getRowClass
+        *
+        * @return string
+        */
+       public function getRowClass() {
+               return 'Title';
+       }
+
+       /**
+        * @see ORMTable::newRow
+        *
+        * @return IORMRow
+        */
+       public function newRow( array $data, $loadDefaults = false ) {
+               return \Title::makeTitle( $data['namespace'], $data['title'] );
+       }
+
+       /**
+        * @see ORMTable::getFields
+        *
+        * @return array
+        */
+       public function getFields() {
+               return array(
+                       'id' => 'int',
+                       'namespace' => 'int',
+                       'title' => 'str',
+               );
+       }
+}
diff --git a/tests/phpunit/db/TestORMRowTest.php 
b/tests/phpunit/db/TestORMRowTest.php
new file mode 100644
index 0000000..aba55ec
--- /dev/null
+++ b/tests/phpunit/db/TestORMRowTest.php
@@ -0,0 +1,212 @@
+<?php
+
+namespace EducationProgram\Tests;
+
+/**
+ * Tests for the TestORMRow class.
+ * TestORMRow is a dummy class to be able to test the abstract ORMRow class.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Test
+ * @author Jeroen De Dauw < [email protected] >
+ */
+use EducationProgram\IORMTable;
+use EducationProgram\ORMRow;
+use EducationProgram\ORMTable;
+
+/**
+ * The database group has as a side effect that temporal database tables are 
created. This makes
+ * it possible to test without poisoning a production database.
+ *
+ * Some of the tests takes more time, and needs therefor longer time before 
they can be aborted
+ * as non-functional. The reason why tests are aborted is assumed to be set up 
of temporal databases
+ * that hold the first tests in a pending state awaiting access to the 
database.
+ *
+ * @since 1.20
+ *
+ * @group ORM
+ * @group Database
+ * @group medium
+ * @covers TestORMRow
+ */
+class TestORMRowTest extends ORMRowTest {
+
+       /**
+        * @since 1.20
+        * @return string
+        */
+       protected function getRowClass() {
+               return 'TestORMRow';
+       }
+
+       /**
+        * @since 1.20
+        * @return IORMTable
+        */
+       protected function getTableInstance() {
+               return TestORMTable::singleton();
+       }
+
+       protected function setUp() {
+               parent::setUp();
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               $isSqlite = $GLOBALS['wgDBtype'] === 'sqlite';
+               $isPostgres = $GLOBALS['wgDBtype'] === 'postgres';
+
+               $idField = $isSqlite ? 'INTEGER' : 'INT unsigned';
+               $primaryKey = $isSqlite ? 'PRIMARY KEY AUTOINCREMENT' : 
'auto_increment PRIMARY KEY';
+
+               if ( $isPostgres ) {
+                       $dbw->query(
+                               'CREATE TABLE IF NOT EXISTS ' . 
$dbw->tableName( 'orm_test' ) . "(
+                                       test_id serial PRIMARY KEY,
+                                       test_name TEXT NOT NULL DEFAULT '',
+                                       test_age INTEGER NOT NULL DEFAULT 0,
+                                       test_height REAL NOT NULL DEFAULT 0,
+                                       test_awesome INTEGER NOT NULL DEFAULT 0,
+                                       test_stuff BYTEA,
+                                       test_moarstuff BYTEA,
+                                       test_time TIMESTAMPTZ
+                                       );",
+                                       __METHOD__
+                               );
+               } else {
+                       $dbw->query(
+                               'CREATE TABLE IF NOT EXISTS ' . 
$dbw->tableName( 'orm_test' ) . '(
+                                       test_id                    ' . $idField 
. '        NOT NULL ' . $primaryKey . ',
+                                       test_name                  VARCHAR(255) 
       NOT NULL,
+                                       test_age                   TINYINT 
unsigned    NOT NULL,
+                                       test_height                FLOAT        
       NOT NULL,
+                                       test_awesome               TINYINT 
unsigned    NOT NULL,
+                                       test_stuff                 BLOB         
       NOT NULL,
+                                       test_moarstuff             BLOB         
       NOT NULL,
+                                       test_time                  
varbinary(14)       NOT NULL
+                               );',
+                               __METHOD__
+                       );
+               }
+       }
+
+       protected function tearDown() {
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->dropTable( 'orm_test', __METHOD__ );
+
+               parent::tearDown();
+       }
+
+       public function constructorTestProvider() {
+               $dbw = wfGetDB( DB_MASTER );
+               return array(
+                       array(
+                               array(
+                                       'name' => 'Foobar',
+                                       'time' => $dbw->timestamp( 
'20120101020202' ),
+                                       'age' => 42,
+                                       'height' => 9000.1,
+                                       'awesome' => true,
+                                       'stuff' => array( 13, 11, 7, 5, 3, 2 ),
+                                       'moarstuff' => (object)array( 'foo' => 
'bar', 'bar' => array( 4, 2 ), 'baz' => true )
+                               ),
+                               true
+                       ),
+               );
+       }
+
+       /**
+        * @since 1.21
+        * @return array
+        */
+       protected function getMockValues() {
+               return array(
+                       'id' => 1,
+                       'str' => 'foobar4645645',
+                       'int' => 42,
+                       'float' => 4.2,
+                       'bool' => '',
+                       'array' => array( 42, 'foobar' ),
+                       'blob' => new stdClass()
+               );
+       }
+}
+
+class TestORMRow extends ORMRow {
+}
+
+class TestORMTable extends ORMTable {
+
+       public function __construct() {
+               $this->fieldPrefix = 'test_';
+       }
+
+       /**
+        * Returns the name of the database table objects of this type are 
stored in.
+        *
+        * @since 1.20
+        *
+        * @return string
+        */
+       public function getName() {
+               return 'orm_test';
+       }
+
+       /**
+        * Returns the name of a IORMRow implementing class that
+        * represents single rows in this table.
+        *
+        * @since 1.20
+        *
+        * @return string
+        */
+       public function getRowClass() {
+               return 'TestORMRow';
+       }
+
+       /**
+        * Returns an array with the fields and their types this object 
contains.
+        * This corresponds directly to the fields in the database, without 
prefix.
+        *
+        * field name => type
+        *
+        * Allowed types:
+        * * id
+        * * str
+        * * int
+        * * float
+        * * bool
+        * * array
+        * * blob
+        *
+        * @since 1.20
+        *
+        * @return array
+        */
+       public function getFields() {
+               return array(
+                       'id' => 'id',
+                       'name' => 'str',
+                       'age' => 'int',
+                       'height' => 'float',
+                       'awesome' => 'bool',
+                       'stuff' => 'array',
+                       'moarstuff' => 'blob',
+                       'time' => 'str', // TS_MW
+               );
+       }
+}
diff --git a/tests/phpunit/rows/CourseTest.php 
b/tests/phpunit/rows/CourseTest.php
index 8405e31..44815dd 100644
--- a/tests/phpunit/rows/CourseTest.php
+++ b/tests/phpunit/rows/CourseTest.php
@@ -22,7 +22,7 @@
         * Returns the name of the subclass of ORMRow that we're testing (in 
this
         * case, \EducationProgram\Course).
         *
-        * @see \ORMRowTest::getRowClass()
+        * @see ORMRowTest::getRowClass()
         * @since 0.4 alpha
         * @return string
         */
@@ -33,7 +33,7 @@
        /**
         * Returns the table whose rows are represented by the class we're 
testing.
         *
-        * @see \ORMRowTest::getTableInstance()
+        * @see ORMRowTest::getTableInstance()
         * @since 0.4 alpha
         * @return \EducationProgram\Courses
         */
@@ -46,7 +46,7 @@
         * of the class we're testing (in this case, \EducationProgram\Course).
         * The test instances we'll use will be created with these arguments.
         *
-        * @see \ORMRowTest::constructorTestProvider()
+        * @see ORMRowTest::constructorTestProvider()
         * @since 0.4 alpha
         * @return array
         */
diff --git a/tests/phpunit/rows/OrgTest.php b/tests/phpunit/rows/OrgTest.php
index 7d38332..c6f53ec 100644
--- a/tests/phpunit/rows/OrgTest.php
+++ b/tests/phpunit/rows/OrgTest.php
@@ -21,7 +21,7 @@
         * Returns the name of the subclass of ORMRow that we're testing (in 
this
         * case, \EducationProgram\Org).
         *
-        * @see \ORMRowTest::getRowClass()
+        * @see ORMRowTest::getRowClass()
         * @since 0.4 alpha
         * @return string
         */
@@ -32,7 +32,7 @@
        /**
         * Returns the table whose rows are represented by the class we're 
testing.
         *
-        * @see \ORMRowTest::getTableInstance()
+        * @see ORMRowTest::getTableInstance()
         * @since 0.4 alpha
         * @return \EducationProgram\Orgs
         */
@@ -45,7 +45,7 @@
         * of the class we're testing (in this case, \EducationProgram\Org).
         * The test instances we'll use will be created with these arguments.
         *
-        * @see \ORMRowTest::constructorTestProvider()
+        * @see ORMRowTest::constructorTestProvider()
         * @since 0.4 alpha
         * @return array
         */
@@ -74,7 +74,7 @@
         * is always munged to start with a capital, and the superclass
         * provides a string that doesn't.
         *
-        * @see \ORMRowTest::getMockValues()
+        * @see ORMRowTest::getMockValues()
         * @since 1.20
         * @return array
         */
diff --git a/tests/phpunit/rows/PageObjectTest.php 
b/tests/phpunit/rows/PageObjectTest.php
index 22c466d..25c2122 100644
--- a/tests/phpunit/rows/PageObjectTest.php
+++ b/tests/phpunit/rows/PageObjectTest.php
@@ -2,7 +2,7 @@
 
 namespace EducationProgram\Tests\Rows;
 
-abstract class PageObjectTest extends \ORMRowTest {
+abstract class PageObjectTest extends ORMRowTest {
        /**
         * Provides pairs of duplicate instances for testing the addition of an
         * instance with the same name/title as one that already exists.

-- 
To view, visit https://gerrit.wikimedia.org/r/246718
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Id223222aff7d375687bbf0126f3514d248660bab
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/EducationProgram
Gerrit-Branch: master
Gerrit-Owner: Reedy <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to