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
14 use Zend\InputFilter\Factory
as InputFilterFactory
;
15 use Zend\InputFilter\InputFilterInterface
;
16 use Zend\Stdlib\ArrayUtils
;
17 use Zend\Stdlib\Hydrator
;
22 * @var InputFilterFactory
24 protected $inputFilterFactory;
27 * @var FormElementManager
29 protected $formElementManager;
32 * @param FormElementManager $formElementManager
34 public function __construct(FormElementManager
$formElementManager = null)
36 if ($formElementManager) {
37 $this->setFormElementManager($formElementManager);
42 * Set input filter factory to use when creating forms
44 * @param InputFilterFactory $inputFilterFactory
47 public function setInputFilterFactory(InputFilterFactory
$inputFilterFactory)
49 $this->inputFilterFactory
= $inputFilterFactory;
54 * Get current input filter factory
56 * If none provided, uses an unconfigured instance.
58 * @return InputFilterFactory
60 public function getInputFilterFactory()
62 if (null === $this->inputFilterFactory
) {
63 $this->setInputFilterFactory(new InputFilterFactory());
65 return $this->inputFilterFactory
;
69 * Set the form element manager
71 * @param FormElementManager $formElementManager
74 public function setFormElementManager(FormElementManager
$formElementManager)
76 $this->formElementManager
= $formElementManager;
81 * Get form element manager
83 * @return FormElementManager
85 public function getFormElementManager()
87 if ($this->formElementManager
=== null) {
88 $this->setFormElementManager(new FormElementManager());
91 return $this->formElementManager
;
95 * Create an element, fieldset, or form
97 * Introspects the 'type' key of the provided $spec, and determines what
98 * type is being requested; if none is provided, assumes the spec
99 * represents simply an element.
101 * @param array|Traversable $spec
102 * @return ElementInterface
103 * @throws Exception\DomainException
105 public function create($spec)
107 $spec = $this->validateSpecification($spec, __METHOD__
);
108 $type = isset($spec['type']) ?
$spec['type'] : 'Zend\Form\Element';
110 $element = $this->getFormElementManager()->get($type);
112 if ($element instanceof FormInterface
) {
113 return $this->configureForm($element, $spec);
116 if ($element instanceof FieldsetInterface
) {
117 return $this->configureFieldset($element, $spec);
120 if ($element instanceof ElementInterface
) {
121 return $this->configureElement($element, $spec);
124 throw new Exception\
DomainException(sprintf(
125 '%s expects the $spec["type"] to implement one of %s, %s, or %s; received %s',
127 'Zend\Form\ElementInterface',
128 'Zend\Form\FieldsetInterface',
129 'Zend\Form\FormInterface',
138 * @return ElementInterface
140 public function createElement($spec)
142 if (!isset($spec['type'])) {
143 $spec['type'] = 'Zend\Form\Element';
146 return $this->create($spec);
153 * @return ElementInterface
155 public function createFieldset($spec)
157 if (!isset($spec['type'])) {
158 $spec['type'] = 'Zend\Form\Fieldset';
161 return $this->create($spec);
168 * @return ElementInterface
170 public function createForm($spec)
172 if (!isset($spec['type'])) {
173 $spec['type'] = 'Zend\Form\Form';
176 return $this->create($spec);
180 * Configure an element based on the provided specification
182 * Specification can contain any of the following:
183 * - type: the Element class to use; defaults to \Zend\Form\Element
184 * - name: what name to provide the element, if any
185 * - options: an array, Traversable, or ArrayAccess object of element options
186 * - attributes: an array, Traversable, or ArrayAccess object of element
187 * attributes to assign
189 * @param ElementInterface $element
190 * @param array|Traversable|ArrayAccess $spec
191 * @throws Exception\DomainException
192 * @return ElementInterface
194 public function configureElement(ElementInterface
$element, $spec)
196 $spec = $this->validateSpecification($spec, __METHOD__
);
198 $name = isset($spec['name']) ?
$spec['name'] : null;
199 $options = isset($spec['options']) ?
$spec['options'] : null;
200 $attributes = isset($spec['attributes']) ?
$spec['attributes'] : null;
202 if ($name !== null && $name !== '') {
203 $element->setName($name);
206 if (is_array($options) ||
$options instanceof Traversable ||
$options instanceof ArrayAccess
) {
207 $element->setOptions($options);
210 if (is_array($attributes) ||
$attributes instanceof Traversable ||
$attributes instanceof ArrayAccess
) {
211 $element->setAttributes($attributes);
218 * Configure a fieldset based on the provided specification
220 * Specification can contain any of the following:
221 * - type: the Fieldset class to use; defaults to \Zend\Form\Fieldset
222 * - name: what name to provide the fieldset, if any
223 * - options: an array, Traversable, or ArrayAccess object of element options
224 * - attributes: an array, Traversable, or ArrayAccess object of element
225 * attributes to assign
226 * - elements: an array or Traversable object where each entry is an array
227 * or ArrayAccess object containing the keys:
228 * - flags: (optional) array of flags to pass to FieldsetInterface::add()
229 * - spec: the actual element specification, per {@link configureElement()}
231 * @param FieldsetInterface $fieldset
232 * @param array|Traversable|ArrayAccess $spec
233 * @throws Exception\DomainException
234 * @return FieldsetInterface
236 public function configureFieldset(FieldsetInterface
$fieldset, $spec)
238 $spec = $this->validateSpecification($spec, __METHOD__
);
239 $fieldset = $this->configureElement($fieldset, $spec);
241 if (isset($spec['object'])) {
242 $this->prepareAndInjectObject($spec['object'], $fieldset, __METHOD__
);
245 if (isset($spec['hydrator'])) {
246 $this->prepareAndInjectHydrator($spec['hydrator'], $fieldset, __METHOD__
);
249 if (isset($spec['elements'])) {
250 $this->prepareAndInjectElements($spec['elements'], $fieldset, __METHOD__
);
253 if (isset($spec['fieldsets'])) {
254 $this->prepareAndInjectFieldsets($spec['fieldsets'], $fieldset, __METHOD__
);
257 $factory = (isset($spec['factory']) ?
$spec['factory'] : $this);
258 $this->prepareAndInjectFactory($factory, $fieldset, __METHOD__
);
264 * Configure a form based on the provided specification
266 * Specification follows that of {@link configureFieldset()}, and adds the
269 * - input_filter: input filter instance, named input filter class, or
270 * array specification for the input filter factory
271 * - hydrator: hydrator instance or named hydrator class
273 * @param FormInterface $form
274 * @param array|Traversable|ArrayAccess $spec
275 * @return FormInterface
277 public function configureForm(FormInterface
$form, $spec)
279 $spec = $this->validateSpecification($spec, __METHOD__
);
280 $form = $this->configureFieldset($form, $spec);
282 if (isset($spec['input_filter'])) {
283 $this->prepareAndInjectInputFilter($spec['input_filter'], $form, __METHOD__
);
286 if (isset($spec['validation_group'])) {
287 $this->prepareAndInjectValidationGroup($spec['validation_group'], $form, __METHOD__
);
294 * Validate a provided specification
296 * Ensures we have an array, Traversable, or ArrayAccess object, and returns it.
298 * @param array|Traversable|ArrayAccess $spec
299 * @param string $method Method invoking the validator
300 * @return array|ArrayAccess
301 * @throws Exception\InvalidArgumentException for invalid $spec
303 protected function validateSpecification($spec, $method)
305 if (is_array($spec)) {
309 if ($spec instanceof Traversable
) {
310 $spec = ArrayUtils
::iteratorToArray($spec);
314 if (!$spec instanceof ArrayAccess
) {
315 throw new Exception\
InvalidArgumentException(sprintf(
316 '%s expects an array, or object implementing Traversable or ArrayAccess; received "%s"',
318 (is_object($spec) ?
get_class($spec) : gettype($spec))
326 * Takes a list of element specifications, creates the elements, and injects them into the provided fieldset
328 * @param array|Traversable|ArrayAccess $elements
329 * @param FieldsetInterface $fieldset
330 * @param string $method Method invoking this one (for exception messages)
333 protected function prepareAndInjectElements($elements, FieldsetInterface
$fieldset, $method)
335 $elements = $this->validateSpecification($elements, $method);
337 foreach ($elements as $elementSpecification) {
338 if (null === $elementSpecification) {
342 $flags = isset($elementSpecification['flags']) ?
$elementSpecification['flags'] : array();
343 $spec = isset($elementSpecification['spec']) ?
$elementSpecification['spec'] : array();
345 if (!isset($spec['type'])) {
346 $spec['type'] = 'Zend\Form\Element';
349 $element = $this->create($spec);
350 $fieldset->add($element, $flags);
355 * Takes a list of fieldset specifications, creates the fieldsets, and injects them into the master fieldset
357 * @param array|Traversable|ArrayAccess $fieldsets
358 * @param FieldsetInterface $masterFieldset
359 * @param string $method Method invoking this one (for exception messages)
362 public function prepareAndInjectFieldsets($fieldsets, FieldsetInterface
$masterFieldset, $method)
364 $fieldsets = $this->validateSpecification($fieldsets, $method);
366 foreach ($fieldsets as $fieldsetSpecification) {
367 $flags = isset($fieldsetSpecification['flags']) ?
$fieldsetSpecification['flags'] : array();
368 $spec = isset($fieldsetSpecification['spec']) ?
$fieldsetSpecification['spec'] : array();
370 $fieldset = $this->createFieldset($spec);
371 $masterFieldset->add($fieldset, $flags);
376 * Prepare and inject an object
378 * Takes a string indicating a class name, instantiates the class
379 * by that name, and injects the class instance as the bound object.
381 * @param string $objectName
382 * @param FieldsetInterface $fieldset
383 * @param string $method
384 * @throws Exception\DomainException
387 protected function prepareAndInjectObject($objectName, FieldsetInterface
$fieldset, $method)
389 if (!is_string($objectName)) {
390 throw new Exception\
DomainException(sprintf(
391 '%s expects string class name; received "%s"',
393 (is_object($objectName) ?
get_class($objectName) : gettype($objectName))
397 if (!class_exists($objectName)) {
398 throw new Exception\
DomainException(sprintf(
399 '%s expects string class name to be a valid class name; received "%s"',
405 $fieldset->setObject(new $objectName);
409 * Prepare and inject a named hydrator
411 * Takes a string indicating a hydrator class name (or a concrete instance), try first to instantiates the class
412 * by pulling it from service manager, and injects the hydrator instance into the form.
414 * @param string|array|Hydrator\HydratorInterface $hydratorOrName
415 * @param FieldsetInterface $fieldset
416 * @param string $method
418 * @throws Exception\DomainException If $hydratorOrName is not a string, does not resolve to a known class, or
419 * the class does not implement Hydrator\HydratorInterface
421 protected function prepareAndInjectHydrator($hydratorOrName, FieldsetInterface
$fieldset, $method)
423 if ($hydratorOrName instanceof Hydrator\HydratorInterface
) {
424 $fieldset->setHydrator($hydratorOrName);
428 if (is_array($hydratorOrName)) {
429 if (!isset($hydratorOrName['type'])) {
430 throw new Exception\
DomainException(sprintf(
431 '%s expects array specification to have a type value',
435 $hydratorOptions = (isset($hydratorOrName['options'])) ?
$hydratorOrName['options'] : array();
436 $hydratorOrName = $hydratorOrName['type'];
438 $hydratorOptions = array();
441 if (is_string($hydratorOrName)) {
442 $hydrator = $this->getHydratorFromName($hydratorOrName);
445 if (! isset($hydrator) ||
!$hydrator instanceof Hydrator\HydratorInterface
) {
446 throw new Exception\
DomainException(sprintf(
447 '%s expects a valid implementation of Zend\Stdlib\Hydrator\HydratorInterface; received "%s"',
453 if (!empty($hydratorOptions) && $hydrator instanceof Hydrator\HydratorOptionsInterface
) {
454 $hydrator->setOptions($hydratorOptions);
457 $fieldset->setHydrator($hydrator);
461 * Prepare and inject a named factory
463 * Takes a string indicating a factory class name (or a concrete instance), try first to instantiates the class
464 * by pulling it from service manager, and injects the factory instance into the fieldset.
466 * @param string|array|Factory $factoryOrName
467 * @param FieldsetInterface $fieldset
468 * @param string $method
470 * @throws Exception\DomainException If $factoryOrName is not a string, does not resolve to a known class, or
471 * the class does not extend Form\Factory
473 protected function prepareAndInjectFactory($factoryOrName, FieldsetInterface
$fieldset, $method)
475 if (is_array($factoryOrName)) {
476 if (!isset($factoryOrName['type'])) {
477 throw new Exception\
DomainException(sprintf(
478 '%s expects array specification to have a type value',
482 $factoryOrName = $factoryOrName['type'];
485 if (is_string($factoryOrName)) {
486 $factoryOrName = $this->getFactoryFromName($factoryOrName);
489 if (!$factoryOrName instanceof Factory
) {
490 throw new Exception\
DomainException(sprintf(
491 '%s expects a valid extention of Zend\Form\Factory; received "%s"',
497 $fieldset->setFormFactory($factoryOrName);
501 * Prepare an input filter instance and inject in the provided form
503 * If the input filter specified is a string, assumes it is a class name,
504 * and attempts to instantiate it. If the class does not exist, or does
505 * not extend InputFilterInterface, an exception is raised.
507 * Otherwise, $spec is passed on to the attached InputFilter Factory
508 * instance in order to create the input filter.
510 * @param string|array|Traversable $spec
511 * @param FormInterface $form
512 * @param string $method
514 * @throws Exception\DomainException for unknown InputFilter class or invalid InputFilter instance
516 protected function prepareAndInjectInputFilter($spec, FormInterface
$form, $method)
518 if ($spec instanceof InputFilterInterface
) {
519 $form->setInputFilter($spec);
523 if (is_string($spec)) {
524 if (!class_exists($spec)) {
525 throw new Exception\
DomainException(sprintf(
526 '%s expects string input filter names to be valid class names; received "%s"',
532 if (!$filter instanceof InputFilterInterface
) {
533 throw new Exception\
DomainException(sprintf(
534 '%s expects a valid implementation of Zend\InputFilter\InputFilterInterface; received "%s"',
539 $form->setInputFilter($filter);
543 $factory = $this->getInputFilterFactory();
544 $filter = $factory->createInputFilter($spec);
545 if (method_exists($filter, 'setFactory')) {
546 $filter->setFactory($factory);
548 $form->setInputFilter($filter);
552 * Prepare a validation group and inject in the provided form
554 * Takes an array of elements names
556 * @param string|array|Traversable $spec
557 * @param FormInterface $form
558 * @param string $method
560 * @throws Exception\DomainException if validation group given is not an array
562 protected function prepareAndInjectValidationGroup($spec, FormInterface
$form, $method)
564 if (!is_array($spec)) {
565 if (!class_exists($spec)) {
566 throw new Exception\
DomainException(sprintf(
567 '%s expects an array for validation group; received "%s"',
574 $form->setValidationGroup($spec);
578 * Try to pull hydrator from service manager, or instantiates it from its name
580 * @param string $hydratorName
582 * @throws Exception\DomainException
584 protected function getHydratorFromName($hydratorName)
586 $services = $this->getFormElementManager()->getServiceLocator();
588 if ($services && $services->has('HydratorManager')) {
589 $hydrators = $services->get('HydratorManager');
590 if ($hydrators->has($hydratorName)) {
591 return $hydrators->get($hydratorName);
595 if ($services && $services->has($hydratorName)) {
596 return $services->get($hydratorName);
599 if (!class_exists($hydratorName)) {
600 throw new Exception\
DomainException(sprintf(
601 'Expects string hydrator name to be a valid class name; received "%s"',
606 $hydrator = new $hydratorName;
611 * Try to pull factory from service manager, or instantiates it from its name
613 * @param string $factoryName
615 * @throws Exception\DomainException
617 protected function getFactoryFromName($factoryName)
619 $services = $this->getFormElementManager()->getServiceLocator();
621 if ($services && $services->has($factoryName)) {
622 return $services->get($factoryName);
625 if (!class_exists($factoryName)) {
626 throw new Exception\
DomainException(sprintf(
627 'Expects string factory name to be a valid class name; received "%s"',
632 $factory = new $factoryName;