1: <?php
2: /**
3: * Base class for JSON models.
4: *
5: * @since 1.0
6: * @package Components
7: * @author Konstantinos Filios <konfilios@gmail.com>
8: */
9: class CBJsonModel extends CFormModel
10: {
11: /**
12: * Throw exception if validation fails.
13: *
14: * @param array $attributes
15: * @param boolean $clearErrors
16: * @return boolean
17: * @throws CException
18: */
19: public function validate($attributes = null, $clearErrors = true)
20: {
21: if (parent::validate($attributes, $clearErrors)) {
22: return true;
23: }
24:
25: $allErrors = array();
26: foreach ($this->getErrors() as $attrName=>$attrErrors) {
27: $allErrors = array_merge($allErrors, $attrErrors);
28: }
29:
30: throw new CException(implode("\n", $allErrors));
31: }
32:
33: /**
34: * Types of non-scalar members.
35: *
36: * @return string[]
37: */
38: public function getAttributeTypes()
39: {
40: return array();
41: }
42:
43: /**
44: * Initialize from source object/array.
45: *
46: * @param array|object $source
47: * @return CBJsonModel
48: */
49: public function copyFrom($source)
50: {
51: if (is_array($source)) {
52: $isSourceArray = true;
53: } else if (is_object($source)) {
54: $isSourceArray = false;
55: } else {
56: throw new CException(get_class($this).' cannot be initialized from a '
57: .gettype($source).'. An array or object is required');
58: }
59:
60: // Our attribute types
61: $attrTypes = $this->getAttributeTypes();
62:
63: foreach($this->attributeNames() as $attrName) {
64: // Loop through our attributes
65:
66: if ($isSourceArray) {
67: // Array key
68: $attrValue = isset($source[$attrName]) ? $source[$attrName] : null;
69: } else {
70: // Object property
71: $attrValue = isset($source->$attrName) ? $source->$attrName : null;
72: }
73:
74: if ($attrValue === null) {
75: // Attribute not in source
76: continue;
77: }
78:
79: if (!isset($attrTypes[$attrName])) {
80: // Scalar type
81: $this->$attrName = $attrValue;
82: } else {
83: // Object or array of objects
84: $attrType = $attrTypes[$attrName];
85:
86: if (substr($attrType, -2) === '[]') {
87: // Array of objects
88:
89: // Remove "[]"
90: $attrType = substr($attrType, 0, -2);
91:
92: if (!is_array($attrValue)) {
93: // Make sure value is an array of objects
94: throw new CException('Attribute '.$attrName.' of '.get_class($this)
95: .' must be an array of '.$attrType.' instances');
96: }
97:
98: $arrayJsonModels = array();
99: foreach ($attrValue as $fromJsonAttributeElement) {
100: // Loop through array of objects and create corresponding json models
101: $elementJsonModel = new $attrType();
102:
103: // Recurse into sub-object
104: $elementJsonModel->copyFrom($fromJsonAttributeElement);
105:
106: $arrayJsonModels[] = $elementJsonModel;
107: }
108: $this->$attrName = $arrayJsonModels;
109:
110: } else {
111: // Object
112: $elementJsonModel = new $attrType();
113:
114: // Recurse into sub-object
115: $this->$attrName = $elementJsonModel->copyFrom($attrValue);
116: }
117: }
118: }
119:
120: return $this;
121: }
122:
123: /**
124: * Model attributes as array.
125: *
126: * @return mixed
127: */
128: public function toArray()
129: {
130: return $this->getAttributes();
131: }
132:
133: /**
134: * Clones a source into a json model.
135: *
136: * @param array|object $source Source to clone attributes from.
137: * @return CBJsonModel Cloned CBJsonModel subtype.
138: */
139: static public function createFromOne($source)
140: {
141: if ($source === null) {
142: return null;
143: }
144:
145: // Instantiate new subtype of CBJsonModel using Late Static Binding.
146: $jsonModelClassName = get_called_class();
147: $jsonModel = new $jsonModelClassName();
148: /* @var $jsonModel CBJsonModel */
149:
150: // Copy attributes (include fromModel's dynamic properties)
151: return $jsonModel->copyFrom($source);
152: }
153:
154: /**
155: * Clones an array of sources into an array of json models.
156: *
157: * Original model array keys are preserved in final array.
158: *
159: * @param array $sources Sources to clone attributes from.
160: * @return CBJsonModel[] Cloned CBJsonModel subtypes.
161: */
162: static public function createFromMany(array $sources)
163: {
164: // Get new subtype of CBJsonModel using Late Static Binding.
165: $jsonModelClassName = get_called_class();
166:
167: $jsonModels = array();
168: foreach ($sources as $key=>$fromModel) {
169: /* @var $fromModel CModel */
170:
171: $jsonModel = new $jsonModelClassName();
172: /* @var $jsonModel CBJsonModel */
173:
174: // Copy attributes (include fromModel's dynamic properties)and add to final array
175: $jsonModels[$key] = $jsonModel->copyFrom($fromModel);
176: }
177:
178: return $jsonModels;
179: }
180:
181: /**
182: * Resolve an object to its proper JSON representation.
183: *
184: * @param mixed $inputObject
185: * @param boolean $doSuppressNulls If true, null properties are suppressed from result.
186: *
187: * @return mixed
188: */
189: static public function resolveObjectRecursively($inputObject, $doSuppressNulls = false)
190: {
191: if (is_array($inputObject)) {
192: //
193: // Input object is an array, resolve its items
194: //
195: $objectArray = array();
196: foreach ($inputObject as $key=>$responseObjectItem) {
197: $objectArray[$key] = self::resolveObjectRecursively($responseObjectItem, $doSuppressNulls);
198: }
199: return $objectArray;
200:
201: } else if (is_object($inputObject)) {
202: //
203: // Input object is a class instance, handle specially
204: //
205: if (is_a($inputObject, 'CBJsonModel')) {
206: //
207: // CBJsonModel subtype, handle attributes specially
208: //
209: $attributes = array();
210: foreach ($inputObject->toArray() as $attrName=>$attrValue) {
211: if (is_array($attrValue) || is_object($attrValue)) {
212: // Recurse
213: $attributes[$attrName] = self::resolveObjectRecursively($attrValue, $doSuppressNulls);
214:
215: } else if (!$doSuppressNulls || $attrValue !== null) {
216: // Check suppression
217: $attributes[$attrName] = $attrValue;
218: }
219: }
220: return $attributes;
221:
222: } else {
223: //
224: // Unknown object type, let json-encoder do its thing
225: //
226: return $inputObject;
227: }
228: } else {
229: //
230: // Input object is a scalar
231: //
232: return $inputObject;
233: }
234: }
235: }