[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/competency/classes/external/ -> exporter.php (source)

   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   * Generic exporter to take a stdClass and prepare it for return by webservice.
  19   *
  20   * @package    core_competency
  21   * @copyright  2015 Damyon Wiese
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  namespace core_competency\external;
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  require_once($CFG->libdir . '/externallib.php');
  28  
  29  use stdClass;
  30  use renderer_base;
  31  use context;
  32  use context_system;
  33  use coding_exception;
  34  use external_single_structure;
  35  use external_multiple_structure;
  36  use external_value;
  37  use external_format_value;
  38  
  39  /**
  40   * Generic exporter to take a stdClass and prepare it for return by webservice, or as the context for a template.
  41   *
  42   * templatable classes implementing export_for_template, should always use a standard exporter if it exists.
  43   * External functions should always use a standard exporter if it exists.
  44   *
  45   * @copyright  2015 Damyon Wiese
  46   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  47   */
  48  abstract class exporter {
  49  
  50      /** @var array $related List of related objects used to avoid DB queries. */
  51      protected $related = array();
  52  
  53      /** @var stdClass|array The data of this exporter. */
  54      protected $data = null;
  55  
  56      /**
  57       * Constructor - saves the persistent object, and the related objects.
  58       *
  59       * @param mixed $data - Either an stdClass or an array of values.
  60       * @param array $related - An optional list of pre-loaded objects related to this object.
  61       */
  62      public function __construct($data, $related = array()) {
  63          $this->data = $data;
  64          // Cache the valid related objects.
  65          foreach (static::define_related() as $key => $classname) {
  66              $isarray = false;
  67              $nullallowed = false;
  68  
  69              // Allow ? to mean null is allowed.
  70              if (substr($classname, -1) === '?') {
  71                  $classname = substr($classname, 0, -1);
  72                  $nullallowed = true;
  73              }
  74  
  75              // Allow [] to mean an array of values.
  76              if (substr($classname, -2) === '[]') {
  77                  $classname = substr($classname, 0, -2);
  78                  $isarray = true;
  79              }
  80  
  81              $missingdataerr = 'Exporter class is missing required related data: (' . get_called_class() . ') ';
  82  
  83              if ($nullallowed && array_key_exists($key, $related) && $related[$key] === null) {
  84                  $this->related[$key] = $related[$key];
  85  
  86              } else if ($isarray) {
  87                  if (array_key_exists($key, $related) && is_array($related[$key])) {
  88                      foreach ($related[$key] as $index => $value) {
  89                          if (!$value instanceof $classname) {
  90                              throw new coding_exception($missingdataerr . $key . ' => ' . $classname . '[]');
  91                          }
  92                      }
  93                      $this->related[$key] = $related[$key];
  94                  } else {
  95                      throw new coding_exception($missingdataerr . $key . ' => ' . $classname . '[]');
  96                  }
  97  
  98              } else {
  99                  if (array_key_exists($key, $related) && $related[$key] instanceof $classname) {
 100                      $this->related[$key] = $related[$key];
 101                  } else {
 102                      throw new coding_exception($missingdataerr . $key . ' => ' . $classname);
 103                  }
 104              }
 105          }
 106      }
 107  
 108      /**
 109       * Function to export the renderer data in a format that is suitable for a
 110       * mustache template. This means raw records are generated as in to_record,
 111       * but all strings are correctly passed through external_format_text (or external_format_string).
 112       *
 113       * @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
 114       * @return stdClass
 115       */
 116      final public function export(renderer_base $output) {
 117          $data = new stdClass();
 118          $properties = self::read_properties_definition();
 119          $context = $this->get_context();
 120          $values = (array) $this->data;
 121  
 122          $othervalues = $this->get_other_values($output);
 123          if (array_intersect_key($values, $othervalues)) {
 124              // Attempt to replace a standard property.
 125              throw new coding_exception('Cannot override a standard property value.');
 126          }
 127          $values += $othervalues;
 128          $record = (object) $values;
 129  
 130          foreach ($properties as $property => $definition) {
 131              if (isset($data->$property)) {
 132                  // This happens when we have already defined the format properties.
 133                  continue;
 134              } else if (!property_exists($record, $property) && array_key_exists('default', $definition)) {
 135                  // We have a default value for this property.
 136                  $record->$property = $definition['default'];
 137              } else if (!property_exists($record, $property) && !empty($definition['optional'])) {
 138                  // Fine, this property can be omitted.
 139                  continue;
 140              } else if (!property_exists($record, $property)) {
 141                  // Whoops, we got something that wasn't defined.
 142                  throw new coding_exception('Unexpected property ' . $property);
 143              }
 144  
 145              $data->$property = $record->$property;
 146  
 147              // If the field is PARAM_RAW and has a format field.
 148              if ($propertyformat = self::get_format_field($properties, $property)) {
 149                  if (!property_exists($record, $propertyformat)) {
 150                      // Whoops, we got something that wasn't defined.
 151                      throw new coding_exception('Unexpected property ' . $propertyformat);
 152                  }
 153                  $format = $record->$propertyformat;
 154                  list($text, $format) = external_format_text($data->$property, $format, $context->id, 'core_competency', '', 0);
 155                  $data->$property = $text;
 156                  $data->$propertyformat = $format;
 157  
 158              } else if ($definition['type'] === PARAM_TEXT) {
 159                  if (!empty($definition['multiple'])) {
 160                      foreach ($data->$property as $key => $value) {
 161                          $data->$property[$key] = external_format_string($value, $context->id);
 162                      }
 163                  } else {
 164                      $data->$property = external_format_string($data->$property, $context->id);
 165                  }
 166              }
 167          }
 168  
 169          return $data;
 170      }
 171  
 172      /**
 173       * Function to guess the correct context, falling back to system context.
 174       *
 175       * @return context
 176       */
 177      protected function get_context() {
 178          $context = null;
 179          if (isset($this->related['context']) && $this->related['context'] instanceof context) {
 180              $context = $this->related['context'];
 181          } else {
 182              $context = context_system::instance();
 183          }
 184          return $context;
 185      }
 186  
 187      /**
 188       * Get the additional values to inject while exporting.
 189       *
 190       * These are additional generated values that are not passed in through $data
 191       * to the exporter. For a persistent exporter - these are generated values that
 192       * do not exist in the persistent class. For your convenience the format_text or
 193       * format_string functions do not need to be applied to PARAM_TEXT fields,
 194       * it will be done automatically during export.
 195       *
 196       * These values are only used when returning data via {@link self::export()},
 197       * they are not used when generating any of the different external structures.
 198       *
 199       * Note: These must be defined in {@link self::define_other_properties()}.
 200       *
 201       * @param renderer_base $output The renderer.
 202       * @return array Keys are the property names, values are their values.
 203       */
 204      protected function get_other_values(renderer_base $output) {
 205          return array();
 206      }
 207  
 208      /**
 209       * Get the read properties definition of this exporter. Read properties combines the
 210       * default properties from the model (persistent or stdClass) with the properties defined
 211       * by {@link self::define_other_properties()}.
 212       *
 213       * @return array Keys are the property names, and value their definition.
 214       */
 215      final public static function read_properties_definition() {
 216          $properties = static::properties_definition();
 217          $customprops = static::define_other_properties();
 218          foreach ($customprops as $property => $definition) {
 219              // Ensures that null is set to its default.
 220              if (!isset($definition['null'])) {
 221                  $customprops[$property]['null'] = NULL_NOT_ALLOWED;
 222              }
 223          }
 224          $properties += $customprops;
 225          return $properties;
 226      }
 227  
 228      /**
 229       * Get the properties definition of this exporter used for create, and update structures.
 230       * The read structures are returned by: {@link self::read_properties_definition()}.
 231       *
 232       * @return array Keys are the property names, and value their definition.
 233       */
 234      final public static function properties_definition() {
 235          $properties = static::define_properties();
 236          foreach ($properties as $property => $definition) {
 237              // Ensures that null is set to its default.
 238              if (!isset($definition['null'])) {
 239                  $properties[$property]['null'] = NULL_NOT_ALLOWED;
 240              }
 241          }
 242          return $properties;
 243      }
 244  
 245      /**
 246       * Return the list of additional properties used only for display.
 247       *
 248       * Additional properties are only ever used for the read structure, and during
 249       * export of the persistent data.
 250       *
 251       * The format of the array returned by this method has to match the structure
 252       * defined in {@link \core_competency\persistent::define_properties()}. The display properties
 253       * can however do some more fancy things. They can define 'multiple' => true to wrap
 254       * values in an external_multiple_structure automatically - or they can define the
 255       * type as a nested array of more properties in order to generate a nested
 256       * external_single_structure.
 257       *
 258       * You can specify an array of values by including a 'multiple' => true array value. This
 259       * will result in a nested external_multiple_structure.
 260       * E.g.
 261       *
 262       *       'arrayofbools' => array(
 263       *           'type' => PARAM_BOOL,
 264       *           'multiple' => true
 265       *       ),
 266       *
 267       * You can return a nested array in the type field, which will result in a nested external_single_structure.
 268       * E.g.
 269       *      'competency' => array(
 270       *          'type' => competency_exporter::read_properties_definition()
 271       *       ),
 272       *
 273       * Other properties can be specifically marked as optional, in which case they do not need
 274       * to be included in the export in {@link self::get_other_values()}. This is useful when exporting
 275       * a substructure which cannot be set as null due to webservices protocol constraints.
 276       * E.g.
 277       *      'competency' => array(
 278       *          'type' => competency_exporter::read_properties_definition(),
 279       *          'optional' => true
 280       *       ),
 281       *
 282       * @return array
 283       */
 284      protected static function define_other_properties() {
 285          return array();
 286      }
 287  
 288      /**
 289       * Return the list of properties.
 290       *
 291       * The format of the array returned by this method has to match the structure
 292       * defined in {@link \core_competency\persistent::define_properties()}.
 293       *
 294       * @return array
 295       */
 296      protected static function define_properties() {
 297          return array();
 298      }
 299  
 300      /**
 301       * Returns a list of objects that are related to this persistent.
 302       *
 303       * Only objects listed here can be cached in this object.
 304       *
 305       * The class name can be suffixed:
 306       * - with [] to indicate an array of values.
 307       * - with ? to indicate that 'null' is allowed.
 308       *
 309       * @return array of 'propertyname' => array('type' => classname, 'required' => true)
 310       */
 311      protected static function define_related() {
 312          return array();
 313      }
 314  
 315      /**
 316       * Get the context structure.
 317       *
 318       * @return external_single_structure
 319       */
 320      final protected static function get_context_structure() {
 321          return array(
 322              'contextid' => new external_value(PARAM_INT, 'The context id', VALUE_OPTIONAL),
 323              'contextlevel' => new external_value(PARAM_ALPHA, 'The context level', VALUE_OPTIONAL),
 324              'instanceid' => new external_value(PARAM_INT, 'The Instance id', VALUE_OPTIONAL),
 325          );
 326      }
 327  
 328      /**
 329       * Get the format field name.
 330       *
 331       * @param  array $definitions List of properties definitions.
 332       * @param  string $property The name of the property that may have a format field.
 333       * @return bool|string False, or the name of the format property.
 334       */
 335      final protected static function get_format_field($definitions, $property) {
 336          $formatproperty = $property . 'format';
 337          if ($definitions[$property]['type'] == PARAM_RAW && isset($definitions[$formatproperty])
 338                  && $definitions[$formatproperty]['type'] == PARAM_INT) {
 339              return $formatproperty;
 340          }
 341          return false;
 342      }
 343  
 344      /**
 345       * Get the format structure.
 346       *
 347       * @param  string $property   The name of the property on which the format applies.
 348       * @param  array  $definition The definition of the format property.
 349       * @param  int    $required   Constant VALUE_*.
 350       * @return external_format_value
 351       */
 352      final protected static function get_format_structure($property, $definition, $required = VALUE_REQUIRED) {
 353          if (array_key_exists('default', $definition)) {
 354              $required = VALUE_DEFAULT;
 355          }
 356          return new external_format_value($property, $required);
 357      }
 358  
 359      /**
 360       * Returns the create structure.
 361       *
 362       * @return external_single_structure
 363       */
 364      final public static function get_create_structure() {
 365          $properties = self::properties_definition();
 366          $returns = array();
 367  
 368          foreach ($properties as $property => $definition) {
 369              if ($property == 'id') {
 370                  // The can not be set on create.
 371                  continue;
 372  
 373              } else if (isset($returns[$property]) && substr($property, -6) === 'format') {
 374                  // We've already treated the format.
 375                  continue;
 376              }
 377  
 378              $required = VALUE_REQUIRED;
 379              $default = null;
 380  
 381              // We cannot use isset here because we want to detect nulls.
 382              if (array_key_exists('default', $definition)) {
 383                  $required = VALUE_DEFAULT;
 384                  $default = $definition['default'];
 385              }
 386  
 387              // Magically treat the contextid fields.
 388              if ($property == 'contextid') {
 389                  if (isset($properties['context'])) {
 390                      throw new coding_exception('There cannot be a context and a contextid column');
 391                  }
 392                  $returns += self::get_context_structure();
 393  
 394              } else {
 395                  $returns[$property] = new external_value($definition['type'], $property, $required, $default, $definition['null']);
 396  
 397                  // Magically treat the format properties.
 398                  if ($formatproperty = self::get_format_field($properties, $property)) {
 399                      if (isset($returns[$formatproperty])) {
 400                          throw new coding_exception('The format for \'' . $property . '\' is already defined.');
 401                      }
 402                      $returns[$formatproperty] = self::get_format_structure($property,
 403                          $properties[$formatproperty], VALUE_REQUIRED);
 404                  }
 405              }
 406          }
 407  
 408          return new external_single_structure($returns);
 409      }
 410  
 411      /**
 412       * Returns the read structure.
 413       *
 414       * @return external_single_structure
 415       */
 416      final public static function get_read_structure() {
 417          $properties = self::read_properties_definition();
 418  
 419          return self::get_read_structure_from_properties($properties);
 420      }
 421  
 422      /**
 423       * Returns the read structure from a set of properties (recursive).
 424       *
 425       * @param array $properties The properties.
 426       * @param int $required Whether is required.
 427       * @param mixed $default The default value.
 428       * @return external_single_structure
 429       */
 430      final protected static function get_read_structure_from_properties($properties, $required = VALUE_REQUIRED, $default = null) {
 431          $returns = array();
 432          foreach ($properties as $property => $definition) {
 433              if (isset($returns[$property]) && substr($property, -6) === 'format') {
 434                  // We've already treated the format.
 435                  continue;
 436              }
 437              $thisvalue = null;
 438  
 439              $type = $definition['type'];
 440              $proprequired = VALUE_REQUIRED;
 441              $propdefault = null;
 442              if (array_key_exists('default', $definition)) {
 443                  $propdefault = $definition['default'];
 444              }
 445              if (array_key_exists('optional', $definition)) {
 446                  // Mark as optional. Note that this should only apply to "reading" "other" properties.
 447                  $proprequired = VALUE_OPTIONAL;
 448              }
 449  
 450              if (is_array($type)) {
 451                  // This is a nested array of more properties.
 452                  $thisvalue = self::get_read_structure_from_properties($type, $proprequired, $propdefault);
 453              } else {
 454                  if ($definition['type'] == PARAM_TEXT) {
 455                      // PARAM_TEXT always becomes PARAM_RAW because filters may be applied.
 456                      $type = PARAM_RAW;
 457                  }
 458                  $thisvalue = new external_value($type, $property, $proprequired, $propdefault, $definition['null']);
 459              }
 460              if (!empty($definition['multiple'])) {
 461                  $returns[$property] = new external_multiple_structure($thisvalue, '', $proprequired, $propdefault);
 462              } else {
 463                  $returns[$property] = $thisvalue;
 464  
 465                  // Magically treat the format properties (not possible for arrays).
 466                  if ($formatproperty = self::get_format_field($properties, $property)) {
 467                      if (isset($returns[$formatproperty])) {
 468                          throw new coding_exception('The format for \'' . $property . '\' is already defined.');
 469                      }
 470                      $returns[$formatproperty] = self::get_format_structure($property, $properties[$formatproperty]);
 471                  }
 472              }
 473          }
 474  
 475          return new external_single_structure($returns, '', $required, $default);
 476      }
 477  
 478      /**
 479       * Returns the update structure.
 480       *
 481       * This structure can never be included at the top level for an external function signature
 482       * because it contains optional parameters.
 483       *
 484       * @return external_single_structure
 485       */
 486      final public static function get_update_structure() {
 487          $properties = self::properties_definition();
 488          $returns = array();
 489  
 490          foreach ($properties as $property => $definition) {
 491              if (isset($returns[$property]) && substr($property, -6) === 'format') {
 492                  // We've already treated the format.
 493                  continue;
 494              }
 495  
 496              $default = null;
 497              $required = VALUE_OPTIONAL;
 498              if ($property == 'id') {
 499                  $required = VALUE_REQUIRED;
 500              }
 501  
 502              // Magically treat the contextid fields.
 503              if ($property == 'contextid') {
 504                  if (isset($properties['context'])) {
 505                      throw new coding_exception('There cannot be a context and a contextid column');
 506                  }
 507                  $returns += self::get_context_structure();
 508  
 509              } else {
 510                  $returns[$property] = new external_value($definition['type'], $property, $required, $default, $definition['null']);
 511  
 512                  // Magically treat the format properties.
 513                  if ($formatproperty = self::get_format_field($properties, $property)) {
 514                      if (isset($returns[$formatproperty])) {
 515                          throw new coding_exception('The format for \'' . $property . '\' is already defined.');
 516                      }
 517                      $returns[$formatproperty] = self::get_format_structure($property,
 518                          $properties[$formatproperty], VALUE_OPTIONAL);
 519                  }
 520              }
 521          }
 522  
 523          return new external_single_structure($returns);
 524      }
 525  
 526  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1