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