使用API平台创建具有多对多关系的实体

问题描述 投票:0回答:5

最近我开始了一个基于API Platform的项目。我像设计其他 Symfony 项目一样设计了我的实体。创建/更新没有关系的正常实体效果非常好,但我有一个具有两个多对多关系的“特殊”实体,为了简单起见,我将这篇文章限制在这两个关系之一上。如果我尝试创建一个新实体,包括通过 API 调用多对多关系的新元素,则会产生 500 错误。

我有点困惑也许你可以帮助我。

缩短堆栈跟踪:

[Tue Mar 13 12:19:35 2018] PHP Fatal error:  Maximum function nesting level of '256' reached, aborting! in <ProjectPath>/vendor/symfony/debug/ErrorHandler.php on line 605
[Tue Mar 13 12:19:35 2018] PHP Stack trace:
[Tue Mar 13 12:19:35 2018] PHP   1. {main}() <ProjectPath>/public/index.php:0
[Tue Mar 13 12:19:35 2018] PHP   2. Symfony\Component\HttpKernel\Kernel->handle() <ProjectPath>/public/index.php:37
[Tue Mar 13 12:19:35 2018] PHP   3. Symfony\Component\HttpKernel\HttpKernel->handle() <ProjectPath>/vendor/symfony/http-kernel/Kernel.php:202
[Tue Mar 13 12:19:35 2018] PHP   4. Symfony\Component\HttpKernel\HttpKernel->handleRaw() <ProjectPath>/vendor/symfony/http-kernel/HttpKernel.php:68
[Tue Mar 13 12:19:35 2018] PHP   5. Symfony\Component\EventDispatcher\EventDispatcher->dispatch() <ProjectPath>/vendor/symfony/http-kernel/HttpKernel.php:127
[Tue Mar 13 12:19:35 2018] PHP   6. Symfony\Component\EventDispatcher\EventDispatcher->doDispatch() <ProjectPath>/vendor/symfony/event-dispatcher/EventDispatcher.php:44
[Tue Mar 13 12:19:35 2018] PHP   7. ApiPlatform\Core\EventListener\DeserializeListener->onKernelRequest() <ProjectPath>/vendor/symfony/event-dispatcher/EventDispatcher.php:212
[Tue Mar 13 12:19:35 2018] PHP   8. Symfony\Component\Serializer\Serializer->deserialize() <ProjectPath>/vendor/api-platform/core/src/EventListener/DeserializeListener.php:71
[Tue Mar 13 12:19:35 2018] PHP   9. Symfony\Component\Serializer\Serializer->denormalize() <ProjectPath>/vendor/symfony/serializer/Serializer.php:133
[Tue Mar 13 12:19:35 2018] PHP  10. ApiPlatform\Core\JsonLd\Serializer\ItemNormalizer->denormalize() <ProjectPath>/vendor/symfony/serializer/Serializer.php:182
[Tue Mar 13 12:19:35 2018] PHP  11. ApiPlatform\Core\Serializer\AbstractItemNormalizer->denormalize() <ProjectPath>/vendor/api-platform/core/src/JsonLd/Serializer/ItemNormalizer.php:108
[Tue Mar 13 12:19:35 2018] PHP  12. Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->denormalize() <ProjectPath>/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php:121
[Tue Mar 13 12:19:35 2018] PHP  13. ApiPlatform\Core\Serializer\AbstractItemNormalizer->setAttributeValue() <ProjectPath>/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php:205
[Tue Mar 13 12:19:35 2018] PHP  14. ApiPlatform\Core\Serializer\AbstractItemNormalizer->setValue() <ProjectPath>/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php:191
[Tue Mar 13 12:19:35 2018] PHP  15. Symfony\Component\PropertyAccess\PropertyAccessor->setValue() <ProjectPath>/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php:344
[Tue Mar 13 12:19:35 2018] PHP  16. Symfony\Component\PropertyAccess\PropertyAccessor->writeProperty() <ProjectPath>/vendor/symfony/property-access/PropertyAccessor.php:217
[Tue Mar 13 12:19:35 2018] PHP  17. Symfony\Component\PropertyAccess\PropertyAccessor->writeCollection() <ProjectPath>/vendor/symfony/property-access/PropertyAccessor.php:627
[Tue Mar 13 12:19:35 2018] PHP  18. App\Entity\Pool->addTag() <ProjectPath>/vendor/symfony/property-access/PropertyAccessor.php:679
[Tue Mar 13 12:19:35 2018] PHP  19. App\Entity\Tag->addPool() <ProjectPath>/src/Entity/Pool.php:284
[Tue Mar 13 12:19:35 2018] PHP  20. App\Entity\Pool->addTag() <ProjectPath>/src/Entity/Tag.php:85
[Tue Mar 13 12:19:35 2018] PHP  21. App\Entity\Tag->addPool() <ProjectPath>/src/Entity/Pool.php:284
[Tue Mar 13 12:19:35 2018] PHP  22. App\Entity\Pool->addTag() <ProjectPath>/src/Entity/Tag.php:85
[Tue Mar 13 12:19:35 2018] PHP  23. App\Entity\Tag->addPool() <ProjectPath>/src/Entity/Pool.php:284
[Tue Mar 13 12:19:35 2018] PHP  24. App\Entity\Pool->addTag() <ProjectPath>/src/Entity/Tag.php:85
[Tue Mar 13 12:19:35 2018] PHP  25. App\Entity\Tag->addPool() <ProjectPath>/src/Entity/Pool.php:284
[Tue Mar 13 12:19:35 2018] PHP  26. App\Entity\Pool->addTag() <ProjectPath>/src/Entity/Tag.php:85
[Tue Mar 13 12:19:35 2018] PHP  27. App\Entity\Tag->addPool() <ProjectPath>/src/Entity/Pool.php:284
[Tue Mar 13 12:19:35 2018] PHP  28. App\Entity\Pool->addTag() <ProjectPath>/src/Entity/Tag.php:85
[Tue Mar 13 12:19:35 2018] PHP  29. App\Entity\Tag->addPool() <ProjectPath>/src/Entity/Pool.php:284
[Tue Mar 13 12:19:35 2018] PHP  30. App\Entity\Pool->addTag() <ProjectPath>/src/Entity/Tag.php:85
[Tue Mar 13 12:19:35 2018] PHP  31. App\Entity\Tag->addPool() <ProjectPath>/src/Entity/Pool.php:284
[Tue Mar 13 12:19:35 2018] PHP  32. App\Entity\Pool->addTag() <ProjectPath>/src/Entity/Tag.php:85
[Tue Mar 13 12:19:35 2018] PHP  33. App\Entity\Tag->addPool() <ProjectPath>/src/Entity/Pool.php:284
[Tue Mar 13 12:19:35 2018] PHP  34. App\Entity\Pool->addTag() <ProjectPath>/src/Entity/Tag.php:85
[Tue Mar 13 12:19:35 2018] PHP  35. App\Entity\Tag->addPool() <ProjectPath>/src/Entity/Pool.php:284

截断实体池

namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Class Pool - This Entity describes a pool of tasks.
 * @package App\Entity
 * @ApiResource(attributes={
 *     "normalization_context"={"groups"={"read"}},
 *     "denormalization_context"={"groups"={"write"}}
 * })
 * @ORM\Entity
 */
class Pool
{
    /**
     * @var  ArrayCollection|Tag[] $tags
     * @param ArrayCollection|Tag[] $tags all tags that are that are related with this pool
     * @ApiProperty(
     *     attributes={
     *         "swagger_context"={
     *             "$ref"="#/definitions/Tag",
     *         }
     *     }
     * )
     * @ORM\ManyToMany(targetEntity="Tag", inversedBy="pools", cascade={"persist"})
     * @Groups({"read", "write"})
     */
    private $tags;

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

    /**
     * @return ArrayCollection
     */
    public function getTags()
    {
        return $this->tags;
    }

    /**
     * @param ArrayCollection $tags
     */
    public function setTags(ArrayCollection $tags)
    {
        $this->tags = $tags;
    }

    /**
     * @param Tag $tag
     */
    public function addTag(Tag $tag):void
    {
        $tag->addPool($this);
        $this->tags->add($tag);
    }

    /**
     * @param Tag $tag
     */
    public function removeTag(Tag $tag):void
    {
        $tag->removePool($this);
        $this->tags->removeElement($tag);
    }
}

截断的标签实体

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Class Tag
 * @package App\Entity
 * @ApiResource(attributes={
 *     "normalization_context"={"groups"={"tag_read"}},
 *     "denormalization_context"={"groups"={"write"}}
 * })
 * @ORM\Entity
 */
class Tag
{
    /**
     * @var ArrayCollection[Pool]
     * @param ArrayCollection[Pool] $tasks all pool that are related with this tag
     * @Groups({"tag_read", "write"})
     * @ApiProperty(
     *     attributes={
     *         "swagger_context"={
     *             "$ref"="#/definitions/Pool",
     *         }
     *     }
     * )
     * @ORM\ManyToMany(targetEntity="Pool", mappedBy="tags",cascade={"persist"})
     */
    private $pools;

    /**
     * @var string
     * @param string $identifier the hashtag
     * @ORM\Column(type="string")
     * @Assert\NotBlank
     * @Groups({"read", "write"})
     */
    private $tag;

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

    /**
     * @return mixed
     */
    public function getTag()
    {
        return $this->tag;
    }

    /**
     * @param mixed $tag
     */
    public function setTag($tag)
    {
        $this->tag = $tag;
    }

    public function addPool(Pool $pool):void
    {
        $pool->addTag($this);
        $this->pools->add($pool);
    }

    public function removePool(Pool $pool):void
    {
        $pool->removeTag($this);
        $this->pools->removeElement($pool);
    }
}

POST 请求到 API 路由

{
  "name": "This is a Test",
  "description": "A pool build for tests",
  "public": true,
  "tags": [{"tag":"testTag"},{"tag":"testTag2"}]
}
php symfony api-platform.com
5个回答
4
投票

为了实现多对多,你应该有 3 个表:Pool、Tag 和 PoolTag

在您的池实体中:

class Pool
{
    /**
    * @ORM\ManyToMany(targetEntity="Tag", inversedBy="pools")
    * @ORM\JoinTable(
    *  name="pool_tag",
    *  joinColumns={
    *      @ORM\JoinColumn(name="pool_id", referencedColumnName="id")
    *  },
    *  inverseJoinColumns={
    *      @ORM\JoinColumn(name="tag_id", referencedColumnName="id")
    *  }
    * )
    */
    private $tags;

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

在您的标签实体中:

class Tag
{
    /**
    * @ORM\ManyToMany(targetEntity="Pool", mappedBy="tags")
    */
    private $pools
}

POST 请求到 API 路由

 {
      "name": "This is a Test",
      "description": "A pool build for tests",
      "public": true,
      "tags": ["/api/tags/1","/api/tags/2"]
  }

2
投票

您的错误:

Maximum function nesting level of '256' reached
实际上是您使用XDebug时发生的错误。这暗示您已经创建了无限递归,这里就是这种情况。

您在

addTag()
中调用
Pool
,在
addPool()
中调用
Tag
,在
addTag()
中调用
Pool
,等等


0
投票

您遇到的问题与API平台本身无关,它是导致无限递归的语义错误(正如有人在之前的评论中所说)。
解决方案可以是在添加和删除方法中添加一个条件以避免此错误:

public function addTag(Tag $tag):void
{
    if (!$this->tags->contains($tag)) {
        $tag->addPool($this);
        $this->tags->add($tag);
    }
}


public function removeTag(Tag $tag):void
{
    if ($this->tags->contains($tag)) {
        $tag->removePool($this);
        $this->tags->removeElement($tag);
    }
}




public function addPool(Pool $pool):void
{
    if (!$this->pools->contains($pool)) {
        $pool->addTag($this);
        $this->pools->add($pool);
    }
}

public function removePool(Pool $pool):void
{
    if ($this->pools->contains($pool)) {
        $pool->removeTag($this);
        $this->pools->removeElement($pool);
    }
}

0
投票

问题是两个 denormalizationContext 在两个实体中的名称相同。 因此,当使用“写入”组对标签进行非规范化时,它会找到池的匹配组,在 denormalizationContext 池中,您会找到该标签的匹配“写入”组,最终会陷入循环非规范化的无限递归中。

解决方案:

为两个实体的 denormalizationContext 设置更明确的名称(例如“tag:write”和“pool:write”),并根据您想要的 denormalize 设置相应的组。

代码示例

/**
 * ...
 * @ApiResource(attributes={
 *     "normalization_context"={"groups"={"pool:read"}},
 *     "denormalization_context"={"groups"={"pool:write"}}
 * })
 */
class Pool
{
    /**
     * @Groups({"pool:read", "pool:write"})
     */
    private $tags;

    /**
     * @Groups({"pool:read", "pool:write", "tag:write"})
     */
    private $otherField;

//...
}

/**
 * ...
 * @ApiResource(attributes={
 *     "normalization_context"={"groups"={"tag:read"}},
 *     "denormalization_context"={"groups"={"tag:write"}}
 * })
 */
class Tag
{
    /**
     * @Groups({"tag:read", "tag:write"})
     */
    private $pools;

    /**
     * @Groups({"tag:read", "tag:write", "pool:write"})
     */
    private $otherField;

//...
}

注意 pool:write denormalizationContext 组如何用于 Pool 实体的所有字段,加上 Tag.otherField 但不在 Tag.pools 上,这会阻止标签的非规范化直至 pools,但不会循环回对标签进行非规范化。


-6
投票

要解决此类问题,您可能需要使用 Symfony 表单(因此是嵌入式表单),如此处所述。

Symfony 文档 - 如何嵌入表单集合

© www.soinside.com 2019 - 2024. All rights reserved.