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