MDL-81601 core_course: Add course index completion status behats
[moodle.git] / completion / data_object.php
blob730ae53010eb1e33307a0072e5c670a38a966610
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.
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/>.
17 /**
18 * Course completion critieria aggregation
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
27 defined('MOODLE_INTERNAL') || die();
30 /**
31 * Trigger for the new data_object api.
33 * See data_object::__constructor
35 define('DATA_OBJECT_FETCH_BY_KEY', 2);
37 /**
38 * A data abstraction object that holds methods and attributes
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
46 abstract class data_object {
48 /* @var string Table that the class maps to in the database */
49 public $table;
51 /* @var array Array of required table fields, must start with 'id'. */
52 public $required_fields = array('id');
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
59 public $optional_fields = array();
61 /* @var Array of unique fields, used in where clauses and constructor */
62 public $unique_fields = array();
64 /* @var int The primary key */
65 public $id;
67 /** @var int completed status. */
68 public $completedself;
71 /**
72 * Constructor. Optionally (and by default) attempts to fetch corresponding row from DB.
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
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.
82 * - array():
83 * load corresponding row from the database, using the columns listed in this array
84 * in the WHERE clause
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
89 public function __construct($params = null, $fetch = true) {
91 if (is_object($params)) {
92 throw new coding_exception('data_object params should be in the form of an array, not an object');
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;
101 // If fetch is false, do not load from database
102 if ($fetch === false) {
103 self::set_properties($this, $params);
104 return;
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));
112 else {
113 $where = array('id' => $params['id']);
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;
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);
136 * Makes sure all the optional fields are loaded.
138 * If id present (==instance exists in db) fetches data from db.
139 * Defaults are used for new instances.
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;
147 if (empty($this->id)) {
148 $this->$field = $default;
149 } else {
150 $this->$field = $DB->get_field($this->table, $field, array('id', $this->id));
156 * Finds and returns a data_object instance based on params.
158 * This function MUST be overridden by all deriving classes.
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.
164 public static function fetch($params) {
165 throw new coding_exception('fetch() method needs to be overridden in each subclass of data_object');
169 * Finds and returns all data_object instances based on params.
171 * This function MUST be overridden by all deriving classes.
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.
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');
182 * Factory method - uses the parameters to retrieve matching instance from the DB.
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.
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');
196 return reset($instances);
197 } else {
198 return false;
203 * Factory method - uses the parameters to retrieve all matching instances from the DB.
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
211 public static function fetch_all_helper($table, $classname, $params) {
212 $instance = new $classname();
214 $classvars = (array)$instance;
215 $params = (array)$params;
217 $wheresql = array();
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;
224 if (is_null($value)) {
225 $wheresql[] = " $var IS NULL ";
226 } else {
227 $wheresql[] = " $var = ? ";
228 $dbparams[] = $value;
232 if (empty($wheresql)) {
233 $wheresql = '';
234 } else {
235 $wheresql = implode("AND", $wheresql);
238 global $DB;
239 if ($datas = $DB->get_records_select($table, $wheresql, $dbparams)) {
241 $result = array();
242 foreach($datas as $data) {
243 $instance = new $classname();
244 self::set_properties($instance, $data);
245 $result[$instance->id] = $instance;
247 return $result;
249 } else {
251 return false;
256 * Updates this object in the Database, based on its object variables. ID must be set.
258 * @return bool success
260 public function update() {
261 global $DB;
263 if (empty($this->id)) {
264 debugging('Can not update data object, no id!');
265 return false;
268 $data = $this->get_record_data();
270 $DB->update_record($this->table, $data);
272 $this->notify_changed(false);
273 return true;
277 * Deletes this object from the database.
279 * @return bool success
281 public function delete() {
282 global $DB;
284 if (empty($this->id)) {
285 debugging('Can not delete data object, no id!');
286 return false;
289 $data = $this->get_record_data();
291 if ($DB->delete_records($this->table, array('id'=>$this->id))) {
292 $this->notify_changed(true);
293 return true;
295 } else {
296 return false;
301 * Returns object with fields and values that are defined in database
303 * @return stdClass
305 public function get_record_data() {
306 $data = new stdClass();
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;
317 return $data;
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.
325 * @return int PK ID if successful, false otherwise
327 public function insert() {
328 global $DB;
330 if (!empty($this->id)) {
331 debugging("Data object already exists!");
332 return false;
335 $data = $this->get_record_data();
337 $this->id = $DB->insert_record($this->table, $data);
339 // set all object properties from real db data
340 $this->update_from_db();
342 $this->notify_changed(false);
343 return $this->id;
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.
352 * @return bool True for success, false otherwise.
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;
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;
365 self::set_properties($this, $params);
367 return true;
371 * Given an associated array or object, cycles through each key/variable
372 * and assigns the value to the corresponding variable in this object.
374 * @final
375 * @param data_object $instance
376 * @param array $params
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;
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.
392 * @param bool $deleted Set this to true if it has been deleted.
394 public function notify_changed($deleted) {