Piattaforma API: normalizzazione e denormalizzazione di entità Doctrine con mapping di ereditarietà
Ho un tipo di entità chiamato a Submission
. A Submission
ha una relazione OneToOne con un SurveyData
tipo di entità.
L'entità SurveyData è in realtà una superclasse mappata. Alla fine avrà diverse dozzine di sottoclassi per entità che memorizzano i dati di diversi sondaggi.
Come da documentazione, ho creato un normalizzatore personalizzato che gestisce la denormalizzazione in base a una type
chiave:
public function denormalize($data, string $type, string $format = null, array $context = [])
{
if ($type === 'App\Entity\SurveyData\SurveyData') { $class = 'App\Entity\SurveyData\\' . $data['type']; $context['resource_class'] = $class; } $context[self::ALREADY_CALLED] = true;
return $this->denormalizer->denormalize($data, 'App\Entity\SurveyData\\' . $data['type'], $format, $context);
}
Con questo in atto, posso creare un nuovo invio con SurveyData incorporato perfettamente. Ecco un esempio del JSON che ho inviato alla richiesta POST:
{
"facility": "/api/facilities/1",
"survey": "/api/surveys/monthly_totals",
"dateDetail": "Q1 2020",
"surveyData": {
"type": "MonthlyTotals",
"num_deliveries": 50,
"num_cesarean": 30,
"num_epidural_anesthesia": 15
},
"created": "2020-08-14T18:59:49.218Z",
"updated": "2020-08-14T18:59:49.218Z",
"user": "brian",
"status": "complete"
}
Tuttavia, quando recupero la raccolta o una singola entità Submission tramite GET, la risposta restituita da API Platform trascura di aggiungere la @id
proprietà alla risposta del sondaggio incorporata. Non sono sicuro che sia perché è un OneToOne che non può essere vuoto, quindi è tracciato internamente:
{
"@id": "/api/submissions/2",
"@type": "Submission",
"id": 2,
"facility": "/api/facilities/1",
"survey": "/api/surveys/monthly_totals",
"dateDetail": "Q1 2020",
"created": "2020-08-14T18:59:49+00:00",
"updated": "2020-08-14T18:59:49+00:00",
"user": "brian",
"status": "complete",
"surveyData": {
"num_deliveries": 50,
"num_cesarean": 30,
"num_epidural_anesthesia": 15
}
}
Il vero problema è quello PUT
e le PATCH
richieste falliscono.
Per una PATCH
richiesta, posso aggiornare i campi nell'entità di invio principale. Tuttavia, se invio la richiesta seguente, le entità Submission e SurveyData vengono rimosse dal database e ottengo il seguente errore dall'API:
"Entity App\\Entity\\Submission@000000002116ebc30000000012ca4827 is not managed. An entity is managed if its fetched from the database or registered as new through EntityManager#persist",
Sintesi con l'intera risposta inclusa una traccia: https://gist.github.com/brianV/c32661186c91b49b013017dde77d5d4a
Ecco un esempio di una PATCH
richiesta che attiva l'errore:
{
"user": "brian",
"surveyData": {
"type": "MonthlyTotals",
"num_deliveries": 100
}
}
Ciò accade anche con ogni richiesta PUT (in cui includo l'intera entità Submission sostitutiva).
In semplice Symfony e Doctrine, questa soluzione funzionerebbe alla grande, ma sembra rompere la piattaforma API.
Come per una richiesta di commento, ecco le Submission
annotazioni dell'entità:
/**
* @ApiResource(
* normalizationContext={"groups"={"submission"}},
* denormalizationContext={"groups"={"submission"}},
* itemOperations={
* "get"={
* "method"="GET",
* "access_control"="is_granted('view', object)",
* },
* "put", "patch", "delete",
* },
* )
* @ORM\Entity(repositoryClass="App\Repository\SubmissionRepository")
* @CustomAssert\SubmissionDataIsValid
*/
class Submission
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups({"submission"})
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Facility")
* @ORM\JoinColumn(nullable=false)
* @Groups({"submission"})
*/
private $facility; /** * @ORM\ManyToOne(targetEntity="App\Entity\Patient", inversedBy="submissions") * @Groups({"submission"}) */ private $patient;
/**
* @ORM\Column(type="string", length=255)
* @Groups({"submission"})
*/
private $survey; /** * @ORM\Column(type="string", length=255, nullable=true) * @Groups({"submission"}) */ private $dateDetail;
/**
* @ORM\Column(type="datetime")
* @Assert\Type("\DateTimeInterface")
* @Groups({"submission"})
*/
private $created; /** * @ORM\Column(type="datetime") * @Assert\Type("\DateTimeInterface") * @Groups({"submission"}) */ private $updated;
/**
* @ORM\Column(type="string", length=255)
* @Groups({"submission"})
*/
private $user; /** * @ORM\Column(type="string", length=255) * @Groups({"submission"}) */ private $status;
/**
* @ORM\OneToOne(targetEntity="App\Entity\SurveyData\SurveyData", inversedBy="submission", cascade={"persist", "remove"}, orphanRemoval=true, fetch="EAGER")
* @Groups({"submission"})
*/
private $surveyData;
Grazie in anticipo per qualsiasi assistenza!
Risposte
Assicurati che la tua SurveyData
proprietà sia normalizzata con il api_platform.jsonld.normalizer.item
servizio quando lavori con gli Submission
URL.
Presumo che tu abbia seguito questi passaggi per incorporare il tuo oggetto? Bene, come descritto qui , poiché non fornisci una @id
proprietà all'interno del tuo oggetto incorporato, Api-Platform considera che stai spingendo un nuovo oggetto invece di modificare quello corrente, ed è per questo che doctrine piange (il tuo messaggio di errore): questo nuovo oggetto non è registrato.
Il modo più semplice per registrare automaticamente questo nuovo oggetto è aggiungere una cascade={"persist"}
proprietà di annotazione alla tua Submission::$surveyData
proprietà:
class Submission
{
/**
* @OneToOne(targetEntity="App\Entity\SurveyData", cascade={"persist"})
*/
private $surveyData,
}
Ma puoi anche usare il EntityManagerInterface::persist()
metodo.
Nota : non sono sicuro che i PATCH
metodi siano completamente compatibili con l'oggetto incorporato, ricordo alcuni problemi su GitHub a riguardo.