[Form] Cleaned up ValueTransformerInterface

This commit removes CollectionToStringTransformer. Transformers should never change the state of the outside world, otherwise hard-to-track bugs might creap in.

This functionality needs to be implemented as a custom FieldType (see EntityChoiceField).
This commit is contained in:
Bernhard Schussek 2011-02-06 19:24:45 +01:00 committed by Fabien Potencier
parent bd3e6c6b49
commit 74d0ac82f7
13 changed files with 13 additions and 395 deletions

View File

@ -47,7 +47,7 @@ class BooleanToStringTransformer extends Configurable implements ValueTransforme
* @param string $value String value.
* @return Boolean Boolean value.
*/
public function reverseTransform($value, $originalValue)
public function reverseTransform($value)
{
if (!is_string($value)) {
throw new UnexpectedTypeException($value, 'string');

View File

@ -1,157 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ValueTransformer;
use Symfony\Component\Form\Configurable;
use Doctrine\Common\Collections\Collection;
/**
* Transforms an instance of Doctrine\Common\Collections\Collection into a string of unique names.
*
* Use-Cases for this transformer include: List of Tag-Names, List Of Group/User-Names or the like.
*
* This transformer only makes sense if you know the list of related collections to be small and
* that they have a unique identifier field that is of meaning to the user (Tag Names) and is
* enforced to be unique in the storage.
*
* This transformer can cause the following SQL operations to happen in the case of an ORM collection:
* 1. Initialize the whole collection using one SELECT query
* 2. For each removed element issue an UPDATE or DELETE stmt (depending on one-to-many or many-to-many)
* 3. For each inserted element issue an INSERT or UPDATE stmt (depending on one-to-many or many-to-many)
* 4. Extra updates if necessary by the ORM.
*
* @todo Refactor to make 'fieldName' optional (identifier).
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class CollectionToStringTransformer extends Configurable implements ValueTransformerInterface
{
protected function configure()
{
$this->addOption('trim', true);
$this->addOption('separator', ',');
$this->addOption('explode_callback', 'explode');
$this->addOption('implode_callback', 'implode');
$this->addOption('create_instance_callback', null);
$this->addRequiredOption('em');
$this->addRequiredOption('class_name');
$this->addRequiredOption('field_name');
parent::configure();
}
/**
* @param string $value
* @param Collection $collection
*/
public function reverseTransform($value, $collection)
{
if (strlen(trim($value)) == 0) {
// don't check for collection count, a straight clear doesnt initialize the collection
$collection->clear();
return $collection;
}
$callback = $this->getOption('explode_callback');
$values = call_user_func($callback, $this->getOption('separator'), $value);
if ($this->getOption('trim') === true) {
$values = array_map('trim', $values);
}
/* @var $em Doctrine\ORM\EntityManager */
$em = $this->getOption('em');
$className = $this->getOption('class_name');
$reflField = $em->getClassMetadata($className)
->getReflectionProperty($this->getOption('field_name'));
// 1. removing elements that are not yet present anymore
foreach ($collection as $object) {
$uniqueIdent = $reflField->getValue($object);
$key = array_search($uniqueIdent, $values);
if (false === $key) {
$collection->removeElement($object);
} else {
// found in the collection, no need to do anything with it so remove it
unset($values[$key]);
}
}
// 2. add elements that are known to the EntityManager but newly connected, query them from the repository
if (count($values)) {
$dql = sprintf('SELECT o FROM %s o WHERE o.%s IN (', $className, $this->getOption('field_name'));
$query = $em->createQuery();
$needles = array();
$i = 0;
foreach ($values as $val) {
$query->setParameter(++$i, $val);
$needles[] = '?'.$i;
}
$dql .= implode(',', $needles).')';
$query->setDql($dql);
$newElements = $query->getResult();
foreach ($newElements as $object) {
$collection->add($object);
$uniqueIdent = $reflField->getValue($object);
$key = array_search($uniqueIdent, $values);
unset($values[$key]);
}
}
// 3. new elements that are not in the repository have to be created and persisted then attached:
if (count($values)) {
$callback = $this->getOption('create_instance_callback');
if (!$callback || !is_callable($callback)) {
throw new TransformationFailedException('Cannot transform list of identifiers, because a new element was detected and it is unknown how to create an instance of this element.');
}
foreach ($values as $newValue) {
$newInstance = call_user_func($callback, $newValue);
if (!($newInstance instanceof $className)) {
throw new TransformationFailedException(sprintf('Error while trying to create a new instance for the identifier "%s". No new instance was created.', $newValue));
}
$collection->add($newInstance);
$em->persist($newInstance);
}
}
return $collection;
}
/**
* Transform a Doctrine Collection into a string of identifies with a separator.
*
* @param Collection $value
* @return string
*/
public function transform($value)
{
if (null === $value) {
return '';
}
$values = array();
$em = $this->getOption('em');
$reflField = $em->getClassMetadata($this->getOption('class_name'))
->getReflectionProperty($this->getOption('field_name'));
foreach ($value as $object) {
$values[] = $reflField->getValue($object);
}
$callback = $this->getOption('implode_callback');
return call_user_func($callback, $this->getOption('separator'), $values);
}
}

View File

@ -95,7 +95,7 @@ class DateTimeToArrayTransformer extends BaseDateTimeTransformer
* @param array $value Localized date string/array
* @return DateTime Normalized date
*/
public function reverseTransform($value, $originalValue)
public function reverseTransform($value)
{
if (null === $value) {
return null;

View File

@ -86,7 +86,7 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
* @param string|array $value Localized date string/array
* @return DateTime Normalized date
*/
public function reverseTransform($value, $originalValue)
public function reverseTransform($value)
{
$inputTimezone = $this->getOption('input_timezone');

View File

@ -62,7 +62,7 @@ class DateTimeToStringTransformer extends Configurable implements ValueTransform
* @param string $value A value as produced by PHP's date() function
* @return DateTime A DateTime object
*/
public function reverseTransform($value, $originalValue)
public function reverseTransform($value)
{
if (empty($value)) {
return null;

View File

@ -60,7 +60,7 @@ class DateTimeToTimestampTransformer extends Configurable implements ValueTransf
* @param string $value A value as produced by PHP's date() function
* @return DateTime A DateTime object
*/
public function reverseTransform($value, $originalValue)
public function reverseTransform($value)
{
if (null === $value) {
return null;

View File

@ -58,9 +58,9 @@ class MoneyToLocalizedStringTransformer extends NumberToLocalizedStringTransform
* @param string $value Localized money string
* @return number Normalized number
*/
public function reverseTransform($value, $originalValue)
public function reverseTransform($value)
{
$value = parent::reverseTransform($value, $originalValue);
$value = parent::reverseTransform($value);
if (null !== $value) {
$value *= $this->getOption('divisor');

View File

@ -74,7 +74,7 @@ class NumberToLocalizedStringTransformer extends Configurable implements ValueTr
*
* @param string $value
*/
public function reverseTransform($value, $originalValue)
public function reverseTransform($value)
{
if (!is_string($value)) {
throw new UnexpectedTypeException($value, 'string');

View File

@ -82,7 +82,7 @@ class PercentToLocalizedStringTransformer extends Configurable implements ValueT
* @param number $value Percentage value.
* @return number Normalized value.
*/
public function reverseTransform($value, $originalValue)
public function reverseTransform($value)
{
if (!is_string($value)) {
throw new UnexpectedTypeException($value, 'string');

View File

@ -48,7 +48,7 @@ class ReversedTransformer implements ValueTransformerInterface
/**
* {@inheritDoc}
*/
public function reverseTransform($value, $originalValue)
public function reverseTransform($value)
{
return $this->reversedTransformer->transform($value);
}

View File

@ -66,10 +66,10 @@ class ValueTransformerChain implements ValueTransformerInterface
* @param mixed $value The transformed value
* @return mixed The reverse-transformed value
*/
public function reverseTransform($value, $originalValue)
public function reverseTransform($value)
{
for ($i = count($this->transformers) - 1; $i >= 0; --$i) {
$value = $this->transformers[$i]->reverseTransform($value, $originalValue);
$value = $this->transformers[$i]->reverseTransform($value);
}
return $value;

View File

@ -65,11 +65,9 @@ interface ValueTransformerInterface
* is passed.
*
* @param mixed $value The value in the transformed representation
* @param mixed $originalValue The original value from the datasource that is about to be overwritten by the new value.
* @return mixed The value in the original representation
* @throws UnexpectedTypeException when the argument is not of the
* expected type
* @throws ValueTransformerException when the transformation fails
*/
function reverseTransform($value, $originalValue);
function reverseTransform($value);
}

View File

@ -1,223 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Tests\Component\Form\ValueTransformer;
require_once __DIR__.'/../DoctrineOrmTestCase.php';
use Symfony\Tests\Component\Form\DoctrineOrmTestCase;
use Symfony\Component\Form\ValueTransformer\CollectionToStringTransformer;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Tools\SchemaTool;
class CollectionToStringTransformerTest extends DoctrineOrmTestCase
{
/**
* @var EntityManager
*/
private $em;
protected function setUp()
{
parent::setUp();
$this->em = $this->createTestEntityManager();
$schemaTool = new SchemaTool($this->em);
$classes = array($this->em->getClassMetadata(__NAMESPACE__.'\Tag'));
try {
$schemaTool->dropSchema($classes);
} catch(\Exception $e) {
}
try {
$schemaTool->createSchema($classes);
} catch(\Exception $e) {
echo $e->getMessage();
}
}
public function testNoEntityManagerThrowsException()
{
$this->setExpectedException('Symfony\Component\Form\Exception\MissingOptionsException');
$transformer = new CollectionToStringTransformer(array(
'class_name' => __NAMESPACE__.'\Tag',
'field_name' => 'name',
));
}
public function testNoClassNameThrowsException()
{
$this->setExpectedException('Symfony\Component\Form\Exception\MissingOptionsException');
$transformer = new CollectionToStringTransformer(array(
'field_name' => 'name',
'em' => $this->em,
));
}
public function testNoFieldNameThrowsException()
{
$this->setExpectedException('Symfony\Component\Form\Exception\MissingOptionsException');
$transformer = new CollectionToStringTransformer(array(
'class_name' => __NAMESPACE__.'\Tag',
'em' => $this->em,
));
}
public function createTransformer()
{
$transformer = new CollectionToStringTransformer(array(
'class_name' => __NAMESPACE__.'\Tag',
'field_name' => 'name',
'em' => $this->em,
'create_instance_callback' => function($tagName) {
return new Tag($tagName);
}
));
return $transformer;
}
public function testTransformEmptyCollection()
{
$transformer = $this->createTransformer();
$ret = $transformer->transform(new ArrayCollection());
$this->assertEquals("", $ret);
}
/**
* @depends testTransformEmptyCollection
*/
public function testTransformCollection()
{
$transformer = $this->createTransformer();
$tags = new ArrayCollection();
$tags->add(new Tag("foo"));
$tags->add(new Tag("bar"));
$this->assertEquals("foo,bar", $transformer->transform($tags));
}
public function createTagCollection()
{
$tags = new ArrayCollection();
$tags->add(new Tag("foo"));
$tags->add(new Tag("bar"));
return $tags;
}
/**
* @depends testTransformEmptyCollection
*/
public function testReverseTransformEmptyString()
{
$transformer = $this->createTransformer();
$col = new ArrayCollection();
$newCol = $transformer->reverseTransform("", $col);
$this->assertSame($col, $newCol, "A collection is an expenive object that is re-used by the transformer!");
$this->assertEquals(0, count($newCol));
}
/**
* @depends testReverseTransformEmptyString
*/
public function testReverseTransformEmptyStringEmptiesCollection()
{
$transformer = $this->createTransformer();
$col = $this->createTagCollection();
$newCol = $transformer->reverseTransform("", $col);
$this->assertSame($col, $newCol, "A collection is an expenive object that is re-used by the transformer!");
$this->assertEquals(0, count($newCol));
}
/**
* @depends testTransformEmptyCollection
*/
public function testReverseTransformUnchanged()
{
$transformer = $this->createTransformer();
$tags = $this->createTagCollection();
$tags = $transformer->reverseTransform("foo,bar", $tags);
$this->assertEquals(2, count($tags));
}
/**
* @depends testTransformEmptyCollection
*/
public function testReverseTransformNewKnownEntity()
{
$transformer = $this->createTransformer();
$newTag = new Tag("baz");
$this->em->persist($newTag);
$this->em->flush();
$tags = $this->createTagCollection();
$tags = $transformer->reverseTransform("foo, bar, baz", $tags);
$this->assertEquals(3, count($tags));
$this->assertTrue($tags->contains($newTag));
}
/**
* @depends testReverseTransformNewKnownEntity
*/
public function testReverseTransformNewUnknownEntity()
{
$transformer = $this->createTransformer();
$tags = $this->createTagCollection();
$tags = $transformer->reverseTransform("foo, bar, baz", $tags);
$this->assertEquals(3, count($tags));
$this->em->flush();
$this->assertSame($this->em, $transformer->getOption('em'));
$this->assertEquals(1, count($this->em->getRepository(__NAMESPACE__.'\Tag')->findAll()));
}
/**
* @depends testReverseTransformNewUnknownEntity
*/
public function testReverseTransformRemoveEntity()
{
$transformer = $this->createTransformer();
$tags = $this->createTagCollection();
$tags = $transformer->reverseTransform("foo", $tags);
$this->assertEquals(1, count($tags));
}
}
/** @Entity */
class Tag
{
/** @Id @GeneratedValue @Column(type="integer") */
public $id;
/** @Column(type="string") */
public $name;
public function __construct($name) {
$this->name = $name;
}
}