) * - template_placeholder: placeholder used in the data template * * @param array|Traversable $options * @return Collection */ public function setOptions($options) { parent::setOptions($options); if (isset($options['target_element'])) { $this->setTargetElement($options['target_element']); } if (isset($options['count'])) { $this->setCount($options['count']); } if (isset($options['allow_add'])) { $this->setAllowAdd($options['allow_add']); } if (isset($options['allow_remove'])) { $this->setAllowRemove($options['allow_remove']); } if (isset($options['should_create_template'])) { $this->setShouldCreateTemplate($options['should_create_template']); } if (isset($options['template_placeholder'])) { $this->setTemplatePlaceholder($options['template_placeholder']); } if (isset($options['create_new_objects'])) { $this->setCreateNewObjects($options['create_new_objects']); } return $this; } /** * Checks if the object can be set in this fieldset * * @param object $object * @return bool */ public function allowObjectBinding($object) { return true; } /** * Set the object used by the hydrator * In this case the "object" is a collection of objects * * @param array|Traversable $object * @return Fieldset|FieldsetInterface * @throws Exception\InvalidArgumentException */ public function setObject($object) { if (!is_array($object) && !$object instanceof Traversable) { throw new Exception\InvalidArgumentException(sprintf( '%s expects an array or Traversable object argument; received "%s"', __METHOD__, (is_object($object) ? get_class($object) : gettype($object)) )); } $this->object = $object; $this->count = count($object); return $this; } /** * Populate values * * @param array|Traversable $data * @throws \Zend\Form\Exception\InvalidArgumentException * @throws \Zend\Form\Exception\DomainException * @return void */ public function populateValues($data) { if (!is_array($data) && !$data instanceof Traversable) { throw new Exception\InvalidArgumentException(sprintf( '%s expects an array or Traversable set of data; received "%s"', __METHOD__, (is_object($data) ? get_class($data) : gettype($data)) )); } // Can't do anything with empty data if (empty($data)) { $this->shouldCreateChildrenOnPrepareElement = false; return; } if (!$this->allowRemove && count($data) < $this->count) { throw new Exception\DomainException(sprintf( 'There are fewer elements than specified in the collection (%s). Either set the allow_remove option ' . 'to true, or re-submit the form.', get_class($this) ) ); } foreach ($data as $key => $value) { if ($this->has($key)) { $elementOrFieldset = $this->get($key); } else { $elementOrFieldset = $this->addNewTargetElementInstance($key); if ($key > $this->lastChildIndex) { $this->lastChildIndex = $key; } } if ($elementOrFieldset instanceof FieldsetInterface) { $elementOrFieldset->populateValues($value); } else { $elementOrFieldset->setAttribute('value', $value); } } if (!$this->createNewObjects()) { $this->replaceTemplateObjects(); } } /** * Checks if this fieldset can bind data * * @return bool */ public function allowValueBinding() { return true; } /** * Bind values to the object * * @param array $values * @return array|mixed|void */ public function bindValues(array $values = array()) { $collection = array(); foreach ($values as $name => $value) { $element = $this->get($name); if ($element instanceof FieldsetInterface) { $collection[] = $element->bindValues($value); } else { $collection[] = $value; } } return $collection; } /** * Set the initial count of target element * * @param $count * @return Collection */ public function setCount($count) { $this->count = $count > 0 ? $count : 0; return $this; } /** * Get the initial count of target element * * @return int */ public function getCount() { return $this->count; } /** * Set the target element * * @param ElementInterface|array|Traversable $elementOrFieldset * @return Collection * @throws \Zend\Form\Exception\InvalidArgumentException */ public function setTargetElement($elementOrFieldset) { if (is_array($elementOrFieldset) || ($elementOrFieldset instanceof Traversable && !$elementOrFieldset instanceof ElementInterface) ) { $factory = $this->getFormFactory(); $elementOrFieldset = $factory->create($elementOrFieldset); } if (!$elementOrFieldset instanceof ElementInterface) { throw new Exception\InvalidArgumentException(sprintf( '%s requires that $elementOrFieldset be an object implementing %s; received "%s"', __METHOD__, __NAMESPACE__ . '\ElementInterface', (is_object($elementOrFieldset) ? get_class($elementOrFieldset) : gettype($elementOrFieldset)) )); } $this->targetElement = $elementOrFieldset; return $this; } /** * Get target element * * @return ElementInterface|null */ public function getTargetElement() { return $this->targetElement; } /** * Get allow add * * @param bool $allowAdd * @return Collection */ public function setAllowAdd($allowAdd) { $this->allowAdd = (bool) $allowAdd; return $this; } /** * Get allow add * * @return bool */ public function allowAdd() { return $this->allowAdd; } /** * @param bool $allowRemove * @return Collection */ public function setAllowRemove($allowRemove) { $this->allowRemove = (bool) $allowRemove; return $this; } /** * @return bool */ public function allowRemove() { return $this->allowRemove; } /** * If set to true, a template prototype is automatically added to the form to ease the creation of dynamic elements through JavaScript * * @param bool $shouldCreateTemplate * @return Collection */ public function setShouldCreateTemplate($shouldCreateTemplate) { $this->shouldCreateTemplate = (bool) $shouldCreateTemplate; return $this; } /** * Get if the collection should create a template * * @return bool */ public function shouldCreateTemplate() { return $this->shouldCreateTemplate; } /** * Set the placeholder used in the template generated to help create new elements in JavaScript * * @param string $templatePlaceholder * @return Collection */ public function setTemplatePlaceholder($templatePlaceholder) { if (is_string($templatePlaceholder)) { $this->templatePlaceholder = $templatePlaceholder; } return $this; } /** * Get the template placeholder * * @return string */ public function getTemplatePlaceholder() { return $this->templatePlaceholder; } /** * @param bool $createNewObjects * @return Collection */ public function setCreateNewObjects($createNewObjects) { $this->createNewObjects = (bool) $createNewObjects; return $this; } /** * @return bool */ public function createNewObjects() { return $this->createNewObjects; } /** * Get a template element used for rendering purposes only * * @return null|ElementInterface|FieldsetInterface */ public function getTemplateElement() { if ($this->templateElement === null) { $this->templateElement = $this->createTemplateElement(); } return $this->templateElement; } /** * Prepare the collection by adding a dummy template element if the user want one * * @param FormInterface $form * @return mixed|void */ public function prepareElement(FormInterface $form) { if (true === $this->shouldCreateChildrenOnPrepareElement) { if ($this->targetElement !== null && $this->count > 0) { while ($this->count > $this->lastChildIndex + 1) { $this->addNewTargetElementInstance(++$this->lastChildIndex); } } } // Create a template that will also be prepared if ($this->shouldCreateTemplate) { $templateElement = $this->getTemplateElement(); $this->add($templateElement); } parent::prepareElement($form); // The template element has been prepared, but we don't want it to be rendered nor validated, so remove it from the list if ($this->shouldCreateTemplate) { $this->remove($this->templatePlaceholder); } } /** * @return array */ public function extract() { if ($this->object instanceof Traversable) { $this->object = ArrayUtils::iteratorToArray($this->object, false); } if (!is_array($this->object)) { return array(); } $values = array(); foreach ($this->object as $key => $value) { if ($this->hydrator) { $values[$key] = $this->hydrator->extract($value); } elseif ($value instanceof $this->targetElement->object) { // @see https://github.com/zendframework/zf2/pull/2848 $targetElement = clone $this->targetElement; $targetElement->object = $value; $values[$key] = $targetElement->extract(); if (!$this->createNewObjects() && $this->has($key)) { $fieldset = $this->get($key); if ($fieldset instanceof Fieldset && $fieldset->allowObjectBinding($value)) { $fieldset->setObject($value); } } } } foreach ($values as $name => $object) { $fieldset = $this->addNewTargetElementInstance($name); if ($fieldset->allowObjectBinding($object)) { $fieldset->setObject($object); $values[$name] = $fieldset->extract(); } else { foreach ($fieldset->fieldsets as $childFieldset) { $childName = $childFieldset->getName(); if (isset($object[$childName])) { $childObject = $object[$childName]; if ($childFieldset->allowObjectBinding($childObject)) { $childFieldset->setObject($childObject); $values[$name][$childName] = $childFieldset->extract(); } } } } } return $values; } /** * Create a new instance of the target element * * @return ElementInterface */ protected function createNewTargetElementInstance() { return clone $this->targetElement; } /** * Add a new instance of the target element * * @return ElementInterface * @throws Exception\DomainException */ protected function addNewTargetElementInstance($name) { $this->shouldCreateChildrenOnPrepareElement = false; $elementOrFieldset = $this->createNewTargetElementInstance(); $elementOrFieldset->setName($name); $this->add($elementOrFieldset); if (!$this->allowAdd && $this->count() > $this->count) { throw new Exception\DomainException(sprintf( 'There are more elements than specified in the collection (%s). Either set the allow_add option ' . 'to true, or re-submit the form.', get_class($this) )); } return $elementOrFieldset; } /** * Create a dummy template element * * @return null|ElementInterface|FieldsetInterface */ protected function createTemplateElement() { if (!$this->shouldCreateTemplate) { return null; } if ($this->templateElement) { return $this->templateElement; } $elementOrFieldset = $this->createNewTargetElementInstance(); $elementOrFieldset->setName($this->templatePlaceholder); return $elementOrFieldset; } /** * Replaces the default template object of a sub element with the corresponding * real entity so that all properties are preserved. * * @return void */ protected function replaceTemplateObjects() { $fieldsets = $this->getFieldsets(); if (!count($fieldsets) || !$this->object) { return; } foreach ($fieldsets as $fieldset) { $i = $fieldset->getName(); if (isset($this->object[$i])) { $fieldset->setObject($this->object[$i]); } } } }