Skip to content

Commit

Permalink
[Form] Refactored logic to read and set values from Field to Property…
Browse files Browse the repository at this point in the history
…Path
  • Loading branch information
Bernhard Schussek authored and fabpot committed Oct 11, 2010
1 parent 1fab031 commit e1be4e9
Show file tree
Hide file tree
Showing 9 changed files with 581 additions and 463 deletions.
159 changes: 3 additions & 156 deletions src/Symfony/Component/Form/Field.php
Expand Up @@ -11,8 +11,6 @@
* with this source code in the file LICENSE.
*/

use Symfony\Component\Form\Exception\InvalidPropertyException;
use Symfony\Component\Form\Exception\PropertyAccessDeniedException;
use Symfony\Component\Form\ValueTransformer\ValueTransformerInterface;
use Symfony\Component\Form\ValueTransformer\TransformationFailedException;

Expand Down Expand Up @@ -274,7 +272,7 @@ public function getData()
*
* @see FieldInterface
*/
public function addError($messageTemplate, array $messageParameters = array(), PropertyPath $path = null, $type = null)
public function addError($messageTemplate, array $messageParameters = array(), PropertyPathIterator $pathIterator = null, $type = null)
{
$this->errors[] = array($messageTemplate, $messageParameters);
}
Expand Down Expand Up @@ -410,8 +408,7 @@ public function updateFromObject(&$objectOrArray)
{
// TODO throw exception if not object or array
if ($this->propertyPath !== null) {
$this->propertyPath->rewind();
$this->setData($this->readPropertyPath($objectOrArray, $this->propertyPath));
$this->setData($this->propertyPath->getValue($objectOrArray));
} else {
// pass object through if the property path is empty
$this->setData($objectOrArray);
Expand All @@ -426,157 +423,7 @@ public function updateObject(&$objectOrArray)
// TODO throw exception if not object or array

if ($this->propertyPath !== null) {
$this->propertyPath->rewind();
$this->updatePropertyPath($objectOrArray, $this->propertyPath);
}
}

/**
* Recursively reads the value of the property path in the data
*
* @param array|object $objectOrArray An object or array
* @param PropertyPath $propertyPath A property path pointing to a property
* in the object/array.
*/
protected function readPropertyPath(&$objectOrArray, PropertyPath $propertyPath)
{
if (is_object($objectOrArray)) {
$value = $this->readProperty($objectOrArray, $propertyPath);
}
// arrays need to be treated separately (due to PHP bug?)
// http://bugs.php.net/bug.php?id=52133
else {
if (!array_key_exists($propertyPath->getCurrent(), $objectOrArray)) {
$objectOrArray[$propertyPath->getCurrent()] = array();
}

$value =& $objectOrArray[$propertyPath->getCurrent()];
}

if ($propertyPath->hasNext()) {
$propertyPath->next();

return $this->readPropertyPath($value, $propertyPath);
} else {
return $value;
}
}

protected function updatePropertyPath(&$objectOrArray, PropertyPath $propertyPath)
{
if ($propertyPath->hasNext()) {
if (is_object($objectOrArray)) {
$value = $this->readProperty($objectOrArray, $propertyPath);
}
// arrays need to be treated separately (due to PHP bug?)
// http://bugs.php.net/bug.php?id=52133
else {
if (!array_key_exists($propertyPath->getCurrent(), $objectOrArray)) {
$objectOrArray[$propertyPath->getCurrent()] = array();
}

$value =& $objectOrArray[$propertyPath->getCurrent()];
}

$propertyPath->next();

$this->updatePropertyPath($value, $propertyPath);
} else {
$this->updateProperty($objectOrArray, $propertyPath);
}
}

/**
* Reads a specific element of the given data
*
* If the data is an array, the value at index $element is returned.
*
* If the data is an object, either the result of get{$element}(),
* is{$element}() or the property $element is returned. If none of these
* is publicly available, an exception is thrown
*
* @param object $object The data to read
* @param string $element The element to read from the data
* @return mixed The value of the element
*/
protected function readProperty($object, PropertyPath $propertyPath)
{
$camelizer = function ($path) {
return preg_replace(array('/(^|_)+(.)/e', '/\.(.)/e'), array("strtoupper('\\2')", "'_'.strtoupper('\\1')"), $path);
};

if ($propertyPath->isIndex()) {
if (!$object instanceof \ArrayAccess) {
throw new InvalidPropertyException(sprintf('Index "%s" cannot be read from object of type "%s" because it doesn\'t implement \ArrayAccess', $propertyPath->getCurrent(), get_class($object)));
}

return $object[$propertyPath->getCurrent()];
} else {
$reflClass = new \ReflectionClass($object);
$getter = 'get'.$camelizer($propertyPath->getCurrent());
$isser = 'is'.$camelizer($propertyPath->getCurrent());
$property = $propertyPath->getCurrent();

if ($reflClass->hasMethod($getter)) {
if (!$reflClass->getMethod($getter)->isPublic()) {
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $getter, $reflClass->getName()));
}

return $object->$getter();
} else if ($reflClass->hasMethod($isser)) {
if (!$reflClass->getMethod($isser)->isPublic()) {
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $isser, $reflClass->getName()));
}

return $object->$isser();
} 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 "get%s()" or "is%s()"?', $property, $reflClass->getName(), ucfirst($property), ucfirst($property)));
}

return $object->$property;
} else if (property_exists($object, $property)) {
// needed to support \stdClass instances
return $object->$property;
} else {
throw new InvalidPropertyException(sprintf('Neither property "%s" nor method "%s()" nor method "%s()" exists in class "%s"', $property, $getter, $isser, $reflClass->getName()));
}
}
}

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 if (property_exists($objectOrArray, $property)) {
// needed to support \stdClass instances
$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();
$this->propertyPath->setValue($objectOrArray, $this->getData());
}
}
}
26 changes: 12 additions & 14 deletions src/Symfony/Component/Form/FieldGroup.php
Expand Up @@ -393,18 +393,18 @@ public function isValid()
/**
* {@inheritDoc}
*/
public function addError($messageTemplate, array $messageParameters = array(), PropertyPath $path = null, $type = null)
public function addError($messageTemplate, array $messageParameters = array(), PropertyPathIterator $pathIterator = null, $type = null)
{
if ($path !== null) {
if ($type === self::FIELD_ERROR && $path->hasNext()) {
$path->next();
if ($pathIterator !== null) {
if ($type === self::FIELD_ERROR && $pathIterator->hasNext()) {
$pathIterator->next();

if ($path->isProperty() && $path->getCurrent() === 'fields') {
$path->next();
if ($pathIterator->isProperty() && $pathIterator->current() === 'fields') {
$pathIterator->next();
}

if ($this->has($path->getCurrent()) && !$this->get($path->getCurrent())->isHidden()) {
$this->get($path->getCurrent())->addError($messageTemplate, $messageParameters, $path, $type);
if ($this->has($pathIterator->current()) && !$this->get($pathIterator->current())->isHidden()) {
$this->get($pathIterator->current())->addError($messageTemplate, $messageParameters, $pathIterator, $type);

return;
}
Expand All @@ -414,14 +414,12 @@ public function addError($messageTemplate, array $messageParameters = array(), P

foreach ($iterator as $field) {
if (null !== ($fieldPath = $field->getPropertyPath())) {
$fieldPath->rewind();

if ($fieldPath->getCurrent() === $path->getCurrent() && !$field->isHidden()) {
if ($path->hasNext()) {
$path->next();
if ($fieldPath->getElement(0) === $pathIterator->current() && !$field->isHidden()) {
if ($pathIterator->hasNext()) {
$pathIterator->next();
}

$field->addError($messageTemplate, $messageParameters, $path, $type);
$field->addError($messageTemplate, $messageParameters, $pathIterator, $type);

return;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/Form/FieldInterface.php
Expand Up @@ -182,10 +182,10 @@ public function bind($taintedData);
* </code>
*
* @param FieldInterface $field
* @param PropertyPath $path
* @param PropertyPathIterator $pathIterator
* @param ConstraintViolation$violation
*/
function addError($messageTemplate, array $messageParameters = array(), PropertyPath $path = null, $type = null);
function addError($messageTemplate, array $messageParameters = array(), PropertyPathIterator $pathIterator = null, $type = null);

/**
* Returns whether the field is bound.
Expand Down
8 changes: 5 additions & 3 deletions src/Symfony/Component/Form/Form.php
Expand Up @@ -142,17 +142,19 @@ final public function bind($taintedValues, array $taintedFiles = null)

if ($this->getParent() === null) {
if ($violations = $this->validator->validate($this, $this->getValidationGroups())) {
// TODO: test me
foreach ($violations as $violation) {
$propertyPath = new PropertyPath($violation->getPropertyPath());
$iterator = $propertyPath->getIterator();

if ($propertyPath->getCurrent() == 'data') {
if ($iterator->current() == 'data') {
$type = self::DATA_ERROR;
$propertyPath->next(); // point at the first data element
$iterator->next(); // point at the first data element
} else {
$type = self::FIELD_ERROR;
}

$this->addError($violation->getMessageTemplate(), $violation->getMessageParameters(), $propertyPath, $type);
$this->addError($violation->getMessageTemplate(), $violation->getMessageParameters(), $iterator, $type);
}
}
}
Expand Down

0 comments on commit e1be4e9

Please sign in to comment.