PluginProbe
Packeta / 2.1
Packeta v2.1
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / deps / nette / forms / src / Forms / Container.php

Container.php in Packeta 2.1, at deps/nette/forms/src/Forms/Container.php

409 lines 14.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * This file is part of the Nette Framework (https://nette.org)
5 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6 */
7 declare (strict_types=1);
8 namespace Packetery\Nette\Forms;
9
10 use Packetery\Nette;
11 use Packetery\Nette\Utils\ArrayHash;
12 /**
13 * Container for form controls.
14 *
15 * @property ArrayHash $values
16 * @property-read \Iterator $controls
17 * @property-read Form|null $form
18 */
19 class Container extends \Packetery\Nette\ComponentModel\Container implements \ArrayAccess
20 {
21 use \Packetery\Nette\ComponentModel\ArrayAccess;
22 private const ARRAY = 'array';
23 /**
24 * Occurs when the form was validated
25 * @var array<callable(self, array|object): void|callable(array|object): void>
26 */
27 public $onValidate = [];
28 /** @var ControlGroup|null */
29 protected $currentGroup;
30 /** @var callable[] extension methods */
31 private static $extMethods = [];
32 /** @var ?bool */
33 private $validated = \false;
34 /** @var ?string */
35 private $mappedType;
36 /********************* data exchange ****************d*g**/
37 /**
38 * Fill-in with default values.
39 * @param array|object $data
40 * @return static
41 */
42 public function setDefaults($data, bool $erase = \false)
43 {
44 $form = $this->getForm(\false);
45 if (!$form || !$form->isAnchored() || !$form->isSubmitted()) {
46 $this->setValues($data, $erase);
47 }
48 return $this;
49 }
50 /**
51 * Fill-in with values.
52 * @param array|object $data
53 * @return static
54 * @internal
55 */
56 public function setValues($data, bool $erase = \false)
57 {
58 if ($data instanceof \Traversable) {
59 $values = \iterator_to_array($data);
60 } elseif (\is_object($data) || \is_array($data) || $data === null) {
61 $values = (array) $data;
62 } else {
63 throw new \Packetery\Nette\InvalidArgumentException(\sprintf('First parameter must be an array or object, %s given.', \gettype($data)));
64 }
65 foreach ($this->getComponents() as $name => $control) {
66 if ($control instanceof Control) {
67 if (\array_key_exists($name, $values)) {
68 $control->setValue($values[$name]);
69 } elseif ($erase) {
70 $control->setValue(null);
71 }
72 } elseif ($control instanceof self) {
73 if (\array_key_exists($name, $values)) {
74 $control->setValues($values[$name], $erase);
75 } elseif ($erase) {
76 $control->setValues([], $erase);
77 }
78 }
79 }
80 return $this;
81 }
82 /**
83 * Returns the values submitted by the form.
84 * @param string|object|null $returnType 'array' for array
85 * @param Control[]|null $controls
86 * @return object|array
87 */
88 public function getValues($returnType = null, array $controls = null)
89 {
90 $form = $this->getForm(\false);
91 if ($form && ($submitter = $form->isSubmitted())) {
92 if ($this->validated === null) {
93 throw new \Packetery\Nette\InvalidStateException('You cannot call getValues() during the validation process. Use getUnsafeValues() instead.');
94 } elseif (!$this->isValid()) {
95 \trigger_error(__METHOD__ . "() invoked but the form is not valid (form '{$this->getName()}').", \E_USER_WARNING);
96 }
97 if ($controls === null && $submitter instanceof SubmitterControl) {
98 $controls = $submitter->getValidationScope();
99 }
100 }
101 $returnType = $returnType === \true ? self::ARRAY : $returnType;
102 return $this->getUnsafeValues($returnType, $controls);
103 }
104 /**
105 * Returns the potentially unvalidated values submitted by the form.
106 * @param string|object|null $returnType 'array' for array
107 * @param Control[]|null $controls
108 * @return object|array
109 */
110 public function getUnsafeValues($returnType, array $controls = null)
111 {
112 if (\is_object($returnType)) {
113 $obj = $returnType;
114 $rc = new \ReflectionClass($obj);
115 } else {
116 $returnType = $returnType ?? $this->mappedType ?? ArrayHash::class;
117 $rc = new \ReflectionClass($returnType === self::ARRAY ? \stdClass::class : $returnType);
118 if ($rc->hasMethod('__construct') && $rc->getMethod('__construct')->getNumberOfRequiredParameters()) {
119 $obj = new \stdClass();
120 $useConstructor = \true;
121 } else {
122 $obj = $rc->newInstance();
123 }
124 }
125 foreach ($this->getComponents() as $name => $control) {
126 $allowed = $controls === null || \in_array($control, $controls, \true);
127 $name = (string) $name;
128 if ($control instanceof Control && $allowed && !$control->isOmitted()) {
129 $obj->{$name} = $control->getValue();
130 } elseif ($control instanceof self) {
131 $type = $returnType === self::ARRAY && !$control->mappedType ? self::ARRAY : ($rc->hasProperty($name) ? \Packetery\Nette\Utils\Reflection::getPropertyType($rc->getProperty($name)) : null);
132 $obj->{$name} = $control->getUnsafeValues($type, $allowed ? null : $controls);
133 }
134 }
135 if (isset($useConstructor)) {
136 return new $returnType(...(array) $obj);
137 }
138 return $returnType === self::ARRAY ? (array) $obj : $obj;
139 }
140 /** @return static */
141 public function setMappedType(string $type)
142 {
143 $this->mappedType = $type;
144 return $this;
145 }
146 /********************* validation ****************d*g**/
147 /**
148 * Is form valid?
149 */
150 public function isValid() : bool
151 {
152 if ($this->validated === null) {
153 throw new \Packetery\Nette\InvalidStateException('You cannot call isValid() during the validation process.');
154 } elseif (!$this->validated) {
155 if ($this->getErrors()) {
156 return \false;
157 }
158 $this->validate();
159 }
160 return !$this->getErrors();
161 }
162 /**
163 * Performs the server side validation.
164 * @param Control[]|null $controls
165 */
166 public function validate(array $controls = null) : void
167 {
168 $this->validated = null;
169 foreach ($controls ?? $this->getComponents() as $control) {
170 if ($control instanceof Control || $control instanceof self) {
171 $control->validate();
172 }
173 }
174 $this->validated = \true;
175 foreach ($this->onValidate as $handler) {
176 $params = \Packetery\Nette\Utils\Callback::toReflection($handler)->getParameters();
177 $types = \array_map([\Packetery\Nette\Utils\Reflection::class, 'getParameterType'], $params);
178 $args = isset($types[0]) && !$this instanceof $types[0] ? [$this->getUnsafeValues($types[0])] : [$this, isset($params[1]) ? $this->getUnsafeValues($types[1]) : null];
179 $handler(...$args);
180 }
181 }
182 /**
183 * Returns all validation errors.
184 */
185 public function getErrors() : array
186 {
187 $errors = [];
188 foreach ($this->getControls() as $control) {
189 $errors = \array_merge($errors, $control->getErrors());
190 }
191 return \array_unique($errors);
192 }
193 /********************* form building ****************d*g**/
194 /** @return static */
195 public function setCurrentGroup(ControlGroup $group = null)
196 {
197 $this->currentGroup = $group;
198 return $this;
199 }
200 /**
201 * Returns current group.
202 */
203 public function getCurrentGroup() : ?ControlGroup
204 {
205 return $this->currentGroup;
206 }
207 /**
208 * Adds the specified component to the IContainer.
209 * @return static
210 * @throws \Packetery\Nette\InvalidStateException
211 */
212 public function addComponent(\Packetery\Nette\ComponentModel\IComponent $component, ?string $name, string $insertBefore = null)
213 {
214 parent::addComponent($component, $name, $insertBefore);
215 if ($this->currentGroup !== null) {
216 $this->currentGroup->add($component);
217 }
218 return $this;
219 }
220 /**
221 * Iterates over all form controls.
222 */
223 public function getControls() : \Iterator
224 {
225 return $this->getComponents(\true, Control::class);
226 }
227 /**
228 * Returns form.
229 */
230 public function getForm(bool $throw = \true) : ?Form
231 {
232 return $this->lookup(Form::class, $throw);
233 }
234 /********************* control factories ****************d*g**/
235 /**
236 * Adds single-line text input control to the form.
237 * @param string|object $label
238 */
239 public function addText(string $name, $label = null, int $cols = null, int $maxLength = null) : Controls\TextInput
240 {
241 return $this[$name] = (new Controls\TextInput($label, $maxLength))->setHtmlAttribute('size', $cols);
242 }
243 /**
244 * Adds single-line text input control used for sensitive input such as passwords.
245 * @param string|object $label
246 */
247 public function addPassword(string $name, $label = null, int $cols = null, int $maxLength = null) : Controls\TextInput
248 {
249 return $this[$name] = (new Controls\TextInput($label, $maxLength))->setHtmlAttribute('size', $cols)->setHtmlType('password');
250 }
251 /**
252 * Adds multi-line text input control to the form.
253 * @param string|object $label
254 */
255 public function addTextArea(string $name, $label = null, int $cols = null, int $rows = null) : Controls\TextArea
256 {
257 return $this[$name] = (new Controls\TextArea($label))->setHtmlAttribute('cols', $cols)->setHtmlAttribute('rows', $rows);
258 }
259 /**
260 * Adds input for email.
261 * @param string|object $label
262 */
263 public function addEmail(string $name, $label = null) : Controls\TextInput
264 {
265 return $this[$name] = (new Controls\TextInput($label))->addRule(Form::EMAIL);
266 }
267 /**
268 * Adds input for integer.
269 * @param string|object $label
270 */
271 public function addInteger(string $name, $label = null) : Controls\TextInput
272 {
273 return $this[$name] = (new Controls\TextInput($label))->setNullable()->addRule(Form::INTEGER);
274 }
275 /**
276 * Adds control that allows the user to upload files.
277 * @param string|object $label
278 */
279 public function addUpload(string $name, $label = null) : Controls\UploadControl
280 {
281 return $this[$name] = new Controls\UploadControl($label, \false);
282 }
283 /**
284 * Adds control that allows the user to upload multiple files.
285 * @param string|object $label
286 */
287 public function addMultiUpload(string $name, $label = null) : Controls\UploadControl
288 {
289 return $this[$name] = new Controls\UploadControl($label, \true);
290 }
291 /**
292 * Adds hidden form control used to store a non-displayed value.
293 */
294 public function addHidden(string $name, $default = null) : Controls\HiddenField
295 {
296 return $this[$name] = (new Controls\HiddenField())->setDefaultValue($default);
297 }
298 /**
299 * Adds check box control to the form.
300 * @param string|object $caption
301 */
302 public function addCheckbox(string $name, $caption = null) : Controls\Checkbox
303 {
304 return $this[$name] = new Controls\Checkbox($caption);
305 }
306 /**
307 * Adds set of radio button controls to the form.
308 * @param string|object $label
309 */
310 public function addRadioList(string $name, $label = null, array $items = null) : Controls\RadioList
311 {
312 return $this[$name] = new Controls\RadioList($label, $items);
313 }
314 /**
315 * Adds set of checkbox controls to the form.
316 * @param string|object $label
317 */
318 public function addCheckboxList(string $name, $label = null, array $items = null) : Controls\CheckboxList
319 {
320 return $this[$name] = new Controls\CheckboxList($label, $items);
321 }
322 /**
323 * Adds select box control that allows single item selection.
324 * @param string|object $label
325 */
326 public function addSelect(string $name, $label = null, array $items = null, int $size = null) : Controls\SelectBox
327 {
328 return $this[$name] = (new Controls\SelectBox($label, $items))->setHtmlAttribute('size', $size > 1 ? $size : null);
329 }
330 /**
331 * Adds select box control that allows multiple item selection.
332 * @param string|object $label
333 */
334 public function addMultiSelect(string $name, $label = null, array $items = null, int $size = null) : Controls\MultiSelectBox
335 {
336 return $this[$name] = (new Controls\MultiSelectBox($label, $items))->setHtmlAttribute('size', $size > 1 ? $size : null);
337 }
338 /**
339 * Adds button used to submit form.
340 * @param string|object $caption
341 */
342 public function addSubmit(string $name, $caption = null) : Controls\SubmitButton
343 {
344 return $this[$name] = new Controls\SubmitButton($caption);
345 }
346 /**
347 * Adds push buttons with no default behavior.
348 * @param string|object $caption
349 */
350 public function addButton(string $name, $caption = null) : Controls\Button
351 {
352 return $this[$name] = new Controls\Button($caption);
353 }
354 /**
355 * Adds graphical button used to submit form.
356 * @param string $src URI of the image
357 * @param string $alt alternate text for the image
358 */
359 public function addImageButton(string $name, string $src = null, string $alt = null) : Controls\ImageButton
360 {
361 return $this[$name] = new Controls\ImageButton($src, $alt);
362 }
363 /** @deprecated use addImageButton() */
364 public function addImage() : Controls\ImageButton
365 {
366 return $this->addImageButton(...\func_get_args());
367 }
368 /**
369 * Adds naming container to the form.
370 * @param string|int $name
371 */
372 public function addContainer($name) : self
373 {
374 $control = new self();
375 $control->currentGroup = $this->currentGroup;
376 if ($this->currentGroup !== null) {
377 $this->currentGroup->add($control);
378 }
379 return $this[$name] = $control;
380 }
381 /********************* extension methods ****************d*g**/
382 public function __call(string $name, array $args)
383 {
384 if (isset(self::$extMethods[$name])) {
385 return self::$extMethods[$name]($this, ...$args);
386 }
387 return parent::__call($name, $args);
388 }
389 public static function extensionMethod(
390 string $name,
391 /*callable*/
392 $callback
393 ) : void
394 {
395 if (\strpos($name, '::') !== \false) {
396 // back compatibility
397 [, $name] = \explode('::', $name);
398 }
399 self::$extMethods[$name] = $callback;
400 }
401 /**
402 * Prevents cloning.
403 */
404 public function __clone()
405 {
406 throw new \Packetery\Nette\NotImplementedException('Form cloning is not supported yet.');
407 }
408 }
409