mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
handling multi types and acc-period/repositories endpoint
This commit is contained in:
parent
4a04628d5b
commit
87e2ac9386
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Serializer\Normalizer;
|
||||
|
||||
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Exception\RuntimeException;
|
||||
|
||||
|
||||
/**
|
||||
* Denormalize an object given a list of supported class
|
||||
*/
|
||||
class DiscriminatedObjectDenormalizer implements ContextAwareDenormalizerInterface, DenormalizerAwareInterface
|
||||
{
|
||||
use DenormalizerAwareTrait;
|
||||
|
||||
/**
|
||||
* The type to set for enabling this type
|
||||
*/
|
||||
public const TYPE = '@multi';
|
||||
|
||||
/**
|
||||
* Should be present in context and contains an array of
|
||||
* allowed types.
|
||||
*/
|
||||
public const ALLOWED_TYPES = 'denormalize_multi.allowed_types';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function denormalize($data, string $type, string $format = null, array $context = [])
|
||||
{
|
||||
foreach ($context[self::ALLOWED_TYPES] as $localType) {
|
||||
if ($this->denormalizer->supportsDenormalization($data, $localType, $format)) {
|
||||
try {
|
||||
return $this->denormalizer->denormalize($data, $localType, $format, $context); } catch (RuntimeException $e) {
|
||||
$lastException = $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException(sprintf("Could not find any denormalizer for those ".
|
||||
"ALLOWED_TYPES: %s", \implode(", ", $context[self::ALLOWED_TYPES])));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function supportsDenormalization($data, string $type, string $format = null, array $context = [])
|
||||
{
|
||||
if (self::TYPE !== $type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 === count($context[self::ALLOWED_TYPES] ?? [])) {
|
||||
throw new \LogicException("The context should contains a list of
|
||||
allowed types");
|
||||
}
|
||||
|
||||
foreach ($context[self::ALLOWED_TYPES] as $localType) {
|
||||
if ($this->denormalizer->supportsDenormalization($data, $localType, $format)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -48,7 +48,6 @@ class AccompanyingCourseApiController extends ApiController
|
||||
break;
|
||||
case Request::METHOD_DELETE:
|
||||
$participation = $accompanyingPeriod->removePerson($person);
|
||||
$participation->setEndDate(new \DateTimeImmutable('now'));
|
||||
break;
|
||||
default:
|
||||
throw new BadRequestException("This method is not supported");
|
||||
@ -66,6 +65,56 @@ class AccompanyingCourseApiController extends ApiController
|
||||
return $this->json($participation, 200, [], ['groups' => [ 'read' ]]);
|
||||
}
|
||||
|
||||
public function resourceApi($id, Request $request, string $_format): Response
|
||||
{
|
||||
return $this->addRemoveSomething('resource', $id, $request, $_format, 'resource',
|
||||
\Chill\PersonBundle\Entity\AccompanyingPeriod\Resource::class);
|
||||
}
|
||||
|
||||
public function addRemoveSomething(string $action, $id, Request $request, string $_format, $property, string $postedDataType, $postedDataContext = []): Response
|
||||
{
|
||||
$entity = $this->getEntity($action, $id, $request);
|
||||
|
||||
$this->checkACL($action, $request, $_format, $entity);
|
||||
$this->onPostCheckACL($action, $request, $_format, $entity);
|
||||
|
||||
try {
|
||||
$postedData = $this->getSerializer()->deserialize($request->getContent(), $postedDataType, $_format, $postedDataContext);
|
||||
} catch (\Symfony\Component\Serializer\Exception\UnexpectedValueException $e) {
|
||||
throw new BadRequestException(sprintf("Unable to deserialize posted ".
|
||||
"data: %s", $e->getMessage()), 0, $e);
|
||||
}
|
||||
|
||||
switch ($request->getMethod()) {
|
||||
case Request::METHOD_DELETE:
|
||||
// oups... how to use property accessor to remove element ?
|
||||
$entity->{'remove'.\ucfirst($property)}($postedData);
|
||||
break;
|
||||
case Request::METHOD_POST:
|
||||
$entity->{'add'.\ucfirst($property)}($postedData);
|
||||
break;
|
||||
default:
|
||||
throw new BadRequestException("this method is not supported");
|
||||
}
|
||||
|
||||
$errors = $this->validator->validate($entity);
|
||||
|
||||
if ($errors->count() > 0) {
|
||||
// only format accepted
|
||||
return $this->json($errors, 422);
|
||||
}
|
||||
|
||||
$this->getDoctrine()->getManager()->flush();
|
||||
|
||||
switch ($request->getMethod()) {
|
||||
case Request::METHOD_DELETE:
|
||||
return $this->json('', 202);
|
||||
case Request::METHOD_POST:
|
||||
return $this->json($postedData, 200, [], ['groups' => [ 'read' ]]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function requestorApi($id, Request $request, string $_format): Response
|
||||
{
|
||||
/** @var AccompanyingPeriod $accompanyingPeriod */
|
||||
|
@ -342,6 +342,18 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
||||
Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE
|
||||
]
|
||||
],
|
||||
'resource' => [
|
||||
'methods' => [
|
||||
Request::METHOD_POST => true,
|
||||
Request::METHOD_DELETE => true,
|
||||
Request::METHOD_GET => false,
|
||||
Request::METHOD_HEAD => false,
|
||||
],
|
||||
'roles' => [
|
||||
Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE,
|
||||
Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE
|
||||
]
|
||||
],
|
||||
'requestor' => [
|
||||
'methods' => [
|
||||
Request::METHOD_POST => true,
|
||||
|
@ -231,7 +231,9 @@ class AccompanyingPeriod
|
||||
*
|
||||
* @ORM\OneToMany(
|
||||
* targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Resource",
|
||||
* mappedBy="accompanyingPeriod"
|
||||
* mappedBy="accompanyingPeriod",
|
||||
* cascade={"persist", "remove"},
|
||||
* orphanRemoval=true
|
||||
* )
|
||||
* @Groups({"read"})
|
||||
*/
|
||||
@ -692,6 +694,7 @@ class AccompanyingPeriod
|
||||
|
||||
public function addResource(Resource $resource): self
|
||||
{
|
||||
$resource->setAccompanyingPeriod($this);
|
||||
$this->resources[] = $resource;
|
||||
|
||||
return $this;
|
||||
@ -699,6 +702,7 @@ class AccompanyingPeriod
|
||||
|
||||
public function removeResource(Resource $resource): void
|
||||
{
|
||||
$resource->setAccompanyingPeriod(null);
|
||||
$this->resources->removeElement($resource);
|
||||
}
|
||||
|
||||
|
@ -28,10 +28,15 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=ResourceRepository::class)
|
||||
* @ORM\Table(name="chill_person_accompanying_period_resource")
|
||||
* @DiscriminatorMap(typeProperty="type", mapping={
|
||||
* "accompanying_period_resource"=Resource::class
|
||||
* })
|
||||
*/
|
||||
class Resource
|
||||
{
|
||||
@ -39,6 +44,7 @@ class Resource
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @Groups({"read"})
|
||||
*/
|
||||
private $id;
|
||||
|
||||
@ -91,7 +97,7 @@ class Resource
|
||||
return $this->thirdParty;
|
||||
}
|
||||
|
||||
public function setThirdParty(?ThirdParty $thirdParty): self
|
||||
private function setThirdParty(?ThirdParty $thirdParty): self
|
||||
{
|
||||
$this->thirdParty = $thirdParty;
|
||||
|
||||
@ -103,7 +109,7 @@ class Resource
|
||||
return $this->person;
|
||||
}
|
||||
|
||||
public function setPerson(?Person $person): self
|
||||
private function setPerson(?Person $person): self
|
||||
{
|
||||
$this->person = $person;
|
||||
|
||||
@ -121,9 +127,35 @@ class Resource
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param $resource Person|ThirdParty
|
||||
*/
|
||||
public function setResource($resource): self
|
||||
{
|
||||
if ($resource instanceof ThirdParty) {
|
||||
$this->setThirdParty($resource);
|
||||
$this->setPerson(NULL);
|
||||
} elseif ($resource instanceof Person) {
|
||||
$this->setPerson($resource);
|
||||
$this->setThirdParty(NULL);
|
||||
} elseif (NULL === $resource) {
|
||||
$this->setPerson(NULL);
|
||||
$this->setThirdParty(NULL);
|
||||
} else {
|
||||
throw new \UnexpectedValueException(sprintf("the resource ".
|
||||
"should be an instance of %s or %s", Person::class,
|
||||
ThirdParty::class));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Person|ThirdParty
|
||||
* @return ThirdParty|Person
|
||||
* @Groups({"read", "write"})
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
|
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Serializer\Normalizer;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
|
||||
use Chill\PersonBundle\Repository\AccompanyingPeriod\ResourceRepository;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
|
||||
use Symfony\Component\Serializer\Exception;
|
||||
use Chill\MainBundle\Serializer\Normalizer\DiscriminatedObjectDenormalizer;
|
||||
|
||||
|
||||
class AccompanyingPeriodResourceNormalizer implements DenormalizerInterface, DenormalizerAwareInterface
|
||||
{
|
||||
use DenormalizerAwareTrait;
|
||||
use ObjectToPopulateTrait;
|
||||
|
||||
public function __construct(ResourceRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function denormalize($data, string $type, string $format = null, array $context = [])
|
||||
{
|
||||
$resource = $this->extractObjectToPopulate($type, $context);
|
||||
|
||||
if ('accompanying_period_resource' !== ($data['type'] ?? NULL)) {
|
||||
throw new Exception\InvalidArgumentException("the key type must be present in data and set to 'accompanying_period_resource'");
|
||||
}
|
||||
|
||||
if ($resource === NULL && \array_key_exists('id', $data)) {
|
||||
$resource = $this->repository->find($data['id']);
|
||||
|
||||
if (NULL === $resource) {
|
||||
throw new Exception\UnexpectedValueException(sprintf("the resource with".
|
||||
"id %d is not found", $data['id']));
|
||||
}
|
||||
|
||||
// if resource found, available only for read-only
|
||||
if (count($data) > 2) {
|
||||
unset($data['id']);
|
||||
unset($data['type']);
|
||||
throw new Exception\ExtraAttributesException($data);
|
||||
}
|
||||
}
|
||||
|
||||
if ($resource === NULL) {
|
||||
$resource = new Resource();
|
||||
}
|
||||
|
||||
if (\array_key_exists('resource', $data)) {
|
||||
$res = $this->denormalizer->denormalize(
|
||||
$data['resource'],
|
||||
DiscriminatedObjectDenormalizer::TYPE,
|
||||
$format,
|
||||
\array_merge(
|
||||
$context,
|
||||
[
|
||||
DiscriminatedObjectDenormalizer::ALLOWED_TYPES =>
|
||||
[
|
||||
Person::class, ThirdParty::class
|
||||
]
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$resource->setResource($res);
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
|
||||
public function supportsDenormalization($data, string $type, string $format = null)
|
||||
{
|
||||
return $type === Resource::class;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Tests\Entity\AccompanyingPeriod;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
|
||||
class ResourceTest extends TestCase
|
||||
{
|
||||
public function testSetResource()
|
||||
{
|
||||
$person = new Person();
|
||||
$thirdParty = new ThirdParty();
|
||||
|
||||
$resource = new Resource();
|
||||
|
||||
$resource->setResource($person);
|
||||
|
||||
$this->assertSame($person, $resource->getResource());
|
||||
$this->assertNull($resource->getThirdParty());
|
||||
|
||||
$resource->setResource($thirdParty);
|
||||
|
||||
$this->assertSame($thirdParty, $resource->getResource());
|
||||
$this->assertNull($resource->getPerson());
|
||||
|
||||
// we repeat adding a person, to ensure that third party is
|
||||
// well reset
|
||||
$resource->setResource($person);
|
||||
$this->assertSame($person, $resource->getResource());
|
||||
$this->assertNull($resource->getThirdParty());
|
||||
|
||||
$resource->setResource(null);
|
||||
$this->assertNull($resource->getThirdParty());
|
||||
$this->assertNull($resource->getPerson());
|
||||
$this->assertNull($resource->getResource());
|
||||
}
|
||||
}
|
@ -1,5 +1,29 @@
|
||||
components:
|
||||
schemas:
|
||||
Date:
|
||||
type: object
|
||||
properties:
|
||||
datetime:
|
||||
type: string
|
||||
format: date-time
|
||||
Person:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
firstName:
|
||||
type: string
|
||||
lastName:
|
||||
type: string
|
||||
text:
|
||||
type: string
|
||||
description: a canonical representation for the person name
|
||||
birthdate:
|
||||
$ref: '#/components/schemas/Date'
|
||||
phonenumber:
|
||||
type: string
|
||||
mobilenumber:
|
||||
type: string
|
||||
PersonById:
|
||||
type: object
|
||||
properties:
|
||||
@ -12,6 +36,12 @@ components:
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
# should go to third party
|
||||
ThirdParty:
|
||||
type: object
|
||||
properties:
|
||||
text:
|
||||
type: string
|
||||
ThirdPartyById:
|
||||
type: object
|
||||
properties:
|
||||
@ -35,7 +65,33 @@ components:
|
||||
type: integer
|
||||
requestorAnonymous:
|
||||
type: boolean
|
||||
|
||||
Resource:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'accompanying_period_resource'
|
||||
readOnly: true
|
||||
id:
|
||||
type: integer
|
||||
readOnly: true
|
||||
resource:
|
||||
anyOf:
|
||||
- $ref: '#/components/schemas/PersonById'
|
||||
- $ref: '#/components/schemas/ThirdPartyById'
|
||||
ResourceById:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- 'accompanying_period_resource'
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
|
||||
paths:
|
||||
/1.0/person/accompanying-course/{id}.json:
|
||||
@ -213,3 +269,79 @@ paths:
|
||||
description: "OK"
|
||||
422:
|
||||
description: "object with validation errors"
|
||||
|
||||
/1.0/person/accompanying-course/{id}/resource.json:
|
||||
post:
|
||||
tags:
|
||||
- person
|
||||
summary: "Add a resource to the accompanying course"
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The accompanying period's id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
requestBody:
|
||||
description: "A resource"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Resource'
|
||||
examples:
|
||||
add person with id 50:
|
||||
summary: "a person with id 50"
|
||||
value:
|
||||
type: accompanying_period_resource
|
||||
resource:
|
||||
type: person
|
||||
id: 50
|
||||
add thirdparty with id 100:
|
||||
summary: "a third party with id 100"
|
||||
value:
|
||||
type: accompanying_period_resource
|
||||
resource:
|
||||
type: thirdparty
|
||||
id: 100
|
||||
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
404:
|
||||
description: "Not found"
|
||||
200:
|
||||
description: "OK"
|
||||
422:
|
||||
description: "object with validation errors"
|
||||
delete:
|
||||
tags:
|
||||
- person
|
||||
summary: "Remove the resource"
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The accompanying period's id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
requestBody:
|
||||
description: "A resource"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ResourceById'
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
404:
|
||||
description: "Not found"
|
||||
200:
|
||||
description: "OK"
|
||||
422:
|
||||
description: "object with validation errors"
|
||||
|
@ -1,5 +1,11 @@
|
||||
services:
|
||||
|
||||
# temporary, waiting for https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/34
|
||||
Chill\PersonBundle\Repository\AccompanyingPeriod\:
|
||||
resource: './../../Repository/AccompanyingPeriod'
|
||||
autowire: true
|
||||
tags: [ doctrine.repository_service ]
|
||||
|
||||
chill.person.repository.person:
|
||||
class: Chill\PersonBundle\Repository\PersonRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
|
Loading…
x
Reference in New Issue
Block a user