Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body