Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
namespace core;
18
 
19
use coding_exception;
20
use invalid_parameter_exception;
21
use lang_string;
22
use ReflectionMethod;
23
use stdClass;
24
 
25
/**
26
 * Abstract class for core objects saved to the DB.
27
 *
28
 * @package    core
29
 * @copyright  2015 Damyon Wiese
30
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31
 */
32
abstract class persistent {
33
 
34
    /** @var string The table name. */
35
    const TABLE = null;
36
 
37
    /** @var array The model data. */
38
    private $data = array();
39
 
40
    /** @var array The list of validation errors. */
41
    private $errors = array();
42
 
43
    /** @var boolean If the data was already validated. */
44
    private $validated = false;
45
 
46
    /**
47
     * Create an instance of this class.
48
     *
49
     * @param int $id If set, this is the id of an existing record, used to load the data.
50
     * @param stdClass $record If set will be passed to {@link self::from_record()}.
51
     */
52
    public function __construct($id = 0, stdClass $record = null) {
53
        global $CFG;
54
 
55
        if ($id > 0) {
56
            $this->raw_set('id', $id);
57
            $this->read();
58
        }
59
        if (!empty($record)) {
60
            $this->from_record($record);
61
        }
62
        if ($CFG->debugdeveloper) {
63
            $this->verify_protected_methods();
64
        }
65
    }
66
 
67
    /**
68
     * This function is used to verify that custom getters and setters are declared as protected.
69
     *
70
     * Persistent properties should always be accessed via get('property') and set('property', 'value') which
71
     * will call the custom getter or setter if it exists. We do not want to allow inconsistent access to the properties.
72
     */
73
    final protected function verify_protected_methods() {
74
        $properties = static::properties_definition();
75
 
76
        foreach ($properties as $property => $definition) {
77
            $method = 'get_' . $property;
78
            if (method_exists($this, $method)) {
79
                $reflection = new ReflectionMethod($this, $method);
80
                if (!$reflection->isProtected()) {
81
                    throw new coding_exception('The method ' . get_class($this) . '::'. $method . ' should be protected.');
82
                }
83
            }
84
            $method = 'set_' . $property;
85
            if (method_exists($this, $method)) {
86
                $reflection = new ReflectionMethod($this, $method);
87
                if (!$reflection->isProtected()) {
88
                    throw new coding_exception('The method ' . get_class($this) . '::'. $method . ' should be protected.');
89
                }
90
            }
91
        }
92
    }
93
 
94
    /**
95
     * Data setter.
96
     *
97
     * This is the main setter for all the properties. Developers can implement their own setters (set_propertyname)
98
     * and they will be called by this function. Custom setters should call internal_set() to finally set the value.
99
     * Internally this is not used {@link self::to_record()} or
100
     * {@link self::from_record()} because the data is not expected to be validated or changed when reading/writing
101
     * raw records from the DB.
102
     *
103
     * @param  string $property The property name.
104
     * @return $this
105
     *
106
     * @throws coding_exception
107
     */
108
    final public function set($property, $value) {
109
        if (!static::has_property($property)) {
110
            throw new coding_exception('Unexpected property \'' . s($property) .'\' requested.');
111
        }
112
        $methodname = 'set_' . $property;
113
        if (method_exists($this, $methodname)) {
114
            $this->$methodname($value);
115
            return $this;
116
        }
117
        return $this->raw_set($property, $value);
118
    }
119
 
120
    /**
121
     * Data setter for multiple properties
122
     *
123
     * Internally calls {@see set} on each property
124
     *
125
     * @param array $values Array of property => value elements
126
     * @return $this
127
     */
128
    final public function set_many(array $values): self {
129
        foreach ($values as $property => $value) {
130
            $this->set($property, $value);
131
        }
132
        return $this;
133
    }
134
 
135
    /**
136
     * Data getter.
137
     *
138
     * This is the main getter for all the properties. Developers can implement their own getters (get_propertyname)
139
     * and they will be called by this function. Custom getters can use raw_get to get the raw value.
140
     * Internally this is not used by {@link self::to_record()} or
141
     * {@link self::from_record()} because the data is not expected to be validated or changed when reading/writing
142
     * raw records from the DB.
143
     *
144
     * @param  string $property The property name.
145
     * @return mixed
146
     */
147
    final public function get($property) {
148
        if (!static::has_property($property)) {
149
            throw new coding_exception('Unexpected property \'' . s($property) .'\' requested.');
150
        }
151
        $methodname = 'get_' . $property;
152
        if (method_exists($this, $methodname)) {
153
            return $this->$methodname();
154
        }
155
 
156
        $properties = static::properties_definition();
157
        // If property can be NULL and value is NULL it needs to return null.
158
        if ($properties[$property]['null'] === NULL_ALLOWED && $this->raw_get($property) === null) {
159
            return null;
160
        }
161
        // Deliberately cast boolean types as such, because clean_param will cast them to integer.
162
        if ($properties[$property]['type'] === PARAM_BOOL) {
163
            return (bool)$this->raw_get($property);
164
        }
165
 
166
        return clean_param($this->raw_get($property), $properties[$property]['type']);
167
    }
168
 
169
    /**
170
     * Internal Data getter.
171
     *
172
     * This is the main getter for all the properties. Developers can implement their own getters
173
     * but they should be calling {@link self::get()} in order to retrieve the value. Essentially
174
     * the getters defined by the developers would only ever be used as helper methods and will not
175
     * be called internally at this stage. In other words, do not expect {@link self::to_record()} or
176
     * {@link self::from_record()} to use them.
177
     *
178
     * This is protected because it is only for raw low level access to the data fields.
179
     * Note this function is named raw_get and not get_raw to avoid naming clashes with a property named raw.
180
     *
181
     * @param  string $property The property name.
182
     * @return mixed
183
     */
184
    final protected function raw_get($property) {
185
        if (!static::has_property($property)) {
186
            throw new coding_exception('Unexpected property \'' . s($property) .'\' requested.');
187
        }
188
        if (!array_key_exists($property, $this->data) && !static::is_property_required($property)) {
189
            $this->raw_set($property, static::get_property_default_value($property));
190
        }
191
        return isset($this->data[$property]) ? $this->data[$property] : null;
192
    }
193
 
194
    /**
195
     * Data setter.
196
     *
197
     * This is the main setter for all the properties. Developers can implement their own setters
198
     * but they should always be calling {@link self::set()} in order to set the value. Essentially
199
     * the setters defined by the developers are helper methods and will not be called internally
200
     * at this stage. In other words do not expect {@link self::to_record()} or
201
     * {@link self::from_record()} to use them.
202
     *
203
     * This is protected because it is only for raw low level access to the data fields.
204
     *
205
     * @param  string $property The property name.
206
     * @param  mixed $value The value.
207
     * @return $this
208
     */
209
    final protected function raw_set($property, $value) {
210
        if (!static::has_property($property)) {
211
            throw new coding_exception('Unexpected property \'' . s($property) .'\' requested.');
212
        }
213
        if (!array_key_exists($property, $this->data) || $this->data[$property] != $value) {
214
            // If the value is changing, we invalidate the model.
215
            $this->validated = false;
216
        }
217
        $this->data[$property] = $value;
218
 
219
        return $this;
220
    }
221
 
222
    /**
223
     * Return the custom definition of the properties of this model.
224
     *
225
     * Each property MUST be listed here.
226
     *
227
     * The result of this method is cached internally for the whole request.
228
     *
229
     * The 'default' value can be a Closure when its value may change during a single request.
230
     * For example if the default value is based on a $CFG property, then it should be wrapped in a closure
231
     * to avoid running into scenarios where the true value of $CFG is not reflected in the definition.
232
     * Do not abuse closures as they obviously add some overhead.
233
     *
234
     * Examples:
235
     *
236
     * array(
237
     *     'property_name' => array(
238
     *         'default' => 'Default value',        // When not set, the property is considered as required.
239
     *         'message' => new lang_string(...),   // Defaults to invalid data error message.
240
     *         'null' => NULL_ALLOWED,              // Defaults to NULL_NOT_ALLOWED. Takes NULL_NOW_ALLOWED or NULL_ALLOWED.
241
     *         'type' => PARAM_TYPE,                // Mandatory.
242
     *         'choices' => array(1, 2, 3)          // An array of accepted values.
243
     *     )
244
     * )
245
     *
246
     * array(
247
     *     'dynamic_property_name' => array(
248
     *         'default' => function() {
249
     *             return $CFG->something;
250
     *         },
251
     *         'type' => PARAM_INT,
252
     *     )
253
     * )
254
     *
255
     * @return array Where keys are the property names.
256
     */
257
    protected static function define_properties() {
258
        return array();
259
    }
260
 
261
    /**
262
     * Get the properties definition of this model..
263
     *
264
     * @return array
265
     */
266
    final public static function properties_definition() {
267
        global $CFG;
268
 
269
        static $cachedef = [];
270
        if (isset($cachedef[static::class])) {
271
            return $cachedef[static::class];
272
        }
273
 
274
        $cachedef[static::class] = static::define_properties();
275
        $def = &$cachedef[static::class];
276
        $def['id'] = array(
277
            'default' => 0,
278
            'type' => PARAM_INT,
279
        );
280
        $def['timecreated'] = array(
281
            'default' => 0,
282
            'type' => PARAM_INT,
283
        );
284
        $def['timemodified'] = array(
285
            'default' => 0,
286
            'type' => PARAM_INT
287
        );
288
        $def['usermodified'] = array(
289
            'default' => 0,
290
            'type' => PARAM_INT
291
        );
292
 
293
        // List of reserved property names. Mostly because we have methods (getters/setters) which would confict with them.
294
        // Think about backwards compability before adding new ones here!
295
        $reserved = array('errors', 'formatted_properties', 'records', 'records_select', 'property_default_value',
296
            'property_error_message', 'sql_fields');
297
 
298
        foreach ($def as $property => $definition) {
299
 
300
            // Ensures that the null property is always set.
301
            if (!array_key_exists('null', $definition)) {
302
                $def[$property]['null'] = NULL_NOT_ALLOWED;
303
            }
304
 
305
            // Warn the developers when they are doing something wrong.
306
            if ($CFG->debugdeveloper) {
307
                if (!array_key_exists('type', $definition)) {
308
                    throw new coding_exception('Missing type for: ' . $property);
309
 
310
                } else if (isset($definition['message']) && !($definition['message'] instanceof lang_string)) {
311
                    throw new coding_exception('Invalid error message for: ' . $property);
312
 
313
                } else if (in_array($property, $reserved)) {
314
                    throw new coding_exception('This property cannot be defined: ' . $property);
315
 
316
                }
317
            }
318
        }
319
 
320
        return $def;
321
    }
322
 
323
    /**
324
     * For a given record, return an array containing only those properties that are defined by the persistent
325
     *
326
     * @param stdClass $record
327
     * @return array
328
     */
329
    final public static function properties_filter(stdClass $record): array {
330
        return array_intersect_key((array) $record, static::properties_definition());
331
    }
332
 
333
    /**
334
     * Gets all the formatted properties.
335
     *
336
     * Formatted properties are properties which have a format associated with them.
337
     *
338
     * @return array Keys are property names, values are property format names.
339
     */
340
    final public static function get_formatted_properties() {
341
        $properties = static::properties_definition();
342
 
343
        $formatted = array();
344
        foreach ($properties as $property => $definition) {
345
            $propertyformat = $property . 'format';
346
            if (($definition['type'] == PARAM_RAW || $definition['type'] == PARAM_CLEANHTML)
347
                    && array_key_exists($propertyformat, $properties)
348
                    && $properties[$propertyformat]['type'] == PARAM_INT) {
349
                $formatted[$property] = $propertyformat;
350
            }
351
        }
352
 
353
        return $formatted;
354
    }
355
 
356
    /**
357
     * Gets the default value for a property.
358
     *
359
     * This assumes that the property exists.
360
     *
361
     * @param string $property The property name.
362
     * @return mixed
363
     */
364
    final protected static function get_property_default_value($property) {
365
        $properties = static::properties_definition();
366
        if (!isset($properties[$property]['default'])) {
367
            return null;
368
        }
369
        $value = $properties[$property]['default'];
370
        if ($value instanceof \Closure) {
371
            return $value();
372
        }
373
        return $value;
374
    }
375
 
376
    /**
377
     * Gets the error message for a property.
378
     *
379
     * This assumes that the property exists.
380
     *
381
     * @param string $property The property name.
382
     * @return lang_string
383
     */
384
    final protected static function get_property_error_message($property) {
385
        $properties = static::properties_definition();
386
        if (!isset($properties[$property]['message'])) {
387
            return new lang_string('invaliddata', 'error');
388
        }
389
        return $properties[$property]['message'];
390
    }
391
 
392
    /**
393
     * Returns whether or not a property was defined.
394
     *
395
     * @param  string $property The property name.
396
     * @return boolean
397
     */
398
    final public static function has_property($property) {
399
        $properties = static::properties_definition();
400
        return isset($properties[$property]);
401
    }
402
 
403
    /**
404
     * Returns whether or not a property is required.
405
     *
406
     * By definition a property with a default value is not required.
407
     *
408
     * @param  string $property The property name.
409
     * @return boolean
410
     */
411
    final public static function is_property_required($property) {
412
        $properties = static::properties_definition();
413
        return !array_key_exists('default', $properties[$property]);
414
    }
415
 
416
    /**
417
     * Populate this class with data from a DB record.
418
     *
419
     * Note that this does not use any custom setter because the data here is intended to
420
     * represent what is stored in the database.
421
     *
422
     * @param \stdClass $record A DB record.
423
     * @return static
424
     */
425
    final public function from_record(stdClass $record) {
426
        $record = static::properties_filter($record);
427
        foreach ($record as $property => $value) {
428
            $this->raw_set($property, $value);
429
        }
430
        return $this;
431
    }
432
 
433
    /**
434
     * Create a DB record from this class.
435
     *
436
     * Note that this does not use any custom getter because the data here is intended to
437
     * represent what is stored in the database.
438
     *
439
     * @return \stdClass
440
     */
441
    final public function to_record() {
442
        $data = new stdClass();
443
        $properties = static::properties_definition();
444
        foreach ($properties as $property => $definition) {
445
            $data->$property = $this->raw_get($property);
446
        }
447
        return $data;
448
    }
449
 
450
    /**
451
     * Load the data from the DB.
452
     *
453
     * @return static
454
     */
455
    final public function read() {
456
        global $DB;
457
 
458
        if ($this->get('id') <= 0) {
459
            throw new coding_exception('id is required to load');
460
        }
461
        $record = $DB->get_record(static::TABLE, array('id' => $this->get('id')), '*', MUST_EXIST);
462
        $this->from_record($record);
463
 
464
        // Validate the data as it comes from the database.
465
        $this->validated = true;
466
 
467
        return $this;
468
    }
469
 
470
    /**
471
     * Hook to execute before a create.
472
     *
473
     * Please note that at this stage the data has already been validated and therefore
474
     * any new data being set will not be validated before it is sent to the database.
475
     *
476
     * This is only intended to be used by child classes, do not put any logic here!
477
     *
478
     * @return void
479
     */
480
    protected function before_create() {
481
    }
482
 
483
    /**
484
     * Insert a record in the DB.
485
     *
486
     * @return static
487
     */
488
    final public function create() {
489
        global $DB, $USER;
490
 
491
        if ($this->raw_get('id')) {
492
            // The validation methods rely on the ID to know if we're updating or not, the ID should be
493
            // falsy whenever we are creating an object.
494
            throw new coding_exception('Cannot create an object that has an ID defined.');
495
        }
496
 
497
        if (!$this->is_valid()) {
498
            throw new invalid_persistent_exception($this->get_errors());
499
        }
500
 
501
        // Before create hook.
502
        $this->before_create();
503
 
504
        // We can safely set those values bypassing the validation because we know what we're doing.
505
        $now = time();
506
        $this->raw_set('timecreated', $now);
507
        $this->raw_set('timemodified', $now);
508
        $this->raw_set('usermodified', $USER->id);
509
 
510
        $record = $this->to_record();
511
        unset($record->id);
512
 
513
        $id = $DB->insert_record(static::TABLE, $record);
514
        $this->raw_set('id', $id);
515
 
516
        // We ensure that this is flagged as validated.
517
        $this->validated = true;
518
 
519
        // After create hook.
520
        $this->after_create();
521
 
522
        return $this;
523
    }
524
 
525
    /**
526
     * Hook to execute after a create.
527
     *
528
     * This is only intended to be used by child classes, do not put any logic here!
529
     *
530
     * @return void
531
     */
532
    protected function after_create() {
533
    }
534
 
535
    /**
536
     * Hook to execute before an update.
537
     *
538
     * Please note that at this stage the data has already been validated and therefore
539
     * any new data being set will not be validated before it is sent to the database.
540
     *
541
     * This is only intended to be used by child classes, do not put any logic here!
542
     *
543
     * @return void
544
     */
545
    protected function before_update() {
546
    }
547
 
548
    /**
549
     * Update the existing record in the DB.
550
     *
551
     * @return bool True on success.
552
     */
553
    final public function update() {
554
        global $DB, $USER;
555
 
556
        if ($this->raw_get('id') <= 0) {
557
            throw new coding_exception('id is required to update');
558
        } else if (!$this->is_valid()) {
559
            throw new invalid_persistent_exception($this->get_errors());
560
        }
561
 
562
        // Before update hook.
563
        $this->before_update();
564
 
565
        // We can safely set those values after the validation because we know what we're doing.
566
        $this->raw_set('timemodified', time());
567
        $this->raw_set('usermodified', $USER->id);
568
 
569
        $record = $this->to_record();
570
        unset($record->timecreated);
571
        $record = (array) $record;
572
 
573
        // Save the record.
574
        $result = $DB->update_record(static::TABLE, $record);
575
 
576
        // We ensure that this is flagged as validated.
577
        $this->validated = true;
578
 
579
        // After update hook.
580
        $this->after_update($result);
581
 
582
        return $result;
583
    }
584
 
585
    /**
586
     * Hook to execute after an update.
587
     *
588
     * This is only intended to be used by child classes, do not put any logic here!
589
     *
590
     * @param bool $result Whether or not the update was successful.
591
     * @return void
592
     */
593
    protected function after_update($result) {
594
    }
595
 
596
    /**
597
     * Saves the record to the database.
598
     *
599
     * If this record has an ID, then {@link self::update()} is called, otherwise {@link self::create()} is called.
600
     * Before and after hooks for create() or update() will be called appropriately.
601
     *
602
     * @return void
603
     */
604
    final public function save() {
605
        if ($this->raw_get('id') <= 0) {
606
            $this->create();
607
        } else {
608
            $this->update();
609
        }
610
    }
611
 
612
    /**
613
     * Hook to execute before a delete.
614
     *
615
     * This is only intended to be used by child classes, do not put any logic here!
616
     *
617
     * @return void
618
     */
619
    protected function before_delete() {
620
    }
621
 
622
    /**
623
     * Delete an entry from the database.
624
     *
625
     * @return bool True on success.
626
     */
627
    final public function delete() {
628
        global $DB;
629
 
630
        if ($this->raw_get('id') <= 0) {
631
            throw new coding_exception('id is required to delete');
632
        }
633
 
634
        // Hook before delete.
635
        $this->before_delete();
636
 
637
        $result = $DB->delete_records(static::TABLE, array('id' => $this->raw_get('id')));
638
 
639
        // Hook after delete.
640
        $this->after_delete($result);
641
 
642
        // Reset the ID to avoid any confusion, this also invalidates the model's data.
643
        if ($result) {
644
            $this->raw_set('id', 0);
645
        }
646
 
647
        return $result;
648
    }
649
 
650
    /**
651
     * Hook to execute after a delete.
652
     *
653
     * This is only intended to be used by child classes, do not put any logic here!
654
     *
655
     * @param bool $result Whether or not the delete was successful.
656
     * @return void
657
     */
658
    protected function after_delete($result) {
659
    }
660
 
661
    /**
662
     * Hook to execute before the validation.
663
     *
664
     * This hook will not affect the validation results in any way but is useful to
665
     * internally set properties which will need to be validated.
666
     *
667
     * This is only intended to be used by child classes, do not put any logic here!
668
     *
669
     * @return void
670
     */
671
    protected function before_validate() {
672
    }
673
 
674
    /**
675
     * Validates the data.
676
     *
677
     * Developers can implement addition validation by defining a method as follows. Note that
678
     * the method MUST return a lang_string() when there is an error, and true when the data is valid.
679
     *
680
     * protected function validate_propertyname($value) {
681
     *     if ($value !== 'My expected value') {
682
     *         return new lang_string('invaliddata', 'error');
683
     *     }
684
     *     return true
685
     * }
686
     *
687
     * It is OK to use other properties in your custom validation methods when you need to, however note
688
     * they might not have been validated yet, so try not to rely on them too much.
689
     *
690
     * Note that the validation methods should be protected. Validating just one field is not
691
     * recommended because of the possible dependencies between one field and another,also the
692
     * field ID can be used to check whether the object is being updated or created.
693
     *
694
     * When validating foreign keys the persistent should only check that the associated model
695
     * exists. The validation methods should not be used to check for a change in that relationship.
696
     * The API method setting the attributes on the model should be responsible for that.
697
     * E.g. On a course model, the method validate_categoryid will check that the category exists.
698
     * However, if a course can never be moved outside of its category it would be up to the calling
699
     * code to ensure that the category ID will not be altered.
700
     *
701
     * @return array|true Returns true when the validation passed, or an array of properties with errors.
702
     */
703
    final public function validate() {
704
        global $CFG;
705
 
706
        // Before validate hook.
707
        $this->before_validate();
708
 
709
        // If this object has not been validated yet.
710
        if ($this->validated !== true) {
711
 
712
            $errors = array();
713
            $properties = static::properties_definition();
714
            foreach ($properties as $property => $definition) {
715
 
716
                // Get the data, bypassing the potential custom getter which could alter the data.
717
                $value = $this->raw_get($property);
718
 
719
                // Check if the property is required.
720
                if ($value === null && static::is_property_required($property)) {
721
                    $errors[$property] = new lang_string('requiredelement', 'form');
722
                    continue;
723
                }
724
 
725
                // Check that type of value is respected.
726
                try {
727
                    if ($definition['type'] === PARAM_BOOL && $value === false) {
728
                        // Validate_param() does not like false with PARAM_BOOL, better to convert it to int.
729
                        $value = 0;
730
                    }
731
                    if ($definition['type'] === PARAM_CLEANHTML) {
732
                        // We silently clean for this type. It may introduce changes even to valid data.
733
                        $value = clean_param($value, PARAM_CLEANHTML);
734
                    }
735
                    validate_param($value, $definition['type'], $definition['null']);
736
                } catch (invalid_parameter_exception $e) {
737
                    $errors[$property] = static::get_property_error_message($property);
738
                    continue;
739
                }
740
 
741
                // Check that the value is part of a list of allowed values.
742
                if (isset($definition['choices']) && !in_array($value, $definition['choices'])) {
743
                    $errors[$property] = static::get_property_error_message($property);
744
                    continue;
745
                }
746
 
747
                // Call custom validation method.
748
                $method = 'validate_' . $property;
749
                if (method_exists($this, $method)) {
750
 
751
                    // Warn the developers when they are doing something wrong.
752
                    if ($CFG->debugdeveloper) {
753
                        $reflection = new ReflectionMethod($this, $method);
754
                        if (!$reflection->isProtected()) {
755
                            throw new coding_exception('The method ' . get_class($this) . '::'. $method . ' should be protected.');
756
                        }
757
                    }
758
 
759
                    $valid = $this->{$method}($value);
760
                    if ($valid !== true) {
761
                        if (!($valid instanceof lang_string)) {
762
                            throw new coding_exception('Unexpected error message.');
763
                        }
764
                        $errors[$property] = $valid;
765
                        continue;
766
                    }
767
                }
768
            }
769
 
770
            $this->validated = true;
771
            $this->errors = $errors;
772
        }
773
 
774
        return empty($this->errors) ? true : $this->errors;
775
    }
776
 
777
    /**
778
     * Returns whether or not the model is valid.
779
     *
780
     * @return boolean True when it is.
781
     */
782
    final public function is_valid() {
783
        return $this->validate() === true;
784
    }
785
 
786
    /**
787
     * Returns the validation errors.
788
     *
789
     * @return array
790
     */
791
    final public function get_errors() {
792
        $this->validate();
793
        return $this->errors;
794
    }
795
 
796
    /**
797
     * Extract a record from a row of data.
798
     *
799
     * Most likely used in combination with {@link self::get_sql_fields()}. This method is
800
     * simple enough to be used by non-persistent classes, keep that in mind when modifying it.
801
     *
802
     * e.g. persistent::extract_record($row, 'user'); should work.
803
     *
804
     * @param stdClass $row The row of data.
805
     * @param string $prefix The prefix the data fields are prefixed with, defaults to the table name followed by underscore.
806
     * @return stdClass The extracted data.
807
     */
808
    public static function extract_record($row, $prefix = null) {
809
        if ($prefix === null) {
810
            $prefix = str_replace('_', '', static::TABLE) . '_';
811
        }
812
        $prefixlength = strlen($prefix);
813
 
814
        $data = new stdClass();
815
        foreach ($row as $property => $value) {
816
            if (strpos($property, $prefix) === 0) {
817
                $propertyname = substr($property, $prefixlength);
818
                $data->$propertyname = $value;
819
            }
820
        }
821
 
822
        return $data;
823
    }
824
 
825
    /**
826
     * Load a list of records.
827
     *
828
     * @param array $filters Filters to apply.
829
     * @param string $sort Field to sort by.
830
     * @param string $order Sort order.
831
     * @param int $skip Limitstart.
832
     * @param int $limit Number of rows to return.
833
     *
834
     * @return static[]
835
     */
836
    public static function get_records($filters = array(), $sort = '', $order = 'ASC', $skip = 0, $limit = 0) {
837
        global $DB;
838
 
839
        $orderby = '';
840
        if (!empty($sort)) {
841
            $orderby = $sort . ' ' . $order;
842
        }
843
 
844
        $records = $DB->get_records(static::TABLE, $filters, $orderby, '*', $skip, $limit);
845
        $instances = array();
846
 
847
        foreach ($records as $record) {
848
            $newrecord = new static(0, $record);
849
            array_push($instances, $newrecord);
850
        }
851
        return $instances;
852
    }
853
 
854
    /**
855
     * Load a single record.
856
     *
857
     * @param array $filters Filters to apply.
858
     * @param int $strictness Similar to the internal DB get_record call, indicate whether a missing record should be
859
     *      ignored/return false ({@see IGNORE_MISSING}) or should cause an exception to be thrown ({@see MUST_EXIST})
860
     * @return false|static
861
     */
862
    public static function get_record(array $filters = [], int $strictness = IGNORE_MISSING) {
863
        global $DB;
864
 
865
        $record = $DB->get_record(static::TABLE, $filters, '*', $strictness);
866
        return $record ? new static(0, $record) : false;
867
    }
868
 
869
    /**
870
     * Load a list of records based on a select query.
871
     *
872
     * @param string $select
873
     * @param array $params
874
     * @param string $sort
875
     * @param string $fields
876
     * @param int $limitfrom
877
     * @param int $limitnum
878
     * @return static[]
879
     */
880
    public static function get_records_select($select, $params = null, $sort = '', $fields = '*', $limitfrom = 0, $limitnum = 0) {
881
        global $DB;
882
 
883
        $records = $DB->get_records_select(static::TABLE, $select, $params, $sort, $fields, $limitfrom, $limitnum);
884
 
885
        // We return class instances.
886
        $instances = array();
887
        foreach ($records as $key => $record) {
888
            $instances[$key] = new static(0, $record);
889
        }
890
 
891
        return $instances;
892
 
893
    }
894
 
895
    /**
896
     * Return the list of fields for use in a SELECT clause.
897
     *
898
     * Having the complete list of fields prefixed allows for multiple persistents to be fetched
899
     * in a single query. Use {@link self::extract_record()} to extract the records from the query result.
900
     *
901
     * @param string $alias The alias used for the table.
902
     * @param string $prefix The prefix to use for each field, defaults to the table name followed by underscore.
903
     * @return string The SQL fragment.
904
     */
905
    public static function get_sql_fields($alias, $prefix = null) {
906
        global $CFG;
907
        $fields = array();
908
 
909
        if ($prefix === null) {
910
            $prefix = str_replace('_', '', static::TABLE) . '_';
911
        }
912
 
913
        // Get the properties and move ID to the top.
914
        $properties = static::properties_definition();
915
        $id = $properties['id'];
916
        unset($properties['id']);
917
        $properties = array('id' => $id) + $properties;
918
 
919
        foreach ($properties as $property => $definition) {
920
            $as = $prefix . $property;
921
            $fields[] = $alias . '.' . $property . ' AS ' . $as;
922
 
923
            // Warn developers that the query will not always work.
924
            if ($CFG->debugdeveloper && strlen($as) > 30) {
925
                throw new coding_exception("The alias '$as' for column '$alias.$property' exceeds 30 characters" .
926
                    " and will therefore not work across all supported databases.");
927
            }
928
        }
929
 
930
        return implode(', ', $fields);
931
    }
932
 
933
    /**
934
     * Count a list of records.
935
     *
936
     * @param array $conditions An array of conditions.
937
     * @return int
938
     */
939
    public static function count_records(array $conditions = array()) {
940
        global $DB;
941
 
942
        $count = $DB->count_records(static::TABLE, $conditions);
943
        return $count;
944
    }
945
 
946
    /**
947
     * Count a list of records.
948
     *
949
     * @param string $select
950
     * @param array $params
951
     * @return int
952
     */
953
    public static function count_records_select($select, $params = null) {
954
        global $DB;
955
 
956
        $count = $DB->count_records_select(static::TABLE, $select, $params);
957
        return $count;
958
    }
959
 
960
    /**
961
     * Check if a record exists by ID.
962
     *
963
     * @param int $id Record ID.
964
     * @return bool
965
     */
966
    public static function record_exists($id) {
967
        global $DB;
968
        return $DB->record_exists(static::TABLE, array('id' => $id));
969
    }
970
 
971
    /**
972
     * Check if a records exists.
973
     *
974
     * @param string $select
975
     * @param array $params
976
     * @return bool
977
     */
978
    public static function record_exists_select($select, array $params = null) {
979
        global $DB;
980
        return $DB->record_exists_select(static::TABLE, $select, $params);
981
    }
982
 
983
}