Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Course completion critieria aggregation
  19   *
  20   * @package core_completion
  21   * @category completion
  22   * @copyright 2009 Catalyst IT Ltd
  23   * @author Aaron Barnes <aaronb@catalyst.net.nz>
  24   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  
  30  /**
  31   * Trigger for the new data_object api.
  32   *
  33   * See data_object::__constructor
  34   */
  35  define('DATA_OBJECT_FETCH_BY_KEY',  2);
  36  
  37  /**
  38   * A data abstraction object that holds methods and attributes
  39   *
  40   * @package core_completion
  41   * @category completion
  42   * @copyright 2009 Catalyst IT Ltd
  43   * @author Aaron Barnes <aaronb@catalyst.net.nz>
  44   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  45   */
  46  abstract class data_object {
  47  
  48      /* @var string Table that the class maps to in the database */
  49      public $table;
  50  
  51      /* @var array Array of required table fields, must start with 'id'. */
  52      public $required_fields = array('id');
  53  
  54      /**
  55       * Array of optional fields with default values - usually long text information that is not always needed.
  56       * If you want to create an instance without optional fields use: new data_object($only_required_fields, false);
  57       * @var array
  58       */
  59      public $optional_fields = array();
  60  
  61      /* @var Array of unique fields, used in where clauses and constructor */
  62      public $unique_fields = array();
  63  
  64      /* @var int The primary key */
  65      public $id;
  66  
  67      /** @var int completed status. */
  68      public $completedself;
  69  
  70  
  71      /**
  72       * Constructor. Optionally (and by default) attempts to fetch corresponding row from DB.
  73       *
  74       * If $fetch is not false, there are a few different things that can happen:
  75       * - true:
  76       *   load corresponding row from the database, using $params as the WHERE clause
  77       *
  78       * - DATA_OBJECT_FETCH_BY_KEY:
  79       *  load corresponding row from the database, using only the $id in the WHERE clause (if set),
  80       *  otherwise using the columns listed in $this->unique_fields.
  81       *
  82       * - array():
  83       *   load corresponding row from the database, using the columns listed in this array
  84       *   in the WHERE clause
  85       *
  86       * @param   array   $params     required parameters and their values for this data object
  87       * @param   mixed   $fetch      if false, do not attempt to fetch from the database, otherwise see notes
  88       */
  89      public function __construct($params = null, $fetch = true) {
  90  
  91          if (is_object($params)) {
  92              throw new coding_exception('data_object params should be in the form of an array, not an object');
  93          }
  94  
  95          // If no params given, apply defaults for optional fields
  96          if (empty($params) || !is_array($params)) {
  97              self::set_properties($this, $this->optional_fields);
  98              return;
  99          }
 100  
 101          // If fetch is false, do not load from database
 102          if ($fetch === false) {
 103              self::set_properties($this, $params);
 104              return;
 105          }
 106  
 107          // Compose where clause only from fields in unique_fields
 108          if ($fetch === DATA_OBJECT_FETCH_BY_KEY && !empty($this->unique_fields)) {
 109              if (empty($params['id'])) {
 110                  $where = array_intersect_key($params, array_flip($this->unique_fields));
 111              }
 112              else {
 113                  $where = array('id' => $params['id']);
 114              }
 115          // Compose where clause from given field names
 116          } else if (is_array($fetch) && !empty($fetch)) {
 117              $where = array_intersect_key($params, array_flip($fetch));
 118          // Use entire params array for where clause
 119          } else {
 120              $where = $params;
 121          }
 122  
 123          // Attempt to load from database
 124          if ($data = $this->fetch($where)) {
 125              // Apply data from database, then data sent to constructor
 126              self::set_properties($this, $data);
 127              self::set_properties($this, $params);
 128          } else {
 129              // Apply defaults for optional fields, then data from constructor
 130              self::set_properties($this, $this->optional_fields);
 131              self::set_properties($this, $params);
 132          }
 133      }
 134  
 135      /**
 136       * Makes sure all the optional fields are loaded.
 137       *
 138       * If id present (==instance exists in db) fetches data from db.
 139       * Defaults are used for new instances.
 140       */
 141      public function load_optional_fields() {
 142          global $DB;
 143          foreach ($this->optional_fields as $field=>$default) {
 144              if (property_exists($this, $field)) {
 145                  continue;
 146              }
 147              if (empty($this->id)) {
 148                  $this->$field = $default;
 149              } else {
 150                  $this->$field = $DB->get_field($this->table, $field, array('id', $this->id));
 151              }
 152          }
 153      }
 154  
 155      /**
 156       * Finds and returns a data_object instance based on params.
 157       *
 158       * This function MUST be overridden by all deriving classes.
 159       *
 160       * @param array $params associative arrays varname => value
 161       * @throws coding_exception This function MUST be overridden
 162       * @return data_object instance  of data_object or false if none found.
 163       */
 164      public static function fetch($params) {
 165          throw new coding_exception('fetch() method needs to be overridden in each subclass of data_object');
 166      }
 167  
 168      /**
 169       * Finds and returns all data_object instances based on params.
 170       *
 171       * This function MUST be overridden by all deriving classes.
 172       *
 173       * @param array $params associative arrays varname => value
 174       * @throws coding_exception This function MUST be overridden
 175       * @return array array of data_object instances or false if none found.
 176       */
 177      public static function fetch_all($params) {
 178          throw new coding_exception('fetch_all() method needs to be overridden in each subclass of data_object');
 179      }
 180  
 181      /**
 182       * Factory method - uses the parameters to retrieve matching instance from the DB.
 183       *
 184       * @final
 185       * @param string $table The table name to fetch from
 186       * @param string $classname The class that you want the result instantiated as
 187       * @param array $params Any params required to select the desired row
 188       * @return object Instance of $classname or false.
 189       */
 190      protected static function fetch_helper($table, $classname, $params) {
 191          if ($instances = self::fetch_all_helper($table, $classname, $params)) {
 192              if (count($instances) > 1) {
 193                  // we should not tolerate any errors here - problems might appear later
 194                  throw new \moodle_exception('morethanonerecordinfetch', 'debug');
 195              }
 196              return reset($instances);
 197          } else {
 198              return false;
 199          }
 200      }
 201  
 202      /**
 203       * Factory method - uses the parameters to retrieve all matching instances from the DB.
 204       *
 205       * @final
 206       * @param string $table The table name to fetch from
 207       * @param string $classname The class that you want the result instantiated as
 208       * @param array $params Any params required to select the desired row
 209       * @return mixed array of object instances or false if not found
 210       */
 211      public static function fetch_all_helper($table, $classname, $params) {
 212          $instance = new $classname();
 213  
 214          $classvars = (array)$instance;
 215          $params    = (array)$params;
 216  
 217          $wheresql = array();
 218  
 219          $dbparams = array();
 220          foreach ($params as $var=>$value) {
 221              if (!in_array($var, $instance->required_fields) and !array_key_exists($var, $instance->optional_fields)) {
 222                  continue;
 223              }
 224              if (is_null($value)) {
 225                  $wheresql[] = " $var IS NULL ";
 226              } else {
 227                  $wheresql[] = " $var = ? ";
 228                  $dbparams[] = $value;
 229              }
 230          }
 231  
 232          if (empty($wheresql)) {
 233              $wheresql = '';
 234          } else {
 235              $wheresql = implode("AND", $wheresql);
 236          }
 237  
 238          global $DB;
 239          if ($datas = $DB->get_records_select($table, $wheresql, $dbparams)) {
 240  
 241              $result = array();
 242              foreach($datas as $data) {
 243                  $instance = new $classname();
 244                  self::set_properties($instance, $data);
 245                  $result[$instance->id] = $instance;
 246              }
 247              return $result;
 248  
 249          } else {
 250  
 251              return false;
 252          }
 253      }
 254  
 255      /**
 256       * Updates this object in the Database, based on its object variables. ID must be set.
 257       *
 258       * @return bool success
 259       */
 260      public function update() {
 261          global $DB;
 262  
 263          if (empty($this->id)) {
 264              debugging('Can not update data object, no id!');
 265              return false;
 266          }
 267  
 268          $data = $this->get_record_data();
 269  
 270          $DB->update_record($this->table, $data);
 271  
 272          $this->notify_changed(false);
 273          return true;
 274      }
 275  
 276      /**
 277       * Deletes this object from the database.
 278       *
 279       * @return bool success
 280       */
 281      public function delete() {
 282          global $DB;
 283  
 284          if (empty($this->id)) {
 285              debugging('Can not delete data object, no id!');
 286              return false;
 287          }
 288  
 289          $data = $this->get_record_data();
 290  
 291          if ($DB->delete_records($this->table, array('id'=>$this->id))) {
 292              $this->notify_changed(true);
 293              return true;
 294  
 295          } else {
 296              return false;
 297          }
 298      }
 299  
 300      /**
 301       * Returns object with fields and values that are defined in database
 302       *
 303       * @return stdClass
 304       */
 305      public function get_record_data() {
 306          $data = new stdClass();
 307  
 308          foreach ($this as $var=>$value) {
 309              if (in_array($var, $this->required_fields) or array_key_exists($var, $this->optional_fields)) {
 310                  if (is_object($value) or is_array($value)) {
 311                      debugging("Incorrect property '$var' found when inserting data object");
 312                  } else {
 313                      $data->$var = $value;
 314                  }
 315              }
 316          }
 317          return $data;
 318      }
 319  
 320      /**
 321       * Records this object in the Database, sets its id to the returned value, and returns that value.
 322       * If successful this function also fetches the new object data from database and stores it
 323       * in object properties.
 324       *
 325       * @return int PK ID if successful, false otherwise
 326       */
 327      public function insert() {
 328          global $DB;
 329  
 330          if (!empty($this->id)) {
 331              debugging("Data object already exists!");
 332              return false;
 333          }
 334  
 335          $data = $this->get_record_data();
 336  
 337          $this->id = $DB->insert_record($this->table, $data);
 338  
 339          // set all object properties from real db data
 340          $this->update_from_db();
 341  
 342          $this->notify_changed(false);
 343          return $this->id;
 344      }
 345  
 346      /**
 347       * Using this object's id field, fetches the matching record in the DB, and looks at
 348       * each variable in turn. If the DB has different data, the db's data is used to update
 349       * the object. This is different from the update() function, which acts on the DB record
 350       * based on the object.
 351       *
 352       * @return bool True for success, false otherwise.
 353       */
 354      public function update_from_db() {
 355          if (empty($this->id)) {
 356              debugging("The object could not be used in its state to retrieve a matching record from the DB, because its id field is not set.");
 357              return false;
 358          }
 359          global $DB;
 360          if (!$params = $DB->get_record($this->table, array('id' => $this->id))) {
 361              debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!");
 362              return false;
 363          }
 364  
 365          self::set_properties($this, $params);
 366  
 367          return true;
 368      }
 369  
 370      /**
 371       * Given an associated array or object, cycles through each key/variable
 372       * and assigns the value to the corresponding variable in this object.
 373       *
 374       * @final
 375       * @param data_object $instance
 376       * @param array $params
 377       */
 378      public static function set_properties(&$instance, $params) {
 379          $params = (array) $params;
 380          foreach ($params as $var => $value) {
 381              if (in_array($var, $instance->required_fields) or array_key_exists($var, $instance->optional_fields)) {
 382                  $instance->$var = $value;
 383              }
 384          }
 385      }
 386  
 387      /**
 388       * Called immediately after the object data has been inserted, updated, or
 389       * deleted in the database. Default does nothing, can be overridden to
 390       * hook in special behaviour.
 391       *
 392       * @param bool $deleted Set this to true if it has been deleted.
 393       */
 394      public function notify_changed($deleted) {
 395      }
 396  }