Reputation: 416
I have an entity type called a Submission
. A Submission
has an OneToOne relationship to a SurveyData
entity type.
The SurveyData entity is actually a mapped superclass. It will eventually have several dozen subclasses for Entities that store the data from different surveys.
As per the documentation, I created a custom Normalizer that handles denormalization based on a type
key:
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);
}
With this in place, I can create a new Submission with embedded SurveyData perfectly. Here's an example of the JSON I sent to the POST request:
{
"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"
}
When I fetch the collection, or a single Submission entity via GET, however, the response returned by API Platform neglects to add the @id
property to the embedded survey response. I'm not sure if this is because it's an OneToOne that can't be blank, so it's internally tracked:
{
"@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
}
}
The real problem is that PUT
and PATCH
requests fail.
For a PATCH
request, I can update fields in the parent Submission entity. However, if I send the below request, the Submission and SurveyData entities get removed from the database, and I get the following error from the 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",
Gist with the entire response including a trace: https://gist.github.com/brianV/c32661186c91b49b013017dde77d5d4a
Here's an example of a PATCH
request that triggers the error:
{
"user": "brian",
"surveyData": {
"type": "MonthlyTotals",
"num_deliveries": 100
}
}
This happens with every PUT request as well (in which I include the entire replacement Submission entity).
In plain Symfony & Doctrine, this solution would work great, but it appears to break API Platform.
As per a comment request, here is the Submission
entity annotations:
/**
* @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;
Thanks in advance for any assistance!
Upvotes: 3
Views: 4128
Reputation: 1281
Make sure that your SurveyData
property is normalized with the api_platform.jsonld.normalizer.item
service when you're working with Submission
URLs.
I assume you have followed those steps to embedd your object ? Well, as described here, since you do not provide an @id
property within your embedded object, Api-Platform considers that you're pushing a new object instead of editing the current one, and that's why doctrine cries (your error message): this new object is not registered.
The easiest way to register automatically this new object is by adding a cascade={"persist"}
annotation property onto your Submission::$surveyData
property:
class Submission
{
/**
* @OneToOne(targetEntity="App\Entity\SurveyData", cascade={"persist"})
*/
private $surveyData,
}
But you may also use the EntityManagerInterface::persist()
method.
Note: I'm not sure that PATCH
methods are fully compatibles with embedded object, I remember some issues on github about that.
Upvotes: 1