Нет описания

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