发布REST API中具有关联文件资源的资源

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

我有两个实体,MediaObjectBookMediaObject是用于管理文件的通用实体,并包含sizemimeTypefilePath之类的字段。 Book具有类似titleauthor的字段,并且还包括指向其MediaObject图像文件的关联cover的链接。

我如何POST一个Book实体及其关联的带有API平台的MediaObject cover图像?我想将其作为一项原子操作来完成。我不想保存没有封面图像的书,也不想孤立的封面图像。因此,我不想POST一个MediaObject封面图像,然后使用POST新建一个Book时获得的ID。 (反之亦然)

https://api-platform.com/docs/core/file-upload/

class MediaObject
{
    ...
    public $filePath;
    ...
}
class Book
{
    ...
    public $coverImage; // i.e. mediaObjectId; associated MediaObject to an image file
    ...
}
rest file-upload associations api-platform.com
1个回答
1
投票

文档具有选项"deserialize"= false。这意味着此操作不会发生反序列化。因此,您必须将整个反序列化过程自己编写到处理程序控制器。您还必须为swagger文档编写字段。

例如:

<?php

declare(strict_types=1);

namespace App\Entity;

// more use...

/**
 * @ApiResource(
 *     iri="http://schema.org/MediaObject",
 *     normalizationContext={
 *         "groups" = {"media:read"}
 *     },
 *     collectionOperations={
 *         "post" = {
 *             "controller" = MediaHandler::class,
 *             "deserialize" = false,
 *             "access_control" = "is_granted('ROLE_USER')",
 *             "validation_groups" = {"Default", "media:collection:post"},
 *             "openapi_context" = {
 *                 "requestBody" = {
 *                     "content" = {
 *                         "multipart/form-data" = {
 *                             "schema" = {
 *                                 "type" = "object",
 *                                 "properties" = {
 *                                     "file" = {
 *                                         "type" = "string",
 *                                         "format" = "binary"
 *                                     },
 *                                     "name" = {
 *                                         "type" = "string"
 *                                     }
 *                                 }
 *                             }
 *                         }
 *                     }
 *                 }
 *             }
 *         },
 *         "get"
 *     },
 *     itemOperations={
 *         "get"
 *     }
 * )
 * @Vich\Uploadable
 * @ORM\Entity(repositoryClass="App\Repository\MediaRepository")
 */
class Media
{
    /**
     * @ApiProperty(iri="http://schema.org/contentUrl")
     * @Groups({"media:read"})
     */
    public $contentUrl;

    /**
     * @Assert\NotNull(groups={"media:collection:post"})
     * @Vich\UploadableField(mapping="media", fileNameProperty="filePath")
     * @Assert\File(
     *     maxSize="2M",
     *     mimeTypes={
     *         "application/pdf",
     *         "application/x-pdf",
     *         "image/jpeg",
     *         "image/jpg",
     *         "image/png"
     *     },
     *     groups={"media:collection:post"}
     * )
     */
    public $file;
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

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

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

    //...
}

控制器处理程序示例:

<?php

declare(strict_types=1);

namespace App\Controller\Api;

// use ...

class MediaHandler extends AbstractController
{
    /**
     * @var EntityManagerInterface
     */
    private EntityManagerInterface $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function __invoke(Request $request): Media
    {
        $uploadedFile = $request->files->get('file');
        if (!$uploadedFile) {
            throw new BadRequestHttpException('"file" is required');
        }

        $mediaObject       = new Media();
        $mediaObject->file = $uploadedFile;
        $mediaObject->setName($request->request->get('name'));

        return $mediaObject;
    }
}

如果“书”存在。并且要将Book添加到MediaObject,可以设置iri字符串并在controller-handler中对其进行解析:

//...
$iriBook = $request->request->get('book');
$book = null;
if ($iriBook) {
    if (!\preg_match('`^/?api/books/(\d+)$`', $iriBook, $match)) {
        throw new BadRequestHttpException('Invalid IRI "'.$iriBook.'"');
    }

    $book = $this->entityManager->getRepository(Book::class)
        ->find($match[1]);

    if (!$book) {
        throw new BadRequestHttpException('Item not found for "'.$iriBook.'"');
    }
}

//..
$mediaObject->setBook($book);

如果是您的情况,则无需采取进一步措施(DataPersist)。

接下来您需要去https://api-platform.com/docs/core/data-persisters/并创建DataPesist处理程序

示例:

<?php

declare(strict_types=1);

namespace App\DataPersister;

use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use App\Entity\Media;
use App\ExtendTrait\ContextAwareDataTrait;
use Doctrine\ORM\EntityManagerInterface;

class MediaObjectDataPersister implements ContextAwareDataPersisterInterface
{
    use ContextAwareDataTrait;

    /**
     * @var EntityManagerInterface
     */
    private EntityManagerInterface $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    /**
     * {@inheritdoc}
     */
    public function supports($data, array $context = []): bool
    {
        return $this->isCollection('post', $context) && $data instanceof Media;
    }

    /**
     * {@inheritdoc}
     *
     * @param $data Media
     *
     * @throws \Exception
     */
    public function persist($data, array $context = []): void
    {
        $book = new Book();
        $book->setName($data->getName());

        // begin transaction and persist and flush $book and $data
    }

    /**
     * {@inheritdoc}
     */
    public function remove($data, array $context = []): void
    {
        // todo remove book
    }
}

P.S。我不测试此代码。我写的想法;)P.S.S. $this->isCollection()它从我的特征开始起作用,可能需要您使用它:

<?php

declare(strict_types=1);

namespace App\ExtendTrait;

/**
 * Trait ContextAwareDataTrait.
 *
 * Helps confirm the operation name
 */
trait ContextAwareDataTrait
{
    public function isItem(string $operationName, array $context, string $resourceClass = null): bool
    {
        if ($resourceClass && ($context['resource_class'] ?? false) !== $resourceClass) {
            return false;
        }

        return ($context['item_operation_name'] ?? null) === $operationName;
    }

    public function isCollection(string $operationName, array $context, string $resourceClass = null): bool
    {
        if ($resourceClass && ($context['resource_class'] ?? false) !== $resourceClass) {
            return false;
        }

        return ($context['collection_operation_name'] ?? null) === $operationName;
    }
}

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