composer package updates
[openemr.git] / vendor / zendframework / zend-form / src / Element / Collection.php
blob6023305aa1e5c0581198f9a1e4aaced7da381945
1 <?php
2 /**
3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
8 */
10 namespace Zend\Form\Element;
12 use Traversable;
13 use Zend\Form\Element;
14 use Zend\Form\ElementInterface;
15 use Zend\Form\Exception;
16 use Zend\Form\Fieldset;
17 use Zend\Form\FieldsetInterface;
18 use Zend\Form\FormInterface;
19 use Zend\Stdlib\ArrayUtils;
21 class Collection extends Fieldset
23 /**
24 * Default template placeholder
26 const DEFAULT_TEMPLATE_PLACEHOLDER = '__index__';
28 /**
29 * Element used in the collection
31 * @var ElementInterface
33 protected $targetElement;
35 /**
36 * Initial count of target element
38 * @var int
40 protected $count = 1;
42 /**
43 * Are new elements allowed to be added dynamically ?
45 * @var bool
47 protected $allowAdd = true;
49 /**
50 * Are existing elements allowed to be removed dynamically ?
52 * @var bool
54 protected $allowRemove = true;
56 /**
57 * Is the template generated ?
59 * @var bool
61 protected $shouldCreateTemplate = false;
63 /**
64 * Placeholder used in template content for making your life easier with JavaScript
66 * @var string
68 protected $templatePlaceholder = self::DEFAULT_TEMPLATE_PLACEHOLDER;
70 /**
71 * Whether or not to create new objects during modify
73 * @var bool
75 protected $createNewObjects = false;
77 /**
78 * Element used as a template
80 * @var ElementInterface|FieldsetInterface
82 protected $templateElement;
84 /**
85 * The index of the last child element or fieldset
87 * @var int
89 protected $lastChildIndex = -1;
91 /**
92 * Should child elements must be created on self::prepareElement()?
94 * @var bool
96 protected $shouldCreateChildrenOnPrepareElement = true;
98 /**
99 * Accepted options for Collection:
100 * - target_element: an array or element used in the collection
101 * - count: number of times the element is added initially
102 * - allow_add: if set to true, elements can be added to the form dynamically (using JavaScript)
103 * - allow_remove: if set to true, elements can be removed to the form
104 * - should_create_template: if set to true, a template is generated (inside a <span>)
105 * - template_placeholder: placeholder used in the data template
107 * @param array|Traversable $options
108 * @return Collection
110 public function setOptions($options)
112 parent::setOptions($options);
114 if (isset($options['target_element'])) {
115 $this->setTargetElement($options['target_element']);
118 if (isset($options['count'])) {
119 $this->setCount($options['count']);
122 if (isset($options['allow_add'])) {
123 $this->setAllowAdd($options['allow_add']);
126 if (isset($options['allow_remove'])) {
127 $this->setAllowRemove($options['allow_remove']);
130 if (isset($options['should_create_template'])) {
131 $this->setShouldCreateTemplate($options['should_create_template']);
134 if (isset($options['template_placeholder'])) {
135 $this->setTemplatePlaceholder($options['template_placeholder']);
138 if (isset($options['create_new_objects'])) {
139 $this->setCreateNewObjects($options['create_new_objects']);
142 return $this;
146 * Checks if the object can be set in this fieldset
148 * @param object $object
149 * @return bool
151 public function allowObjectBinding($object)
153 return true;
157 * Set the object used by the hydrator
158 * In this case the "object" is a collection of objects
160 * @param array|Traversable $object
161 * @return Fieldset|FieldsetInterface
162 * @throws Exception\InvalidArgumentException
164 public function setObject($object)
166 if (! is_array($object) && ! $object instanceof Traversable) {
167 throw new Exception\InvalidArgumentException(sprintf(
168 '%s expects an array or Traversable object argument; received "%s"',
169 __METHOD__,
170 (is_object($object) ? get_class($object) : gettype($object))
174 $this->object = $object;
175 $this->count = max(count($object), $this->count);
177 return $this;
181 * Populate values
183 * @param array|Traversable $data
184 * @throws \Zend\Form\Exception\InvalidArgumentException
185 * @throws \Zend\Form\Exception\DomainException
186 * @return void
188 public function populateValues($data)
190 if (! is_array($data) && ! $data instanceof Traversable) {
191 throw new Exception\InvalidArgumentException(sprintf(
192 '%s expects an array or Traversable set of data; received "%s"',
193 __METHOD__,
194 (is_object($data) ? get_class($data) : gettype($data))
198 if (! $this->allowRemove && count($data) < $this->count) {
199 throw new Exception\DomainException(sprintf(
200 'There are fewer elements than specified in the collection (%s). Either set the allow_remove option '
201 . 'to true, or re-submit the form.',
202 get_class($this)
206 // Check to see if elements have been replaced or removed
207 $toRemove = [];
208 foreach ($this as $name => $elementOrFieldset) {
209 if (isset($data[$name])) {
210 continue;
213 if (! $this->allowRemove) {
214 throw new Exception\DomainException(sprintf(
215 'Elements have been removed from the collection (%s) but the allow_remove option is not true.',
216 get_class($this)
220 $toRemove[] = $name;
223 foreach ($toRemove as $name) {
224 $this->remove($name);
227 foreach ($data as $key => $value) {
228 if ($this->has($key)) {
229 $elementOrFieldset = $this->get($key);
230 } else {
231 $elementOrFieldset = $this->addNewTargetElementInstance($key);
233 if ($key > $this->lastChildIndex) {
234 $this->lastChildIndex = $key;
238 if ($elementOrFieldset instanceof FieldsetInterface) {
239 $elementOrFieldset->populateValues($value);
240 } else {
241 $elementOrFieldset->setAttribute('value', $value);
245 if (! $this->createNewObjects()) {
246 $this->replaceTemplateObjects();
251 * Checks if this fieldset can bind data
253 * @return bool
255 public function allowValueBinding()
257 return true;
261 * Bind values to the object
263 * @param array $values
264 * @param array $validationGroup
266 * @return array|mixed|void
268 public function bindValues(array $values = [], array $validationGroup = null)
270 $collection = [];
271 foreach ($values as $name => $value) {
272 $element = $this->get($name);
274 if ($element instanceof FieldsetInterface) {
275 $collection[] = $element->bindValues($value, $validationGroup);
276 } else {
277 $collection[] = $value;
281 return $collection;
285 * Set the initial count of target element
287 * @param $count
288 * @return Collection
290 public function setCount($count)
292 $this->count = $count > 0 ? $count : 0;
293 return $this;
297 * Get the initial count of target element
299 * @return int
301 public function getCount()
303 return $this->count;
307 * Set the target element
309 * @param ElementInterface|array|Traversable $elementOrFieldset
310 * @return Collection
311 * @throws \Zend\Form\Exception\InvalidArgumentException
313 public function setTargetElement($elementOrFieldset)
315 if (is_array($elementOrFieldset)
316 || ($elementOrFieldset instanceof Traversable && ! $elementOrFieldset instanceof ElementInterface)
318 $factory = $this->getFormFactory();
319 $elementOrFieldset = $factory->create($elementOrFieldset);
322 if (! $elementOrFieldset instanceof ElementInterface) {
323 throw new Exception\InvalidArgumentException(sprintf(
324 '%s requires that $elementOrFieldset be an object implementing %s; received "%s"',
325 __METHOD__,
326 __NAMESPACE__ . '\ElementInterface',
327 (is_object($elementOrFieldset) ? get_class($elementOrFieldset) : gettype($elementOrFieldset))
331 $this->targetElement = $elementOrFieldset;
333 return $this;
337 * Get target element
339 * @return ElementInterface|null
341 public function getTargetElement()
343 return $this->targetElement;
347 * Get allow add
349 * @param bool $allowAdd
350 * @return Collection
352 public function setAllowAdd($allowAdd)
354 $this->allowAdd = (bool) $allowAdd;
355 return $this;
359 * Get allow add
361 * @return bool
363 public function allowAdd()
365 return $this->allowAdd;
369 * @param bool $allowRemove
370 * @return Collection
372 public function setAllowRemove($allowRemove)
374 $this->allowRemove = (bool) $allowRemove;
375 return $this;
379 * @return bool
381 public function allowRemove()
383 return $this->allowRemove;
387 * If set to true, a template prototype is automatically added to the form
388 * to ease the creation of dynamic elements through JavaScript
390 * @param bool $shouldCreateTemplate
391 * @return Collection
393 public function setShouldCreateTemplate($shouldCreateTemplate)
395 $this->shouldCreateTemplate = (bool) $shouldCreateTemplate;
397 return $this;
401 * Get if the collection should create a template
403 * @return bool
405 public function shouldCreateTemplate()
407 return $this->shouldCreateTemplate;
411 * Set the placeholder used in the template generated to help create new elements in JavaScript
413 * @param string $templatePlaceholder
414 * @return Collection
416 public function setTemplatePlaceholder($templatePlaceholder)
418 if (is_string($templatePlaceholder)) {
419 $this->templatePlaceholder = $templatePlaceholder;
422 return $this;
426 * Get the template placeholder
428 * @return string
430 public function getTemplatePlaceholder()
432 return $this->templatePlaceholder;
436 * @param bool $createNewObjects
437 * @return Collection
439 public function setCreateNewObjects($createNewObjects)
441 $this->createNewObjects = (bool) $createNewObjects;
442 return $this;
446 * @return bool
448 public function createNewObjects()
450 return $this->createNewObjects;
454 * Get a template element used for rendering purposes only
456 * @return null|ElementInterface|FieldsetInterface
458 public function getTemplateElement()
460 if ($this->templateElement === null) {
461 $this->templateElement = $this->createTemplateElement();
464 return $this->templateElement;
468 * Prepare the collection by adding a dummy template element if the user want one
470 * @param FormInterface $form
471 * @return mixed|void
473 public function prepareElement(FormInterface $form)
475 if (true === $this->shouldCreateChildrenOnPrepareElement) {
476 if ($this->targetElement !== null && $this->count > 0) {
477 while ($this->count > $this->lastChildIndex + 1) {
478 $this->addNewTargetElementInstance(++$this->lastChildIndex);
483 // Create a template that will also be prepared
484 if ($this->shouldCreateTemplate) {
485 $templateElement = $this->getTemplateElement();
486 $this->add($templateElement);
489 parent::prepareElement($form);
491 // The template element has been prepared, but we don't want it to be
492 // rendered nor validated, so remove it from the list.
493 if ($this->shouldCreateTemplate) {
494 $this->remove($this->templatePlaceholder);
499 * @return array
500 * @throws \Zend\Form\Exception\InvalidArgumentException
501 * @throws \Zend\Stdlib\Exception\InvalidArgumentException
502 * @throws \Zend\Form\Exception\DomainException
503 * @throws \Zend\Form\Exception\InvalidElementException
505 public function extract()
507 if ($this->object instanceof Traversable) {
508 $this->object = ArrayUtils::iteratorToArray($this->object, false);
511 if (! is_array($this->object)) {
512 return [];
515 $values = [];
517 foreach ($this->object as $key => $value) {
518 // If a hydrator is provided, our work here is done
519 if ($this->hydrator) {
520 $values[$key] = $this->hydrator->extract($value);
521 continue;
524 // If the target element is a fieldset that can accept the provided value
525 // we should clone it, inject the value and extract the data
526 if ($this->targetElement instanceof FieldsetInterface) {
527 if (! $this->targetElement->allowObjectBinding($value)) {
528 continue;
530 $targetElement = clone $this->targetElement;
531 $targetElement->setObject($value);
532 $values[$key] = $targetElement->extract();
533 if (! $this->createNewObjects() && $this->has($key)) {
534 $this->get($key)->setObject($value);
536 continue;
539 // If the target element is a non-fieldset element, just use the value
540 if ($this->targetElement instanceof ElementInterface) {
541 $values[$key] = $value;
542 if (! $this->createNewObjects() && $this->has($key)) {
543 $this->get($key)->setValue($value);
545 continue;
549 return $values;
553 * Create a new instance of the target element
555 * @return ElementInterface
557 protected function createNewTargetElementInstance()
559 return clone $this->targetElement;
563 * Add a new instance of the target element
565 * @param string $name
566 * @return ElementInterface
567 * @throws Exception\DomainException
569 protected function addNewTargetElementInstance($name)
571 $this->shouldCreateChildrenOnPrepareElement = false;
573 $elementOrFieldset = $this->createNewTargetElementInstance();
574 $elementOrFieldset->setName($name);
576 $this->add($elementOrFieldset);
578 if (! $this->allowAdd && $this->count() > $this->count) {
579 throw new Exception\DomainException(sprintf(
580 'There are more elements than specified in the collection (%s). Either set the allow_add option ' .
581 'to true, or re-submit the form.',
582 get_class($this)
586 return $elementOrFieldset;
590 * Create a dummy template element
592 * @return null|ElementInterface|FieldsetInterface
594 protected function createTemplateElement()
596 if (! $this->shouldCreateTemplate) {
597 return;
600 if ($this->templateElement) {
601 return $this->templateElement;
604 $elementOrFieldset = $this->createNewTargetElementInstance();
605 $elementOrFieldset->setName($this->templatePlaceholder);
607 return $elementOrFieldset;
611 * Replaces the default template object of a sub element with the corresponding
612 * real entity so that all properties are preserved.
614 * @return void
616 protected function replaceTemplateObjects()
618 $fieldsets = $this->getFieldsets();
620 if (! count($fieldsets) || ! $this->object) {
621 return;
624 foreach ($fieldsets as $fieldset) {
625 $i = $fieldset->getName();
626 if (isset($this->object[$i])) {
627 $fieldset->setObject($this->object[$i]);