Google_Model   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 292
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 117
c 1
b 0
f 1
dl 0
loc 292
rs 3.36
wmc 63

19 Methods

Rating   Name   Duplication   Size   Complexity  
A dataType() 0 6 2
A __isset() 0 3 1
A __construct() 0 8 3
A toSimpleObject() 0 25 5
C __get() 0 33 13
A getSimpleValue() 0 16 5
A offsetExists() 0 3 2
A offsetSet() 0 7 2
A gapiInit() 0 3 1
A keyType() 0 7 3
A nullPlaceholderCheck() 0 6 2
A getMappedName() 0 6 2
A offsetUnset() 0 3 1
A camelCase() 0 6 1
A __unset() 0 3 1
A isAssociativeArray() 0 12 4
A assertIsArray() 0 5 3
B mapTypes() 0 31 10
A offsetGet() 0 5 2

How to fix   Complexity   

Complex Class

Complex classes like Google_Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Google_Model, and based on these observations, apply Extract Interface, too.

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
    $keyType = $this->keyType($key);
57
    $keyDataType = $this->dataType($key);
58
    if ($keyType && !isset($this->processed[$key])) {
59
      if (isset($this->modelData[$key])) {
60
        $val = $this->modelData[$key];
61
      } elseif ($keyDataType == 'array' || $keyDataType == 'map') {
62
        $val = array();
63
      } else {
64
        $val = null;
65
      }
66
67
      if ($this->isAssociativeArray($val)) {
68
        if ($keyDataType && 'map' == $keyDataType) {
69
          foreach ($val as $arrayKey => $arrayItem) {
70
              $this->modelData[$key][$arrayKey] =
71
                new $keyType($arrayItem);
72
          }
73
        } else {
74
          $this->modelData[$key] = new $keyType($val);
75
        }
76
      } else if (is_array($val)) {
77
        $arrayObject = array();
78
        foreach ($val as $arrayIndex => $arrayItem) {
79
          $arrayObject[$arrayIndex] = new $keyType($arrayItem);
80
        }
81
        $this->modelData[$key] = $arrayObject;
82
      }
83
      $this->processed[$key] = true;
84
    }
85
86
    return isset($this->modelData[$key]) ? $this->modelData[$key] : null;
87
  }
88
89
  /**
90
   * Initialize this object's properties from an array.
91
   *
92
   * @param array $array Used to seed this object's properties.
93
   * @return void
94
   */
95
  protected function mapTypes($array)
96
  {
97
    // Hard initialise simple types, lazy load more complex ones.
98
    foreach ($array as $key => $val) {
99
      if ($keyType = $this->keyType($key)) {
100
        $dataType = $this->dataType($key);
101
        if ($dataType == 'array' || $dataType == 'map') {
102
          $this->$key = array();
103
          foreach ($val as $itemKey => $itemVal) {
104
            if ($itemVal instanceof $keyType) {
105
              $this->{$key}[$itemKey] = $itemVal;
106
            } else {
107
              $this->{$key}[$itemKey] = new $keyType($itemVal);
108
            }
109
          }
110
        } elseif ($val instanceof $keyType) {
111
          $this->$key = $val;
112
        } else {
113
          $this->$key = new $keyType($val);
114
        }
115
        unset($array[$key]);
116
      } elseif (property_exists($this, $key)) {
117
          $this->$key = $val;
118
          unset($array[$key]);
119
      } elseif (property_exists($this, $camelKey = $this->camelCase($key))) {
120
          // This checks if property exists as camelCase, leaving it in array as snake_case
121
          // in case of backwards compatibility issues.
122
          $this->$camelKey = $val;
123
      }
124
    }
125
    $this->modelData = $array;
126
  }
127
128
  /**
129
   * Blank initialiser to be used in subclasses to do  post-construction initialisation - this
130
   * avoids the need for subclasses to have to implement the variadics handling in their
131
   * constructors.
132
   */
133
  protected function gapiInit()
134
  {
135
    return;
136
  }
137
138
  /**
139
   * Create a simplified object suitable for straightforward
140
   * conversion to JSON. This is relatively expensive
141
   * due to the usage of reflection, but shouldn't be called
142
   * a whole lot, and is the most straightforward way to filter.
143
   */
144
  public function toSimpleObject()
145
  {
146
    $object = new stdClass();
147
148
    // Process all other data.
149
    foreach ($this->modelData as $key => $val) {
150
      $result = $this->getSimpleValue($val);
151
      if ($result !== null) {
152
        $object->$key = $this->nullPlaceholderCheck($result);
153
      }
154
    }
155
156
    // Process all public properties.
157
    $reflect = new ReflectionObject($this);
158
    $props = $reflect->getProperties(ReflectionProperty::IS_PUBLIC);
159
    foreach ($props as $member) {
160
      $name = $member->getName();
161
      $result = $this->getSimpleValue($this->$name);
162
      if ($result !== null) {
163
        $name = $this->getMappedName($name);
164
        $object->$name = $this->nullPlaceholderCheck($result);
165
      }
166
    }
167
168
    return $object;
169
  }
170
171
  /**
172
   * Handle different types of values, primarily
173
   * other objects and map and array data types.
174
   */
175
  private function getSimpleValue($value)
176
  {
177
    if ($value instanceof Google_Model) {
178
      return $value->toSimpleObject();
179
    } else if (is_array($value)) {
180
      $return = array();
181
      foreach ($value as $key => $a_value) {
182
        $a_value = $this->getSimpleValue($a_value);
183
        if ($a_value !== null) {
184
          $key = $this->getMappedName($key);
185
          $return[$key] = $this->nullPlaceholderCheck($a_value);
186
        }
187
      }
188
      return $return;
189
    }
190
    return $value;
191
  }
192
193
  /**
194
   * Check whether the value is the null placeholder and return true null.
195
   */
196
  private function nullPlaceholderCheck($value)
197
  {
198
    if ($value === self::NULL_VALUE) {
199
      return null;
200
    }
201
    return $value;
202
  }
203
204
  /**
205
   * If there is an internal name mapping, use that.
206
   */
207
  private function getMappedName($key)
208
  {
209
    if (isset($this->internal_gapi_mappings, $this->internal_gapi_mappings[$key])) {
210
      $key = $this->internal_gapi_mappings[$key];
211
    }
212
    return $key;
213
  }
214
215
  /**
216
   * Returns true only if the array is associative.
217
   * @param array $array
218
   * @return bool True if the array is associative.
219
   */
220
  protected function isAssociativeArray($array)
221
  {
222
    if (!is_array($array)) {
0 ignored issues
show
introduced by
The condition is_array($array) is always true.
Loading history...
223
      return false;
224
    }
225
    $keys = array_keys($array);
226
    foreach ($keys as $key) {
227
      if (is_string($key)) {
228
        return true;
229
      }
230
    }
231
    return false;
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)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $obj of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
The condition is_array($obj) is always true.
Loading history...
243
      throw new Google_Exception(
244
          "Incorrect parameter type passed to $method(). Expected an array."
245
      );
246
    }
247
  }
248
249
  public function offsetExists($offset)
250
  {
251
    return isset($this->$offset) || isset($this->modelData[$offset]);
252
  }
253
254
  public function offsetGet($offset)
255
  {
256
    return isset($this->$offset) ?
257
        $this->$offset :
258
        $this->__get($offset);
259
  }
260
261
  public function offsetSet($offset, $value)
262
  {
263
    if (property_exists($this, $offset)) {
264
      $this->$offset = $value;
265
    } else {
266
      $this->modelData[$offset] = $value;
267
      $this->processed[$offset] = true;
268
    }
269
  }
270
271
  public function offsetUnset($offset)
272
  {
273
    unset($this->modelData[$offset]);
274
  }
275
276
  protected function keyType($key)
277
  {
278
    $keyType = $key . "Type";
279
280
    // ensure keyType is a valid class
281
    if (property_exists($this, $keyType) && class_exists($this->$keyType)) {
282
      return $this->$keyType;
283
    }
284
  }
285
286
  protected function dataType($key)
287
  {
288
    $dataType = $key . "DataType";
289
290
    if (property_exists($this, $dataType)) {
291
      return $this->$dataType;
292
    }
293
  }
294
295
  public function __isset($key)
296
  {
297
    return isset($this->modelData[$key]);
298
  }
299
300
  public function __unset($key)
301
  {
302
    unset($this->modelData[$key]);
303
  }
304
305
  /**
306
   * Convert a string to camelCase
307
   * @param  string $value
308
   * @return string
309
   */
310
  private function camelCase($value)
311
  {
312
    $value = ucwords(str_replace(array('-', '_'), ' ', $value));
313
    $value = str_replace(' ', '', $value);
314
    $value[0] = strtolower($value[0]);
315
    return $value;
316
  }
317
}
318