Pas 26: Expunerea unui API cu API Platform

5.0 version
Maintained

Expunerea unui API cu API Platform

Am terminat implementarea site-ului de carte de oaspeți. Pentru a permite o utilizare mai largă a datelor, ce zici de expunerea unui API? O aplicație mobilă poate fi utilizată pentru a afișa toate conferințele, comentariile lor și poate permite participanților să trimită comentarii.

În acest pas, vom implementa API-ul numai în citire.

Instalarea API Platform

Expunerea unui API prin elaborarea codului necesar este posibilă, dar dacă dorim să utilizăm standarde, ar fi bine să folosim o soluție care să se ocupe deja de partea dificilă. O soluție precum API Platform:

1
$ symfony composer req api

Expunerea unui API pentru conferințe

Câteva adnotări la clasa de Conference este tot ce avem nevoie pentru a configura API-ul:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
--- a/src/Entity/Conference.php
+++ b/src/Entity/Conference.php
@@ -2,15 +2,24 @@

 namespace App\Entity;

+use ApiPlatform\Core\Annotation\ApiResource;
 use App\Repository\ConferenceRepository;
 use Doctrine\Common\Collections\ArrayCollection;
 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\String\Slugger\SluggerInterface;

 /**
  * @ORM\Entity(repositoryClass=ConferenceRepository::class)
  * @UniqueEntity("slug")
+ *
+ * @ApiResource(
+ *     collectionOperations={"get"={"normalization_context"={"groups"="conference:list"}}},
+ *     itemOperations={"get"={"normalization_context"={"groups"="conference:item"}}},
+ *     order={"year"="DESC", "city"="ASC"},
+ *     paginationEnabled=false
+ * )
  */
 class Conference
 {
@@ -18,21 +26,29 @@ class Conference
      * @ORM\Id()
      * @ORM\GeneratedValue()
      * @ORM\Column(type="integer")
+     *
+     * @Groups({"conference:list", "conference:item"})
      */
     private $id;

     /**
      * @ORM\Column(type="string", length=255)
+     *
+     * @Groups({"conference:list", "conference:item"})
      */
     private $city;

     /**
      * @ORM\Column(type="string", length=4)
+     *
+     * @Groups({"conference:list", "conference:item"})
      */
     private $year;

     /**
      * @ORM\Column(type="boolean")
+     *
+     * @Groups({"conference:list", "conference:item"})
      */
     private $isInternational;

@@ -43,6 +59,8 @@ class Conference

     /**
      * @ORM\Column(type="string", length=255, unique=true)
+     *
+     * @Groups({"conference:list", "conference:item"})
      */
     private $slug;

Principala adnotare @ApiResource configurează API-ul pentru conferințe. Limitează operațiunile posibile la get și configurează diverse lucruri: cum ar fi câmpurile de afișat și modul de a ordona conferințele.

În mod implicit, principalul punct de intrare pentru API este /api datorită configurației din config/routes/api_platform.yaml care a fost adăugată de rețeta pachetului.

O interfață web îți permite să interacționezi cu API-ul:

Folosește-o pentru a testa diversele posibilități:

Imaginează-ți timpul necesar pentru a implementa toate acestea de la zero!

Expunerea unui API pentru comentarii

Fă același lucru pentru comentarii:

patch_file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
--- a/src/Entity/Comment.php
+++ b/src/Entity/Comment.php
@@ -2,12 +2,25 @@

 namespace App\Entity;

+use ApiPlatform\Core\Annotation\ApiFilter;
+use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
 use App\Repository\CommentRepository;
 use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
 use Symfony\Component\Validator\Constraints as Assert;

 /**
  * @ORM\Entity(repositoryClass=CommentRepository::class)
  * @ORM\HasLifecycleCallbacks()
+ *
+ * @ApiResource(
+ *     collectionOperations={"get"={"normalization_context"={"groups"="comment:list"}}},
+ *     itemOperations={"get"={"normalization_context"={"groups"="comment:item"}}},
+ *     order={"createdAt"="DESC"},
+ *     paginationEnabled=false
+ * )
+ *
+ * @ApiFilter(SearchFilter::class, properties={"conference": "exact"})
  */
 class Comment
 {
@@ -15,18 +27,24 @@ class Comment
      * @ORM\Id()
      * @ORM\GeneratedValue()
      * @ORM\Column(type="integer")
+     *
+     * @Groups({"comment:list", "comment:item"})
      */
     private $id;

     /**
      * @ORM\Column(type="string", length=255)
      * @Assert\NotBlank
+     *
+     * @Groups({"comment:list", "comment:item"})
      */
     private $author;

     /**
      * @ORM\Column(type="text")
      * @Assert\NotBlank
+     *
+     * @Groups({"comment:list", "comment:item"})
      */
     private $text;

@@ -34,22 +52,30 @@ class Comment
      * @ORM\Column(type="string", length=255)
      * @Assert\NotBlank
      * @Assert\Email
+     *
+     * @Groups({"comment:list", "comment:item"})
      */
     private $email;

     /**
      * @ORM\Column(type="datetime")
+     *
+     * @Groups({"comment:list", "comment:item"})
      */
     private $createdAt;

     /**
      * @ORM\ManyToOne(targetEntity=Conference::class, inversedBy="comments")
      * @ORM\JoinColumn(nullable=false)
+     *
+     * @Groups({"comment:list", "comment:item"})
      */
     private $conference;

     /**
      * @ORM\Column(type="string", length=255, nullable=true)
+     *
+     * @Groups({"comment:list", "comment:item"})
      */
     private $photoFilename;

Același tip de adnotări sunt utilizate pentru a configura clasa.

Restrângerea comentariilor expuse de API

În mod implicit, platforma API expune toate intrările din baza de date. Dar pentru comentarii, numai cele publicate ar trebui să facă parte din API.

Atunci cănd este nevoie să restricționezi elementele returnate de API, creează un serviciu care implementează QueryCollectionExtensionInterface pentru a controla interogarea Doctrine folosită pentru colecții și/sau QueryItemExtensionInterface pentru a controla articolele:

src/Api/FilterPublishedCommentQueryExtension.php
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace App\Api;

use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use App\Entity\Comment;
use Doctrine\ORM\QueryBuilder;

class FilterPublishedCommentQueryExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
    public function applyToCollection(QueryBuilder $qb, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
    {
        if (Comment::class === $resourceClass) {
            $qb->andWhere(sprintf("%s.state = 'published'", $qb->getRootAliases()[0]));
        }
    }

    public function applyToItem(QueryBuilder $qb, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = [])
    {
        if (Comment::class === $resourceClass) {
            $qb->andWhere(sprintf("%s.state = 'published'", $qb->getRootAliases()[0]));
        }
    }
}

Clasa de extensie de interogare își aplică logica numai pentru resursa Comment și modifică constructorul de interogări Doctrine pentru a lua în considerare doar comentariile în starea published.

Configurarea CORS

În mod implicit, politica de securitate de aceeași origine a clienților HTTP moderni interzice apelarea API-ului din alte domenii. Bundle-ul CORS, instalat ca parte a composer req api, expediază anteturi de partajare a resurselor cu origine încrucișată pe baza variabilei de mediu CORS_ALLOW_ORIGIN.

În mod implicit, valoarea sa, definită în .env, permite solicitări HTTP de la localhost și 127.0.0.1 pe orice port. Exact acest lucru este necesar pentru următorul pas, deoarece vom crea un SPA care va avea propriul său server web care va apela API-ul.


  • « Previous Pas 25: Notificarea prin toate mijloacele
  • Next » Pas 27: Construirea unui SPA

This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.