Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  /*
   3   * Copyright 2011 Google Inc.
   4   *
   5   * Licensed under the Apache License, Version 2.0 (the "License");
   6   * you may not use this file except in compliance with the License.
   7   * You may obtain a copy of the License at
   8   *
   9   *     http://www.apache.org/licenses/LICENSE-2.0
  10   *
  11   * Unless required by applicable law or agreed to in writing, software
  12   * distributed under the License is distributed on an "AS IS" BASIS,
  13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14   * See the License for the specific language governing permissions and
  15   * limitations under the License.
  16   */
  17  
  18  /**
  19   * This class defines attributes, valid values, and usage which is generated
  20   * from a given json schema.
  21   * http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5
  22   *
  23   */
  24  class Google_Model implements ArrayAccess
  25  {
  26    /**
  27     * If you need to specify a NULL JSON value, use Google_Model::NULL_VALUE
  28     * instead - it will be replaced when converting to JSON with a real null.
  29     */
  30    const NULL_VALUE = "{}gapi-php-null";
  31    protected $internal_gapi_mappings = array();
  32    protected $modelData = array();
  33    protected $processed = array();
  34  
  35    /**
  36     * Polymorphic - accepts a variable number of arguments dependent
  37     * on the type of the model subclass.
  38     */
  39    final public function __construct()
  40    {
  41      if (func_num_args() == 1 && is_array(func_get_arg(0))) {
  42        // Initialize the model with the array's contents.
  43        $array = func_get_arg(0);
  44        $this->mapTypes($array);
  45      }
  46      $this->gapiInit();
  47    }
  48  
  49    /**
  50     * Getter that handles passthrough access to the data array, and lazy object creation.
  51     * @param string $key Property name.
  52     * @return mixed The value if any, or null.
  53     */
  54    public function __get($key)
  55    {
  56      $keyTypeName = $this->keyType($key);
  57      $keyDataType = $this->dataType($key);
  58      if (isset($this->$keyTypeName) && !isset($this->processed[$key])) {
  59        if (isset($this->modelData[$key])) {
  60          $val = $this->modelData[$key];
  61        } else if (isset($this->$keyDataType) &&
  62            ($this->$keyDataType == 'array' || $this->$keyDataType == 'map')) {
  63          $val = array();
  64        } else {
  65          $val = null;
  66        }
  67  
  68        if ($this->isAssociativeArray($val)) {
  69          if (isset($this->$keyDataType) && 'map' == $this->$keyDataType) {
  70            foreach ($val as $arrayKey => $arrayItem) {
  71                $this->modelData[$key][$arrayKey] =
  72                  $this->createObjectFromName($keyTypeName, $arrayItem);
  73            }
  74          } else {
  75            $this->modelData[$key] = $this->createObjectFromName($keyTypeName, $val);
  76          }
  77        } else if (is_array($val)) {
  78          $arrayObject = array();
  79          foreach ($val as $arrayIndex => $arrayItem) {
  80            $arrayObject[$arrayIndex] =
  81              $this->createObjectFromName($keyTypeName, $arrayItem);
  82          }
  83          $this->modelData[$key] = $arrayObject;
  84        }
  85        $this->processed[$key] = true;
  86      }
  87  
  88      return isset($this->modelData[$key]) ? $this->modelData[$key] : null;
  89    }
  90  
  91    /**
  92     * Initialize this object's properties from an array.
  93     *
  94     * @param array $array Used to seed this object's properties.
  95     * @return void
  96     */
  97    protected function mapTypes($array)
  98    {
  99      // Hard initialise simple types, lazy load more complex ones.
 100      foreach ($array as $key => $val) {
 101        if ( !property_exists($this, $this->keyType($key)) &&
 102          property_exists($this, $key)) {
 103            $this->$key = $val;
 104            unset($array[$key]);
 105        } elseif (property_exists($this, $camelKey = Google_Utils::camelCase($key))) {
 106            // This checks if property exists as camelCase, leaving it in array as snake_case
 107            // in case of backwards compatibility issues.
 108            $this->$camelKey = $val;
 109        }
 110      }
 111      $this->modelData = $array;
 112    }
 113  
 114    /**
 115     * Blank initialiser to be used in subclasses to do  post-construction initialisation - this
 116     * avoids the need for subclasses to have to implement the variadics handling in their
 117     * constructors.
 118     */
 119    protected function gapiInit()
 120    {
 121      return;
 122    }
 123  
 124    /**
 125     * Create a simplified object suitable for straightforward
 126     * conversion to JSON. This is relatively expensive
 127     * due to the usage of reflection, but shouldn't be called
 128     * a whole lot, and is the most straightforward way to filter.
 129     */
 130    public function toSimpleObject()
 131    {
 132      $object = new stdClass();
 133  
 134      // Process all other data.
 135      foreach ($this->modelData as $key => $val) {
 136        $result = $this->getSimpleValue($val);
 137        if ($result !== null) {
 138          $object->$key = $this->nullPlaceholderCheck($result);
 139        }
 140      }
 141  
 142      // Process all public properties.
 143      $reflect = new ReflectionObject($this);
 144      $props = $reflect->getProperties(ReflectionProperty::IS_PUBLIC);
 145      foreach ($props as $member) {
 146        $name = $member->getName();
 147        $result = $this->getSimpleValue($this->$name);
 148        if ($result !== null) {
 149          $name = $this->getMappedName($name);
 150          $object->$name = $this->nullPlaceholderCheck($result);
 151        }
 152      }
 153  
 154      return $object;
 155    }
 156  
 157    /**
 158     * Handle different types of values, primarily
 159     * other objects and map and array data types.
 160     */
 161    private function getSimpleValue($value)
 162    {
 163      if ($value instanceof Google_Model) {
 164        return $value->toSimpleObject();
 165      } else if (is_array($value)) {
 166        $return = array();
 167        foreach ($value as $key => $a_value) {
 168          $a_value = $this->getSimpleValue($a_value);
 169          if ($a_value !== null) {
 170            $key = $this->getMappedName($key);
 171            $return[$key] = $this->nullPlaceholderCheck($a_value);
 172          }
 173        }
 174        return $return;
 175      }
 176      return $value;
 177    }
 178    
 179    /**
 180     * Check whether the value is the null placeholder and return true null.
 181     */
 182    private function nullPlaceholderCheck($value)
 183    {
 184      if ($value === self::NULL_VALUE) {
 185        return null;
 186      }
 187      return $value;
 188    }
 189  
 190    /**
 191     * If there is an internal name mapping, use that.
 192     */
 193    private function getMappedName($key)
 194    {
 195      if (isset($this->internal_gapi_mappings) &&
 196          isset($this->internal_gapi_mappings[$key])) {
 197        $key = $this->internal_gapi_mappings[$key];
 198      }
 199      return $key;
 200    }
 201  
 202    /**
 203     * Returns true only if the array is associative.
 204     * @param array $array
 205     * @return bool True if the array is associative.
 206     */
 207    protected function isAssociativeArray($array)
 208    {
 209      if (!is_array($array)) {
 210        return false;
 211      }
 212      $keys = array_keys($array);
 213      foreach ($keys as $key) {
 214        if (is_string($key)) {
 215          return true;
 216        }
 217      }
 218      return false;
 219    }
 220  
 221    /**
 222     * Given a variable name, discover its type.
 223     *
 224     * @param $name
 225     * @param $item
 226     * @return object The object from the item.
 227     */
 228    private function createObjectFromName($name, $item)
 229    {
 230      $type = $this->$name;
 231      return new $type($item);
 232    }
 233  
 234    /**
 235     * Verify if $obj is an array.
 236     * @throws Google_Exception Thrown if $obj isn't an array.
 237     * @param array $obj Items that should be validated.
 238     * @param string $method Method expecting an array as an argument.
 239     */
 240    public function assertIsArray($obj, $method)
 241    {
 242      if ($obj && !is_array($obj)) {
 243        throw new Google_Exception(
 244            "Incorrect parameter type passed to $method(). Expected an array."
 245        );
 246      }
 247    }
 248  
 249    public function offsetExists($offset): bool
 250    {
 251      return isset($this->$offset) || isset($this->modelData[$offset]);
 252    }
 253  
 254    #[\ReturnTypeWillChange]
 255    public function offsetGet($offset)
 256    {
 257      return isset($this->$offset) ?
 258          $this->$offset :
 259          $this->__get($offset);
 260    }
 261  
 262    public function offsetSet($offset, $value): void
 263    {
 264      if (property_exists($this, $offset)) {
 265        $this->$offset = $value;
 266      } else {
 267        $this->modelData[$offset] = $value;
 268        $this->processed[$offset] = true;
 269      }
 270    }
 271  
 272    public function offsetUnset($offset): void
 273    {
 274      unset($this->modelData[$offset]);
 275    }
 276  
 277    protected function keyType($key)
 278    {
 279      return $key . "Type";
 280    }
 281  
 282    protected function dataType($key)
 283    {
 284      return $key . "DataType";
 285    }
 286  
 287    public function __isset($key)
 288    {
 289      return isset($this->modelData[$key]);
 290    }
 291  
 292    public function __unset($key)
 293    {
 294      unset($this->modelData[$key]);
 295    }
 296  }