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\Form;
16:
17: use Cake\Event\Event;
18: use Cake\Event\EventDispatcherInterface;
19: use Cake\Event\EventDispatcherTrait;
20: use Cake\Event\EventListenerInterface;
21: use Cake\Event\EventManager;
22: use Cake\Form\Schema;
23: use Cake\Utility\Hash;
24: use Cake\Validation\Validator;
25: use Cake\Validation\ValidatorAwareInterface;
26: use Cake\Validation\ValidatorAwareTrait;
27: use ReflectionMethod;
28:
29: /**
30: * Form abstraction used to create forms not tied to ORM backed models,
31: * or to other permanent datastores. Ideal for implementing forms on top of
32: * API services, or contact forms.
33: *
34: * ### Building a form
35: *
36: * This class is most useful when subclassed. In a subclass you
37: * should define the `_buildSchema`, `_buildValidator` and optionally,
38: * the `_execute` methods. These allow you to declare your form's
39: * fields, validation and primary action respectively.
40: *
41: * You can also define the validation and schema by chaining method
42: * calls off of `$form->schema()` and `$form->validator()`.
43: *
44: * Forms are conventionally placed in the `App\Form` namespace.
45: */
46: class Form implements EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface
47: {
48: /**
49: * Schema class.
50: *
51: * @var string
52: */
53: protected $_schemaClass = Schema::class;
54:
55: use EventDispatcherTrait;
56: use ValidatorAwareTrait;
57:
58: /**
59: * The alias this object is assigned to validators as.
60: *
61: * @var string
62: */
63: const VALIDATOR_PROVIDER_NAME = 'form';
64:
65: /**
66: * The name of the event dispatched when a validator has been built.
67: *
68: * @var string
69: */
70: const BUILD_VALIDATOR_EVENT = 'Form.buildValidator';
71:
72: /**
73: * The schema used by this form.
74: *
75: * @var \Cake\Form\Schema
76: */
77: protected $_schema;
78:
79: /**
80: * The errors if any
81: *
82: * @var array
83: */
84: protected $_errors = [];
85:
86: /**
87: * The validator used by this form.
88: *
89: * @var \Cake\Validation\Validator
90: */
91: protected $_validator;
92:
93: /**
94: * Form's data.
95: *
96: * @var array
97: * @since 3.7.0
98: */
99: protected $_data = [];
100:
101: /**
102: * Constructor
103: *
104: * @param \Cake\Event\EventManager|null $eventManager The event manager.
105: * Defaults to a new instance.
106: */
107: public function __construct(EventManager $eventManager = null)
108: {
109: if ($eventManager !== null) {
110: $this->setEventManager($eventManager);
111: }
112:
113: $this->getEventManager()->on($this);
114: }
115:
116: /**
117: * Get the Form callbacks this form is interested in.
118: *
119: * The conventional method map is:
120: *
121: * - Form.buildValidator => buildValidator
122: *
123: * @return array
124: */
125: public function implementedEvents()
126: {
127: return [
128: 'Form.buildValidator' => 'buildValidator',
129: ];
130: }
131:
132: /**
133: * Get/Set the schema for this form.
134: *
135: * This method will call `_buildSchema()` when the schema
136: * is first built. This hook method lets you configure the
137: * schema or load a pre-defined one.
138: *
139: * @param \Cake\Form\Schema|null $schema The schema to set, or null.
140: * @return \Cake\Form\Schema the schema instance.
141: */
142: public function schema(Schema $schema = null)
143: {
144: if ($schema === null && empty($this->_schema)) {
145: $schema = $this->_buildSchema(new $this->_schemaClass());
146: }
147: if ($schema) {
148: $this->_schema = $schema;
149: }
150:
151: return $this->_schema;
152: }
153:
154: /**
155: * A hook method intended to be implemented by subclasses.
156: *
157: * You can use this method to define the schema using
158: * the methods on Cake\Form\Schema, or loads a pre-defined
159: * schema from a concrete class.
160: *
161: * @param \Cake\Form\Schema $schema The schema to customize.
162: * @return \Cake\Form\Schema The schema to use.
163: */
164: protected function _buildSchema(Schema $schema)
165: {
166: return $schema;
167: }
168:
169: /**
170: * Get/Set the validator for this form.
171: *
172: * This method will call `_buildValidator()` when the validator
173: * is first built. This hook method lets you configure the
174: * validator or load a pre-defined one.
175: *
176: * @param \Cake\Validation\Validator|null $validator The validator to set, or null.
177: * @return \Cake\Validation\Validator the validator instance.
178: * @deprecated 3.6.0 Use Form::getValidator()/setValidator() instead.
179: */
180: public function validator(Validator $validator = null)
181: {
182: deprecationWarning(
183: 'Form::validator() is deprecated. ' .
184: 'Use Form::getValidator()/setValidator() instead.'
185: );
186:
187: if ($validator === null && empty($this->_validator)) {
188: $validator = $this->_buildValidator(new $this->_validatorClass());
189: }
190: if ($validator) {
191: $this->_validator = $validator;
192: $this->setValidator('default', $validator);
193: }
194:
195: return $this->getValidator();
196: }
197:
198: /**
199: * A hook method intended to be implemented by subclasses.
200: *
201: * You can use this method to define the validator using
202: * the methods on Cake\Validation\Validator or loads a pre-defined
203: * validator from a concrete class.
204: *
205: * @param \Cake\Validation\Validator $validator The validator to customize.
206: * @return \Cake\Validation\Validator The validator to use.
207: * @deprecated 3.6.0 Use Form::getValidator()/setValidator() and buildValidator() instead.
208: */
209: protected function _buildValidator(Validator $validator)
210: {
211: return $validator;
212: }
213:
214: /**
215: * Callback method for Form.buildValidator event.
216: *
217: * @param \Cake\Event\Event $event The Form.buildValidator event instance.
218: * @param \Cake\Validation\Validator $validator The validator to customize.
219: * @param string $name Validator name
220: * @return void
221: */
222: public function buildValidator(Event $event, Validator $validator, $name)
223: {
224: $this->_buildValidator($validator);
225: }
226:
227: /**
228: * Used to check if $data passes this form's validation.
229: *
230: * @param array $data The data to check.
231: * @return bool Whether or not the data is valid.
232: */
233: public function validate(array $data)
234: {
235: $validator = $this->getValidator();
236: if (!$validator->count()) {
237: $method = new ReflectionMethod($this, 'validator');
238: if ($method->getDeclaringClass()->getName() !== __CLASS__) {
239: $validator = $this->validator();
240: }
241: }
242: $this->_errors = $validator->errors($data);
243:
244: return count($this->_errors) === 0;
245: }
246:
247: /**
248: * Get the errors in the form
249: *
250: * Will return the errors from the last call
251: * to `validate()` or `execute()`.
252: *
253: * @return array Last set validation errors.
254: * @deprecated 3.7.0 Use Form::getErrors() instead.
255: */
256: public function errors()
257: {
258: deprecationWarning(
259: 'Form::errors() is deprecated. ' .
260: 'Use Form::getErrors() instead.'
261: );
262:
263: return $this->getErrors();
264: }
265:
266: /**
267: * Get the errors in the form
268: *
269: * Will return the errors from the last call
270: * to `validate()` or `execute()`.
271: *
272: * @return array Last set validation errors.
273: */
274: public function getErrors()
275: {
276: return $this->_errors;
277: }
278:
279: /**
280: * Set the errors in the form.
281: *
282: * ```
283: * $errors = [
284: * 'field_name' => ['rule_name' => 'message']
285: * ];
286: *
287: * $form->setErrors($errors);
288: * ```
289: *
290: * @since 3.5.1
291: * @param array $errors Errors list.
292: * @return $this
293: */
294: public function setErrors(array $errors)
295: {
296: $this->_errors = $errors;
297:
298: return $this;
299: }
300:
301: /**
302: * Execute the form if it is valid.
303: *
304: * First validates the form, then calls the `_execute()` hook method.
305: * This hook method can be implemented in subclasses to perform
306: * the action of the form. This may be sending email, interacting
307: * with a remote API, or anything else you may need.
308: *
309: * @param array $data Form data.
310: * @return bool False on validation failure, otherwise returns the
311: * result of the `_execute()` method.
312: */
313: public function execute(array $data)
314: {
315: if (!$this->validate($data)) {
316: return false;
317: }
318:
319: return $this->_execute($data);
320: }
321:
322: /**
323: * Hook method to be implemented in subclasses.
324: *
325: * Used by `execute()` to execute the form's action.
326: *
327: * @param array $data Form data.
328: * @return bool
329: */
330: protected function _execute(array $data)
331: {
332: return true;
333: }
334:
335: /**
336: * Get field data.
337: *
338: * @param string|null $field The field name or null to get data array with
339: * all fields.
340: * @return mixed
341: * @since 3.7.0
342: */
343: public function getData($field = null)
344: {
345: if ($field === null) {
346: return $this->_data;
347: }
348:
349: return Hash::get($this->_data, $field);
350: }
351:
352: /**
353: * Set form data.
354: *
355: * @param array $data Data array.
356: * @return $this
357: * @since 3.7.0
358: */
359: public function setData(array $data)
360: {
361: $this->_data = $data;
362:
363: return $this;
364: }
365:
366: /**
367: * Get the printable version of a Form instance.
368: *
369: * @return array
370: */
371: public function __debugInfo()
372: {
373: $special = [
374: '_schema' => $this->schema()->__debugInfo(),
375: '_errors' => $this->getErrors(),
376: '_validator' => $this->getValidator()->__debugInfo()
377: ];
378:
379: return $special + get_object_vars($this);
380: }
381: }
382: