public function createAction() { // bind form to page model $page = new Page(); $this->form->bind($this->request, $page); if ($this->form->isValid()) { try { // path for page $parent = $this->form->get('parent')->getData(); $path = $parent . '/' . $page->name; // save page $this->dm->persist($page, $path); $this->dm->flush(); // redirect with message $this->request->getSession()->setFlash('notice', 'Page created!'); return $this->redirect($this->generateUrl('admin')); } catch (HTTPErrorException $e) { $path = new PropertyPath('name'); $this->form->addError(new DataError('Name already in use.'), $path->getIterator()); } } return $this->render('SandboxAdminBundle:Admin:create.html.twig', array('form' => $this->form)); }
/** * Validates the form and its domain object * * @throws FormException If the option "validator" was not set */ public function validate() { $validator = $this->getOption('validator'); if (null === $validator) { throw new MissingOptionsException('The option "validator" is required for validating', array('validator')); } // Validate the form in group "Default" // Validation of the data in the custom group is done by validateData(), // which is constrained by the Execute constraint if ($violations = $validator->validate($this)) { foreach ($violations as $violation) { $propertyPath = new PropertyPath($violation->getPropertyPath()); $iterator = $propertyPath->getIterator(); $template = $violation->getMessageTemplate(); $parameters = $violation->getMessageParameters(); if ($iterator->current() == 'data') { $iterator->next(); // point at the first data element $error = new DataError($template, $parameters); } else { $error = new FieldError($template, $parameters); } $this->addError($error, $iterator); } } }
/** * Binds the form with values and files. * * This method is final because it is very easy to break a form when * overriding this method and adding logic that depends on $taintedFiles. * You should override doBind() instead where the uploaded files are * already merged into the data array. * * @param array $taintedValues The form data of the $_POST array * @param array $taintedFiles An array of uploaded files * @return boolean Whether the form is valid */ public final function bind($taintedValues, array $taintedFiles = null) { if (null === $taintedFiles) { if ($this->isMultipart() && $this->getParent() === null) { throw new \InvalidArgumentException('You must provide a files array for multipart forms'); } $taintedFiles = array(); } if (null === $taintedValues) { $taintedValues = array(); } $this->doBind(self::deepArrayUnion($taintedValues, $taintedFiles)); if ($this->getParent() === null) { if ($this->validator === null) { throw new FormException('A validator is required for binding. Forgot to pass it to the constructor of the form?'); } if ($violations = $this->validator->validate($this, $this->getValidationGroups())) { // TODO: test me foreach ($violations as $violation) { $propertyPath = new PropertyPath($violation->getPropertyPath()); $iterator = $propertyPath->getIterator(); if ($iterator->current() == 'data') { $type = self::DATA_ERROR; $iterator->next(); // point at the first data element } else { $type = self::FIELD_ERROR; } $this->addError(new FieldError($violation->getMessageTemplate(), $violation->getMessageParameters()), $iterator, $type); } } } }
public function testToString() { $path = new PropertyPath('reference.traversable[index].property'); $this->assertEquals('reference.traversable[index].property', $path->__toString()); }
public function testNextThrowsExceptionIfNoNextElement() { $path = new PropertyPath('property'); $this->setExpectedException('OutOfBoundsException'); $path->next(); }
protected function updateProperty(&$objectOrArray, PropertyPath $propertyPath) { if (is_object($objectOrArray) && $propertyPath->isIndex()) { if (!$objectOrArray instanceof \ArrayAccess) { throw new InvalidPropertyException(sprintf('Index "%s" cannot be modified in object of type "%s" because it doesn\'t implement \\ArrayAccess', $propertyPath->getCurrent(), get_class($objectOrArray))); } $objectOrArray[$propertyPath->getCurrent()] = $this->getData(); } else { if (is_object($objectOrArray)) { $reflClass = new \ReflectionClass($objectOrArray); $setter = 'set' . ucfirst($propertyPath->getCurrent()); $property = $propertyPath->getCurrent(); if ($reflClass->hasMethod($setter)) { if (!$reflClass->getMethod($setter)->isPublic()) { throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->getName())); } $objectOrArray->{$setter}($this->getData()); } else { if ($reflClass->hasProperty($property)) { if (!$reflClass->getProperty($property)->isPublic()) { throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public in class "%s". Maybe you should create the method "set%s()"?', $property, $reflClass->getName(), ucfirst($property))); } $objectOrArray->{$property} = $this->getData(); } else { throw new InvalidPropertyException(sprintf('Neither element "%s" nor method "%s()" exists in class "%s"', $property, $setter, $reflClass->getName())); } } } else { $objectOrArray[$propertyPath->getCurrent()] = $this->getData(); } } }
public function testAddErrorMapsErrorsOntoFieldsInAnonymousGroups() { $error = new FieldError('Message'); // path is expected to point at "address" $expectedPath = new PropertyPath('address'); $expectedPathIterator = $expectedPath->getIterator(); $field = $this->createMockField('address'); $field->expects($this->any())->method('getPropertyPath')->will($this->returnValue(new PropertyPath('address'))); $field->expects($this->once())->method('addError')->with($this->equalTo($error), $this->equalTo($expectedPathIterator), $this->equalTo(FieldGroup::DATA_ERROR)); $group = new TestFieldGroup('author'); $group2 = new TestFieldGroup('anonymous', array('property_path' => null)); $group2->add($field); $group->add($group2); $path = new PropertyPath('address'); $group->addError($error, $path->getIterator(), FieldGroup::DATA_ERROR); }
/** * Binds the form with values and files. * * This method is final because it is very easy to break a form when * overriding this method and adding logic that depends on $taintedFiles. * You should override doBind() instead where the uploaded files are * already merged into the data array. * * @param array $taintedValues The form data of the $_POST array * @param array $taintedFiles An array of uploaded files * @return boolean Whether the form is valid */ public final function bind($taintedValues, array $taintedFiles = null) { if ($taintedFiles === null) { if ($this->isMultipart() && $this->getParent() === null) { throw new \InvalidArgumentException('You must provide a files array for multipart forms'); } $taintedFiles = array(); } if (null === $taintedValues) { $taintedValues = array(); } $this->doBind(self::deepArrayUnion($taintedValues, $taintedFiles)); if ($this->getParent() === null) { if ($violations = $this->validator->validate($this, $this->getValidationGroups())) { foreach ($violations as $violation) { $propertyPath = new PropertyPath($violation->getPropertyPath()); if ($propertyPath->getCurrent() == 'data') { $type = self::DATA_ERROR; $propertyPath->next(); // point at the first data element } else { $type = self::FIELD_ERROR; } $this->addError($violation->getMessage(), $propertyPath, $type); } } } }
/** * Initializes the choices and returns them * * The choices are generated from the entities. If the entities have a * composite identifier, the choices are indexed using ascending integers. * Otherwise the identifiers are used as indices. * * If the entities were passed in the "choices" option, this method * does not have any significant overhead. Otherwise, if a query builder * was passed in the "query_builder" option, this builder is now used * to construct a query which is executed. In the last case, all entities * for the underlying class are fetched from the repository. * * If the option "property" was passed, the property path in that option * is used as option values. Otherwise this method tries to convert * objects to strings using __toString(). * * @return array An array of choices */ protected function getInitializedChoices() { if ($this->getOption('choices')) { $entities = parent::getInitializedChoices(); } else { if ($qb = $this->getQueryBuilder()) { $entities = $qb->getQuery()->execute(); } else { $class = $this->getOption('class'); $em = $this->getOption('em'); $entities = $em->getRepository($class)->findAll(); } } $propertyPath = null; $choices = array(); $this->entities = array(); // The propery option defines, which property (path) is used for // displaying entities as strings if ($this->getOption('property')) { $propertyPath = new PropertyPath($this->getOption('property')); } foreach ($entities as $key => $entity) { if ($propertyPath) { // If the property option was given, use it $value = $propertyPath->getValue($entity); } else { // Otherwise expect a __toString() method in the entity $value = (string) $entity; } if (count($this->getIdentifierFields()) > 1) { // When the identifier consists of multiple field, use // naturally ordered keys to refer to the choices $choices[$key] = $value; $this->entities[$key] = $entity; } else { // When the identifier is a single field, index choices by // entity ID for performance reasons $id = current($this->getIdentifierValues($entity)); $choices[$id] = $value; $this->entities[$id] = $entity; } } return $choices; }
/** * {@inheritDoc} */ public function addError($message, PropertyPath $path = null, $type = null) { if ($path !== null) { if ($type === self::FIELD_ERROR && $path->hasNext()) { $path->next(); if ($this->has($path->getCurrent()) && !$this->get($path->getCurrent())->isHidden()) { $this->get($path->getCurrent())->addError($message, $path, $type); return; } } else { if ($type === self::DATA_ERROR) { $iterator = new RecursiveFieldsWithPropertyPathIterator($this); $iterator = new \RecursiveIteratorIterator($iterator); foreach ($iterator as $field) { if (null !== ($fieldPath = $field->getPropertyPath())) { $fieldPath->rewind(); if ($fieldPath->getCurrent() === $path->getCurrent() && !$field->isHidden()) { if ($path->hasNext()) { $path->next(); } $field->addError($message, $path, $type); return; } } } } } } parent::addError($message); }
public function testAddErrorMapsDataValidationErrorsOntoNestedFields() { // path is expected to point at "street" $expectedPath = new PropertyPath('address.street'); $expectedPath->next(); $field = $this->createMockField('address'); $field->expects($this->any())->method('getPropertyPath')->will($this->returnValue(new PropertyPath('address'))); $field->expects($this->once())->method('addError')->with($this->equalTo('Message'), $this->equalTo($expectedPath), $this->equalTo(FieldGroup::DATA_ERROR)); $group = new FieldGroup('author'); $group->add($field); $group->addError('Message', new PropertyPath('address.street'), FieldGroup::DATA_ERROR); }
public function testAddErrorMapsErrorsOntoFieldsInVirtualGroups() { $error = new DataError('Message'); // path is expected to point at "address" $expectedPath = new PropertyPath('address'); $expectedPathIterator = $expectedPath->getIterator(); $field = $this->createMockField('address'); $field->expects($this->any())->method('getPropertyPath')->will($this->returnValue(new PropertyPath('address'))); $field->expects($this->once())->method('addError')->with($this->equalTo($error), $this->equalTo($expectedPathIterator)); $form = new Form('author'); $nestedForm = new Form('nested', array('virtual' => true)); $nestedForm->add($field); $form->add($nestedForm); $path = new PropertyPath('address'); $form->addError($error, $path->getIterator()); }