Php API平台资源的多个关键标识符

Php API平台资源的多个关键标识符,php,rest,symfony,doctrine-orm,api-platform.com,Php,Rest,Symfony,Doctrine Orm,Api Platform.com,我有一个图表对象,它包含一组序列对象,这些序列对象包含一组数据对象。我不需要RESTAPI通过代理主键来识别序列和数据对象,而是希望根据它们在各自集合中的位置来识别它们 数据库模式如下所示。最初我考虑制作serie的图表id/位置和data的图表id/序列id/位置组合主键,但是,条令只能为(即serie)这样做,为了保持一致,我对所有表使用代理键 chart - id (PK autoincrement) - name serie - id (PK autoincrement) - posi

我有一个图表对象,它包含一组序列对象,这些序列对象包含一组数据对象。我不需要RESTAPI通过代理主键来识别序列和数据对象,而是希望根据它们在各自集合中的位置来识别它们

数据库模式如下所示。最初我考虑制作serie的
图表id/位置
和data的
图表id/序列id/位置
组合主键,但是,条令只能为(即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;序列位置=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
将序列和数据的主键
$id
标识符设置为false,将序列的
$chart
$position
以及数据的
$Serie
$position
设置为true

Entity/Chart.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;
    }
}

遗憾的是,我发现api平台对于设计/实现好的多级api(以及通常不符合ORM中存储的实体的api资源)来说太幼稚,而且非常有限,并且很快就会导致无法读取/无法维护的代码


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

(.)感谢kaznovac,我相信api平台在典型用途中做得很好,但在一些边缘案例中也遇到了挑战。我很确定我可以用它做我想做的事情,但是我希望我会做的和我现在的尝试不同。不幸的是,我找不到全面的文档,而只能找到经常相互冲突的零碎提示。
<?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

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;
    }
}