create generic data transformer with get an object by id, and add test

for CalendarType
This commit is contained in:
Julien Fastré 2022-05-20 19:23:47 +02:00
parent b6e0379583
commit 67fad5d764
11 changed files with 422 additions and 159 deletions

View File

@ -12,16 +12,15 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\Form;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CalendarRange;
use Chill\CalendarBundle\Entity\CancelReason;
use Chill\MainBundle\Entity\Location;
use Chill\CalendarBundle\Form\DataTransformer\IdToCalendarRangeDataTransformer;
use Chill\MainBundle\Form\DataTransformer\IdToLocationDataTransformer;
use Chill\MainBundle\Form\DataTransformer\IdToUserDataTransformer;
use Chill\MainBundle\Form\DataTransformer\IdToUsersDataTransformer;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\PersonBundle\Form\DataTransformer\PersonsToIdDataTransformer;
use Chill\ThirdPartyBundle\Form\DataTransformer\ThirdPartiesToIdDataTransformer;
use DateTimeImmutable;
use Doctrine\Persistence\ObjectManager;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
@ -32,16 +31,32 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class CalendarType extends AbstractType
{
protected ObjectManager $om;
private IdToCalendarRangeDataTransformer $calendarRangeDataTransformer;
protected TranslatableStringHelper $translatableStringHelper;
private IdToLocationDataTransformer $idToLocationDataTransformer;
private IdToUserDataTransformer $idToUserDataTransformer;
private IdToUsersDataTransformer $idToUsersDataTransformer;
private ThirdPartiesToIdDataTransformer $partiesToIdDataTransformer;
private PersonsToIdDataTransformer $personsToIdDataTransformer;
public function __construct(
TranslatableStringHelper $translatableStringHelper,
ObjectManager $om
PersonsToIdDataTransformer $personsToIdDataTransformer,
IdToUserDataTransformer $idToUserDataTransformer,
IdToUsersDataTransformer $idToUsersDataTransformer,
IdToLocationDataTransformer $idToLocationDataTransformer,
ThirdPartiesToIdDataTransformer $partiesToIdDataTransformer,
IdToCalendarRangeDataTransformer $idToCalendarRangeDataTransformer
) {
$this->translatableStringHelper = $translatableStringHelper;
$this->om = $om;
$this->personsToIdDataTransformer = $personsToIdDataTransformer;
$this->idToUserDataTransformer = $idToUserDataTransformer;
$this->idToUsersDataTransformer = $idToUsersDataTransformer;
$this->idToLocationDataTransformer = $idToLocationDataTransformer;
$this->partiesToIdDataTransformer = $partiesToIdDataTransformer;
$this->calendarRangeDataTransformer = $idToCalendarRangeDataTransformer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
@ -67,11 +82,8 @@ class CalendarType extends AbstractType
]);
if ($options['data'] instanceof Calendar && $options['data']->getId() === null) {
$builder->add('mainUser', PickUserDynamicType::class, [
'multiple' => false,
'required' => true,
'help' => 'chill_calendar.form.The main user is mandatory. He will organize the appointment.',
]);
$builder->add('mainUser', HiddenType::class);
$builder->get('mainUser')->addModelTransformer($this->idToUserDataTransformer);
}
$builder->add('startDate', HiddenType::class);
@ -110,89 +122,20 @@ class CalendarType extends AbstractType
$builder->add('persons', HiddenType::class);
$builder->get('persons')
->addModelTransformer(new CallbackTransformer(
static function (iterable $personsAsIterable): string {
$personIds = [];
foreach ($personsAsIterable as $value) {
$personIds[] = $value->getId();
}
return implode(',', $personIds);
},
function (?string $personsAsString): array {
if (null === $personsAsString) {
return [];
}
return array_map(
fn (string $id): ?Person => $this->om->getRepository(Person::class)->findOneBy(['id' => (int) $id]),
explode(',', $personsAsString)
);
}
));
$builder->add('professionals', HiddenType::class);
$builder->get('professionals')
->addModelTransformer(new CallbackTransformer(
static function (iterable $thirdpartyAsIterable): string {
$thirdpartyIds = [];
foreach ($thirdpartyAsIterable as $value) {
$thirdpartyIds[] = $value->getId();
}
return implode(',', $thirdpartyIds);
},
function (?string $thirdpartyAsString): array {
if (null === $thirdpartyAsString) {
return [];
}
return array_map(
fn (string $id): ?ThirdParty => $this->om->getRepository(ThirdParty::class)->findOneBy(['id' => (int) $id]),
explode(',', $thirdpartyAsString)
);
}
));
->addModelTransformer($this->personsToIdDataTransformer);
/*
$builder->add('professionals', HiddenType::class);
$builder->get('professionals')
->addModelTransformer($this->partiesToIdDataTransformer);
*/
$builder->add('calendarRange', HiddenType::class);
$builder->get('calendarRange')
->addModelTransformer(new CallbackTransformer(
static function (?CalendarRange $calendarRange): ?int {
if (null !== $calendarRange) {
$res = $calendarRange->getId();
} else {
$res = -1;
}
return $res;
},
function (?string $calendarRangeId): ?CalendarRange {
if (null !== $calendarRangeId) {
$res = $this->om->getRepository(CalendarRange::class)->findOneBy(['id' => (int) $calendarRangeId]);
} else {
$res = null;
}
return $res;
}
));
->addModelTransformer($this->calendarRangeDataTransformer);
$builder->add('location', HiddenType::class)
->get('location')
->addModelTransformer(new CallbackTransformer(
static function (?Location $location): string {
if (null === $location) {
return '';
}
return $location->getId();
},
function (?string $id): ?Location {
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
}
));
->addModelTransformer($this->idToLocationDataTransformer);
}
public function configureOptions(OptionsResolver $resolver)
@ -200,10 +143,6 @@ class CalendarType extends AbstractType
$resolver->setDefaults([
'data_class' => Calendar::class,
]);
$resolver
->setRequired(['accompanyingPeriod'])
->setAllowedTypes('accompanyingPeriod', [\Chill\PersonBundle\Entity\AccompanyingPeriod::class, 'null']);
}
public function getBlockPrefix()

View File

@ -0,0 +1,23 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\CalendarBundle\Form\DataTransformer;
use Chill\CalendarBundle\Repository\CalendarRangeRepository;
use Chill\MainBundle\Form\DataTransformer\IdToEntityDataTransformer;
class IdToCalendarRangeDataTransformer extends IdToEntityDataTransformer
{
public function __construct(CalendarRangeRepository $repository)
{
parent::__construct($repository, false);
}
}

View File

@ -0,0 +1,201 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\CalendarBundle\Tests\Form;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CalendarRange;
use Chill\CalendarBundle\Form\CalendarType;
use Chill\CalendarBundle\Form\DataTransformer\IdToCalendarRangeDataTransformer;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\DataTransformer\IdToLocationDataTransformer;
use Chill\MainBundle\Form\DataTransformer\IdToUserDataTransformer;
use Chill\MainBundle\Form\DataTransformer\IdToUsersDataTransformer;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\DataTransformer\PersonsToIdDataTransformer;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Form\DataTransformer\ThirdPartiesToIdDataTransformer;
use DateTimeImmutable;
use Doctrine\Common\Collections\Collection;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use ReflectionProperty;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/**
* @internal
* @coversNothing
*/
final class CalendarTypeTest extends TypeTestCase
{
use ProphecyTrait;
private IdToCalendarRangeDataTransformer $calendarRangeDataTransformer;
private IdToLocationDataTransformer $idToLocationDataTransformer;
private IdToUserDataTransformer $idToUserDataTransformer;
private IdToUsersDataTransformer $idToUsersDataTransformer;
private ThirdPartiesToIdDataTransformer $partiesToIdDataTransformer;
private PersonsToIdDataTransformer $personsToIdDataTransformer;
private TokenStorageInterface $tokenStorage;
protected function setUp(): void
{
$this->personsToIdDataTransformer = $this->buildMultiToIdDataTransformer(PersonsToIdDataTransformer::class, Person::class);
$this->idToUserDataTransformer = $this->buildSingleToIdDataTransformer(IdToUserDataTransformer::class, User::class);
$this->idToUsersDataTransformer = $this->buildMultiToIdDataTransformer(IdToUsersDataTransformer::class, User::class);
$this->idToLocationDataTransformer = $this->buildSingleToIdDataTransformer(IdToLocationDataTransformer::class, Location::class);
$this->partiesToIdDataTransformer = $this->buildMultiToIdDataTransformer(ThirdPartiesToIdDataTransformer::class, ThirdParty::class);
$this->calendarRangeDataTransformer = $this->buildSingleToIdDataTransformer(IdToCalendarRangeDataTransformer::class, CalendarRange::class);
$tokenStorage = $this->prophesize(TokenStorageInterface::class);
$token = $this->prophesize(TokenInterface::class);
$token->getUser()->willReturn(new User());
$tokenStorage->getToken()->willReturn($token->reveal());
$this->tokenStorage = $tokenStorage->reveal();
parent::setUp();
}
public function testSubmitValidData()
{
$formData = [
'mainUser' => '1',
'users' => '2,3',
'professionnals' => '4,5',
'startDate' => '2022-05-05 14:00:00',
'endDate' => '2022-05-05 14:30:00',
'persons' => '7',
'calendarRange' => '8',
'location' => '9',
'sendSMS' => '1',
];
$calendar = new Calendar();
$calendar->setMainUser(new class() extends User {
public function getId()
{
return '1';
}
});
$form = $this->factory->create(CalendarType::class, $calendar);
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$this->assertEquals(DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2022-05-05 14:00:00'), $calendar->getStartDate());
$this->assertEquals(DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2022-05-05 14:30:00'), $calendar->getEndDate());
$this->assertEquals(7, $calendar->getPersons()->first()->getId());
$this->assertEquals(8, $calendar->getCalendarRange()->getId());
$this->assertEquals(9, $calendar->getLocation()->getId());
$this->assertEquals(true, $calendar->getSendSMS());
}
protected function getExtensions()
{
$parents = parent::getExtensions();
$calendarType = new CalendarType(
$this->personsToIdDataTransformer,
$this->idToUserDataTransformer,
$this->idToUsersDataTransformer,
$this->idToLocationDataTransformer,
$this->partiesToIdDataTransformer,
$this->calendarRangeDataTransformer
);
$commentType = new CommentType($this->tokenStorage);
return array_merge(
parent::getExtensions(),
[new PreloadedExtension([$calendarType, $commentType], [])]
);
}
private function buildMultiToIdDataTransformer(
string $classTransformer,
string $objClass
) {
$transformer = $this->prophesize($classTransformer);
$transformer->transform(Argument::type('array'))
->will(static function ($args) {
return implode(
',',
array_map(static function ($p) { return $p->getId(); }, $args[0])
);
});
$transformer->transform(Argument::exact(null))
->willReturn([]);
$transformer->transform(Argument::type(Collection::class))
->will(static function ($args) {
return implode(
',',
array_map(static function ($p) { return $p->getId(); }, $args[0]->toArray())
);
});
$transformer->reverseTransform(Argument::type('string'))
->will(static function ($args) use ($objClass) {
if (null === $args[0]) {
return [];
}
return array_map(
static function ($id) use ($objClass) {
$obj = new $objClass();
$reflectionProperty = new ReflectionProperty($objClass, 'id');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($obj, (int) $id);
return $obj;
},
explode(',', $args[0])
);
});
return $transformer->reveal();
}
private function buildSingleToIdDataTransformer(
string $classTransformer,
string $class
) {
$transformer = $this->prophesize($classTransformer);
$transformer->transform(Argument::type('object'))
->will(static function ($args) {
return (string) $args[0]->getId();
});
$transformer->transform(Argument::exact(null))
->willReturn('');
$transformer->reverseTransform(Argument::type('string'))
->will(static function ($args) use ($class) {
if (null === $args[0]) {
return null;
}
$obj = new $class();
$reflectionProperty = new ReflectionProperty($class, 'id');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($obj, (int) $args[0]);
return $obj;
});
return $transformer->reveal();
}
}

View File

@ -14,6 +14,7 @@ namespace Chill\MainBundle\Form\DataTransformer;
use Closure;
use Doctrine\Persistence\ObjectRepository;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use function call_user_func;
/**
@ -59,7 +60,13 @@ class IdToEntityDataTransformer implements DataTransformerInterface
return null;
}
return $this->repository->findOneBy(['id' => (int) $value]);
$object = $this->repository->findOneBy(['id' => (int) $value]);
if (null === $object) {
throw new TransformationFailedException('could not find any object by object id');
}
return $object;
}
/**
@ -71,12 +78,22 @@ class IdToEntityDataTransformer implements DataTransformerInterface
$ids = [];
foreach ($value as $v) {
$ids[] = call_user_func($this->getId, $v);
$ids[] = $id = call_user_func($this->getId, $v);
if (null === $id) {
throw new TransformationFailedException('id is null');
}
}
return implode(',', $ids);
}
return call_user_func($this->getId, $value);
$id = call_user_func($this->getId, $value);
if (null === $id) {
throw new TransformationFailedException('id is null');
}
return (string) $id;
}
}

View File

@ -0,0 +1,22 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Form\DataTransformer;
use Chill\MainBundle\Repository\LocationRepository;
class IdToLocationDataTransformer extends IdToEntityDataTransformer
{
public function __construct(LocationRepository $repository)
{
parent::__construct($repository, false);
}
}

View File

@ -0,0 +1,22 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Form\DataTransformer;
use Chill\MainBundle\Repository\UserRepository;
class IdToUserDataTransformer extends IdToEntityDataTransformer
{
public function __construct(UserRepository $repository)
{
parent::__construct($repository, false);
}
}

View File

@ -0,0 +1,22 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Form\DataTransformer;
use Chill\MainBundle\Repository\UserRepository;
class IdToUsersDataTransformer extends IdToEntityDataTransformer
{
public function __construct(UserRepository $repository)
{
parent::__construct($repository, true);
}
}

View File

@ -11,65 +11,13 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Form\DataTransformer;
use Chill\PersonBundle\Entity\Person;
use Doctrine\Persistence\ObjectManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Chill\MainBundle\Form\DataTransformer\IdToEntityDataTransformer;
use Chill\PersonBundle\Repository\PersonRepository;
class PersonToIdTransformer implements DataTransformerInterface
class PersonToIdTransformer extends IdToEntityDataTransformer
{
/**
* @var ObjectManager
*/
private $om;
public function __construct(ObjectManager $om)
public function __construct(PersonRepository $repository)
{
$this->om = $om;
}
/**
* Transforms a string (id) to an object (issue).
*
* @param string $id
*
* @throws TransformationFailedException if object (issue) is not found.
*
* @return Person|null
*/
public function reverseTransform($id)
{
if (!$id) {
return null;
}
$issue = $this->om
->getRepository(\Chill\PersonBundle\Entity\Person::class)
->findOneBy(['id' => $id]);
if (null === $issue) {
throw new TransformationFailedException(sprintf(
'An issue with id "%s" does not exist!',
$id
));
}
return $issue;
}
/**
* Transforms an object (issue) to a string (id).
*
* @param Person|null $issue
*
* @return string
*/
public function transform($issue)
{
if (null === $issue) {
return '';
}
return $issue->getId();
parent::__construct($repository, false);
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Form\DataTransformer;
use Chill\MainBundle\Form\DataTransformer\IdToEntityDataTransformer;
use Chill\PersonBundle\Repository\PersonRepository;
class PersonsToIdDataTransformer extends IdToEntityDataTransformer
{
public function __construct(PersonRepository $repository)
{
parent::__construct($repository, true);
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ThirdPartyBundle\Form\DataTransformer;
use Chill\MainBundle\Form\DataTransformer\IdToEntityDataTransformer;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
class ThirdPartiesToIdDataTransformer extends IdToEntityDataTransformer
{
public function __construct(ThirdPartyRepository $repository)
{
parent::__construct($repository, true);
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ThirdPartyBundle\Form\DataTransformer;
use Chill\MainBundle\Form\DataTransformer\IdToEntityDataTransformer;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
class ThirdPartyToIdDataTransformer extends IdToEntityDataTransformer
{
public function __construct(ThirdPartyRepository $repository)
{
parent::__construct($repository, false);
}
}