- <?php
- /**
- * Zend Framework (http://framework.zend.com/)
- *
- * @link http://github.com/zendframework/zf2 for the canonical source repository
- * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
- * @license http://framework.zend.com/license/new-bsd New BSD License
- */
-
- namespace Zend\Form;
-
- use ArrayAccess;
- use Traversable;
- use Zend\InputFilter\Factory as InputFilterFactory;
- use Zend\InputFilter\InputFilterInterface;
- use Zend\Stdlib\ArrayUtils;
- use Zend\Stdlib\Hydrator;
-
- class Factory
- {
- /**
- * @var InputFilterFactory
- */
- protected $inputFilterFactory;
-
- /**
- * @var FormElementManager
- */
- protected $formElementManager;
-
- /**
- * @param FormElementManager $formElementManager
- */
- public function __construct(FormElementManager $formElementManager = null)
- {
- if ($formElementManager) {
- $this->setFormElementManager($formElementManager);
- }
- }
-
- /**
- * Set input filter factory to use when creating forms
- *
- * @param InputFilterFactory $inputFilterFactory
- * @return Factory
- */
- public function setInputFilterFactory(InputFilterFactory $inputFilterFactory)
- {
- $this->inputFilterFactory = $inputFilterFactory;
- return $this;
- }
-
- /**
- * Get current input filter factory
- *
- * If none provided, uses an unconfigured instance.
- *
- * @return InputFilterFactory
- */
- public function getInputFilterFactory()
- {
- if (null === $this->inputFilterFactory) {
- $this->setInputFilterFactory(new InputFilterFactory());
- }
- return $this->inputFilterFactory;
- }
-
- /**
- * Set the form element manager
- *
- * @param FormElementManager $formElementManager
- * @return Factory
- */
- public function setFormElementManager(FormElementManager $formElementManager)
- {
- $this->formElementManager = $formElementManager;
- return $this;
- }
-
- /**
- * Get form element manager
- *
- * @return FormElementManager
- */
- public function getFormElementManager()
- {
- if ($this->formElementManager === null) {
- $this->setFormElementManager(new FormElementManager());
- }
-
- return $this->formElementManager;
- }
-
- /**
- * Create an element, fieldset, or form
- *
- * Introspects the 'type' key of the provided $spec, and determines what
- * type is being requested; if none is provided, assumes the spec
- * represents simply an element.
- *
- * @param array|Traversable $spec
- * @return ElementInterface
- * @throws Exception\DomainException
- */
- public function create($spec)
- {
- $spec = $this->validateSpecification($spec, __METHOD__);
- $type = isset($spec['type']) ? $spec['type'] : 'Zend\Form\Element';
-
- $element = $this->getFormElementManager()->get($type);
-
- if ($element instanceof FormInterface) {
- return $this->configureForm($element, $spec);
- }
-
- if ($element instanceof FieldsetInterface) {
- return $this->configureFieldset($element, $spec);
- }
-
- if ($element instanceof ElementInterface) {
- return $this->configureElement($element, $spec);
- }
-
- throw new Exception\DomainException(sprintf(
- '%s expects the $spec["type"] to implement one of %s, %s, or %s; received %s',
- __METHOD__,
- 'Zend\Form\ElementInterface',
- 'Zend\Form\FieldsetInterface',
- 'Zend\Form\FormInterface',
- $type
- ));
- }
-
- /**
- * Create an element
- *
- * @param array $spec
- * @return ElementInterface
- */
- public function createElement($spec)
- {
- if (!isset($spec['type'])) {
- $spec['type'] = 'Zend\Form\Element';
- }
-
- return $this->create($spec);
- }
-
- /**
- * Create a fieldset
- *
- * @param array $spec
- * @return ElementInterface
- */
- public function createFieldset($spec)
- {
- if (!isset($spec['type'])) {
- $spec['type'] = 'Zend\Form\Fieldset';
- }
-
- return $this->create($spec);
- }
-
- /**
- * Create a form
- *
- * @param array $spec
- * @return ElementInterface
- */
- public function createForm($spec)
- {
- if (!isset($spec['type'])) {
- $spec['type'] = 'Zend\Form\Form';
- }
-
- return $this->create($spec);
- }
-
- /**
- * Configure an element based on the provided specification
- *
- * Specification can contain any of the following:
- * - type: the Element class to use; defaults to \Zend\Form\Element
- * - name: what name to provide the element, if any
- * - options: an array, Traversable, or ArrayAccess object of element options
- * - attributes: an array, Traversable, or ArrayAccess object of element
- * attributes to assign
- *
- * @param ElementInterface $element
- * @param array|Traversable|ArrayAccess $spec
- * @throws Exception\DomainException
- * @return ElementInterface
- */
- public function configureElement(ElementInterface $element, $spec)
- {
- $spec = $this->validateSpecification($spec, __METHOD__);
-
- $name = isset($spec['name']) ? $spec['name'] : null;
- $options = isset($spec['options']) ? $spec['options'] : null;
- $attributes = isset($spec['attributes']) ? $spec['attributes'] : null;
-
- if ($name !== null && $name !== '') {
- $element->setName($name);
- }
-
- if (is_array($options) || $options instanceof Traversable || $options instanceof ArrayAccess) {
- $element->setOptions($options);
- }
-
- if (is_array($attributes) || $attributes instanceof Traversable || $attributes instanceof ArrayAccess) {
- $element->setAttributes($attributes);
- }
-
- return $element;
- }
-
- /**
- * Configure a fieldset based on the provided specification
- *
- * Specification can contain any of the following:
- * - type: the Fieldset class to use; defaults to \Zend\Form\Fieldset
- * - name: what name to provide the fieldset, if any
- * - options: an array, Traversable, or ArrayAccess object of element options
- * - attributes: an array, Traversable, or ArrayAccess object of element
- * attributes to assign
- * - elements: an array or Traversable object where each entry is an array
- * or ArrayAccess object containing the keys:
- * - flags: (optional) array of flags to pass to FieldsetInterface::add()
- * - spec: the actual element specification, per {@link configureElement()}
- *
- * @param FieldsetInterface $fieldset
- * @param array|Traversable|ArrayAccess $spec
- * @throws Exception\DomainException
- * @return FieldsetInterface
- */
- public function configureFieldset(FieldsetInterface $fieldset, $spec)
- {
- $spec = $this->validateSpecification($spec, __METHOD__);
- $fieldset = $this->configureElement($fieldset, $spec);
-
- if (isset($spec['object'])) {
- $this->prepareAndInjectObject($spec['object'], $fieldset, __METHOD__);
- }
-
- if (isset($spec['hydrator'])) {
- $this->prepareAndInjectHydrator($spec['hydrator'], $fieldset, __METHOD__);
- }
-
- if (isset($spec['elements'])) {
- $this->prepareAndInjectElements($spec['elements'], $fieldset, __METHOD__);
- }
-
- if (isset($spec['fieldsets'])) {
- $this->prepareAndInjectFieldsets($spec['fieldsets'], $fieldset, __METHOD__);
- }
-
- $factory = (isset($spec['factory']) ? $spec['factory'] : $this);
- $this->prepareAndInjectFactory($factory, $fieldset, __METHOD__);
-
- return $fieldset;
- }
-
- /**
- * Configure a form based on the provided specification
- *
- * Specification follows that of {@link configureFieldset()}, and adds the
- * following keys:
- *
- * - input_filter: input filter instance, named input filter class, or
- * array specification for the input filter factory
- * - hydrator: hydrator instance or named hydrator class
- *
- * @param FormInterface $form
- * @param array|Traversable|ArrayAccess $spec
- * @return FormInterface
- */
- public function configureForm(FormInterface $form, $spec)
- {
- $spec = $this->validateSpecification($spec, __METHOD__);
- $form = $this->configureFieldset($form, $spec);
-
- if (isset($spec['input_filter'])) {
- $this->prepareAndInjectInputFilter($spec['input_filter'], $form, __METHOD__);
- }
-
- if (isset($spec['validation_group'])) {
- $this->prepareAndInjectValidationGroup($spec['validation_group'], $form, __METHOD__);
- }
-
- return $form;
- }
-
- /**
- * Validate a provided specification
- *
- * Ensures we have an array, Traversable, or ArrayAccess object, and returns it.
- *
- * @param array|Traversable|ArrayAccess $spec
- * @param string $method Method invoking the validator
- * @return array|ArrayAccess
- * @throws Exception\InvalidArgumentException for invalid $spec
- */
- protected function validateSpecification($spec, $method)
- {
- if (is_array($spec)) {
- return $spec;
- }
-
- if ($spec instanceof Traversable) {
- $spec = ArrayUtils::iteratorToArray($spec);
- return $spec;
- }
-
- if (!$spec instanceof ArrayAccess) {
- throw new Exception\InvalidArgumentException(sprintf(
- '%s expects an array, or object implementing Traversable or ArrayAccess; received "%s"',
- $method,
- (is_object($spec) ? get_class($spec) : gettype($spec))
- ));
- }
-
- return $spec;
- }
-
- /**
- * Takes a list of element specifications, creates the elements, and injects them into the provided fieldset
- *
- * @param array|Traversable|ArrayAccess $elements
- * @param FieldsetInterface $fieldset
- * @param string $method Method invoking this one (for exception messages)
- * @return void
- */
- protected function prepareAndInjectElements($elements, FieldsetInterface $fieldset, $method)
- {
- $elements = $this->validateSpecification($elements, $method);
-
- foreach ($elements as $elementSpecification) {
- $flags = isset($elementSpecification['flags']) ? $elementSpecification['flags'] : array();
- $spec = isset($elementSpecification['spec']) ? $elementSpecification['spec'] : array();
-
- if (!isset($spec['type'])) {
- $spec['type'] = 'Zend\Form\Element';
- }
-
- $element = $this->create($spec);
- $fieldset->add($element, $flags);
- }
- }
-
- /**
- * Takes a list of fieldset specifications, creates the fieldsets, and injects them into the master fieldset
- *
- * @param array|Traversable|ArrayAccess $fieldsets
- * @param FieldsetInterface $masterFieldset
- * @param string $method Method invoking this one (for exception messages)
- * @return void
- */
- public function prepareAndInjectFieldsets($fieldsets, FieldsetInterface $masterFieldset, $method)
- {
- $fieldsets = $this->validateSpecification($fieldsets, $method);
-
- foreach ($fieldsets as $fieldsetSpecification) {
- $flags = isset($fieldsetSpecification['flags']) ? $fieldsetSpecification['flags'] : array();
- $spec = isset($fieldsetSpecification['spec']) ? $fieldsetSpecification['spec'] : array();
-
- $fieldset = $this->createFieldset($spec);
- $masterFieldset->add($fieldset, $flags);
- }
- }
-
- /**
- * Prepare and inject an object
- *
- * Takes a string indicating a class name, instantiates the class
- * by that name, and injects the class instance as the bound object.
- *
- * @param string $objectName
- * @param FieldsetInterface $fieldset
- * @param string $method
- * @throws Exception\DomainException
- * @return void
- */
- protected function prepareAndInjectObject($objectName, FieldsetInterface $fieldset, $method)
- {
- if (!is_string($objectName)) {
- throw new Exception\DomainException(sprintf(
- '%s expects string class name; received "%s"',
- $method,
- (is_object($objectName) ? get_class($objectName) : gettype($objectName))
- ));
- }
-
- if (!class_exists($objectName)) {
- throw new Exception\DomainException(sprintf(
- '%s expects string class name to be a valid class name; received "%s"',
- $method,
- $objectName
- ));
- }
-
- $fieldset->setObject(new $objectName);
- }
-
- /**
- * Prepare and inject a named hydrator
- *
- * Takes a string indicating a hydrator class name (or a concrete instance), try first to instantiates the class
- * by pulling it from service manager, and injects the hydrator instance into the form.
- *
- * @param string|array|Hydrator\HydratorInterface $hydratorOrName
- * @param FieldsetInterface $fieldset
- * @param string $method
- * @return void
- * @throws Exception\DomainException If $hydratorOrName is not a string, does not resolve to a known class, or
- * the class does not implement Hydrator\HydratorInterface
- */
- protected function prepareAndInjectHydrator($hydratorOrName, FieldsetInterface $fieldset, $method)
- {
- if ($hydratorOrName instanceof Hydrator\HydratorInterface) {
- $fieldset->setHydrator($hydratorOrName);
- return;
- }
-
- if (is_array($hydratorOrName)) {
- if (!isset($hydratorOrName['type'])) {
- throw new Exception\DomainException(sprintf(
- '%s expects array specification to have a type value',
- $method
- ));
- }
- $hydratorOptions = (isset($hydratorOrName['options'])) ? $hydratorOrName['options'] : array();
- $hydratorOrName = $hydratorOrName['type'];
- } else {
- $hydratorOptions = array();
- }
-
- if (is_string($hydratorOrName)) {
- $hydrator = $this->getHydratorFromName($hydratorOrName);
- }
-
- if (!$hydrator instanceof Hydrator\HydratorInterface) {
- throw new Exception\DomainException(sprintf(
- '%s expects a valid implementation of Zend\Form\Hydrator\HydratorInterface; received "%s"',
- $method,
- $hydratorOrName
- ));
- }
-
- if (!empty($hydratorOptions) && $hydrator instanceof Hydrator\HydratorOptionsInterface) {
- $hydrator->setOptions($hydratorOptions);
- }
-
- $fieldset->setHydrator($hydrator);
- }
-
- /**
- * Prepare and inject a named factory
- *
- * Takes a string indicating a factory class name (or a concrete instance), try first to instantiates the class
- * by pulling it from service manager, and injects the factory instance into the fieldset.
- *
- * @param string|array|Factory $factoryOrName
- * @param FieldsetInterface $fieldset
- * @param string $method
- * @return void
- * @throws Exception\DomainException If $factoryOrName is not a string, does not resolve to a known class, or
- * the class does not extend Form\Factory
- */
- protected function prepareAndInjectFactory($factoryOrName, FieldsetInterface $fieldset, $method)
- {
- if (is_array($factoryOrName)) {
- if (!isset($factoryOrName['type'])) {
- throw new Exception\DomainException(sprintf(
- '%s expects array specification to have a type value',
- $method
- ));
- }
- $factoryOrName = $factoryOrName['type'];
- }
-
- if (is_string($factoryOrName)) {
- $factoryOrName = $this->getFactoryFromName($factoryOrName);
- }
-
- if (!$factoryOrName instanceof Factory) {
- throw new Exception\DomainException(sprintf(
- '%s expects a valid extention of Zend\Form\Factory; received "%s"',
- $method,
- $factoryOrName
- ));
- }
-
- $fieldset->setFormFactory($factoryOrName);
- }
-
- /**
- * Prepare an input filter instance and inject in the provided form
- *
- * If the input filter specified is a string, assumes it is a class name,
- * and attempts to instantiate it. If the class does not exist, or does
- * not extend InputFilterInterface, an exception is raised.
- *
- * Otherwise, $spec is passed on to the attached InputFilter Factory
- * instance in order to create the input filter.
- *
- * @param string|array|Traversable $spec
- * @param FormInterface $form
- * @param string $method
- * @return void
- * @throws Exception\DomainException for unknown InputFilter class or invalid InputFilter instance
- */
- protected function prepareAndInjectInputFilter($spec, FormInterface $form, $method)
- {
- if ($spec instanceof InputFilterInterface) {
- $form->setInputFilter($spec);
- return;
- }
-
- if (is_string($spec)) {
- if (!class_exists($spec)) {
- throw new Exception\DomainException(sprintf(
- '%s expects string input filter names to be valid class names; received "%s"',
- $method,
- $spec
- ));
- }
- $filter = new $spec;
- if (!$filter instanceof InputFilterInterface) {
- throw new Exception\DomainException(sprintf(
- '%s expects a valid implementation of Zend\InputFilter\InputFilterInterface; received "%s"',
- $method,
- $spec
- ));
- }
- $form->setInputFilter($filter);
- return;
- }
-
- $factory = $this->getInputFilterFactory();
- $filter = $factory->createInputFilter($spec);
- if (method_exists($filter, 'setFactory')) {
- $filter->setFactory($factory);
- }
- $form->setInputFilter($filter);
- }
-
- /**
- * Prepare a validation group and inject in the provided form
- *
- * Takes an array of elements names
- *
- * @param string|array|Traversable $spec
- * @param FormInterface $form
- * @param string $method
- * @return void
- * @throws Exception\DomainException if validation group given is not an array
- */
- protected function prepareAndInjectValidationGroup($spec, FormInterface $form, $method)
- {
- if (!is_array($spec)) {
- if (!class_exists($spec)) {
- throw new Exception\DomainException(sprintf(
- '%s expects an array for validation group; received "%s"',
- $method,
- $spec
- ));
- }
- }
-
- $form->setValidationGroup($spec);
- }
-
- /**
- * Try to pull hydrator from service manager, or instantiates it from its name
- *
- * @param string $hydratorName
- * @return mixed
- * @throws Exception\DomainException
- */
- protected function getHydratorFromName($hydratorName)
- {
- $services = $this->getFormElementManager()->getServiceLocator();
-
- if ($services && $services->has('HydratorManager')) {
- $hydrators = $services->get('HydratorManager');
- if ($hydrators->has($hydratorName)) {
- return $hydrators->get($hydratorName);
- }
- }
-
- if ($services && $services->has($hydratorName)) {
- return $services->get($hydratorName);
- }
-
- if (!class_exists($hydratorName)) {
- throw new Exception\DomainException(sprintf(
- 'Expects string hydrator name to be a valid class name; received "%s"',
- $hydratorName
- ));
- }
-
- $hydrator = new $hydratorName;
- return $hydrator;
- }
-
- /**
- * Try to pull factory from service manager, or instantiates it from its name
- *
- * @param string $factoryName
- * @return mixed
- * @throws Exception\DomainException
- */
- protected function getFactoryFromName($factoryName)
- {
- $services = $this->getFormElementManager()->getServiceLocator();
-
- if ($services && $services->has($factoryName)) {
- return $services->get($factoryName);
- }
-
- if (!class_exists($factoryName)) {
- throw new Exception\DomainException(sprintf(
- 'Expects string factory name to be a valid class name; received "%s"',
- $factoryName
- ));
- }
-
- $factory = new $factoryName;
- return $factory;
- }
- }