ArrayContext.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\View\Form;
  16. use Cake\Http\ServerRequest;
  17. use Cake\Utility\Hash;
  18. /**
  19. * Provides a basic array based context provider for FormHelper.
  20. *
  21. * This adapter is useful in testing or when you have forms backed by
  22. * simple array data structures.
  23. *
  24. * Important keys:
  25. *
  26. * - `defaults` The default values for fields. These values
  27. * will be used when there is no request data set. Data should be nested following
  28. * the dot separated paths you access your fields with.
  29. * - `required` A nested array of fields, relationships and boolean
  30. * flags to indicate a field is required.
  31. * - `schema` An array of data that emulate the column structures that
  32. * Cake\Database\Schema\Schema uses. This array allows you to control
  33. * the inferred type for fields and allows auto generation of attributes
  34. * like maxlength, step and other HTML attributes. If you want
  35. * primary key/id detection to work. Make sure you have provided a `_constraints`
  36. * array that contains `primary`. See below for an example.
  37. * - `errors` An array of validation errors. Errors should be nested following
  38. * the dot separated paths you access your fields with.
  39. *
  40. * ### Example
  41. *
  42. * ```
  43. * $data = [
  44. * 'schema' => [
  45. * 'id' => ['type' => 'integer'],
  46. * 'title' => ['type' => 'string', 'length' => 255],
  47. * '_constraints' => [
  48. * 'primary' => ['type' => 'primary', 'columns' => ['id']]
  49. * ]
  50. * ],
  51. * 'defaults' => [
  52. * 'id' => 1,
  53. * 'title' => 'First post!',
  54. * ]
  55. * ];
  56. * ```
  57. */
  58. class ArrayContext implements ContextInterface
  59. {
  60. /**
  61. * The request object.
  62. *
  63. * @var \Cake\Http\ServerRequest
  64. */
  65. protected $_request;
  66. /**
  67. * Context data for this object.
  68. *
  69. * @var array
  70. */
  71. protected $_context;
  72. /**
  73. * Constructor.
  74. *
  75. * @param \Cake\Http\ServerRequest $request The request object.
  76. * @param array $context Context info.
  77. */
  78. public function __construct(ServerRequest $request, array $context)
  79. {
  80. $this->_request = $request;
  81. $context += [
  82. 'schema' => [],
  83. 'required' => [],
  84. 'defaults' => [],
  85. 'errors' => [],
  86. ];
  87. $this->_context = $context;
  88. }
  89. /**
  90. * Get the fields used in the context as a primary key.
  91. *
  92. * @return array
  93. */
  94. public function primaryKey()
  95. {
  96. if (empty($this->_context['schema']['_constraints']) ||
  97. !is_array($this->_context['schema']['_constraints'])
  98. ) {
  99. return [];
  100. }
  101. foreach ($this->_context['schema']['_constraints'] as $data) {
  102. if (isset($data['type']) && $data['type'] === 'primary') {
  103. return isset($data['columns']) ? (array)$data['columns'] : [];
  104. }
  105. }
  106. return [];
  107. }
  108. /**
  109. * {@inheritDoc}
  110. */
  111. public function isPrimaryKey($field)
  112. {
  113. $primaryKey = $this->primaryKey();
  114. return in_array($field, $primaryKey);
  115. }
  116. /**
  117. * Returns whether or not this form is for a create operation.
  118. *
  119. * For this method to return true, both the primary key constraint
  120. * must be defined in the 'schema' data, and the 'defaults' data must
  121. * contain a value for all fields in the key.
  122. *
  123. * @return bool
  124. */
  125. public function isCreate()
  126. {
  127. $primary = $this->primaryKey();
  128. foreach ($primary as $column) {
  129. if (!empty($this->_context['defaults'][$column])) {
  130. return false;
  131. }
  132. }
  133. return true;
  134. }
  135. /**
  136. * Get the current value for a given field.
  137. *
  138. * This method will coalesce the current request data and the 'defaults'
  139. * array.
  140. *
  141. * @param string $field A dot separated path to the field a value
  142. * is needed for.
  143. * @param array $options Options:
  144. * - `default`: Default value to return if no value found in request
  145. * data or context record.
  146. * - `schemaDefault`: Boolean indicating whether default value from
  147. * context's schema should be used if it's not explicitly provided.
  148. * @return mixed
  149. */
  150. public function val($field, $options = [])
  151. {
  152. $options += [
  153. 'default' => null,
  154. 'schemaDefault' => true
  155. ];
  156. $val = $this->_request->getData($field);
  157. if ($val !== null) {
  158. return $val;
  159. }
  160. if ($options['default'] !== null || !$options['schemaDefault']) {
  161. return $options['default'];
  162. }
  163. if (empty($this->_context['defaults']) || !is_array($this->_context['defaults'])) {
  164. return null;
  165. }
  166. // Using Hash::check here incase the default value is actually null
  167. if (Hash::check($this->_context['defaults'], $field)) {
  168. return Hash::get($this->_context['defaults'], $field);
  169. }
  170. return Hash::get($this->_context['defaults'], $this->stripNesting($field));
  171. }
  172. /**
  173. * Check if a given field is 'required'.
  174. *
  175. * In this context class, this is simply defined by the 'required' array.
  176. *
  177. * @param string $field A dot separated path to check required-ness for.
  178. * @return bool
  179. */
  180. public function isRequired($field)
  181. {
  182. if (!is_array($this->_context['required'])) {
  183. return false;
  184. }
  185. $required = Hash::get($this->_context['required'], $field);
  186. if ($required === null) {
  187. $required = Hash::get($this->_context['required'], $this->stripNesting($field));
  188. }
  189. return (bool)$required;
  190. }
  191. /**
  192. * {@inheritDoc}
  193. */
  194. public function getRequiredMessage($field)
  195. {
  196. return null;
  197. }
  198. /**
  199. * {@inheritDoc}
  200. */
  201. public function fieldNames()
  202. {
  203. $schema = $this->_context['schema'];
  204. unset($schema['_constraints'], $schema['_indexes']);
  205. return array_keys($schema);
  206. }
  207. /**
  208. * Get the abstract field type for a given field name.
  209. *
  210. * @param string $field A dot separated path to get a schema type for.
  211. * @return null|string An abstract data type or null.
  212. * @see \Cake\Database\Type
  213. */
  214. public function type($field)
  215. {
  216. if (!is_array($this->_context['schema'])) {
  217. return null;
  218. }
  219. $schema = Hash::get($this->_context['schema'], $field);
  220. if ($schema === null) {
  221. $schema = Hash::get($this->_context['schema'], $this->stripNesting($field));
  222. }
  223. return isset($schema['type']) ? $schema['type'] : null;
  224. }
  225. /**
  226. * Get an associative array of other attributes for a field name.
  227. *
  228. * @param string $field A dot separated path to get additional data on.
  229. * @return array An array of data describing the additional attributes on a field.
  230. */
  231. public function attributes($field)
  232. {
  233. if (!is_array($this->_context['schema'])) {
  234. return [];
  235. }
  236. $schema = Hash::get($this->_context['schema'], $field);
  237. if ($schema === null) {
  238. $schema = Hash::get($this->_context['schema'], $this->stripNesting($field));
  239. }
  240. $whitelist = ['length' => null, 'precision' => null];
  241. return array_intersect_key((array)$schema, $whitelist);
  242. }
  243. /**
  244. * Check whether or not a field has an error attached to it
  245. *
  246. * @param string $field A dot separated path to check errors on.
  247. * @return bool Returns true if the errors for the field are not empty.
  248. */
  249. public function hasError($field)
  250. {
  251. if (empty($this->_context['errors'])) {
  252. return false;
  253. }
  254. return (bool)Hash::check($this->_context['errors'], $field);
  255. }
  256. /**
  257. * Get the errors for a given field
  258. *
  259. * @param string $field A dot separated path to check errors on.
  260. * @return array An array of errors, an empty array will be returned when the
  261. * context has no errors.
  262. */
  263. public function error($field)
  264. {
  265. if (empty($this->_context['errors'])) {
  266. return [];
  267. }
  268. return Hash::get($this->_context['errors'], $field);
  269. }
  270. /**
  271. * Strips out any numeric nesting
  272. *
  273. * For example users.0.age will output as users.age
  274. *
  275. * @param string $field A dot separated path
  276. * @return string A string with stripped numeric nesting
  277. */
  278. protected function stripNesting($field)
  279. {
  280. return preg_replace('/\.\d*\./', '.', $field);
  281. }
  282. }