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