-
-
Notifications
You must be signed in to change notification settings - Fork 933
Description
API Platform version(s) affected: 3.2.5
Description
When setting #[ApiProperty(readableLink: true, writableLink: false)] to a property, writableLink is not respected in the openapi spec generation under certain circumstances:
- The entity serialization groups are organized by the same names. For example operation based names, so that multiple entities have e.g. serialization group "post" for "POST" operations.
- A parent entity uses writableLink=true for a child entity
- The child entity also uses writableLink=true for its child entity
- The childs schema is generated after the parent entity schema.
If this is the case writableLink=false will not be generated properly in the first childrecord for its childrecords properties configured with writableLink=false.
How to reproduce
Following classes and configurations are needed:
Company.php:
#[ApiResource(
operations: [
new Post(denormalizationContext: ['groups' => ['post']]),
]
)]
#[ORM\Entity(repositoryClass: CompanyRepository::class)]
class Company
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true, nullable: false)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
private ?Uuid $id = null;
#[Groups(['post'])]
#[ApiProperty(readableLink: true, writableLink: true)]
#[ORM\OneToMany(mappedBy: 'company', targetEntity: User::class, cascade: ['persist'], orphanRemoval: true)]
#[ORM\JoinColumn(nullable: false)]
private iterable $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
public function getId(): Uuid
{
return $this->id;
}
public function getUsers(): iterable
{
return $this->users;
}
public function setUsers(iterable $users): self
{
$this->users = $users;
return $this;
}
public function addUser(User $user): self
{
if (!$this->users->contains($user)) {
$this->users->add($user);
$user->setCompany($this);
}
return $this;
}
public function removeUser(User $user): self
{
$this->users->removeElement($user);
return $this;
}
public function getUsers(): iterable
{
return $this->users;
}
public function addUser(User $user): self
{
if (!$this->users->contains($user)) {
$this->users->add($user);
$user->setCompany($this);
}
return $this;
}
public function removeUser(User $user): self
{
$this->users->removeElement($user);
return $this;
}
}
User.php
#[ApiResource(
operations: [
new Post(denormalizationContext: ['groups' => ['post']]),
]
)]
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
private ?Uuid $id = null;
#[ORM\ManyToOne(targetEntity: Company::class, inversedBy: 'users')]
private ?Company $company = null;
#[Groups(['post'])]
#[ORM\OneToOne(mappedBy: 'owner', targetEntity: UserSettings::class)]
#[ApiProperty(readableLink: true, writableLink: false)]
private ?UserSettings $userSettings = null;
public function getId(): Uuid
{
return $this->id;
}
public function getUserSettings(): UserSettings
{
return $this->userSettings;
}
public function setUserSettings(UserSettings $userSettings): self
{
$this->userSettings = $userSettings;
return $this;
}
}
UserSettings.php
#[ApiResource(
operations: [
new Post(denormalizationContext: ['groups' => ['post']]),
]
)]
#[ORM\Entity(repositoryClass: UserSettingsRepository::class)]
class UserSettings
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
private ?Uuid $id = null;
#[Groups(['post'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $settingOne = null;
#[Groups(['post'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $settingTwo = null;
#[ORM\OneToOne(inversedBy: 'userSettings')]
#[ORM\JoinColumn(nullable: false)]
private User $owner;
public function getId(): Uuid
{
return $this->id;
}
public function getOwner(): ?User
{
return $this->owner;
}
public function setOwner(User $owner): self
{
$this->owner = $owner;
return $this;
}
public function getSettingOne(): ?string
{
return $this->settingOne;
}
public function setSettingOne(?string $settingOne): self
{
$this->settingOne = $settingOne;
return $this;
}
public function getSettingTwo(): ?string
{
return $this->settingTwo;
}
public function setSettingTwo(?string $settingTwo): self
{
$this->settingTwo = $settingTwo;
return $this;
}
}
As company uses the user-post scheme and the entity starts with a c. The user-post scheme is generated when creating the company scheme, which leads to this behaviour.
This is a usecase to me and i think also to others who use operation based serialization groups.
Possible Solution
Passing the schema type of the parent entity when building child schemes. I will provide a pull request.