mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2026-01-14 13:21:25 +00:00
189 lines
6.4 KiB
Markdown
189 lines
6.4 KiB
Markdown
# Normalizing for DocGen
|
|
|
|
In Chill, some entities can be normalized in the `docgen` format, which is specifically used for document generation.
|
|
|
|
## The `docgen` Format Requirements
|
|
|
|
This format has specific requirements regarding `null` values. When serializing a `null` value, it must not be serialized as a literal `null`. Instead, it must be serialized as an object (or array) containing all the keys that would be present if the object were not null.
|
|
|
|
Each key must have, as value:
|
|
- An empty string value (for scalars).
|
|
- A boolean
|
|
- An empty array (for collections).
|
|
- Or, if the expected type is another object, it must contain all the keys for that object, recursively.
|
|
|
|
This ensures that the document generator always finds the expected keys, even if the data is missing.
|
|
|
|
Additionally, every normalized form must include an `isNull` key (boolean). This helps the document template distinguish between an actual object and its "null" representation.
|
|
|
|
## Attribute-Based Normalization
|
|
|
|
The simplest way to support `docgen` normalization is to use Symfony Serializer attributes.
|
|
|
|
- Use the group `docgen:read` on properties that should be included.
|
|
- For translatable fields (stored as JSON/array of translations), you must also add the context `is-translatable => true`.
|
|
|
|
### Example: `Country.php`
|
|
|
|
The `Country` entity demonstrates this approach:
|
|
|
|
```php
|
|
namespace Chill\MainBundle\Entity;
|
|
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
use Symfony\Component\Serializer\Attribute\Context;
|
|
use Symfony\Component\Serializer\Attribute\Groups;
|
|
use Symfony\Component\Serializer\Attribute\SerializedName;
|
|
|
|
#[ORM\Entity]
|
|
class Country
|
|
{
|
|
#[Groups(['read', 'docgen:read'])]
|
|
#[SerializedName('code')]
|
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 3)]
|
|
private string $countryCode = '';
|
|
|
|
#[Groups(['read', 'docgen:read'])]
|
|
#[ORM\Id]
|
|
#[ORM\Column(name: 'id', type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
|
private ?int $id = null;
|
|
|
|
#[Groups(['read', 'docgen:read'])]
|
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)]
|
|
#[Context(['is-translatable' => true], groups: ['docgen:read'])]
|
|
private array $name = [];
|
|
|
|
// ...
|
|
}
|
|
```
|
|
|
|
For working properly, the property or return type must be properly set.
|
|
|
|
## Custom Normalizer
|
|
|
|
For more complex entities, you may need to implement a custom normalizer.
|
|
|
|
### Requirements for `docgen` Normalizers
|
|
|
|
1. **Handle `null` in `supportsNormalization`**:
|
|
The normalizer must return `true` if the data is `null` AND the context contains a `docgen:expects` key matching the class the normalizer handles.
|
|
|
|
```php
|
|
public function supportsNormalization($data, $format = null, array $context = []): bool
|
|
{
|
|
if ('docgen' === $format) {
|
|
return $data instanceof MyEntity
|
|
|| (null === $data && MyEntity::class === ($context['docgen:expects'] ?? null));
|
|
}
|
|
return false;
|
|
}
|
|
```
|
|
|
|
2. **Implementation of `getSupportedTypes`**:
|
|
To avoid side effects and optimize performance, you must return `'*' => false` along with the specific class.
|
|
|
|
```php
|
|
public function getSupportedTypes(?string $format): array
|
|
{
|
|
if ('docgen' === $format) {
|
|
return [
|
|
MyEntity::class => true,
|
|
'*' => false,
|
|
];
|
|
}
|
|
return [];
|
|
}
|
|
```
|
|
|
|
3. **Handle `null` in `normalize`**:
|
|
The `normalize` method must detect when the input is `null` and return the "empty" structure with all keys.
|
|
|
|
### Using `NormalizeNullValueHelper`
|
|
|
|
To help with normalizing `null` values recursively, you can use the `\Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper` class.
|
|
|
|
This helper takes an array defining the keys and their expected types. If a type is a class-string, it will call the serializer again to normalize a `null` value for that class.
|
|
|
|
### Example: `AddressNormalizer.php`
|
|
|
|
The `AddressNormalizer` is a good example of a custom normalizer handling `docgen`:
|
|
|
|
```php
|
|
class AddressNormalizer implements NormalizerInterface, NormalizerAwareInterface
|
|
{
|
|
use NormalizerAwareTrait;
|
|
|
|
private const array NULL_VALUE = [
|
|
'address_id' => 'int',
|
|
'text' => 'string',
|
|
'street' => 'string',
|
|
// ...
|
|
'validFrom' => \DateTimeInterface::class,
|
|
'postcode' => PostalCode::class,
|
|
];
|
|
|
|
public function normalize($address, $format = null, array $context = []): array
|
|
{
|
|
if ($address instanceof Address) {
|
|
// ... normal normalization logic
|
|
if ('docgen' === $format) {
|
|
$data['postcode'] = $this->normalizer->normalize(
|
|
$address->getPostcode(),
|
|
$format,
|
|
[...$context, 'docgen:expects' => PostalCode::class]
|
|
);
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
if (null === $address && 'docgen' === $format) {
|
|
$helper = new NormalizeNullValueHelper($this->normalizer);
|
|
return $helper->normalize(self::NULL_VALUE, $format, $context);
|
|
}
|
|
|
|
// ...
|
|
}
|
|
|
|
// ... supportsNormalization and getSupportedTypes as described above
|
|
}
|
|
```
|
|
|
|
## Testing `docgen` Normalization
|
|
|
|
To ensure that your normalizer strictly follows the `docgen` requirements, you should extend `\Chill\DocGeneratorBundle\Test\DocGenNormalizerTestAbstract`.
|
|
|
|
This abstract test class performs several critical checks:
|
|
- It ensures that the normalized form of a non-null object and a `null` value have **exactly the same keys** (same array shape).
|
|
- It verifies that the `isNull` key is present and correctly set (`false` for objects, `true` for `null`).
|
|
- It recursively checks that sub-objects also have consistent keys.
|
|
- It ensures that `null` values are never returned (they should be empty strings, empty arrays, etc.).
|
|
|
|
### Example Test: `AddressDocGenNormalizerTest.php`
|
|
|
|
You need to implement two methods: `provideNotNullObject()` and `provideDocGenExpectClass()`.
|
|
|
|
```php
|
|
namespace Chill\MainBundle\Tests\Serializer\Normalizer;
|
|
|
|
use Chill\DocGeneratorBundle\Test\DocGenNormalizerTestAbstract;
|
|
use Chill\MainBundle\Entity\Address;
|
|
|
|
class AddressDocGenNormalizerTest extends DocGenNormalizerTestAbstract
|
|
{
|
|
public function provideNotNullObject(): object
|
|
{
|
|
// Return a fully populated instance of the entity
|
|
return new Address()
|
|
->setStreet('Rue de la Loi')
|
|
// ...
|
|
;
|
|
}
|
|
|
|
public function provideDocGenExpectClass(): string
|
|
{
|
|
return Address::class;
|
|
}
|
|
}
|
|
```
|
|
|