Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   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  
  68      /**
  69       * Constructor. Optionally (and by default) attempts to fetch corresponding row from DB.
  70       *
  71       * If $fetch is not false, there are a few different things that can happen:
  72       * - true:
  73       *   load corresponding row from the database, using $params as the WHERE clause
  74       *
  75       * - DATA_OBJECT_FETCH_BY_KEY:
  76       *  load corresponding row from the database, using only the $id in the WHERE clause (if set),
  77       *  otherwise using the columns listed in $this->unique_fields.
  78       *
  79       * - array():
  80       *   load corresponding row from the database, using the columns listed in this array
  81       *   in the WHERE clause
  82       *
  83       * @param   array   $params     required parameters and their values for this data object
  84       * @param   mixed   $fetch      if false, do not attempt to fetch from the database, otherwise see notes
  85       */
  86      public function __construct($params = null, $fetch = true) {
  87  
  88          if (is_object($params)) {
  89              throw new coding_exception('data_object params should be in the form of an array, not an object');
  90          }
  91  
  92          // If no params given, apply defaults for optional fields
  93          if (empty($params) || !is_array($params)) {
  94              self::set_properties($this, $this->optional_fields);
  95              return;
  96          }
  97  
  98          // If fetch is false, do not load from database
  99          if ($fetch === false) {
 100              self::set_properties($this, $params);
 101              return;
 102          }
 103  
 104          // Compose where clause only from fields in unique_fields
 105          if ($fetch === DATA_OBJECT_FETCH_BY_KEY && !empty($this->unique_fields)) {
 106              if (empty($params['id'])) {
 107                  $where = array_intersect_key($params, array_flip($this->unique_fields));
 108              }
 109              else {
 110                  $where = array('id' => $params['id']);
 111              }
 112          // Compose where clause from given field names
 113          } else if (is_array($fetch) && !empty($fetch)) {
 114              $where = array_intersect_key($params, array_flip($fetch));
 115          // Use entire params array for where clause
 116          } else {
 117              $where = $params;
 118          }
 119  
 120          // Attempt to load from database
 121          if ($data = $this->fetch($where)) {
 122              // Apply data from database, then data sent to constructor
 123              self::set_properties($this, $data);
 124              self::set_properties($this, $params);
 125          } else {
 126              // Apply defaults for optional fields, then data from constructor
 127              self::set_properties($this, $this->optional_fields);
 128              self::set_properties($this, $params);
 129          }
 130      }
 131  
 132      /**
 133       * Makes sure all the optional fields are loaded.
 134       *
 135       * If id present (==instance exists in db) fetches data from db.
 136       * Defaults are used for new instances.
 137       */
 138      public function load_optional_fields() {
 139          global $DB;
 140          foreach ($this->optional_fields as $field=>$default) {
 141              if (property_exists($this, $field)) {
 142                  continue;
 143              }
 144              if (empty($this->id)) {
 145                  $this->$field = $default;
 146              } else {
 147                  $this->$field = $DB->get_field($this->table, $field, array('id', $this->id));
 148              }
 149          }
 150      }
 151  
 152      /**
 153       * Finds and returns a data_object instance based on params.
 154       *
 155       * This function MUST be overridden by all deriving classes.
 156       *
 157       * @param array $params associative arrays varname => value
 158       * @throws coding_exception This function MUST be overridden
 159       * @return data_object instance  of data_object or false if none found.
 160       */
 161      public static function fetch($params) {
 162          throw new coding_exception('fetch() method needs to be overridden in each subclass of data_object');
 163      }
 164  
 165      /**
 166       * Finds and returns all data_object instances based on params.
 167       *
 168       * This function MUST be overridden by all deriving classes.
 169       *
 170       * @param array $params associative arrays varname => value
 171       * @throws coding_exception This function MUST be overridden
 172       * @return array array of data_object instances or false if none found.
 173       */
 174      public static function fetch_all($params) {
 175          throw new coding_exception('fetch_all() method needs to be overridden in each subclass of data_object');
 176      }
 177  
 178      /**
 179       * Factory method - uses the parameters to retrieve matching instance from the DB.
 180       *
 181       * @final
 182       * @param string $table The table name to fetch from
 183       * @param string $classname The class that you want the result instantiated as
 184       * @param array $params Any params required to select the desired row
 185       * @return object Instance of $classname or false.
 186       */
 187      protected static function fetch_helper($table, $classname, $params) {
 188          if ($instances = self::fetch_all_helper($table, $classname, $params)) {
 189              if (count($instances) > 1) {
 190                  // we should not tolerate any errors here - problems might appear later
 191                  throw new \moodle_exception('morethanonerecordinfetch', 'debug');
 192              }
 193              return reset($instances);
 194          } else {
 195              return false;
 196          }
 197      }
 198  
 199      /**
 200       * Factory method - uses the parameters to retrieve all matching instances from the DB.
 201       *
 202       * @final
 203       * @param string $table The table name to fetch from
 204       * @param string $classname The class that you want the result instantiated as
 205       * @param array $params Any params required to select the desired row
 206       * @return mixed array of object instances or false if not found
 207       */
 208      public static function fetch_all_helper($table, $classname, $params) {
 209          $instance = new $classname();
 210  
 211          $classvars = (array)$instance;
 212          $params    = (array)$params;
 213  
 214          $wheresql = array();
 215  
 216          $dbparams = array();
 217          foreach ($params as $var=>$value) {
 218              if (!in_array($var, $instance->required_fields) and !array_key_exists($var, $instance->optional_fields)) {
 219                  continue;
 220              }
 221              if (is_null($value)) {
 222                  $wheresql[] = " $var IS NULL ";
 223              } else {
 224                  $wheresql[] = " $var = ? ";
 225                  $dbparams[] = $value;
 226              }
 227          }
 228  
 229          if (empty($wheresql)) {
 230              $wheresql = '';
 231          } else {
 232              $wheresql = implode("AND", $wheresql);
 233          }
 234  
 235          global $DB;
 236          if ($datas = $DB->get_records_select($table, $wheresql, $dbparams)) {
 237  
 238              $result = array();
 239              foreach($datas as $data) {
 240                  $instance = new $classname();
 241                  self::set_properties($instance, $data);
 242                  $result[$instance->id] = $instance;
 243              }
 244              return $result;
 245  
 246          } else {
 247  
 248              return false;
 249          }
 250      }
 251  
 252      /**
 253       * Updates this object in the Database, based on its object variables. ID must be set.
 254       *
 255       * @return bool success
 256       */
 257      public function update() {
 258          global $DB;
 259  
 260          if (empty($this->id)) {
 261              debugging('Can not update data object, no id!');
 262              return false;
 263          }
 264  
 265          $data = $this->get_record_data();
 266  
 267          $DB->update_record($this->table, $data);
 268  
 269          $this->notify_changed(false);
 270          return true;
 271      }
 272  
 273      /**
 274       * Deletes this object from the database.
 275       *
 276       * @return bool success
 277       */
 278      public function delete() {
 279          global $DB;
 280  
 281          if (empty($this->id)) {
 282              debugging('Can not delete data object, no id!');
 283              return false;
 284          }
 285  
 286          $data = $this->get_record_data();
 287  
 288          if ($DB->delete_records($this->table, array('id'=>$this->id))) {
 289              $this->notify_changed(true);
 290              return true;
 291  
 292          } else {
 293              return false;
 294          }
 295      }
 296  
 297      /**
 298       * Returns object with fields and values that are defined in database
 299       *
 300       * @return stdClass
 301       */
 302      public function get_record_data() {
 303          $data = new stdClass();
 304  
 305          foreach ($this as $var=>$value) {
 306              if (in_array($var, $this->required_fields) or array_key_exists($var, $this->optional_fields)) {
 307                  if (is_object($value) or is_array($value)) {
 308                      debugging("Incorrect property '$var' found when inserting data object");
 309                  } else {
 310                      $data->$var = $value;
 311                  }
 312              }
 313          }
 314          return $data;
 315      }
 316  
 317      /**
 318       * Records this object in the Database, sets its id to the returned value, and returns that value.
 319       * If successful this function also fetches the new object data from database and stores it
 320       * in object properties.
 321       *
 322       * @return int PK ID if successful, false otherwise
 323       */
 324      public function insert() {
 325          global $DB;
 326  
 327          if (!empty($this->id)) {
 328              debugging("Data object already exists!");
 329              return false;
 330          }
 331  
 332          $data = $this->get_record_data();
 333  
 334          $this->id = $DB->insert_record($this->table, $data);
 335  
 336          // set all object properties from real db data
 337          $this->update_from_db();
 338  
 339          $this->notify_changed(false);
 340          return $this->id;
 341      }
 342  
 343      /**
 344       * Using this object's id field, fetches the matching record in the DB, and looks at
 345       * each variable in turn. If the DB has different data, the db's data is used to update
 346       * the object. This is different from the update() function, which acts on the DB record
 347       * based on the object.
 348       *
 349       * @return bool True for success, false otherwise.
 350       */
 351      public function update_from_db() {
 352          if (empty($this->id)) {
 353              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.");
 354              return false;
 355          }
 356          global $DB;
 357          if (!$params = $DB->get_record($this->table, array('id' => $this->id))) {
 358              debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!");
 359              return false;
 360          }
 361  
 362          self::set_properties($this, $params);
 363  
 364          return true;
 365      }
 366  
 367      /**
 368       * Given an associated array or object, cycles through each key/variable
 369       * and assigns the value to the corresponding variable in this object.
 370       *
 371       * @final
 372       * @param data_object $instance
 373       * @param array $params
 374       */
 375      public static function set_properties(&$instance, $params) {
 376          $params = (array) $params;
 377          foreach ($params as $var => $value) {
 378              if (in_array($var, $instance->required_fields) or array_key_exists($var, $instance->optional_fields)) {
 379                  $instance->$var = $value;
 380              }
 381          }
 382      }
 383  
 384      /**
 385       * Called immediately after the object data has been inserted, updated, or
 386       * deleted in the database. Default does nothing, can be overridden to
 387       * hook in special behaviour.
 388       *
 389       * @param bool $deleted Set this to true if it has been deleted.
 390       */
 391      public function notify_changed($deleted) {
 392      }
 393  }