Merge branch 'MDL-33189-master-1' of git://git.luns.net.uk/moodle
[moodle.git] / lib / completion / data_object.php
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/>.
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  */
27 defined('MOODLE_INTERNAL') || die();
29 /**
30  * A data abstraction object that holds methods and attributes
31  *
32  * @package core_completion
33  * @category completion
34  * @copyright 2009 Catalyst IT Ltd
35  * @author Aaron Barnes <aaronb@catalyst.net.nz>
36  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  */
38 abstract class data_object {
40     /* @var string Table that the class maps to in the database */
41     public $table;
43     /* @var array Array of required table fields, must start with 'id'. */
44     public $required_fields = array('id');
46     /**
47      * Array of optional fields with default values - usually long text information that is not always needed.
48      * If you want to create an instance without optional fields use: new data_object($only_required_fields, false);
49      * @var array
50      */
51     public $optional_fields = array();
53     /* @var int The primary key */
54     public $id;
56     /**
57      * Constructor. Optionally (and by default) attempts to fetch corresponding row from DB.
58      *
59      * @param array $params an array with required parameters for this data object.
60      * @param bool $fetch Whether to fetch corresponding row from DB or not,
61      *        optional fields might not be defined if false used
62      */
63     public function __construct($params = null, $fetch = true) {
64         if (!empty($params) and (is_array($params) or is_object($params))) {
65             if ($fetch) {
66                 if ($data = $this->fetch($params)) {
67                     self::set_properties($this, $data);
68                 } else {
69                     self::set_properties($this, $this->optional_fields);//apply defaults for optional fields
70                     self::set_properties($this, $params);
71                 }
73             } else {
74                 self::set_properties($this, $params);
75             }
77         } else {
78             self::set_properties($this, $this->optional_fields);//apply defaults for optional fields
79         }
80     }
82     /**
83      * Makes sure all the optional fields are loaded.
84      *
85      * If id present (==instance exists in db) fetches data from db.
86      * Defaults are used for new instances.
87      */
88     public function load_optional_fields() {
89         global $DB;
90         foreach ($this->optional_fields as $field=>$default) {
91             if (property_exists($this, $field)) {
92                 continue;
93             }
94             if (empty($this->id)) {
95                 $this->$field = $default;
96             } else {
97                 $this->$field = $DB->get_field($this->table, $field, array('id', $this->id));
98             }
99         }
100     }
102     /**
103      * Finds and returns a data_object instance based on params.
104      *
105      * This function MUST be overridden by all deriving classes.
106      *
107      * @param array $params associative arrays varname => value
108      * @throws coding_exception This function MUST be overridden
109      * @return data_object instance  of data_object or false if none found.
110      */
111     public static function fetch($params) {
112         throw new coding_exception('fetch() method needs to be overridden in each subclass of data_object');
113     }
115     /**
116      * Finds and returns all data_object instances based on params.
117      *
118      * This function MUST be overridden by all deriving classes.
119      *
120      * @param array $params associative arrays varname => value
121      * @throws coding_exception This function MUST be overridden
122      * @return array array of data_object instances or false if none found.
123      */
124     public static function fetch_all($params) {
125         throw new coding_exception('fetch_all() method needs to be overridden in each subclass of data_object');
126     }
128     /**
129      * Factory method - uses the parameters to retrieve matching instance from the DB.
130      *
131      * @final
132      * @param string $table The table name to fetch from
133      * @param string $classname The class that you want the result instantiated as
134      * @param array $params Any params required to select the desired row
135      * @return object Instance of $classname or false.
136      */
137     protected static function fetch_helper($table, $classname, $params) {
138         if ($instances = self::fetch_all_helper($table, $classname, $params)) {
139             if (count($instances) > 1) {
140                 // we should not tolerate any errors here - problems might appear later
141                 print_error('morethanonerecordinfetch','debug');
142             }
143             return reset($instances);
144         } else {
145             return false;
146         }
147     }
149     /**
150      * Factory method - uses the parameters to retrieve all matching instances from the DB.
151      *
152      * @final
153      * @param string $table The table name to fetch from
154      * @param string $classname The class that you want the result instantiated as
155      * @param array $params Any params required to select the desired row
156      * @return mixed array of object instances or false if not found
157      */
158     public static function fetch_all_helper($table, $classname, $params) {
159         $instance = new $classname();
161         $classvars = (array)$instance;
162         $params    = (array)$params;
164         $wheresql = array();
166         foreach ($params as $var=>$value) {
167             if (!in_array($var, $instance->required_fields) and !array_key_exists($var, $instance->optional_fields)) {
168                 continue;
169             }
170             if (is_null($value)) {
171                 $wheresql[] = " $var IS NULL ";
172             } else {
173                 $wheresql[] = " $var = ? ";
174                 $params[] = $value;
175             }
176         }
178         if (empty($wheresql)) {
179             $wheresql = '';
180         } else {
181             $wheresql = implode("AND", $wheresql);
182         }
184         global $DB;
185         if ($datas = $DB->get_records_select($table, $wheresql, $params)) {
187             $result = array();
188             foreach($datas as $data) {
189                 $instance = new $classname();
190                 self::set_properties($instance, $data);
191                 $result[$instance->id] = $instance;
192             }
193             return $result;
195         } else {
197             return false;
198         }
199     }
201     /**
202      * Updates this object in the Database, based on its object variables. ID must be set.
203      *
204      * @return bool success
205      */
206     public function update() {
207         global $DB;
209         if (empty($this->id)) {
210             debugging('Can not update data object, no id!');
211             return false;
212         }
214         $data = $this->get_record_data();
216         $DB->update_record($this->table, $data);
218         $this->notify_changed(false);
219         return true;
220     }
222     /**
223      * Deletes this object from the database.
224      *
225      * @return bool success
226      */
227     public function delete() {
228         global $DB;
230         if (empty($this->id)) {
231             debugging('Can not delete data object, no id!');
232             return false;
233         }
235         $data = $this->get_record_data();
237         if ($DB->delete_records($this->table, array('id'=>$this->id))) {
238             $this->notify_changed(true);
239             return true;
241         } else {
242             return false;
243         }
244     }
246     /**
247      * Returns object with fields and values that are defined in database
248      *
249      * @return stdClass
250      */
251     public function get_record_data() {
252         $data = new stdClass();
254         foreach ($this as $var=>$value) {
255             if (in_array($var, $this->required_fields) or array_key_exists($var, $this->optional_fields)) {
256                 if (is_object($value) or is_array($value)) {
257                     debugging("Incorrect property '$var' found when inserting data object");
258                 } else {
259                     $data->$var = $value;
260                 }
261             }
262         }
263         return $data;
264     }
266     /**
267      * Records this object in the Database, sets its id to the returned value, and returns that value.
268      * If successful this function also fetches the new object data from database and stores it
269      * in object properties.
270      *
271      * @return int PK ID if successful, false otherwise
272      */
273     public function insert() {
274         global $DB;
276         if (!empty($this->id)) {
277             debugging("Data object already exists!");
278             return false;
279         }
281         $data = $this->get_record_data();
283         $this->id = $DB->insert_record($this->table, $data);
285         // set all object properties from real db data
286         $this->update_from_db();
288         $this->notify_changed(false);
289         return $this->id;
290     }
292     /**
293      * Using this object's id field, fetches the matching record in the DB, and looks at
294      * each variable in turn. If the DB has different data, the db's data is used to update
295      * the object. This is different from the update() function, which acts on the DB record
296      * based on the object.
297      *
298      * @return bool True for success, false otherwise.
299      */
300     public function update_from_db() {
301         if (empty($this->id)) {
302             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.");
303             return false;
304         }
305         global $DB;
306         if (!$params = $DB->get_record($this->table, array('id' => $this->id))) {
307             debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!");
308             return false;
309         }
311         self::set_properties($this, $params);
313         return true;
314     }
316     /**
317      * Given an associated array or object, cycles through each key/variable
318      * and assigns the value to the corresponding variable in this object.
319      *
320      * @final
321      * @param data_object $instance
322      * @param array $params
323      */
324     public static function set_properties(&$instance, $params) {
325         $params = (array) $params;
326         foreach ($params as $var => $value) {
327             if (in_array($var, $instance->required_fields) or array_key_exists($var, $instance->optional_fields)) {
328                 $instance->$var = $value;
329             }
330         }
331     }
333     /**
334      * Called immediately after the object data has been inserted, updated, or
335      * deleted in the database. Default does nothing, can be overridden to
336      * hook in special behaviour.
337      *
338      * @param bool $deleted Set this to true if it has been deleted.
339      */
340     public function notify_changed($deleted) {
341     }