Model.php 10.0 KB

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