Skip to content

Commit

Permalink
ModelsToArrayTransformer respects order
Browse files Browse the repository at this point in the history
  • Loading branch information
VincentLanglet committed Dec 25, 2021
1 parent f73875c commit ba2874f
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 86 deletions.
2 changes: 1 addition & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ parameters:
message: '#^Method Sonata\\AdminBundle\\Tests\\Fixtures\\Entity\\FooToStringNull\:\:__toString\(\) should return string but returns null\.$#'
path: tests/Fixtures/Entity/FooToStringNull.php
- # The phpstan-param is less precise than the psalm-param https://github.com/phpstan/phpstan/issues/4703
message: '#^Parameter \#3 \$idx of method Sonata\\AdminBundle\\Model\\ModelManagerInterface\<T of object\>\:\:addIdentifiersToQuery\(\) expects non-empty-array\<int\|string\>, non-empty-array\<array\<string\>\|int\|string\> given\.$#'
message: '#^Parameter \#1 \$value of method Sonata\\AdminBundle\\Form\\DataTransformer\\ModelsToArrayTransformer\<T of object\>\:\:reverseTransform\(\) expects array\<int\|string\>\|null, array\<array\<string\>\|int\|string\> given\.$#'
path: src/Form/DataTransformer/ModelToIdPropertyTransformer.php
- # `treatPhpdocAsCertain: false` should not report this https://github.com/phpstan/phpstan/issues/5333
message: '#^Instanceof between Symfony\\Component\\Routing\\Route and Symfony\\Component\\Routing\\Route will always evaluate to true\.$#'
Expand Down
11 changes: 1 addition & 10 deletions src/Form/DataTransformer/ModelToIdPropertyTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Util\ClassUtils;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Sonata\AdminBundle\Util\TraversableToCollection;
use Symfony\Component\Form\DataTransformerInterface;

/**
Expand Down Expand Up @@ -118,15 +117,7 @@ public function reverseTransform($value)

unset($value['_labels']);

if ([] === $value) {
$result = $value;
} else {
$query = $this->modelManager->createQuery($this->className);
$this->modelManager->addIdentifiersToQuery($this->className, $query, $value);
$result = $this->modelManager->executeQuery($query);
}

return TraversableToCollection::transform($result);
return (new ModelsToArrayTransformer($this->modelManager, $this->className))->reverseTransform($value);
}

/**
Expand Down
71 changes: 37 additions & 34 deletions src/Form/DataTransformer/ModelsToArrayTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@

namespace Sonata\AdminBundle\Form\DataTransformer;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Util\ClassUtils;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Sonata\AdminBundle\Util\TraversableToCollection;
use Sonata\Doctrine\Adapter\AdapterInterface;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
Expand Down Expand Up @@ -68,7 +67,15 @@ public function transform($value): array

$array = [];
foreach ($value as $model) {
$array[] = implode(AdapterInterface::ID_SEPARATOR, $this->getIdentifierValues($model));
$identifier = $this->modelManager->getNormalizedIdentifier($model);
if (null === $identifier) {
throw new TransformationFailedException(sprintf(
'No identifier was found for the model "%s".',
ClassUtils::getClass($model)
));
}

$array[] = $identifier;
}

return $array;
Expand All @@ -94,42 +101,38 @@ public function reverseTransform($value)
}

if ([] === $value) {
$result = $value;
} else {
$query = $this->modelManager->createQuery($this->class);
$this->modelManager->addIdentifiersToQuery($this->class, $query, $value);
$result = $this->modelManager->executeQuery($query);
return new ArrayCollection();
}

$collection = TraversableToCollection::transform($result);

$diffCount = \count($value) - $collection->count();

if (0 !== $diffCount) {
throw new TransformationFailedException(sprintf(
'%u keys could not be found in the provided values: "%s".',
$diffCount,
implode('", "', $value)
));
$query = $this->modelManager->createQuery($this->class);
$this->modelManager->addIdentifiersToQuery($this->class, $query, $value);
$queryResult = $this->modelManager->executeQuery($query);

$modelsById = [];
foreach ($queryResult as $model) {
$identifier = $this->modelManager->getNormalizedIdentifier($model);
if (null === $identifier) {
throw new TransformationFailedException(sprintf(
'No identifier was found for the model "%s".',
ClassUtils::getClass($model)
));
}

$modelsById[$identifier] = $model;
}

return $collection;
}
$result = [];
foreach ($value as $identifier) {
if (!isset($modelsById[$identifier])) {
throw new TransformationFailedException(sprintf(
'No model was found for the identifier "%s".',
$identifier,
));
}

/**
* @return array<int|string>
*
* @phpstan-param T $model
*/
private function getIdentifierValues(object $model): array
{
try {
return $this->modelManager->getIdentifierValues($model);
} catch (\Exception $e) {
throw new \InvalidArgumentException(sprintf(
'Unable to retrieve the identifier values for entity %s',
ClassUtils::getClass($model)
), 0, $e);
$result[] = $modelsById[$identifier];
}

return new ArrayCollection($result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ public function testReverseTransformMultiple(array $expected, $params, Foo $enti

return $collection;
});
$modelManager
->method('getNormalizedIdentifier')
->willReturnMap([
[$entity1, '123'],
[$entity2, '456'],
[$entity3, '789'],
]);

$result = $transformer->reverseTransform($params);
static::assertInstanceOf(Collection::class, $result);
Expand Down
89 changes: 48 additions & 41 deletions tests/Form/DataTransformer/ModelsToArrayTransformerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Sonata\AdminBundle\Form\DataTransformer\ModelsToArrayTransformer;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Sonata\AdminBundle\Tests\Fixtures\Entity\Entity;
use Sonata\AdminBundle\Tests\Fixtures\Entity\Foo;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
Expand All @@ -34,50 +35,16 @@ public function testConstructor(): void
static::assertInstanceOf(ModelsToArrayTransformer::class, $transformer);
}

/**
* @param array<int|string>|null $value
*
* @dataProvider reverseTransformProvider
*/
public function testReverseTransform(?array $value): void
public function testReverseTransformWithNull(): void
{
$modelManager = $this->createMock(ModelManagerInterface::class);

if (null !== $value) {
$proxyQuery = $this->createStub(ProxyQueryInterface::class);
$modelManager
->method('createQuery')
->with(static::equalTo(Foo::class))
->willReturn($proxyQuery);
$modelManager
->method('executeQuery')
->with(static::equalTo($proxyQuery))
->willReturn($value);
}

$transformer = new ModelsToArrayTransformer(
$modelManager,
Foo::class
);

$result = $transformer->reverseTransform($value);

if (null === $value) {
static::assertNull($result);
} else {
static::assertInstanceOf(Collection::class, $result);
static::assertCount(\count($value), $result);
}
}

/**
* @phpstan-return iterable<array-key, array{array<int|string>|null}>
*/
public function reverseTransformProvider(): iterable
{
yield [['a']];
yield [['a', 'b', 3]];
yield [null];
static::assertNull($transformer->reverseTransform(null));
}

public function testReverseTransformWithEmptyArray(): void
Expand All @@ -104,6 +71,39 @@ public function testReverseTransformWithEmptyArray(): void
static::assertCount(0, $result);
}

public function testReverseTransformRespectOrder(): void
{
$object1 = new Entity(1);
$object2 = new Entity(2);
$object3 = new Entity(3);

$proxyQuery = $this->createStub(ProxyQueryInterface::class);
$modelManager = $this->createMock(ModelManagerInterface::class);
$modelManager
->method('createQuery')
->willReturn($proxyQuery);
$modelManager
->method('executeQuery')
->willReturn([$object1, $object2, $object3]);
$modelManager
->method('getNormalizedIdentifier')
->willReturnMap([
[$object1, '1'],
[$object2, '2'],
[$object3, '3'],
]);

$transformer = new ModelsToArrayTransformer(
$modelManager,
Entity::class
);

$result = $transformer->reverseTransform([1, 3, 2]);

static::assertInstanceOf(Collection::class, $result);
static::assertSame([$object1, $object3, $object2], $result->toArray());
}

/**
* @psalm-suppress InvalidArgument
*/
Expand All @@ -126,8 +126,9 @@ public function testReverseTransformUnexpectedType(): void

public function testReverseTransformFailed(): void
{
$value = ['a', 'b'];
$reverseTransformCollection = ['a'];
$object1 = new Entity(1);
$object2 = new Entity(2);

$modelManager = $this->createMock(ModelManagerInterface::class);
$proxyQuery = $this->createStub(ProxyQueryInterface::class);
$modelManager
Expand All @@ -137,16 +138,22 @@ public function testReverseTransformFailed(): void
$modelManager
->method('executeQuery')
->with(static::equalTo($proxyQuery))
->willReturn($reverseTransformCollection);
->willReturn([$object1]);
$modelManager
->method('getNormalizedIdentifier')
->willReturnMap([
[$object1, '1'],
[$object2, '2'],
]);

$transformer = new ModelsToArrayTransformer(
$modelManager,
Foo::class
);

$this->expectException(TransformationFailedException::class);
$this->expectExceptionMessage('1 keys could not be found in the provided values: "a", "b".');
$this->expectExceptionMessage('No model was found for the identifier "2".');

$transformer->reverseTransform($value);
$transformer->reverseTransform([1, 2]);
}
}

0 comments on commit ba2874f

Please sign in to comment.