JsonableBehavior.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. <?php
  2. /**
  3. * Copyright 2011, PJ Hile (http://www.pjhile.com)
  4. *
  5. * Licensed under The MIT License
  6. * Redistributions of files must retain the above copyright notice.
  7. *
  8. * @version 0.1
  9. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  10. */
  11. App::uses('ModelBehavior', 'Model');
  12. /**
  13. * A behavior that will json_encode (and json_decode) fields if they contain an array or specific pattern.
  14. *
  15. * Requres: PHP 5 >= 5.2.0 or PECL json >= 1.2.0
  16. *
  17. * This is a port of the Serializeable behavior by Matsimitsu (http://www.matsimitsu.nl)
  18. * Modified by Mark Scherer (http://www.dereuromark.de)
  19. *
  20. * Now supports different input/output formats:
  21. * - "list" is useful as some kind of pseudo enums or simple lists
  22. * - "params" is useful for multiple key/value pairs
  23. * - can be used to create dynamic forms (and tables)
  24. *
  25. * Also automatically cleans lists and works with custom separators etc
  26. *
  27. * @link http://www.dereuromark.de/2011/07/05/introducing-two-cakephp-behaviors/
  28. */
  29. class JsonableBehavior extends ModelBehavior {
  30. public $decoded = null;
  31. /**
  32. * //TODO: json input/ouput directly, clean
  33. * @var array
  34. */
  35. public $_defaultSettings = array(
  36. 'fields' => array(), # empty => autodetect - only works with array!
  37. 'input' => 'array', # json, array, param, list (param/list only works with specific fields)
  38. 'output' => 'array', # json, array, param, list (param/list only works with specific fields)
  39. 'separator' => '|', # only for param or list
  40. 'keyValueSeparator' => ':', # only for param
  41. 'leftBound' => '{', # only for list
  42. 'rightBound' => '}', # only for list
  43. 'clean' => true, # only for param or list (autoclean values on insert)
  44. 'sort' => false, # only for list
  45. 'unique' => true, # only for list (autoclean values on insert),
  46. 'map' => array(), # map on a different DB field
  47. );
  48. public function setup(Model $Model, $config = array()) {
  49. $this->settings[$Model->alias] = array_merge($this->_defaultSettings, $config);
  50. //extract($this->settings[$Model->alias]);
  51. if (!is_array($this->settings[$Model->alias]['fields'])) {
  52. $this->settings[$Model->alias]['fields'] = (array)$this->settings[$Model->alias]['fields'];
  53. }
  54. if (!is_array($this->settings[$Model->alias]['map'])) {
  55. $this->settings[$Model->alias]['map'] = (array)$this->settings[$Model->alias]['map'];
  56. }
  57. }
  58. /**
  59. * Decodes the fields
  60. *
  61. * @param object $Model
  62. * @param array $results
  63. * @return array
  64. */
  65. public function afterFind(Model $Model, $results, $primary = false) {
  66. $results = $this->decodeItems($Model, $results);
  67. return $results;
  68. }
  69. /**
  70. * Decodes the fields of an array (if the value itself was encoded)
  71. *
  72. * @param array $arr
  73. * @return array
  74. */
  75. public function decodeItems(Model $Model, $arr) {
  76. foreach ($arr as $akey => $val) {
  77. if (!isset($val[$Model->alias])) {
  78. return $arr;
  79. }
  80. $fields = $this->settings[$Model->alias]['fields'];
  81. foreach ($val[$Model->alias] as $key => $v) {
  82. if (empty($fields) && !is_array($v) || !in_array($key, $fields)) {
  83. continue;
  84. }
  85. if ($this->isEncoded($Model, $v)) {
  86. if (!empty($this->settings[$Model->alias]['map'])) {
  87. $keys = array_keys($this->settings[$Model->alias]['fields'], $key);
  88. if (!empty($keys)) {
  89. $key = $this->settings[$Model->alias]['map'][array_shift($keys)];
  90. }
  91. }
  92. $arr[$akey][$Model->alias][$key] = $this->decoded;
  93. }
  94. }
  95. }
  96. return $arr;
  97. }
  98. /**
  99. * Saves all fields that do not belong to the current Model into 'with' helper model.
  100. *
  101. * @param object $Model
  102. * @return boolean Success
  103. */
  104. public function beforeSave(Model $Model, $options = array()) {
  105. $data = $Model->data[$Model->alias];
  106. $usedFields = $this->settings[$Model->alias]['fields'];
  107. $mappedFields = $this->settings[$Model->alias]['map'];
  108. if (empty($mappedFields)) {
  109. $mappedFields = $usedFields;
  110. }
  111. $fields = array();
  112. foreach ($mappedFields as $index => $map) {
  113. if (empty($map) || $map == $usedFields[$index]) {
  114. $fields[$usedFields[$index]] = $usedFields[$index];
  115. continue;
  116. }
  117. $fields[$map] = $usedFields[$index];
  118. }
  119. foreach ($data as $key => $val) {
  120. if (!empty($fields) && !array_key_exists($key, $fields)) {
  121. continue;
  122. }
  123. if (!empty($fields)) {
  124. $key = $fields[$key];
  125. }
  126. if (!empty($this->settings[$Model->alias]['fields']) || is_array($val)) {
  127. $Model->data[$Model->alias][$key] = $this->_encode($Model, $val);
  128. }
  129. }
  130. return true;
  131. }
  132. /**
  133. * JsonableBehavior::_encode()
  134. *
  135. * @param Model $Model
  136. * @param mixed $val
  137. * @return string
  138. */
  139. public function _encode(Model $Model, $val) {
  140. if (!empty($this->settings[$Model->alias]['fields'])) {
  141. if ($this->settings[$Model->alias]['input'] === 'param') {
  142. $val = $this->_fromParam($Model, $val);
  143. } elseif ($this->settings[$Model->alias]['input'] === 'list') {
  144. $val = $this->_fromList($Model, $val);
  145. if ($this->settings[$Model->alias]['unique']) {
  146. $val = array_unique($val);
  147. }
  148. if ($this->settings[$Model->alias]['sort']) {
  149. sort($val);
  150. }
  151. }
  152. }
  153. if (is_array($val)) {
  154. $val = json_encode($val);
  155. }
  156. return $val;
  157. }
  158. /**
  159. * Fields are absolutely necessary to function properly!
  160. *
  161. * @param Model $Model
  162. * @param mixed $val
  163. * @return mixed
  164. */
  165. public function _decode(Model $Model, $val) {
  166. $decoded = json_decode($val);
  167. if ($decoded === false) {
  168. return false;
  169. }
  170. $decoded = (array)$decoded;
  171. if ($this->settings[$Model->alias]['output'] === 'param') {
  172. $decoded = $this->_toParam($Model, $decoded);
  173. } elseif ($this->settings[$Model->alias]['output'] === 'list') {
  174. $decoded = $this->_toList($Model, $decoded);
  175. }
  176. return $decoded;
  177. }
  178. /**
  179. * array() => param1:value1|param2:value2|...
  180. */
  181. public function _toParam(Model $Model, $val) {
  182. $res = array();
  183. foreach ($val as $key => $v) {
  184. $res[] = $key . $this->settings[$Model->alias]['keyValueSeparator'] . $v;
  185. }
  186. return implode($this->settings[$Model->alias]['separator'], $res);
  187. }
  188. public function _fromParam(Model $Model, $val) {
  189. $leftBound = $this->settings[$Model->alias]['leftBound'];
  190. $rightBound = $this->settings[$Model->alias]['rightBound'];
  191. $separator = $this->settings[$Model->alias]['separator'];
  192. $res = array();
  193. $pieces = String::tokenize($val, $separator, $leftBound, $rightBound);
  194. foreach ($pieces as $piece) {
  195. $subpieces = String::tokenize($piece, $this->settings[$Model->alias]['keyValueSeparator'], $leftBound, $rightBound);
  196. if (count($subpieces) < 2) {
  197. continue;
  198. }
  199. $res[$subpieces[0]] = $subpieces[1];
  200. }
  201. return $res;
  202. }
  203. /**
  204. * array() => value1|value2|value3|...
  205. */
  206. public function _toList(Model $Model, $val) {
  207. return implode($this->settings[$Model->alias]['separator'], $val);
  208. }
  209. public function _fromList(Model $Model, $val) {
  210. extract($this->settings[$Model->alias]);
  211. return String::tokenize($val, $separator, $leftBound, $rightBound);
  212. }
  213. /**
  214. * Checks if string is encoded array/object
  215. *
  216. * @param string string to check
  217. * @return boolean
  218. */
  219. public function isEncoded(Model $Model, $str) {
  220. $this->decoded = $this->_decode($Model, $str);
  221. if ($this->decoded !== false) {
  222. return true;
  223. }
  224. return false;
  225. }
  226. }