mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-25 16:14:59 +00:00
Add MotiveNormalizer and support for parent/child relationships in motives
- Introduced `MotiveNormalizer` to handle normalization of `Motive` entities. - Enhanced serialization groups to support parent-to-children and children-to-parent relationships. - Updated TypeScript types and entities to align with the new schema. - Adjusted API responses and query behavior to reflect hierarchical motive relationships.
This commit is contained in:
@@ -13,6 +13,7 @@ namespace Chill\TicketBundle\Controller;
|
|||||||
|
|
||||||
use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectNormalizer;
|
use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectNormalizer;
|
||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
|
use Chill\TicketBundle\Serializer\Normalizer\MotiveNormalizer;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
@@ -21,13 +22,13 @@ final class MotiveApiController extends ApiController
|
|||||||
protected function customizeQuery(string $action, Request $request, $query): void
|
protected function customizeQuery(string $action, Request $request, $query): void
|
||||||
{
|
{
|
||||||
/* @var $query QueryBuilder */
|
/* @var $query QueryBuilder */
|
||||||
$query->andWhere('e.active = TRUE');
|
$query->andWhere('e.active = TRUE')->andWhere('e.parent IS NULL');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getContextForSerialization(string $action, Request $request, string $_format, $entity): array
|
protected function getContextForSerialization(string $action, Request $request, string $_format, $entity): array
|
||||||
{
|
{
|
||||||
return match ($request->getMethod()) {
|
return match ($request->getMethod()) {
|
||||||
Request::METHOD_GET => ['groups' => ['read', StoredObjectNormalizer::DOWNLOAD_LINK_ONLY]],
|
Request::METHOD_GET => ['groups' => ['read', 'read:extended', StoredObjectNormalizer::DOWNLOAD_LINK_ONLY, MotiveNormalizer::GROUP_PARENT_TO_CHILDREN]],
|
||||||
default => parent::getContextForSerialization($action, $request, $_format, $entity),
|
default => parent::getContextForSerialization($action, $request, $_format, $entity),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -15,30 +15,25 @@ use Chill\DocStoreBundle\Entity\StoredObject;
|
|||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\Common\Collections\ReadableCollection;
|
use Doctrine\Common\Collections\ReadableCollection;
|
||||||
|
use Doctrine\Common\Collections\Selectable;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
|
||||||
|
|
||||||
#[ORM\Entity()]
|
#[ORM\Entity()]
|
||||||
#[ORM\Table(name: 'motive', schema: 'chill_ticket')]
|
#[ORM\Table(name: 'motive', schema: 'chill_ticket')]
|
||||||
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['ticket_motive' => Motive::class])]
|
|
||||||
class Motive
|
class Motive
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
||||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||||
#[Serializer\Groups(['read'])]
|
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]'])]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]'])]
|
||||||
#[Serializer\Groups(['read'])]
|
|
||||||
private array $label = [];
|
private array $label = [];
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, nullable: false, options: ['default' => true])]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, nullable: false, options: ['default' => true])]
|
||||||
#[Serializer\Groups(['read'])]
|
|
||||||
private bool $active = true;
|
private bool $active = true;
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, nullable: true, enumType: EmergencyStatusEnum::class)]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, nullable: true, enumType: EmergencyStatusEnum::class)]
|
||||||
#[Serializer\Groups(['read'])]
|
|
||||||
private ?EmergencyStatusEnum $makeTicketEmergency = null;
|
private ?EmergencyStatusEnum $makeTicketEmergency = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,15 +44,17 @@ class Motive
|
|||||||
private Collection $storedObjects;
|
private Collection $storedObjects;
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['jsonb' => true, 'default' => '[]'])]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['jsonb' => true, 'default' => '[]'])]
|
||||||
#[Serializer\Groups(['read'])]
|
|
||||||
private array $supplementaryComments = [];
|
private array $supplementaryComments = [];
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Motive::class, inversedBy: 'children')]
|
#[ORM\ManyToOne(targetEntity: Motive::class, inversedBy: 'children')]
|
||||||
private ?Motive $parent = null;
|
private ?Motive $parent = null;
|
||||||
|
|
||||||
|
|
||||||
#[ORM\OneToMany(targetEntity: Motive::class, mappedBy: 'motive')]
|
/**
|
||||||
private Collection $children;
|
* @var Collection<int, Motive>&Selectable<int, Motive>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: Motive::class, mappedBy: 'parent')]
|
||||||
|
private Collection&Selectable $children;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -77,7 +74,6 @@ class Motive
|
|||||||
$this->storedObjects->removeElement($storedObject);
|
$this->storedObjects->removeElement($storedObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Serializer\Groups(['read'])]
|
|
||||||
public function getStoredObjects(): ReadableCollection
|
public function getStoredObjects(): ReadableCollection
|
||||||
{
|
{
|
||||||
return $this->storedObjects;
|
return $this->storedObjects;
|
||||||
@@ -189,4 +185,14 @@ class Motive
|
|||||||
{
|
{
|
||||||
$this->children->removeElement($child);
|
$this->children->removeElement($child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getChildren(): ReadableCollection&Selectable
|
||||||
|
{
|
||||||
|
return $this->children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParent(): ?Motive
|
||||||
|
{
|
||||||
|
return $this->parent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -42,7 +42,7 @@ class MotiveHistory implements TrackCreationInterface
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
#[ORM\ManyToOne(targetEntity: Motive::class)]
|
#[ORM\ManyToOne(targetEntity: Motive::class)]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
#[Serializer\Groups(['read'])]
|
#[Serializer\Groups(['read', 'read:children-to-parent'])]
|
||||||
private Motive $motive,
|
private Motive $motive,
|
||||||
#[ORM\ManyToOne(targetEntity: Ticket::class)]
|
#[ORM\ManyToOne(targetEntity: Ticket::class)]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
@@ -8,14 +8,32 @@ import { Person } from "ChillPersonAssets/types";
|
|||||||
import { Thirdparty } from "../../../../ChillThirdPartyBundle/Resources/public/types";
|
import { Thirdparty } from "../../../../ChillThirdPartyBundle/Resources/public/types";
|
||||||
import { StoredObject } from "ChillDocStoreAssets/types";
|
import { StoredObject } from "ChillDocStoreAssets/types";
|
||||||
|
|
||||||
export interface Motive {
|
interface MotiveBase {
|
||||||
type: "ticket_motive";
|
type: "ticket_motive";
|
||||||
id: number;
|
id: number;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
label: TranslatableString;
|
label: TranslatableString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent a motive with basic information and parent motive.
|
||||||
|
*
|
||||||
|
* Match the "read" and "read:children-to-parent" serializer groups.
|
||||||
|
*/
|
||||||
|
export interface MotiveWithParent extends MotiveBase {
|
||||||
|
parent: MotiveWithParent|null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a motive for a ticket, including details like emergency status, stored objects, and supplementary comments.
|
||||||
|
*
|
||||||
|
* Match the "read:extended" serializer group in MotiveNormalizer.
|
||||||
|
*/
|
||||||
|
export interface Motive extends MotiveBase {
|
||||||
makeTicketEmergency: TicketEmergencyState;
|
makeTicketEmergency: TicketEmergencyState;
|
||||||
storedObjects: StoredObject[];
|
storedObjects: StoredObject[];
|
||||||
supplementaryComments: { label: string }[];
|
supplementaryComments: { label: string }[];
|
||||||
|
children: Motive[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TicketState = "open" | "closed" | "close";
|
export type TicketState = "open" | "closed" | "close";
|
||||||
@@ -45,7 +63,7 @@ export interface MotiveHistory {
|
|||||||
id: number;
|
id: number;
|
||||||
startDate: null;
|
startDate: null;
|
||||||
endDate: null | DateTime;
|
endDate: null | DateTime;
|
||||||
motive: Motive;
|
motive: MotiveWithParent;
|
||||||
createdBy: User | null;
|
createdBy: User | null;
|
||||||
createdAt: DateTime | null;
|
createdAt: DateTime | null;
|
||||||
}
|
}
|
||||||
@@ -140,7 +158,7 @@ interface BaseTicket<
|
|||||||
createdAt: DateTime | null;
|
createdAt: DateTime | null;
|
||||||
currentAddressees: UserGroupOrUser[];
|
currentAddressees: UserGroupOrUser[];
|
||||||
currentPersons: Person[];
|
currentPersons: Person[];
|
||||||
currentMotive: null | Motive;
|
currentMotive: null | MotiveWithParent;
|
||||||
currentState: TicketState | null;
|
currentState: TicketState | null;
|
||||||
emergency: TicketEmergencyState | null;
|
emergency: TicketEmergencyState | null;
|
||||||
caller: Person | Thirdparty | null;
|
caller: Person | Thirdparty | null;
|
||||||
|
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\TicketBundle\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\TicketBundle\Entity\Motive;
|
||||||
|
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes a Motive object into an array format, supporting different serialization groups
|
||||||
|
* to customize the output depending on the context.
|
||||||
|
*
|
||||||
|
* There are several serialization groups available:
|
||||||
|
* - 'read': Basic information about the motive.
|
||||||
|
* - 'read:extended': Includes additional details like stored objects and supplementary comments.
|
||||||
|
* - 'read:parent-to-children': Normalizes children recursively without exposing parent to avoid cycles.
|
||||||
|
* - 'read:children-to-parent': Normalizes parent recursively without exposing children to avoid cycles.
|
||||||
|
*/
|
||||||
|
final class MotiveNormalizer implements NormalizerInterface, NormalizerAwareInterface
|
||||||
|
{
|
||||||
|
use NormalizerAwareTrait;
|
||||||
|
|
||||||
|
public const GROUP_PARENT_TO_CHILDREN = 'read:parent-to-children';
|
||||||
|
public const GROUP_CHILDREN_TO_PARENT = 'read:children-to-parent';
|
||||||
|
|
||||||
|
public function normalize($object, ?string $format = null, array $context = []): array
|
||||||
|
{
|
||||||
|
if (!$object instanceof Motive) {
|
||||||
|
throw new UnexpectedValueException('Expected instance of '.Motive::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
$groups = $context[AbstractNormalizer::GROUPS] ?? [];
|
||||||
|
if (is_string($groups)) {
|
||||||
|
$groups = [$groups];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
if (in_array('read', $groups, true) || in_array('read:extended', $groups, true)) {
|
||||||
|
// Build base representation
|
||||||
|
$data = [
|
||||||
|
'type' => 'ticket_motive',
|
||||||
|
'id' => $object->getId(),
|
||||||
|
'label' => $object->getLabel(),
|
||||||
|
'active' => $object->isActive(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('read:extended', $groups, true)) {
|
||||||
|
$data['makeTicketEmergency'] = $object->getMakeTicketEmergency();
|
||||||
|
$data['supplementaryComments'] = $object->getSupplementaryComments();
|
||||||
|
// Normalize stored objects (delegated to their own normalizer when present)
|
||||||
|
$storedObjects = [];
|
||||||
|
foreach ($object->getStoredObjects() as $storedObject) {
|
||||||
|
$storedObjects[] = $this->normalizer->normalize($storedObject, $format, $context);
|
||||||
|
}
|
||||||
|
$data['storedObjects'] = $storedObjects;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array(self::GROUP_PARENT_TO_CHILDREN, $groups, true)) {
|
||||||
|
// Normalize children recursively (but we do not expose parent to avoid cycles)
|
||||||
|
$children = [];
|
||||||
|
foreach ($object->getChildren() as $child) {
|
||||||
|
$children[] = $this->normalizer->normalize($child, $format, $context);
|
||||||
|
}
|
||||||
|
$data['children'] = $children;
|
||||||
|
} elseif (in_array(self::GROUP_CHILDREN_TO_PARENT, $groups, true)) {
|
||||||
|
$data['parent'] = $this->normalizer->normalize($object->getParent(), $format, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
|
||||||
|
{
|
||||||
|
return $data instanceof Motive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optimization hint for the Serializer (available since Symfony 5.3+).
|
||||||
|
*
|
||||||
|
* @return array<class-string, bool>
|
||||||
|
*/
|
||||||
|
public function getSupportedTypes(?string $format): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Motive::class => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@@ -51,7 +51,7 @@ final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInte
|
|||||||
]),
|
]),
|
||||||
'currentAddressees' => $this->normalizer->normalize($object->getCurrentAddressee(), $format, ['groups' => 'read']),
|
'currentAddressees' => $this->normalizer->normalize($object->getCurrentAddressee(), $format, ['groups' => 'read']),
|
||||||
'currentInputs' => $this->normalizer->normalize($object->getCurrentInputs(), $format, ['groups' => 'read']),
|
'currentInputs' => $this->normalizer->normalize($object->getCurrentInputs(), $format, ['groups' => 'read']),
|
||||||
'currentMotive' => $this->normalizer->normalize($object->getMotive(), $format, ['groups' => 'read']),
|
'currentMotive' => $this->normalizer->normalize($object->getMotive(), $format, ['groups' => ['read', MotiveNormalizer::GROUP_CHILDREN_TO_PARENT]]),
|
||||||
'currentState' => $object->getState()?->value ?? 'open',
|
'currentState' => $object->getState()?->value ?? 'open',
|
||||||
'emergency' => $object->getEmergencyStatus()?->value ?? 'no',
|
'emergency' => $object->getEmergencyStatus()?->value ?? 'no',
|
||||||
'caller' => $this->normalizer->normalize($object->getCaller(), $format, ['groups' => 'read']),
|
'caller' => $this->normalizer->normalize($object->getCaller(), $format, ['groups' => 'read']),
|
||||||
|
@@ -0,0 +1,154 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\TicketBundle\Tests\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||||
|
use Chill\TicketBundle\Entity\Motive;
|
||||||
|
use Chill\TicketBundle\Serializer\Normalizer\MotiveNormalizer;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @covers \Chill\TicketBundle\Serializer\Normalizer\MotiveNormalizer
|
||||||
|
*/
|
||||||
|
final class MotiveNormalizerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testNormalizeReadBasic(): void
|
||||||
|
{
|
||||||
|
$motive = new Motive();
|
||||||
|
$motive->setLabel(['fr' => 'Logement', 'en' => 'Housing']);
|
||||||
|
// active is true by default
|
||||||
|
|
||||||
|
$normalizer = new MotiveNormalizer();
|
||||||
|
$normalizer->setNormalizer($this->buildDummyNormalizer());
|
||||||
|
|
||||||
|
$actual = $normalizer->normalize($motive, 'json', ['groups' => 'read']);
|
||||||
|
|
||||||
|
self::assertSame('ticket_motive', $actual['type']);
|
||||||
|
self::assertNull($actual['id']);
|
||||||
|
self::assertSame(['fr' => 'Logement', 'en' => 'Housing'], $actual['label']);
|
||||||
|
self::assertTrue($actual['active']);
|
||||||
|
// no extended fields here
|
||||||
|
self::assertArrayNotHasKey('makeTicketEmergency', $actual);
|
||||||
|
self::assertArrayNotHasKey('supplementaryComments', $actual);
|
||||||
|
self::assertArrayNotHasKey('storedObjects', $actual);
|
||||||
|
self::assertArrayNotHasKey('children', $actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizeExtended(): void
|
||||||
|
{
|
||||||
|
$motive = new Motive();
|
||||||
|
$motive->setLabel(['fr' => 'Financier']);
|
||||||
|
$motive->setMakeTicketEmergency(EmergencyStatusEnum::YES);
|
||||||
|
$motive->addSupplementaryComment(['label' => 'Justifier le revenu']);
|
||||||
|
$motive->addStoredObject(new StoredObject('pending'));
|
||||||
|
|
||||||
|
$normalizer = new MotiveNormalizer();
|
||||||
|
$normalizer->setNormalizer($this->buildDummyNormalizer());
|
||||||
|
|
||||||
|
$actual = $normalizer->normalize($motive, 'json', ['groups' => ['read', 'read:extended']]);
|
||||||
|
|
||||||
|
self::assertSame('ticket_motive', $actual['type']);
|
||||||
|
self::assertSame(['fr' => 'Financier'], $actual['label']);
|
||||||
|
self::assertSame(EmergencyStatusEnum::YES, $actual['makeTicketEmergency']);
|
||||||
|
self::assertSame([
|
||||||
|
['label' => 'Justifier le revenu'],
|
||||||
|
], $actual['supplementaryComments']);
|
||||||
|
self::assertSame([
|
||||||
|
['stored_object'],
|
||||||
|
], $actual['storedObjects']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizeParentToChildren(): void
|
||||||
|
{
|
||||||
|
$parent = new Motive();
|
||||||
|
$parent->setLabel(['fr' => 'Parent']);
|
||||||
|
$child1 = new Motive();
|
||||||
|
$child1->setLabel(['fr' => 'Enfant 1']);
|
||||||
|
$child2 = new Motive();
|
||||||
|
$child2->setLabel(['fr' => 'Enfant 2']);
|
||||||
|
|
||||||
|
// build relation
|
||||||
|
$child1->setParent($parent);
|
||||||
|
$child2->setParent($parent);
|
||||||
|
|
||||||
|
$normalizer = new MotiveNormalizer();
|
||||||
|
$normalizer->setNormalizer($this->buildDummyNormalizer());
|
||||||
|
|
||||||
|
$actual = $normalizer->normalize($parent, 'json', ['groups' => [MotiveNormalizer::GROUP_PARENT_TO_CHILDREN]]);
|
||||||
|
|
||||||
|
// children must be normalized by the injected normalizer and parent not exposed
|
||||||
|
self::assertArrayHasKey('children', $actual);
|
||||||
|
self::assertSame([
|
||||||
|
['motive' => 'normalized'],
|
||||||
|
['motive' => 'normalized'],
|
||||||
|
], $actual['children']);
|
||||||
|
self::assertArrayNotHasKey('parent', $actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizeChildrenToParent(): void
|
||||||
|
{
|
||||||
|
$parent = new Motive();
|
||||||
|
$parent->setLabel(['fr' => 'Parent']);
|
||||||
|
$child = new Motive();
|
||||||
|
$child->setLabel(['fr' => 'Enfant']);
|
||||||
|
$child->setParent($parent);
|
||||||
|
|
||||||
|
$normalizer = new MotiveNormalizer();
|
||||||
|
$normalizer->setNormalizer($this->buildDummyNormalizer());
|
||||||
|
|
||||||
|
$actual = $normalizer->normalize($child, 'json', ['groups' => [MotiveNormalizer::GROUP_CHILDREN_TO_PARENT]]);
|
||||||
|
|
||||||
|
// parent must be normalized by the injected normalizer and children not exposed
|
||||||
|
self::assertArrayHasKey('parent', $actual);
|
||||||
|
self::assertSame(['motive' => 'normalized'], $actual['parent']);
|
||||||
|
self::assertArrayNotHasKey('children', $actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSupportsAndSupportedTypes(): void
|
||||||
|
{
|
||||||
|
$motive = new Motive();
|
||||||
|
$normalizer = new MotiveNormalizer();
|
||||||
|
|
||||||
|
self::assertTrue($normalizer->supportsNormalization($motive, 'json'));
|
||||||
|
self::assertFalse($normalizer->supportsNormalization(new \stdClass(), 'json'));
|
||||||
|
|
||||||
|
$supported = $normalizer->getSupportedTypes('json');
|
||||||
|
self::assertArrayHasKey(Motive::class, $supported);
|
||||||
|
self::assertTrue($supported[Motive::class]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildDummyNormalizer(): NormalizerInterface
|
||||||
|
{
|
||||||
|
return new class () implements NormalizerInterface {
|
||||||
|
public function normalize($object, ?string $format = null, array $context = []): array
|
||||||
|
{
|
||||||
|
if ($object instanceof StoredObject) {
|
||||||
|
return ['stored_object'];
|
||||||
|
}
|
||||||
|
if ($object instanceof Motive) {
|
||||||
|
return ['motive' => 'normalized'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['normalized'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsNormalization($data, ?string $format = null): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user