Merge branch 'MDL-65056-master' of https://github.com/sarjona/moodle
[moodle.git] / completion / data_object.php
CommitLineData
2be4d090 1<?php
836375ec
SH
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/>.
2be4d090 16
836375ec
SH
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
27defined('MOODLE_INTERNAL') || die();
2be4d090 28
dbfcf440
AB
29
30/**
31 * Trigger for the new data_object api.
32 *
33 * See data_object::__constructor
34 */
35define('DATA_OBJECT_FETCH_BY_KEY', 2);
36
2be4d090
MD
37/**
38 * A data abstraction object that holds methods and attributes
836375ec
SH
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
2be4d090
MD
45 */
46abstract class data_object {
836375ec 47
95dd54ee 48 /* @var string Table that the class maps to in the database */
2be4d090
MD
49 public $table;
50
95dd54ee 51 /* @var array Array of required table fields, must start with 'id'. */
2be4d090
MD
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);
836375ec 57 * @var array
2be4d090
MD
58 */
59 public $optional_fields = array();
60
dbfcf440
AB
61 /* @var Array of unique fields, used in where clauses and constructor */
62 public $unique_fields = array();
63
95dd54ee 64 /* @var int The primary key */
2be4d090
MD
65 public $id;
66
dbfcf440 67
2be4d090
MD
68 /**
69 * Constructor. Optionally (and by default) attempts to fetch corresponding row from DB.
836375ec 70 *
dbfcf440
AB
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
2be4d090 85 */
f55ff38a 86 public function __construct($params = null, $fetch = true) {
2be4d090 87
dbfcf440
AB
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']);
2be4d090 111 }
dbfcf440
AB
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 }
2be4d090 119
dbfcf440
AB
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);
2be4d090 125 } else {
dbfcf440
AB
126 // Apply defaults for optional fields, then data from constructor
127 self::set_properties($this, $this->optional_fields);
128 self::set_properties($this, $params);
2be4d090
MD
129 }
130 }
131
132 /**
133 * Makes sure all the optional fields are loaded.
836375ec 134 *
2be4d090
MD
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) {
4cc977a6 141 if (property_exists($this, $field)) {
2be4d090
MD
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.
2be4d090 154 *
836375ec
SH
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.
2be4d090 160 */
7c109ea3
PS
161 public static function fetch($params) {
162 throw new coding_exception('fetch() method needs to be overridden in each subclass of data_object');
163 }
2be4d090
MD
164
165 /**
166 * Finds and returns all data_object instances based on params.
167 *
836375ec
SH
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
7c109ea3 172 * @return array array of data_object instances or false if none found.
2be4d090 173 */
7c109ea3
PS
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 }
2be4d090
MD
177
178 /**
179 * Factory method - uses the parameters to retrieve matching instance from the DB.
836375ec
SH
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.
2be4d090
MD
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 print_error('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.
836375ec
SH
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
2be4d090
MD
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
a106fb81 216 $dbparams = array();
2be4d090
MD
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 = ? ";
a106fb81 225 $dbparams[] = $value;
2be4d090
MD
226 }
227 }
228
229 if (empty($wheresql)) {
230 $wheresql = '';
231 } else {
232 $wheresql = implode("AND", $wheresql);
233 }
234
235 global $DB;
a106fb81 236 if ($datas = $DB->get_records_select($table, $wheresql, $dbparams)) {
6142a4a5 237
2be4d090
MD
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 {
6142a4a5 247
2be4d090
MD
248 return false;
249 }
250 }
251
252 /**
253 * Updates this object in the Database, based on its object variables. ID must be set.
836375ec 254 *
95dd54ee 255 * @return bool success
2be4d090
MD
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.
836375ec 275 *
95dd54ee 276 * @return bool success
2be4d090
MD
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
836375ec
SH
299 *
300 * @return stdClass
2be4d090
MD
301 */
302 public function get_record_data() {
365a5941 303 $data = new stdClass();
2be4d090
MD
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.
836375ec 321 *
2be4d090
MD
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.
836375ec
SH
348 *
349 * @return bool True for success, false otherwise.
2be4d090
MD
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.
836375ec
SH
370 *
371 * @final
372 * @param data_object $instance
373 * @param array $params
2be4d090
MD
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 /**
6142a4a5
PS
385 * Called immediately after the object data has been inserted, updated, or
386 * deleted in the database. Default does nothing, can be overridden to
2be4d090
MD
387 * hook in special behaviour.
388 *
836375ec 389 * @param bool $deleted Set this to true if it has been deleted.
2be4d090 390 */
f55ff38a
AA
391 public function notify_changed($deleted) {
392 }
dbfcf440 393}