@matarld

Subresources, the easy way

Mathias Arlaud

@matarld
mtarld
les-tilleuls.coop
@matarld

First, what is a

subresource?

@matarld

REST -Principles

basically nothing.

@matarld

End of the presentation, press any key to exit...

@matarld

(that we can access from another resource)

just a resource.

@matarld
@matarld

I want to

list team players

Last minute feature
@matarld
GET /players?team=france
@matarld
  use ApiPlatform\Metadata\ApiFilter;
  use ApiPlatform\Metadata\ApiResource;
  use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;

  #[ApiResource]
+ #[ApiFilter(SearchFilter::class, properties: ['team.id' => 'exact'])]
  class Player
  {
      // ...
  }
@matarld
GET /players?team=thatsatypo
{
  "@type": "hydra:Collection",
  "hydra:member": []
}
@matarld
GET /players?team=france

the player resources

that belongs to france team

@matarld

resource

subresource

GET /teams/france/players
@matarld

A good old ❤️ story...

- API Platform and subresources -

@matarld
PATCH /teams/france/players/dupont
GET /teams/france/players/dupont
GET /teams/france/players
DELETE /teams/france/players/dupont
PUT /teams/france/players/dupont
POST /teams/france/players
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;

#[ApiResource]
class Team
{
  #[ApiSubresource]
  private Collection $players;
  
  // ...
}
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;

#[ApiResource]
class Team
{
  #[ApiSubresource]
  private Collection $players;
  
  // ...
}
@matarld
use ApiPlatform\Metadata\{ApiResource,Get,GetCollection,Link};

#[ApiResource]
#[GetCollection(
    uriTemplate: '/teams/{teamId}/players',
    uriVariables: [
        'teamId' => new Link(fromClass: Team::class, toProperty: 'team'),
    ],
)]
#[Get(
    uriTemplate: '/teams/{teamId}/players/{playerId}',
    uriVariables: [
        'teamId' => new Link(fromClass: Team::class, toProperty: 'team'),
        'playerId' => new Link(fromClass: Player::class),
    ],
)]
class Player
{
    public Team $team;
}
GET /players
GET /players/{p}
POST /players
PUT /players/{p}
PATCH /players/{p}
DELETE /players/{p}
GET /teams/{t}/players
GET /teams/{t}/players/{p}
@matarld
uriTemplate: '/teams/{teamId}/players',
uriVariables: [
    'teamId' => new Link(fromClass: Team::class, toProperty: 'team'),
],
@matarld
{
  "@type": "hydra:Collection",
  "hydra:member": [
      {"@id": "/players/dupont"},
      {"@id": "/players/fickou"}
  ]
}
@matarld
#[ApiResource]
#[GetCollection('/teams/{teamId}/players')]
#[Get('/teams/{teamId}/players/{playerId}')]
class Player
{
    public Team $team;
}
#[Get]
#[GetCollection]
#[Put]
#[Patch]
#[Post]
#[Delete]

#[GetCollection('/teams/{teamId}/players')]
#[Get('/teams/{teamId}/players/{playerId}')]
class Player
{
    public Team $team;
}
@matarld
#[GetCollection(
    uriTemplate: '/teams/{teamId}/players',
+   itemUriTemplate: '/teams/{teamId}/players/{playerId}',
)]
#[Get]
#[GetCollection]
#[Put]
#[Patch]
#[Post]
#[Delete]
#[GetCollection('/teams/{teamId}/players')]
#[Get('/teams/{teamId}/players/{playerId}')]
class Player
{
    public Team $team;
}
@matarld
{
  "@type": "hydra:Collection",
  "hydra:member": [
      {"@id": "/teams/france/players/dupont"},
      {"@id": "/teams/france/players/fickou"}
  ]
}
@matarld
resources:

  App\Entity\Player: 
    operations:
    
      ApiPlatform\Metadata\GetCollection:
        uriTemplate: /teams/{teamId}/players
        uriVariable:
          teamId: { fromClass: App\Entity\Team, toProperty: team }
        itemUriTemplate: /teams/{teamId}/players/{playerId}
      
      ApiPlatform\Metadata\Get:
        uriTemplate: /teams/{teamId}/players/{playerId}
        uriVariable:
          teamId: { fromClass: App\Entity\Team, toProperty: team }
          playerId: { fromClass: App\Entity\Player }
@matarld

I want to get

a player's sprints

Urging feature
@matarld
GET /players/{playerId}/sprints
GET /players/{playerId}/sprints/{sprintId}
@matarld
#[GetCollection(
    uriTemplate: '/players/{playerId}/sprints',
    uriVariables: [
        'playerId' => new Link(fromClass: Player::class, toProperty: 'player'),
    ],
    itemUriTemplate: '/players/{playerId}/sprints/{sprintId}',
)]
class Sprint
{
    public Player $player;
}
@matarld
#[GetCollection(
    uriTemplate: '/players/{playerId}/sprints',
    uriVariables: [
        'playerId' => new Link(fromClass: Player::class, toProperty: 'player'),
    ],
    itemUriTemplate: '/players/{playerId}/sprints/{sprintId}',
)]
#[Get(
    uriTemplate: '/players/{playerId}/sprints/{sprintId}',
    uriVariables: [
        'playerId' => new Link(fromClass: Player::class, toProperty: 'player'),
        'sprintId' => new Link(fromClass: Sprint::class),
    ],
)]
class Sprint
{
    public Player $player;
}
@matarld
#[Get(
+   status: 404,
    uriTemplate: '/players/{playerId}/sprints/{sprintId}',
-   uriVariables: [
-       'playerId' => new Link(fromClass: Player::class, toProperty: 'player'),
-       'sprintId' => new Link(fromClass: Sprint::class),
-   ],
)]
@matarld
#[Get(
    status: 404,
+   openApi: false,
    uriTemplate: '/players/{playerId}/sprints/{sprintId}',
)]
@matarld
#[NotExposed(
    uriTemplate: '/players/{playerId}/sprints/{sprintId}',
)]
@matarld

I want to list

sold seats

Very important feature
@matarld
GET /stadium/gerland/matches/fr-nz/orders/001/lines/a6b993249c88/seats
Stadiums
Matches
Orders
Order lines
Seats
@matarld
GET /matches/fr-nz/orders
1
30000
90000
GET /orders/{order}/lines
1+N
GET /lines/{line}/seats
1+N+Σɴ(M)
@matarld
GET /seats?stadium=gerland&match=fr-nz&status=sold
1
@matarld

I want to report

team injuries

Last feature
@matarld
POST /teams/italy/injuries
@matarld
#[Post(
    uriTemplate: '/teams/{teamId}/injuries',
    uriVariables: [
        'teamId' => new Link(fromClass: Team::class, toProperty: 'team'),
    ],
)]
class Injury
{
    public Team $team;
}
@matarld
{
  "player": "/players/ceccarelli",
  "type": "sprain",
  "duration": 10,
  "team": "/teams/italy"
}
POST /teams/italy/injuries
"/teams/italy"
/teams/italy
@matarld
{
  "player": "/players/ceccarelli",
  "type": "sprain",
  "duration": 10
}
POST /teams/italy/injuries
/teams/italy
@matarld
#[Post(
    uriTemplate: '/teams/{teamId}/injuries',
    uriVariables: [
        'teamId' => new Link(fromClass: Team::class, toProperty: 'team'),
    ],
)]
@matarld
#[Post(
    uriTemplate: '/teams/{teamId}/injuries',
    uriVariables: [
        'teamId' => new Link(fromClass: Team::class, toProperty: 'team'),
    ],
+   extraProperties: [
+       'parent_uri_template' => '/teams/{teamId}',
+   ],
)]
@matarld

I want to get

match details

The very last feature
@matarld
#[Get]
class Match
{
    public string $score;
    
    /** @var list<Event> */
    public array $events;
}
@matarld
{
  "@id": "/matches/fr-nz",
  "score": "27-13",
  "events": [
    {
      "@id": "/events/97b470a",
      "actor": "https://schema.org/Referee",
      "value": "https://ffr.fr/enums/events/kick_off"
    },
    {
      "@id": "/events/46a0368",
      "actor": "https://schema.org/Player",
      "value": "https://ffr.fr/enums/events/hit_ball"
    },
    {...}
  ]
}
@matarld
#[Get]
class Match
{
    public string $score;
    
    /** @var list<Event> */
+   #[ApiProperty(readableLink: false)]
    public array $events;
}
@matarld
{
  "@id": "/matches/fr-nz",
  "score": "27-13",
  "events": [
      "/events/97b470a",
      "/events/46a0368",
      "..."
  ]
}
@matarld
#[Get]
class Match
{
    public string $score;
    
    /** @var list<Event> */
-   #[ApiProperty(readableLink: false)]
+   #[ApiProperty(uriTemplate: '/matches/{id}/events')]
    public array $events;
}
@matarld
{
  "@id": "/matches/fr-nz",
  "score": "27-13",
  "events": "/matches/fr-nz/events"
}
{
  "@type": "hydra:Collection",
  "hydra:member": [
    {
      "@id": "/matches/fr-nz/events/97b470a",
      "actor": "https://schema.org/Referee",
      "value": "https://ffr.fr/enums/events/kick_off"
    },
    {
      "@id": "/matches/fr-nz/events/46a0368",
      "actor": "https://schema.org/Player",
      "value": "https://ffr.fr/enums/events/hit_ball"
    },
    {...}
  ]
}
?preload=/events
@matarld

I want to get

a team sponsor

The ultimate last feature (pinky promise!)
@matarld
GET /teams/france/sponsors/le-coq-sportif
@matarld
GET /teams/france/sponsors/le-coq-sportif
GET /sponsors/le-coq-sportif
@matarld
#[Get]
#[Get(
    uriTemplate: '/teams/{teamId}/sponsors/{sponsorId}',
    uriVariables: [
        'teamId' => new Link(fromClass: Team::class, fromProperty: 'sponsor'),
        'sponsorId' => new Link(fromClass: Sponsor::class),
    ],
)]
class Sponsor
{
}
@matarld
#[Get]
#[Get(
+   status: 301
    uriTemplate: '/teams/{teamId}/sponsors/{sponsorId}',
    uriVariables: [
        'teamId' => new Link(fromClass: Team::class, fromProperty: 'sponsor'),
        'sponsorId' => new Link(fromClass: Sponsor::class),
    ],
)]
class Sponsor
{
}
@matarld
@matarld
#[Get]
#[Get(
    status: 301
    uriTemplate: '/teams/{teamId}/sponsors/{sponsorId}',
    uriVariables: [
        'teamId' => new Link(fromClass: Team::class, fromProperty: 'sponsor'),
        'sponsorId' => new Link(fromClass: Sponsor::class),
    ],
+   extraProperties: [
+       'canonical_uri_template' => '/sponsors/{sponsorId}',
+   ],
)]
class Sponsor
{
}

Good job!

@matarld

[APIP Con] Subresources and API-P 3

By Mathias Arlaud

[APIP Con] Subresources and API-P 3

  • 1,273