Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ad23675
Embedded Collections camps/periods/days
pmattmann Jul 18, 2021
97f7408
Merge remote-tracking branch 'ecamp/devel' into cosinus/api-validation
pmattmann Jul 18, 2021
7d76aa2
Add Camp:write
pmattmann Jul 28, 2021
761acd0
selectively embed referenced entitiy
pmattmann Jul 29, 2021
e5d69f7
excample fixed
pmattmann Jul 29, 2021
a2f3007
fix post on camp
pmattmann Jul 29, 2021
90f03ee
code-review changes
pmattmann Jul 30, 2021
23c83ee
Sort named arguments
carlobeltrame Aug 4, 2021
7205e60
Make the camp name writable again
carlobeltrame Aug 4, 2021
3ff4f41
Make dayResponsibles readable again
carlobeltrame Aug 4, 2021
e7c3516
Revert readability of camp on day
carlobeltrame Aug 4, 2021
c94ed0d
Fix return type declarations to make examples in swagger work automat…
carlobeltrame Aug 4, 2021
5847e8c
getExamplePayload needs to take into account different schema for dif…
carlobeltrame Aug 5, 2021
8153a0a
Always add some defaults to the context
carlobeltrame Aug 6, 2021
f6d07a2
Add missing normalization groups so the tests pass again
carlobeltrame Aug 6, 2021
25bd154
Work around broken circular reference detection in HAL format
carlobeltrame Aug 6, 2021
eea56b9
Ignore the embedded getters when trying to read the Doctrine metadata
carlobeltrame Aug 6, 2021
c98a70a
Consistently use our group name system for all entities
carlobeltrame Aug 9, 2021
5b029cd
Temporarily replace API platform's eager loading extension
carlobeltrame Aug 9, 2021
4895495
Merge branch 'devel' into feature/embedded-entities
carlobeltrame Aug 9, 2021
4e08a3e
Const for NormalizationCtx; use SwaggerDefName
pmattmann Aug 17, 2021
c65be86
Merge remote-tracking branch 'ecamp/devel' into feature/embedded-enti…
pmattmann Aug 30, 2021
b0cb899
prevent adding same alias twice; doctrine does not like it
pmattmann Aug 30, 2021
e69b947
use self for ITEM_NORMALIZATION_CONTEXT
pmattmann Aug 30, 2021
1e74ed3
Merge remote-tracking branch 'origin/devel' into feature/embedded-ent…
carlobeltrame Sep 4, 2021
476731b
refactor: Extract method for preventing duplicate selects
carlobeltrame Sep 4, 2021
a62928f
Comment out unused code to prevent lowering the code coverage too much
carlobeltrame Sep 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/src/Entity/BaseEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Serializer\Annotation\Groups;

/**
* @ORM\MappedSuperclass
Expand All @@ -25,6 +26,7 @@ abstract class BaseEntity {
* @ORM\CustomIdGenerator(class=IdGenerator::class)
*/
#[ApiProperty(writable: false, example: '1a2b3c4d')]
#[Groups(['read'])]
protected string $id;

/**
Expand Down
62 changes: 47 additions & 15 deletions api/src/Entity/Camp.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,38 @@
'post' => [
'security' => 'is_fully_authenticated()',
'input_formats' => ['jsonld', 'jsonapi', 'json'],
'validation_groups' => ['Default', 'camp:create'],
'validation_groups' => ['Default', 'Camp:create'],
'denormalization_context' => [
'groups' => ['write', 'create', 'Camp:Periods'],
'allow_extra_attributes' => false,
],
],
],
itemOperations: [
'get' => ['security' => 'object.owner == user or is_granted("ROLE_ADMIN")'],
'get' => [
'security' => 'object.owner == user or is_granted("ROLE_ADMIN")',
'normalization_context' => [
'groups' => ['read', 'Camp:Periods', 'Period:Days'],
'allow_extra_attributes' => false,
],
],
'patch' => [
'security' => 'object.owner == user or is_granted("ROLE_ADMIN")',
'denormalization_context' => [
'groups' => ['camp:update'],
'groups' => ['write', 'update'],
'allow_extra_attributes' => false,
],
],
'delete' => ['security' => 'object.owner == user or is_granted("ROLE_ADMIN")'],
]
],
normalizationContext: [
'groups' => ['read'],
'allow_extra_attributes' => false,
],
denormalizationContext: [
'groups' => ['write'],
'allow_extra_attributes' => false,
],
)]
class Camp extends BaseEntity implements BelongsToCampInterface {
/**
Expand All @@ -56,9 +74,13 @@ class Camp extends BaseEntity implements BelongsToCampInterface {
* @ORM\OrderBy({"start": "ASC"})
*/
#[Assert\Valid]
#[Assert\Count(min: 1, groups: ['camp:create'])]
#[ApiProperty(writableLink: true, example: '[{ "description": "Hauptlager", "start": "2022-01-01", "end": "2022-01-08" }]')]
#[Groups(['Default'])]
#[Assert\Count(min: 1, groups: ['Camp:create'])]
#[ApiProperty(
readableLink: false,
writableLink: true,
example: '/periods?camp=/camps/1a2b3c4d',
)]
#[Groups(['read', 'create'])]
public Collection $periods;

/**
Expand Down Expand Up @@ -116,7 +138,7 @@ class Camp extends BaseEntity implements BelongsToCampInterface {
#[InputFilter\CleanHTML]
#[Assert\NotBlank]
#[ApiProperty(example: 'SoLa 2022')]
#[Groups(['Default', 'camp:update'])]
#[Groups(['read', 'create'])]
public string $name;

/**
Expand All @@ -128,8 +150,8 @@ class Camp extends BaseEntity implements BelongsToCampInterface {
#[InputFilter\CleanHTML]
#[Assert\NotBlank]
#[Assert\Length(max: 32)]
#[ApiProperty(example: 'Abteilungs-Sommerlager 2022')]
#[Groups(['Default', 'camp:update'])]
#[ApiProperty(writable: true, example: 'Abteilungs-Sommerlager 2022')]
#[Groups(['read', 'write'])]
public string $title;

/**
Expand All @@ -141,7 +163,7 @@ class Camp extends BaseEntity implements BelongsToCampInterface {
#[InputFilter\CleanHTML]
#[Assert\Length(max: 128)]
#[ApiProperty(example: 'Piraten')]
#[Groups(['Default', 'camp:update'])]
#[Groups(['read', 'write'])]
public ?string $motto = null;

/**
Expand All @@ -153,7 +175,7 @@ class Camp extends BaseEntity implements BelongsToCampInterface {
#[InputFilter\CleanHTML]
#[Assert\Length(max: 128)]
#[ApiProperty(example: 'Wiese hinter der alten Mühle')]
#[Groups(['Default', 'camp:update'])]
#[Groups(['read', 'write'])]
public ?string $addressName = null;

/**
Expand All @@ -165,7 +187,7 @@ class Camp extends BaseEntity implements BelongsToCampInterface {
#[InputFilter\CleanHTML]
#[Assert\Length(max: 128)]
#[ApiProperty(example: 'Schönriedweg 23')]
#[Groups(['Default', 'camp:update'])]
#[Groups(['read', 'write'])]
public ?string $addressStreet = null;

/**
Expand All @@ -177,7 +199,7 @@ class Camp extends BaseEntity implements BelongsToCampInterface {
#[InputFilter\CleanHTML]
#[Assert\Length(max: 128)]
#[ApiProperty(example: '1234')]
#[Groups(['Default', 'camp:update'])]
#[Groups(['read', 'write'])]
public ?string $addressZipcode = null;

/**
Expand All @@ -189,7 +211,7 @@ class Camp extends BaseEntity implements BelongsToCampInterface {
#[InputFilter\CleanHTML]
#[Assert\Length(max: 128)]
#[ApiProperty(example: 'Hintertüpfingen')]
#[Groups(['Default', 'camp:update'])]
#[Groups(['read', 'write'])]
public ?string $addressCity = null;

/**
Expand Down Expand Up @@ -222,6 +244,16 @@ public function __construct() {
$this->materialLists = new ArrayCollection();
}

#[ApiProperty(
readableLink: true,
example: '[{ "description": "Hauptlager", "start": "2022-01-01", "end": "2022-01-08" }]'
)]
#[SerializedName('periods')]
#[Groups(['Camp:Periods'])]
public function getEmbeddedPeriods(): Collection {
return $this->periods;
}

#[ApiProperty(readable: false)]
public function getCamp(): ?Camp {
return $this;
Expand Down
17 changes: 15 additions & 2 deletions api/src/Entity/Day.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;

/**
Expand All @@ -26,6 +27,14 @@
#[ApiResource(
collectionOperations: ['get'],
itemOperations: ['get'],
normalizationContext: [
'groups' => ['read'],
'allow_extra_attributes' => false,
],
denormalizationContext: [
'groups' => ['write'],
'allow_extra_attributes' => false,
],
)]
#[ApiFilter(SearchFilter::class, properties: ['period'])]
#[UniqueEntity(fields: ['period', 'dayOffset'])]
Expand All @@ -44,7 +53,8 @@ class Day extends BaseEntity implements BelongsToCampInterface {
* @ORM\ManyToOne(targetEntity="Period", inversedBy="days")
* @ORM\JoinColumn(nullable=false, onDelete="cascade")
*/
#[ApiProperty(writable: false, example: '/periods/1a2b3c4d')]
#[ApiProperty(readableLink: false, writable: false, example: '/periods/1a2b3c4d')]
#[Groups(['read'])]
public ?Period $period = null;

/**
Expand All @@ -53,13 +63,15 @@ class Day extends BaseEntity implements BelongsToCampInterface {
* @ORM\Column(type="integer")
*/
#[ApiProperty(writable: false, example: '1')]
#[Groups(['read'])]
public int $dayOffset = 0;

public function __construct() {
$this->dayResponsibles = new ArrayCollection();
}

#[ApiProperty(readable: false)]
#[ApiProperty(readableLink: false, writable: false, example: '/camps/1a2b3c4d')]
#[Groups(['read'])]
public function getCamp(): ?Camp {
return $this->period?->camp;
}
Expand All @@ -69,6 +81,7 @@ public function getCamp(): ?Camp {
*/
#[ApiProperty(example: '2')]
#[SerializedName('number')]
#[Groups(['read'])]
public function getDayNumber(): int {
return $this->dayOffset + 1;
}
Expand Down
62 changes: 49 additions & 13 deletions api/src/Entity/Period.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Symfony\Component\Validator\Constraints as Assert;

/**
Expand All @@ -20,15 +21,28 @@
* @ORM\Entity
*/
#[ApiResource(
collectionOperations: ['get', 'post'],
itemOperations: [
collectionOperations: [
'get',
'patch' => ['denormalization_context' => [
'groups' => ['period:update'],
'allow_extra_attributes' => false,
]],
'post',
],
itemOperations: [
'get' => [
'normalization_context' => [
'groups' => ['read', 'Period:Camp', 'Period:Days'],
'allow_extra_attributes' => false,
],
],
'patch',
'delete',
]
],
normalizationContext: [
'groups' => ['read'],
'allow_extra_attributes' => false,
],
denormalizationContext: [
'groups' => ['write'],
'allow_extra_attributes' => false,
],
)]
#[ApiFilter(SearchFilter::class, properties: ['camp'])]
class Period extends BaseEntity implements BelongsToCampInterface {
Expand All @@ -38,7 +52,12 @@ class Period extends BaseEntity implements BelongsToCampInterface {
* @ORM\OneToMany(targetEntity="Day", mappedBy="period", orphanRemoval=true)
* @ORM\OrderBy({"dayOffset": "ASC"})
*/
#[ApiProperty(writable: false, example: '["/days/1a2b3c4d"]')]
#[ApiProperty(
readableLink: false,
writable: false,
example: '/days?period=/periods/1a2b3c4d'
)]
#[Groups(['read'])]
public Collection $days;

/**
Expand Down Expand Up @@ -66,8 +85,8 @@ class Period extends BaseEntity implements BelongsToCampInterface {
* @ORM\ManyToOne(targetEntity="Camp", inversedBy="periods")
* @ORM\JoinColumn(nullable=false)
*/
#[ApiProperty(example: '/camps/1a2b3c4d')]
#[Groups(['Default'])]
#[ApiProperty(readableLink: false, example: '/camps/1a2b3c4d')]
#[Groups(['read'])]
public ?Camp $camp = null;

/**
Expand All @@ -79,7 +98,7 @@ class Period extends BaseEntity implements BelongsToCampInterface {
*/
#[Assert\NotBlank]
#[ApiProperty(example: 'Hauptlager')]
#[Groups(['Default', 'period:update'])]
#[Groups(['Properties:read', 'Properties:write'])]
public ?string $description = null;

/**
Expand All @@ -95,7 +114,7 @@ class Period extends BaseEntity implements BelongsToCampInterface {
*/
#[Assert\LessThanOrEqual(propertyPath: 'end')]
#[ApiProperty(example: '2022-01-01', openapiContext: ['format' => 'date'])]
#[Groups(['Default', 'period:update'])]
#[Groups(['Properties:read', 'Properties:write'])]
public ?DateTimeInterface $start = null;

/**
Expand All @@ -106,7 +125,7 @@ class Period extends BaseEntity implements BelongsToCampInterface {
*/
#[Assert\GreaterThanOrEqual(propertyPath: 'start')]
#[ApiProperty(example: '2022-01-08', openapiContext: ['format' => 'date'])]
#[Groups(['Default', 'period:update'])]
#[Groups(['Properties:read', 'Properties:write'])]
public ?DateTimeInterface $end = null;

public function __construct() {
Expand All @@ -115,6 +134,23 @@ public function __construct() {
$this->materialItems = new ArrayCollection();
}

#[ApiProperty(
readableLink: true,
example: '[{ "dayOffset": 0, "number": 1 }]'
)]
#[SerializedName('days')]
#[Groups('Period:Days')]
public function getEmbeddedDays(): Collection {
return $this->days;
}

#[ApiProperty(readableLink: true)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bei getEmbeddedDays hast du ein Example drin, hier nicht. Müsste man das ergänzen oder fällt er zurück auf das Example der Property?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bei den getEmbedded* Methoden brauchts kein Example, das wird sowieso nicht in Swagger angezeigt. Stattdessen werden, wenn man den Return Type korrekt annotiert, die eingebetteten Felder perfekt automatisch angezeigt, inklusive den korrekten Normalisierungs-Gruppen 🥳

    /**
     * @return Day[]
     */
    #[ApiProperty(readableLink: true)]
    #[SerializedName('days')]
    #[Groups('Period:Days')]
    public function getEmbeddedDays(): array {
        return $this->days->getValues();
    }

    #[ApiProperty(readableLink: true)]
    #[SerializedName('camp')]
    #[Groups(['Period:Camp'])]
    public function getEmbeddedCamp(): ?Camp {
        return $this->camp;
    }

führt zu folgendem Output in Swagger:
Screenshot from 2021-08-04 10-13-22

#[SerializedName('camp')]
#[Groups(['Period:Camp'])]
public function getEmbeddedCamp(): ?Camp {
return $this->camp;
}

public function getCamp(): ?Camp {
return $this->camp;
}
Expand Down