Piattaforma API: normalizzazione e denormalizzazione di entità Doctrine con mapping di ereditarietà

Aug 22 2020

Ho un tipo di entità chiamato a Submission. A Submissionha una relazione OneToOne con un SurveyDatatipo 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 typechiave:

  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 @idproprietà 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 PUTe le PATCHrichieste falliscono.

Per una PATCHrichiesta, 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 PATCHrichiesta 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 Submissionannotazioni 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

1 rugolinifr Aug 22 2020 at 07:10

Assicurati che la tua SurveyDataproprietà sia normalizzata con il api_platform.jsonld.normalizer.itemservizio quando lavori con gli SubmissionURL.

Presumo che tu abbia seguito questi passaggi per incorporare il tuo oggetto? Bene, come descritto qui , poiché non fornisci una @idproprietà 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::$surveyDataproprietà:

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 PATCHmetodi siano completamente compatibili con l'oggetto incorporato, ricordo alcuni problemi su GitHub a riguardo.