PageRenderTime 54ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/classes/persistent.php

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