当前位置: 首页 > 知识库问答 >
问题:

API平台资源的多个密钥标识符

慕容成文
2023-03-14

我有一个包含 Serie 对象的集合的 Chart 对象,这些 Serie 对象包含数据对象的集合。我希望根据它们在各自集合中的位置来标识 REST API 来识别 Serie 和 Data 对象,而不是要求 REST API 通过代理主键来识别 Serie 和 Data 对象。

数据库模式如下。最初我考虑使serie的chart_id/位置和data的chart_id/serie_id/位置复合主键,但是,Doctrine只能对一个级别(即Serie)这样做,并且对所有表使用代理键保持一致。

chart
- id (PK autoincrement)
- name

serie
- id (PK autoincrement)
- position (int with unique constraint with chart_id)
- name
- chart_id (FK)

data
- id (PK autoincrement)
- position (int with unique constraint with serie_id)
- name
- serie_id (FK)
- value

/charts/1 的完全水合响应将返回如下所示的 JSON。例如,要找到名称为 Series1Data1 的数据对象,url 将是 /charts/1/series/0/datas/1,或者如有必要,/datas/chart=1;seriePosition=0;dataPosition=1 也可以工作。

{
    "id": 1,
    "name": "chart1"
    "series": [{
            "chart": "/chart/1",
            "position": 0,
            "name": "series0.chart1",
            "datas": [{
                    "serie": "/charts/1/series/0",
                    "position": 0,
                    "name": "datas0.series0.chart1"
                }, {
                    "serie": "/series/chart=1;position=0",
                    "position": 1,
                    "name": "datas1.series0.chart1"
                }
            ]
        }, {
            "chart": "/chart/1",
            "position": 1,
            "name": "series1.chart1",
            "datas": []
        }
    ]
}

为了更改标识符,我使用@ApiProperty将Serie和Data的主键$id标识符设为false,并将Serie的 $position$Serie$position

实体/图表.php

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
//use ApiPlatform\Core\Annotation\ApiProperty;
//use ApiPlatform\Core\Annotation\ApiSubresource;
//use Symfony\Component\Serializer\Annotation\Groups;
//use Symfony\Component\Serializer\Annotation\SerializedName;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

/**
 * @ORM\Entity()
 * @ApiResource()
 */
class Chart
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\OneToMany(targetEntity=Serie::class, mappedBy="chart")
     */
    private $series;

    /**
     * @ORM\Column(type="string", length=45)
     */
    private $name;
    
    public function __construct()
    {
        $this->series = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getSeries(): Collection
    {
        return $this->series;
    }

    public function addSeries(Serie $series): self
    {
        exit(sprintf('Chart::addSeries() $this->series->contains($series): %s', $this->series->contains($series)?'true':'false'));
        if (!$this->series->contains($series)) {
            $this->series[] = $series;
            $series->setChart($this);
        }

        return $this;
    }

    public function removeSeries(Serie $series): self
    {
        if ($this->series->removeElement($series)) {
            if ($series->getChart() === $this) {
                $series->setChart(null);
            }
        }

        return $this;
    }

    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

     public function getName()
    {
        return $this->name;
    }
}

实体/Serie.php

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
//use ApiPlatform\Core\Annotation\ApiSubresource;
//use Symfony\Component\Serializer\Annotation\Groups;
//use Symfony\Component\Serializer\Annotation\SerializedName;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

/**
 * @ORM\Entity()
 * @ORM\Table(uniqueConstraints={@ORM\UniqueConstraint(name="unique_position_serie", columns={"chart_id", "position"}), @ORM\UniqueConstraint(name="unique_name_serie", columns={"chart_id", "name"})})
 * @ApiResource(
 *   collectionOperations={
 *     "get1" = {  
 *       "method" = "get",
 *     },
 *     "get2" = {  
 *       "method" = "get",
 *       "path" = "/charts/{id}/series",
 *       "requirements" = {
 *         "id" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "id",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Chart ID",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *         }
 *       }
 *     },
 *     "post1" = {  
 *       "method" = "post",
 *     },
 *     "post2" = {  
 *       "method" = "post",
 *       "path" = "/charts/{id}/series",
 *       "requirements" = {
 *         "id" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "id",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Chart ID",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *         }
 *       }
 *     }
 *   },
 *   itemOperations={
 *     "get1" = {  
 *       "method" = "get",
 *     },
 *     "get2" = {  
 *       "method" = "get",
 *       "path" = "/charts/{id}/series/{position}",
 *       "requirements" = {
 *         "id" = "\d+",
 *         "position" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Series position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *         }
 *       }
 *     },
 *     "put1" = {  
 *       "method" = "put",
 *     },
 *     "put2" = {  
 *       "method" = "put",
 *       "path" = "/charts/{id}/series/{position}",
 *       "requirements" = {
 *         "id" = "\d+",
 *         "position" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Series position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *         }
 *       }
 *     },
 *     "patch1" = {  
 *       "method" = "patch",
 *     },
 *     "patch2" = {  
 *       "method" = "patch",
 *       "path" = "/charts/{id}/series/{position}",
 *       "requirements" = {
 *         "id" = "\d+",
 *         "position" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Series position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *         }
 *       }
 *     },
 *     "delete1" = {  
 *       "method" = "delete",
 *     },
 *     "delete2" = {  
 *       "method" = "delete",
 *       "path" = "/charts/{id}/series/{position}",
 *       "requirements" = {
 *         "id" = "\d+",
 *         "position" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Series position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *         }
 *       }
 *     }
 *   }
 * )
 */

class Serie
{
    /**
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="IDENTITY")
    * @ORM\Column(type="integer")
    * @ApiProperty(identifier=false)
    */
    private $id;

    /**
    * @ORM\ManyToOne(targetEntity=Chart::class, inversedBy="series")
    * @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
    * @ApiProperty(identifier=true)
    * ApiProperty(push=true)
    */
    private $chart;

    /**
    * @ORM\Column(type="integer")
    * @ApiProperty(identifier=true)
    * ApiProperty(push=true)
    */
    private $position;

    /**
    * @ORM\OneToMany(targetEntity=Data::class, mappedBy="serie")
    */
    private $data;

    /**
    * @ORM\Column(type="string", length=45)
    */
    private $name;

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

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getChart(): ?Chart
    {
        return $this->chart;
    }

    public function setChart(?Chart $chart): self
    {
        $this->chart = $chart;

        return $this;
    }

    public function getData(): Collection
    {
        return $this->data;
    }

    public function addData(Data $data): self
    {
        if (!$this->data->contains($data)) {
            $this->data[] = $data;
            $data->setSerie($this);
        }

        return $this;
    }

    public function removeData(Data $data): self
    {
        if ($this->data->removeElement($data)) {
            if ($data->getSerie() === $this) {
                $data->setSerie(null);
            }
        }

        return $this;
    }

    public function setPosition(int $position)
    {
        $this->position = $position;

        return $this;
    }

    public function getPosition()
    {
        return $this->position;
    }

    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    public function getName()
    {
        return $this->name;
    }
}

实体/数据.php

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
//use ApiPlatform\Core\Annotation\ApiSubresource;
//use Symfony\Component\Serializer\Annotation\Groups;
//use Symfony\Component\Serializer\Annotation\SerializedName;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 * @ORM\Table(uniqueConstraints={@ORM\UniqueConstraint(name="unique_name_data", columns={"serie_id", "name"}), @ORM\UniqueConstraint(name="unique_position_data", columns={"serie_id", "position"})})
 * @ApiResource(
 *   collectionOperations={
 *     "get1" = {  
 *       "method" = "get",
 *     },
 *     "get2" = {  
 *       "method" = "get",
 *       "path" = "/charts/{id}/series/{position}/datas",
 *       "requirements" = {
 *         "id" = "\d+",
 *         "position" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "id",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Chart ID",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           },
 *           {
 *             "name" = "position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Series position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *         }
 *       }
 *     },
 *     "post1" = {  
 *       "method" = "post",
 *     },
 *     "post2" = {  
 *       "method" = "post",
 *       "path" = "/charts/{id}/series/{position}/datas",
 *       "requirements" = {
 *         "id" = "\d+",
 *         "position" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "id",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Chart ID",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           },
 *           {
 *             "name" = "position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Series position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *         }
 *       }
 *     }
 *   },
 *   itemOperations={
 *     "get1" = {  
 *       "method" = "get",
 *     },
 *     "get2" = {  
 *       "method" = "get",
 *       "path" = "/charts/{id}/series/{series_position}/datas/{position}",
 *       "requirements" = {
 *         "id" = "\d+",
 *         "series_position" = "\d+",
 *         "position" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "series_position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Series position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           },
 *           {
 *             "name" = "position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Datas position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *           }
 *       }
 *     },
 *     "put1" = {  
 *       "method" = "put",
 *     },
 *     "put2" = {  
 *       "method" = "put",
 *       "path" = "/charts/{id}/series/{series_position}/datas/{position}",
 *       "requirements" = {
 *         "id" = "\d+",
 *         "series_position" = "\d+",
 *         "position" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "series_position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Series position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           },
 *           {
 *             "name" = "position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Datas position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *           }
 *       }
 *     },
 *     "patch1" = {  
 *       "method" = "patch",
 *     },
 *     "patch2" = {  
 *       "method" = "patch",
 *       "path" = "/charts/{id}/series/{series_position}/datas/{position}",
 *       "requirements" = {
 *         "id" = "\d+",
 *         "series_position" = "\d+",
 *         "position" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "series_position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Series position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           },
 *           {
 *             "name" = "position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Datas position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *           }
 *       }
 *     },
 *     "delete1" = {  
 *       "method" = "delete",
 *     },
 *     "delete2" = {  
 *       "method" = "delete",
 *       "path" = "/charts/{id}/series/{series_position}/datas/{position}",
 *       "requirements" = {
 *         "id" = "\d+",
 *         "series_position" = "\d+",
 *         "position" = "\d+",
 *       },
 *       "openapi_context" = {
 *         "parameters" = {
 *           {
 *             "name" = "series_position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Series position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           },
 *           {
 *             "name" = "position",
 *             "in" = "path",
 *             "required" = true,
 *             "description" = "Datas position in chart",
 *             "schema" = {
 *               "type" = "integer"
 *             }
 *           }
 *           }
 *       }
 *     }
 *   }
 * )
 */
class Data
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     * @ORM\Column(type="integer")
     * @ApiProperty(identifier=false)
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity=Serie::class, inversedBy="data")
     * @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
     * @ApiProperty(identifier=true)
     * ApiProperty(push=true)
     */
    private $serie;

    /**
     * @ORM\Column(type="integer")
     * @ApiProperty(identifier=true)
     * ApiProperty(push=true)
     */
    private $position;

    /**
     * @ORM\Column(type="string", length=45)
     */
    private $name;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getSerie(): ?Serie
    {
        return $this->serie;
    }

    public function setSerie(?Serie $serie): self
    {
        $this->serie = $serie;

        return $this;
    }

    public function setPosition(int $position)
    {
        $this->position = $position;

        return $this;
    }

    public function getPosition()
    {
        return $this->position;
    }

    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    public function getName()
    {
        return $this->name;
    }
}

API-Platform能够为图表和系列项目生成IRI,但不能为数据项生成。我怀疑这是因为Data的< code>$serie identifier属性需要chartId和它的位置,但不知道如何解决它。

我查看了子资源,然而,它们只支持GET请求,子资源将被弃用,转而支持多个Api资源(然而,我甚至不知道“多个Api资源”是什么意思)。此外,可能与装饰IriConver有关,但不确定。

我需要做什么才能让数据资源通过其在系列集合中的位置来识别,并让Swagger文档反映出这一点?

编辑-其他内容

我改变了实体,试图实现两种不同的方法,但不幸的是,这两种方法都不完全有效。我应该把精力集中在这两种方法中的一种吗?

>

  • 父级的标识符在查询中的位置(即/datas/chart=1;serie_position=0;data_position=0.

    其中父项的标识符在路径中(即< code >/charts/1/series/0/datas/0 )

    如果我想使用占位符<code>chart_id</code>而不是<code>id</ccode>(即<code>/charts/{chart_id}/series/{series_position}/datas/{position},我如何删除或重命名图表的<code>id</code>?

    我应该用不同的方法来做这件事,这样我就不需要所有的注释,也许可以重命名图表的id?在某种程度上,我可以让Swagger提供字段,但不认为我做得对。也许通过装饰api_platform.openapi.factory?

  • 共有2个答案

    张昊穹
    2023-03-14

    Api平台可以用不同的方式处理这种情况。

    一种是创建自己的状态提供程序,如 v3 文档中所述

    数据类中使用:

    #[ApiResource(
       operations: [
          new Get(
             uriTemplate: 'charts/{id}/series/{series_id}/data/{position}'),
             provider: YourCustomeStateProvider::class
          ...
       ]
    )]
    

    如果要按chart_id重命名 id 占位符,您可以:

      < li >将$id变量重命名为$chartId,并在其上放置< code > #[API property(identifier:true)]属性 < li >或使用< code > #[serialized name(" chart _ id ")]属性$id

    然后,在您的状态提供程序中,您通过< code > $ uri variables[' chart _ id ']、< code > $ uri variables[' series _ id ']和< code > $ uri variables[' position ']检索您的变量,并将它们用于原则(repo/query builder/等)。)来检索所需的数据。

    注意:分页和过滤器也需要一些自定义(即,请参阅自定义状态提供程序的分页)

    对于写入endpoint,您必须创建自己的状态处理器。

    您也可以使用v3中描述的子资源。但是请记住,它不会凭空出现:更新子资源是一个非常主观的话题,在这种情况下,Api平台默认的提供者/处理器不会总是满足您的需求。这就是为什么文档建议定制提供者/处理器。

    包翔
    2023-03-14

    遗憾的是,我发现api-platform对于设计/实现好的多级api(以及通常与orm中存储的实体不一致的api资源)来说太过天真,非常有限,并且很快导致代码不可读/不可维护。

    对于类似的用例,我求助于具有三个必需参数的单个apiendpoint(更不用说一个半月后api平台从项目中删除了)。

     类似资料:
    • 我曾经处理过存储在Azure Key Vault中的秘密,但这是第一次使用证书进行身份验证,而不是使用秘密。 我有一个存在Azure密钥库里的证书。我想使用此证书与Azure AD应用程序进行身份验证。 我知道,对于存储在Azure密钥库中的秘密,我们可以使用@microsoft.keyvault(secreturi='secret identifier')通过应用程序设置在Azure函数中引用它

    • 根据文档,默认情况下,api平台将加载相关资源。 但是在默认配置中,我对具有关系的资源的所有查询(主要是典型的关系)都用对象IRI而不是序列化对象填充这些属性。 例如,对于该实体: 然后我们就有了相应的: 我已经在关系上设置了,即使理论上不需要。我的配置是默认的,但是以防万一我真的在我的中写了这个: 的结果确认应用了正确的配置: 然而结果会是这样的: “boostLead”、“channel”和“

    • 如果通过传递profileId或profileName(两者都是唯一的),可以对配置文件资源执行PUT和DELETE操作,那么形成URL的正确方法是什么? 我想在发送时支持对配置文件资源的更新和删除操作,并在发送时支持对配置文件资源的更新和删除操作。 发送profileId时,用于执行PUT操作的URL如下所示: 发送profileName时,URL是什么样子的?profileName是否应作为查

    • 我需要创建一个GETendpoint来返回通过超文本传输协议客户端从另一个应用程序获取的资源,而不是基于实体。我获取的资源是一个数组: 然后我需要查询数据库以获取一些数据以添加到资源数组中。 所以我在中创建了它: 但是现在,我希望我的api返回json api响应格式:https://jsonapi.org/. 基于实体的资源,api平台完全支持。我不需要做太多。我只是在实体类中添加“key”并配

    • 我有一个spring boot应用程序。我正在尝试读取一些我放在main/resources文件夹下的文件。我看到spring boot自动读取resources文件夹下的application.properties。但是,它似乎无法读取我放在resources文件夹下的其他xml/文本文件。 我是否应该为程序添加任何附加配置,以自动读取资源文件夹下的所有文件,而不必显式提供路径?

    • 我刚刚开始学习cloudinary和node JS,我不确定为什么我无法覆盖api resources_by_标签返回的10个图像的默认限制。因为在node JS的文档中没有关于如何调用可选参数的示例(我至少可以找到),所以我很可能在API语法中有错误: