diff --git a/CHANGELOG.md b/CHANGELOG.md
index 947649b59..a308e03cf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ and this project adheres to
* ajout d'un bouton "recherche avancée" sur la page d'accueil
* [person] create an accompanying course: add client-side validation if no origin (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/210)
* [person] fix bounds for computing current person address: the new address appears immediatly
+* [docgen] create a normalizer and serializer for normalization on doc format
## Test releases
diff --git a/composer.json b/composer.json
index 7b356f698..18722a420 100644
--- a/composer.json
+++ b/composer.json
@@ -91,7 +91,8 @@
},
"autoload-dev": {
"psr-4": {
- "App\\": "tests/app/src/"
+ "App\\": "tests/app/src/",
+ "Chill\\DocGeneratorBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests"
}
},
"minimum-stability": "dev",
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 46b6698ec..7943ab3aa 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -4,6 +4,7 @@ parameters:
- src/
excludePaths:
- src/Bundle/*/Tests/*
+ - src/Bundle/*/tests/*
- src/Bundle/*/Test/*
- src/Bundle/*/config/*
- src/Bundle/*/migrations/*
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 3faa756b3..80367c691 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -37,6 +37,9 @@
src/Bundle/ChillCalendarBundle/Tests/
+
+ src/Bundle/ChillDocGeneratorBundle/tests/
+
diff --git a/src/Bundle/ChillDocGeneratorBundle/Serializer/Encoder/DocGenEncoder.php b/src/Bundle/ChillDocGeneratorBundle/Serializer/Encoder/DocGenEncoder.php
new file mode 100644
index 000000000..da98504ae
--- /dev/null
+++ b/src/Bundle/ChillDocGeneratorBundle/Serializer/Encoder/DocGenEncoder.php
@@ -0,0 +1,69 @@
+isAssociative($data)) {
+ throw new UnexpectedValueException("Only associative arrays are allowed; lists are not allowed");
+ }
+
+ $result = [];
+ $this->recusiveEncoding($data, $result, '');
+
+ return $result;
+ }
+
+ private function recusiveEncoding(array $data, array &$result, $path)
+ {
+ if ($this->isAssociative($data)) {
+ foreach ($data as $key => $value) {
+ if (\is_array($value)) {
+ $this->recusiveEncoding($value, $result, $this->canonicalizeKey($path, $key));
+ } else {
+ $result[$this->canonicalizeKey($path, $key)] = $value;
+ }
+ }
+ } else {
+ foreach ($data as $elem) {
+
+ if (!$this->isAssociative($elem)) {
+ throw new UnexpectedValueException(sprintf("Embedded loops are not allowed. See data under %s path", $path));
+ }
+
+ $sub = [];
+ $this->recusiveEncoding($elem, $sub, '');
+ $result[$path][] = $sub;
+ }
+ }
+ }
+
+ private function canonicalizeKey(string $path, string $key): string
+ {
+ return $path === '' ? $key : $path.'_'.$key;
+ }
+
+ private function isAssociative(array $data)
+ {
+ $keys = \array_keys($data);
+
+ return $keys !== \array_keys($keys);
+ }
+
+
+ /**
+ * @inheritDoc
+ */
+ public function supportsEncoding(string $format)
+ {
+ return $format === 'docgen';
+ }
+}
diff --git a/src/Bundle/ChillDocGeneratorBundle/Serializer/Helper/NormalizeNullValueHelper.php b/src/Bundle/ChillDocGeneratorBundle/Serializer/Helper/NormalizeNullValueHelper.php
new file mode 100644
index 000000000..be5e971d7
--- /dev/null
+++ b/src/Bundle/ChillDocGeneratorBundle/Serializer/Helper/NormalizeNullValueHelper.php
@@ -0,0 +1,47 @@
+normalizer = $normalizer;
+ }
+
+ public function normalize(array $attributes, string $format = 'docgen', ?array $context = [])
+ {
+ $data = [];
+
+ foreach ($attributes as $key => $class) {
+ if (is_numeric($key)) {
+ $data[$class] = '';
+ } else {
+ switch ($class) {
+ case 'array':
+ case 'bool':
+ case 'double':
+ case 'float':
+ case 'int':
+ case 'resource':
+ case 'string':
+ case 'null':
+ $data[$key] = '';
+ break;
+ default:
+ $data[$key] = $this->normalizer->normalize(null, $format, \array_merge(
+ $context,
+ ['docgen:expects' => $class]
+ ));
+ break;
+ }
+ }
+ }
+
+ return $data;
+ }
+}
diff --git a/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php b/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php
new file mode 100644
index 000000000..08e2c63d9
--- /dev/null
+++ b/src/Bundle/ChillDocGeneratorBundle/Serializer/Normalizer/DocGenObjectNormalizer.php
@@ -0,0 +1,177 @@
+classMetadataFactory = $classMetadataFactory;
+ $this->propertyAccess = PropertyAccess::createPropertyAccessor();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function normalize($object, string $format = null, array $context = [])
+ {
+ $classMetadataKey = $object ?? $context['docgen:expects'];
+
+ if (!$this->classMetadataFactory->hasMetadataFor($classMetadataKey)) {
+ throw new LogicException(sprintf("This object does not have metadata: %s. Add groups on this entity to allow to serialize with the format %s and groups %s", is_object($object) ? get_class($object) : $context['docgen:expects'], $format, \implode(', ', $context['groups'])));
+ }
+
+ $metadata = $this->classMetadataFactory->getMetadataFor($classMetadataKey);
+ $expectedGroups = \array_key_exists(AbstractNormalizer::GROUPS, $context) ?
+ \is_array($context[AbstractNormalizer::GROUPS]) ? $context[AbstractNormalizer::GROUPS] : [$context[AbstractNormalizer::GROUPS]]
+ : [];
+ $attributes = \array_filter(
+ $metadata->getAttributesMetadata(),
+ function (AttributeMetadata $a) use ($expectedGroups) {
+ foreach ($a->getGroups() as $g) {
+ if (\in_array($g, $expectedGroups, true)) {
+ return true;
+ }
+ }
+
+ return false;
+ });
+
+ if (null === $object) {
+ return $this->normalizeNullData($format, $context, $metadata, $attributes);
+ }
+
+ return $this->normalizeObject($object, $format, $context, $expectedGroups, $metadata, $attributes);
+ }
+
+ /**
+ * @param string $format
+ * @param array $context
+ * @param array $expectedGroups
+ * @param ClassMetadata $metadata
+ * @param array|AttributeMetadata[] $attributes
+ */
+ private function normalizeNullData(string $format, array $context, ClassMetadata $metadata, array $attributes): array
+ {
+ $keys = [];
+
+ foreach ($attributes as $attribute) {
+ $key = $attribute->getSerializedName() ?? $attribute->getName();
+ $keys[$key] = $this->getExpectedType($attribute, $metadata->getReflectionClass());
+ }
+
+ $normalizer = new NormalizeNullValueHelper($this->normalizer);
+
+ return $normalizer->normalize($keys, $format, $context);
+ }
+
+ /**
+ * @param $object
+ * @param $format
+ * @param array $context
+ * @param array $expectedGroups
+ * @param ClassMetadata $metadata
+ * @param array|AttributeMetadata[] $attributes
+ * @return array
+ * @throws ExceptionInterface
+ */
+ private function normalizeObject($object, $format, array $context, array $expectedGroups, ClassMetadata $metadata, array $attributes)
+ {
+ $data = [];
+ $reflection = $metadata->getReflectionClass();
+
+ foreach ($attributes as $attribute) {
+ /** @var AttributeMetadata $attribute */
+ $value = $this->propertyAccess->getValue($object, $attribute->getName());
+ $key = $attribute->getSerializedName() ?? $attribute->getName();
+
+ if (is_object($value)) {
+ $data[$key] =
+ $this->normalizer->normalize($value, $format, \array_merge(
+ $context, $attribute->getNormalizationContextForGroups($expectedGroups)
+ ));
+ } elseif (null === $value) {
+ $data[$key] = $this->normalizeNullOutputValue($format, $context, $attribute, $reflection);
+ } else {
+ $data[$key] = (string) $value;
+ }
+ }
+
+ return $data;
+ }
+
+ private function getExpectedType(AttributeMetadata $attribute, \ReflectionClass $reflection): string
+ {
+ // we have to get the expected content
+ if ($reflection->hasProperty($attribute->getName())) {
+ $type = $reflection->getProperty($attribute->getName())->getType();
+ } elseif ($reflection->hasMethod($attribute->getName())) {
+ $type = $reflection->getMethod($attribute->getName())->getReturnType();
+ } else {
+ throw new \LogicException(sprintf(
+ "Could not determine how the content is determined for the attribute %s. Add attribute property only on property or method", $attribute->getName()
+ ));
+ }
+
+ if (null === $type) {
+ throw new \LogicException(sprintf(
+ "Could not determine the type for this attribute: %s. Add a return type to the method or property declaration", $attribute->getName()
+ ));
+ }
+
+ return $type->getName();
+ }
+
+ /**
+ */
+ private function normalizeNullOutputValue($format, array $context, AttributeMetadata $attribute, \ReflectionClass $reflection)
+ {
+ $type = $this->getExpectedType($attribute, $reflection);
+
+ switch ($type) {
+ case 'array':
+ case 'bool':
+ case 'double':
+ case 'float':
+ case 'int':
+ case 'resource':
+ case 'string':
+ return '';
+ default:
+ return $this->normalizer->normalize(
+ null,
+ $format,
+ \array_merge(
+ $context,
+ ['docgen:expects' => $type]
+ )
+ );
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function supportsNormalization($data, string $format = null): bool
+ {
+ return $format === 'docgen' && (is_object($data) || null === $data);
+ }
+}
diff --git a/src/Bundle/ChillDocGeneratorBundle/config/services.yaml b/src/Bundle/ChillDocGeneratorBundle/config/services.yaml
index fb8ec28a1..6d87dbd25 100644
--- a/src/Bundle/ChillDocGeneratorBundle/config/services.yaml
+++ b/src/Bundle/ChillDocGeneratorBundle/config/services.yaml
@@ -8,3 +8,10 @@ services:
autowire: true
autoconfigure: true
resource: '../Repository/'
+
+ Chill\DocGeneratorBundle\Serializer\Normalizer\:
+ autowire: true
+ autoconfigure: true
+ resource: '../Serializer/Normalizer/'
+ tags:
+ - { name: 'serializer.normalizer', priority: -152 }
diff --git a/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Encoder/DocGenEncoderTest.php b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Encoder/DocGenEncoderTest.php
new file mode 100644
index 000000000..a6267e489
--- /dev/null
+++ b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Encoder/DocGenEncoderTest.php
@@ -0,0 +1,113 @@
+encoder = new DocGenEncoder();
+ }
+
+ /**
+ * @dataProvider generateEncodeData
+ */
+ public function testEncode($expected, $data, string $msg)
+ {
+ $generated = $this->encoder->encode($data, 'docgen');
+ $this->assertEquals($expected, $generated, $msg);
+ }
+
+ public function testEmbeddedLoopsThrowsException()
+ {
+ $this->expectException(UnexpectedValueException::class);
+
+ $data = [
+ 'data' => [
+ ['item' => 'one'],
+ [
+ 'embedded' => [
+ [
+ ['subitem' => 'two'],
+ ['subitem' => 'three']
+ ]
+ ]
+ ],
+ ]
+ ];
+
+ $this->encoder->encode($data, 'docgen');
+ }
+
+ public function generateEncodeData()
+ {
+ yield [ ['tests' => 'ok'], ['tests' => 'ok'], "A simple test with a simple array"];
+
+ yield [
+ // expected:
+ ['item_subitem' => 'value'],
+ // data:
+ ['item' => ['subitem' => 'value']],
+ "A test with multidimensional array"
+ ];
+
+ yield [
+ // expected:
+ [ 'data' => [['item' => 'one'], ['item' => 'two']] ],
+ // data:
+ [ 'data' => [['item' => 'one'], ['item' => 'two']] ],
+ "a list of items"
+ ];
+
+ yield [
+ // expected:
+ [ 'data' => [['item_subitem' => 'alpha'], ['item' => 'two']] ],
+ // data:
+ [ 'data' => [['item' => ['subitem' => 'alpha']], ['item' => 'two'] ] ],
+ "a list of items with multidimensional array inside item"
+ ];
+
+ yield [
+ // expected:
+ [
+ 'persons' => [
+ [
+ 'firstname' => 'Jonathan',
+ 'lastname' => 'Dupont',
+ 'dateOfBirth_long' => '16 juin 1981',
+ 'dateOfBirth_short' => '16/06/1981',
+ 'father_firstname' => 'Marcel',
+ 'father_lastname' => 'Dupont',
+ 'father_dateOfBirth_long' => '10 novembre 1953',
+ 'father_dateOfBirth_short' => '10/11/1953'
+ ],
+ ]
+ ],
+ // data:
+ [
+ 'persons' => [
+ [
+ 'firstname' => 'Jonathan',
+ 'lastname' => 'Dupont',
+ 'dateOfBirth' => [ 'long' => '16 juin 1981', 'short' => '16/06/1981'],
+ 'father' => [
+ 'firstname' => 'Marcel',
+ 'lastname' => 'Dupont',
+ 'dateOfBirth' => ['long' => '10 novembre 1953', 'short' => '10/11/1953']
+ ]
+ ],
+ ]
+ ],
+ "a longer list, with near real data inside and embedded associative arrays"
+ ];
+ }
+}
diff --git a/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Normalizer/DocGenObjectNormalizerTest.php b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Normalizer/DocGenObjectNormalizerTest.php
new file mode 100644
index 000000000..4d0031f55
--- /dev/null
+++ b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Normalizer/DocGenObjectNormalizerTest.php
@@ -0,0 +1,80 @@
+normalizer = self::$container->get(NormalizerInterface::class);
+ }
+
+ public function testNormalizationBasic()
+ {
+ $user = new User();
+ $user->setUsername('User Test');
+ $user->setMainCenter($center = new Center());
+ $center->setName('test');
+
+ $normalized = $this->normalizer->normalize($user, 'docgen', [ AbstractNormalizer::GROUPS => ['docgen:read']]);
+ $expected = [
+ 'label' => 'User Test',
+ 'email' => '',
+ 'mainCenter' => [
+ 'name' => 'test'
+ ]
+ ];
+
+ $this->assertEquals($expected, $normalized, "test normalization fo an user");
+ }
+
+ public function testNormalizeWithNullValueEmbedded()
+ {
+ $user = new User();
+ $user->setUsername('User Test');
+
+ $normalized = $this->normalizer->normalize($user, 'docgen', [ AbstractNormalizer::GROUPS => ['docgen:read']]);
+ $expected = [
+ 'label' => 'User Test',
+ 'email' => '',
+ 'mainCenter' => [
+ 'name' => ''
+ ]
+ ];
+
+ $this->assertEquals($expected, $normalized, "test normalization fo an user with null center");
+ }
+
+ public function testNormalizeNullObjectWithObjectEmbedded()
+ {
+ $normalized = $this->normalizer->normalize(null, 'docgen', [
+ AbstractNormalizer::GROUPS => ['docgen:read'],
+ 'docgen:expects' => User::class,
+ ]);
+
+ $expected = [
+ 'label' => '',
+ 'email' => '',
+ 'mainCenter' => [
+ 'name' => ''
+ ]
+ ];
+
+ $this->assertEquals($expected, $normalized, "test normalization for a null user");
+
+ }
+
+
+}
diff --git a/src/Bundle/ChillMainBundle/Entity/Center.php b/src/Bundle/ChillMainBundle/Entity/Center.php
index 2d87a20ab..a0c540a06 100644
--- a/src/Bundle/ChillMainBundle/Entity/Center.php
+++ b/src/Bundle/ChillMainBundle/Entity/Center.php
@@ -1,19 +1,19 @@
- *
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
@@ -23,12 +23,11 @@ namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
+use Symfony\Component\Serializer\Annotation as Serializer;
/**
* @ORM\Entity
* @ORM\Table(name="centers")
- *
- * @author Julien Fastré
*/
class Center implements HasCenterInterface
{
@@ -46,9 +45,10 @@ class Center implements HasCenterInterface
* @var string
*
* @ORM\Column(type="string", length=255)
+ * @Serializer\Groups({"docgen:read"})
*/
- private $name;
-
+ private string $name = '';
+
/**
* @var Collection
*
@@ -58,8 +58,8 @@ class Center implements HasCenterInterface
* )
*/
private $groupCenters;
-
-
+
+
/**
* Center constructor.
*/
@@ -67,7 +67,7 @@ class Center implements HasCenterInterface
{
$this->groupCenters = new \Doctrine\Common\Collections\ArrayCollection();
}
-
+
/**
* @return string
*/
@@ -75,7 +75,7 @@ class Center implements HasCenterInterface
{
return $this->name;
}
-
+
/**
* @param $name
* @return $this
@@ -85,7 +85,7 @@ class Center implements HasCenterInterface
$this->name = $name;
return $this;
}
-
+
/**
* @return int
*/
@@ -93,7 +93,7 @@ class Center implements HasCenterInterface
{
return $this->id;
}
-
+
/**
* @return ArrayCollection|Collection
*/
@@ -101,7 +101,7 @@ class Center implements HasCenterInterface
{
return $this->groupCenters;
}
-
+
/**
* @param GroupCenter $groupCenter
* @return $this
@@ -111,7 +111,7 @@ class Center implements HasCenterInterface
$this->groupCenters->add($groupCenter);
return $this;
}
-
+
/**
* @return string
*/
@@ -119,7 +119,7 @@ class Center implements HasCenterInterface
{
return $this->getName();
}
-
+
/**
* @return $this|Center
*/
diff --git a/src/Bundle/ChillMainBundle/Entity/User.php b/src/Bundle/ChillMainBundle/Entity/User.php
index 2048667e1..2d8636e5f 100644
--- a/src/Bundle/ChillMainBundle/Entity/User.php
+++ b/src/Bundle/ChillMainBundle/Entity/User.php
@@ -8,7 +8,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Chill\MainBundle\Entity\UserJob;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
-use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
+use Symfony\Component\Serializer\Annotation as Serializer;
/**
* User
@@ -16,7 +16,7 @@ use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
* @ORM\Entity
* @ORM\Table(name="users")
* @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="acl_cache_region")
- * @DiscriminatorMap(typeProperty="type", mapping={
+ * @Serializer\DiscriminatorMap(typeProperty="type", mapping={
* "user"=User::class
* })
*/
@@ -51,6 +51,7 @@ class User implements AdvancedUserInterface {
/**
* @ORM\Column(type="string", length=200)
+ * @Serializer\Groups({"docgen:read"})
*/
private string $label = '';
@@ -58,8 +59,9 @@ class User implements AdvancedUserInterface {
* @var string
*
* @ORM\Column(type="string", length=150, nullable=true)
+ * @Serializer\Groups({"docgen:read"})
*/
- private $email;
+ private ?string $email = null;
/**
* @var string
@@ -123,6 +125,7 @@ class User implements AdvancedUserInterface {
/**
* @var Center|null
* @ORM\ManyToOne(targetEntity=Center::class)
+ * @Serializer\Groups({"docgen:read"})
*/
private ?Center $mainCenter = null;
diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php
index f1681c60a..aeb437336 100644
--- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php
+++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CenterNormalizer.php
@@ -1,18 +1,18 @@
- *
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
@@ -27,7 +27,7 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
- *
+ *
*
*/
class CenterNormalizer implements NormalizerInterface, DenormalizerInterface
@@ -52,7 +52,7 @@ class CenterNormalizer implements NormalizerInterface, DenormalizerInterface
public function supportsNormalization($data, string $format = null): bool
{
- return $data instanceof Center;
+ return $data instanceof Center && $format === 'json';
}
public function denormalize($data, string $type, string $format = null, array $context = [])
diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php
index 8e333a2a4..288a6865d 100644
--- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php
+++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php
@@ -19,23 +19,78 @@
namespace Chill\MainBundle\Serializer\Normalizer;
+use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
-class DateNormalizer implements NormalizerInterface, DenormalizerInterface
+class DateNormalizer implements ContextAwareNormalizerInterface, DenormalizerInterface
{
+ private RequestStack $requestStack;
+ private ParameterBagInterface $parameterBag;
+
+ public function __construct(RequestStack $requestStack, ParameterBagInterface $parameterBag)
+ {
+ $this->requestStack = $requestStack;
+ $this->parameterBag = $parameterBag;
+ }
+
public function normalize($date, string $format = null, array $context = array())
{
/** @var \DateTimeInterface $date */
- return [
- 'datetime' => $date->format(\DateTimeInterface::ISO8601)
- ];
+ switch($format) {
+ case 'json':
+ return [
+ 'datetime' => $date->format(\DateTimeInterface::ISO8601)
+ ];
+ case 'docgen':
+
+ if (null === $date) {
+ return [
+ 'long' => '', 'short' => ''
+ ];
+ }
+
+ $hasTime = $date->format('His') !== "000000";
+ $request = $this->requestStack->getCurrentRequest();
+ $locale = null !== $request ? $request->getLocale() : $this->parameterBag->get('kernel.default_locale');
+ $formatterLong = \IntlDateFormatter::create(
+ $locale,
+ \IntlDateFormatter::LONG,
+ $hasTime ? \IntlDateFormatter::SHORT: \IntlDateFormatter::NONE
+ );
+ $formatterShort = \IntlDateFormatter::create(
+ $locale,
+ \IntlDateFormatter::SHORT,
+ $hasTime ? \IntlDateFormatter::SHORT: \IntlDateFormatter::NONE
+ );
+
+ return [
+ 'short' => $formatterShort->format($date),
+ 'long' => $formatterLong->format($date)
+ ];
+ }
}
- public function supportsNormalization($data, string $format = null): bool
+ public function supportsNormalization($data, string $format = null, array $context = []): bool
{
- return $data instanceof \DateTimeInterface;
+ if ($format === 'json') {
+ return $data instanceof \DateTimeInterface;
+ } elseif ($format === 'docgen') {
+ return $data instanceof \DateTimeInterface || (
+ $data === null
+ && \array_key_exists('docgen:expects', $context)
+ && (
+ $context['docgen:expects'] === \DateTimeInterface::class
+ || $context['docgen:expects'] === \DateTime::class
+ || $context['docgen:expects'] === \DateTimeImmutable::class
+ )
+ );
+ }
+
+ return false;
}
public function denormalize($data, string $type, string $format = null, array $context = [])
diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php
index af29ad4f4..13315bc9c 100644
--- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php
+++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/UserNormalizer.php
@@ -52,6 +52,6 @@ class UserNormalizer implements NormalizerInterface, NormalizerAwareInterface
public function supportsNormalization($data, string $format = null): bool
{
- return $data instanceof User;
+ return $format === 'json' && $data instanceof User;
}
}
diff --git a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DateNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DateNormalizerTest.php
new file mode 100644
index 000000000..03e3015e8
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DateNormalizerTest.php
@@ -0,0 +1,88 @@
+prophet = new Prophet();
+ }
+
+ public function testSupports()
+ {
+ $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new \DateTime(), 'json'));
+ $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new \DateTimeImmutable(), 'json'));
+ $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new \DateTime(), 'docgen'));
+ $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new \DateTimeImmutable(), 'docgen'));
+ $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(null, 'docgen', ['docgen:expects' => \DateTimeImmutable::class]));
+ $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(null, 'docgen', ['docgen:expects' => \DateTimeInterface::class]));
+ $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(null, 'docgen', ['docgen:expects' => \DateTime::class]));
+ $this->assertFalse($this->buildDateNormalizer()->supportsNormalization(new \stdClass(), 'docgen'));
+ $this->assertFalse($this->buildDateNormalizer()->supportsNormalization(new \DateTime(), 'xml'));
+ }
+
+ /**
+ * @dataProvider generateDataNormalize
+ */
+ public function testNormalize($expected, $date, $format, $locale, $msg)
+ {
+ $this->assertEquals($expected, $this->buildDateNormalizer($locale)->normalize($date, $format, []), $msg);
+ }
+
+ private function buildDateNormalizer(string $locale = null): DateNormalizer
+ {
+ $requestStack = $this->prophet->prophesize(RequestStack::class);
+ $parameterBag = new ParameterBag();
+ $parameterBag->set('kernel.default_locale', 'fr');
+
+ if ($locale === null) {
+ $requestStack->getCurrentRequest()->willReturn(null);
+ } else {
+ $request = $this->prophet->prophesize(Request::class);
+ $request->getLocale()->willReturn($locale);
+ $requestStack->getCurrentRequest()->willReturn($request->reveal());
+ }
+
+ return new DateNormalizer($requestStack->reveal(), $parameterBag);
+ }
+
+ public function generateDataNormalize()
+ {
+ $datetime = \DateTime::createFromFormat('Y-m-d H:i:sO', '2021-06-05 15:05:01+02:00');
+ $date = \DateTime::createFromFormat('Y-m-d H:i:sO', '2021-06-05 00:00:00+02:00');
+ yield [
+ ['datetime' => '2021-06-05T15:05:01+0200'],
+ $datetime, 'json', null, 'simple normalization to json'
+ ];
+
+ yield [
+ ['long' => '5 juin 2021', 'short' => '05/06/2021'],
+ $date, 'docgen', 'fr', 'normalization to docgen for a date, with current request'
+ ];
+
+ yield [
+ ['long' => '5 juin 2021', 'short' => '05/06/2021'],
+ $date, 'docgen', null, 'normalization to docgen for a date, without current request'
+ ];
+
+ yield [
+ ['long' => '5 juin 2021 à 15:05', 'short' => '05/06/2021 15:05'],
+ $datetime, 'docgen', null, 'normalization to docgen for a datetime, without current request'
+ ];
+
+ yield [
+ ['long' => '', 'short' => ''],
+ null, 'docgen', null, 'normalization to docgen for a null datetime'
+ ];
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php
index 15f33abe7..74c7be856 100644
--- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php
+++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php
@@ -1139,11 +1139,11 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
public function getGroupSequence()
{
- if ($this->getStep() == self::STEP_DRAFT) {
+ if ($this->getStep() == self::STEP_DRAFT)
+ {
return [[self::STEP_DRAFT]];
- }
-
- if ($this->getStep() == self::STEP_CONFIRMED) {
+ } elseif ($this->getStep() == self::STEP_CONFIRMED)
+ {
return [[self::STEP_DRAFT, self::STEP_CONFIRMED]];
}
diff --git a/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php b/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php
index 67185c36a..afca96370 100644
--- a/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php
+++ b/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php
@@ -37,20 +37,20 @@ class MaritalStatus
* @ORM\Id()
* @ORM\Column(type="string", length=7)
*/
- private $id;
+ private ?string $id;
/**
* @var string array
* @ORM\Column(type="json")
*/
- private $name;
+ private array $name;
/**
* Get id
*
* @return string
*/
- public function getId()
+ public function getId(): string
{
return $this->id;
}
@@ -61,7 +61,7 @@ class MaritalStatus
* @param string $id
* @return MaritalStatus
*/
- public function setId($id)
+ public function setId(string $id): self
{
$this->id = $id;
return $this;
@@ -73,7 +73,7 @@ class MaritalStatus
* @param string array $name
* @return MaritalStatus
*/
- public function setName($name)
+ public function setName(array $name): self
{
$this->name = $name;
@@ -85,7 +85,7 @@ class MaritalStatus
*
* @return string array
*/
- public function getName()
+ public function getName(): array
{
return $this->name;
}
diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php
index dcb70510b..a7e8fec68 100644
--- a/src/Bundle/ChillPersonBundle/Entity/Person.php
+++ b/src/Bundle/ChillPersonBundle/Entity/Person.php
@@ -219,7 +219,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* groups={"general", "creation"}
* )
*/
- private ?\DateTime $maritalStatusDate;
+ private ?\DateTime $maritalStatusDate = null;
/**
* Comment on marital status
@@ -252,7 +252,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* The person's phonenumber
* @var string
*
- * @ORM\Column(type="text", length=40, nullable=true)
+ * @ORM\Column(type="text")
* @Assert\Regex(
* pattern="/^([\+{1}])([0-9\s*]{4,20})$/",
* groups={"general", "creation"}
@@ -262,13 +262,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* groups={"general", "creation"}
* )
*/
- private $phonenumber = '';
+ private string $phonenumber = '';
/**
* The person's mobile phone number
* @var string
*
- * @ORM\Column(type="text", length=40, nullable=true)
+ * @ORM\Column(type="text")
* @Assert\Regex(
* pattern="/^([\+{1}])([0-9\s*]{4,20})$/",
* groups={"general", "creation"}
@@ -278,7 +278,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* groups={"general", "creation"}
* )
*/
- private $mobilenumber = '';
+ private string $mobilenumber = '';
/**
* @var Collection
@@ -1094,9 +1094,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* Get nationality
*
- * @return Chill\MainBundle\Entity\Country
+ * @return Country
*/
- public function getNationality()
+ public function getNationality(): ?Country
{
return $this->nationality;
}
@@ -1176,7 +1176,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
*
* @return string
*/
- public function getPhonenumber()
+ public function getPhonenumber(): string
{
return $this->phonenumber;
}
@@ -1199,7 +1199,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
*
* @return string
*/
- public function getMobilenumber()
+ public function getMobilenumber(): string
{
return $this->mobilenumber;
}
diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php
new file mode 100644
index 000000000..769e1c907
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php
@@ -0,0 +1,116 @@
+personRender = $personRender;
+ $this->translator = $translator;
+ $this->translatableStringHelper = $translatableStringHelper;
+ }
+
+ public function normalize($person, string $format = null, array $context = [])
+ {
+ /** @var Person $person */
+
+ $dateContext = $context;
+ $dateContext['docgen:expects'] = \DateTimeInterface::class;
+
+ if (null === $person) {
+ return $this->normalizeNullValue($format, $context);
+ }
+
+ return [
+ 'firstname' => $person->getFirstName(),
+ 'lastname' => $person->getLastName(),
+ 'altNames' => \implode(
+ ', ',
+ \array_map(
+ function (PersonAltName $altName) {
+ return $altName->getLabel();
+ },
+ $person->getAltNames()->toArray()
+ )
+ ),
+ 'text' => $this->personRender->renderString($person, []),
+ 'birthdate' => $this->normalizer->normalize($person->getBirthdate(), $format, $dateContext),
+ 'deathdate' => $this->normalizer->normalize($person->getDeathdate(), $format, $dateContext),
+ 'gender' => $this->translator->trans($person->getGender()),
+ 'maritalStatus' => null !== ($ms = $person->getMaritalStatus()) ? $this->translatableStringHelper->localize($ms->getName()) : '',
+ 'maritalStatusDate' => $this->normalizer->normalize($person->getMaritalStatusDate(), $format, $dateContext),
+ 'email' => $person->getEmail(),
+ 'firstPhoneNumber' => $person->getPhonenumber() ?? $person->getMobilenumber(),
+ 'fixPhoneNumber' => $person->getPhonenumber(),
+ 'mobilePhoneNumber' => $person->getMobilenumber(),
+ 'nationality' => null !== ($c = $person->getNationality()) ? $this->translatableStringHelper->localize($c->getName()) : '',
+ 'placeOfBirth' => $person->getPlaceOfBirth(),
+ 'memo' => $person->getMemo(),
+ 'numberOfChildren' => (string) $person->getNumberOfChildren(),
+ ];
+ }
+
+ private function normalizeNullValue(string $format, array $context)
+ {
+ $normalizer = new NormalizeNullValueHelper($this->normalizer);
+
+ $attributes = [
+ 'firstname', 'lastname', 'altNames', 'text',
+ 'birthdate' => \DateTimeInterface::class,
+ 'deathdate' => \DateTimeInterface::class,
+ 'gender', 'maritalStatus',
+ 'maritalStatusDate' => \DateTimeInterface::class,
+ 'email', 'firstPhoneNumber', 'fixPhoneNumber', 'mobilePhoneNumber', 'nationality',
+ 'placeOfBirth', 'memo', 'numberOfChildren'
+ ];
+
+ return $normalizer->normalize($attributes, $format, $context);
+ }
+
+ public function supportsNormalization($data, string $format = null, array $context = [])
+ {
+ if ($format !== 'docgen') {
+ return false;
+ }
+
+ return
+ $data instanceof Person
+ || (
+ \array_key_exists('docgen:expects', $context)
+ && $context['docgen:expects'] === Person::class
+ );
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php
similarity index 72%
rename from src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php
rename to src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php
index b7e0069ee..3b42e86db 100644
--- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php
+++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php
@@ -39,7 +39,7 @@ use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
* Serialize a Person entity
*
*/
-class PersonNormalizer implements
+class PersonJsonNormalizer implements
NormalizerInterface,
NormalizerAwareInterface,
DenormalizerInterface,
@@ -105,7 +105,7 @@ class PersonNormalizer implements
public function supportsNormalization($data, string $format = null): bool
{
- return $data instanceof Person;
+ return $data instanceof Person && $format === 'json';
}
public function denormalize($data, string $type, string $format = null, array $context = [])
@@ -128,45 +128,48 @@ class PersonNormalizer implements
$person = new Person();
}
- $properties = ['firstName', 'lastName', 'phonenumber', 'mobilenumber', 'gender'];
+ foreach (['firstName', 'lastName', 'phonenumber', 'mobilenumber', 'gender',
+ 'birthdate', 'deathdate', 'center']
+ as $item) {
- $properties = array_filter(
- $properties,
- static fn (string $property): bool => array_key_exists($property, $data)
- );
-
- foreach ($properties as $item) {
- $callable = [$person, sprintf('set%s', ucfirst($item))];
-
- if (is_callable($callable)) {
- $closure = \Closure::fromCallable($callable);
-
- $closure($data[$item]);
+ if (!\array_key_exists($item, $data)) {
+ continue;
}
- }
- $propertyToClassMapping = [
- 'birthdate' => \DateTime::class,
- 'deathdate' => \DateTime::class,
- 'center' => Center::class,
- ];
-
- $propertyToClassMapping = array_filter(
- $propertyToClassMapping,
- static fn (string $item): bool => array_key_exists($item, $data)
- );
-
- foreach ($propertyToClassMapping as $item => $class) {
- $object = $this->denormalizer->denormalize($data[$item], $class, $format, $context);
-
- if ($object instanceof $class) {
- $callable = [$object, sprintf('set%s', ucfirst($item))];
-
- if (is_callable($callable)) {
- $closure = \Closure::fromCallable($callable);
-
- $closure($object);
- }
+ switch ($item) {
+ case 'firstName':
+ $person->setFirstName($data[$item]);
+ break;
+ case 'lastName':
+ $person->setLastName($data[$item]);
+ break;
+ case 'phonenumber':
+ $person->setPhonenumber($data[$item]);
+ break;
+ case 'mobilenumber':
+ $person->setMobilenumber($data[$item]);
+ break;
+ case 'gender':
+ $person->setGender($data[$item]);
+ break;
+ case 'birthdate':
+ $object = $this->denormalizer->denormalize($data[$item], \DateTime::class, $format, $context);
+ if ($object instanceof \DateTime) {
+ $person->setBirthdate($object);
+ }
+ break;
+ case 'deathdate':
+ $object = $this->denormalizer->denormalize($data[$item], \DateTime::class, $format, $context);
+ if ($object instanceof \DateTime) {
+ $person->setDeathdate($object);
+ }
+ break;
+ case 'center':
+ $object = $this->denormalizer->denormalize($data[$item], Center::class, $format, $context);
+ $person->setCenter($object);
+ break;
+ default:
+ throw new \LogicException("item not defined: $item");
}
}
diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonDocGenNormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonDocGenNormalizerTest.php
new file mode 100644
index 000000000..96a744913
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonDocGenNormalizerTest.php
@@ -0,0 +1,71 @@
+normalizer = self::$container->get(NormalizerInterface::class);
+ }
+
+ /**
+ * @dataProvider generateData
+ */
+ public function testNormalize(?Person $person, $expected, $msg)
+ {
+ $normalized = $this->normalizer->normalize($person, 'docgen', ['docgen:expects' => Person::class]);
+
+ $this->assertEquals($expected, $normalized, $msg);
+ }
+
+ public function generateData()
+ {
+ $person = new Person();
+ $person
+ ->setFirstName('Renaud')
+ ->setLastName('Mégane')
+ ;
+
+ $expected = \array_merge(
+ self::BLANK, ['firstname' => 'Renaud', 'lastname' => 'Mégane',
+ 'text' => 'Renaud Mégane']
+ );
+
+ yield [$person, $expected, 'partial normalization for a person'];
+
+ yield [null, self::BLANK, 'normalization for a null person'];
+ }
+
+
+ private const BLANK = [
+ 'firstname' => '',
+ 'lastname' => '',
+ 'altNames' => '',
+ 'text' => '',
+ 'birthdate' => ['short' => '', 'long' => ''],
+ 'deathdate' => ['short' => '', 'long' => ''],
+ 'gender' => '',
+ 'maritalStatus' => '',
+ 'maritalStatusDate' => ['short' => '', 'long' => ''],
+ 'email' => '',
+ 'firstPhoneNumber' => '',
+ 'fixPhoneNumber' => '',
+ 'mobilePhoneNumber' => '',
+ 'nationality' => '',
+ 'placeOfBirth' => '',
+ 'memo' => '',
+ 'numberOfChildren' => ''
+ ];
+
+}
diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonNormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonNormalizerTest.php
new file mode 100644
index 000000000..4839d84f5
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonJsonNormalizerTest.php
@@ -0,0 +1,28 @@
+normalizer = self::$container->get(NormalizerInterface::class);
+ }
+
+ public function testNormalization()
+ {
+ $person = new Person();
+ $result = $this->normalizer->normalize($person, 'json', [AbstractNormalizer::GROUPS => [ 'read' ]]);
+
+ $this->assertIsArray($result);
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/config/services/serializer.yaml b/src/Bundle/ChillPersonBundle/config/services/serializer.yaml
index f64b3121b..5a1e54400 100644
--- a/src/Bundle/ChillPersonBundle/config/services/serializer.yaml
+++ b/src/Bundle/ChillPersonBundle/config/services/serializer.yaml
@@ -4,6 +4,7 @@ services:
Chill\PersonBundle\Serializer\Normalizer\:
autowire: true
+ autoconfigure: true
resource: '../../Serializer/Normalizer'
tags:
- { name: 'serializer.normalizer', priority: 64 }
diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20211112170027.php b/src/Bundle/ChillPersonBundle/migrations/Version20211112170027.php
new file mode 100644
index 000000000..65434bb33
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/migrations/Version20211112170027.php
@@ -0,0 +1,37 @@
+addSql('UPDATE chill_person_person SET mobilenumber = \'\' WHERE mobilenumber IS NULL');
+ $this->addSql('UPDATE chill_person_person SET phonenumber = \'\' WHERE phonenumber IS NULL');
+ $this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber SET NOT NULL');
+ $this->addSql('ALTER TABLE chill_person_person ALTER phonenumber SET NOT NULL');
+ $this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber SET DEFAULT \'\'');
+ $this->addSql('ALTER TABLE chill_person_person ALTER phonenumber SET DEFAULT \'\'');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber DROP NOT NULL');
+ $this->addSql('ALTER TABLE chill_person_person ALTER phonenumber DROP NOT NULL');
+ $this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber SET DEFAULT NULL');
+ $this->addSql('ALTER TABLE chill_person_person ALTER phonenumber SET DEFAULT NULL');
+ }
+}