разместить ресурс со связанным файловым ресурсом в REST API - PullRequest
0 голосов
/ 03 марта 2020

У меня есть две сущности, MediaObject и Book. MediaObject - это общая сущность c для управления файлами и включает такие поля, как size, mimeType и filePath. Book имеет такие поля, как title, author, а также содержит ссылку на связанный MediaObject для его cover файла изображения.

Как я могу POST Book сущность со связанным с ней MediaObject cover образом с API-платформой? Я хотел бы сделать это как одну операцию Atom c. Я не хочу, чтобы книги были сохранены без обложки, и я не хочу, чтобы обложки были бесхозными. Поэтому я не хочу POST a MediaObject обложку, а затем использовать идентификатор, который я получаю при POST использовании нового Book. (или наоборот)

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

1 Ответ

1 голос
/ 03 марта 2020

В документации есть опция "deserialize"= false. Это означает, что для этой операции десериализация не произойдет. Следовательно, вы должны записать весь процесс десериализации самостоятельно в контроллер обработчика. Вы также должны написать поля для документации по чванству.

Например:

<?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 и проанализировать ее в контроллере-обработчике:

//...
    public function __construct(EntityManagerInterface $entityManager, IriConverterInterface $iriConverter)
    {
        $this->entityManager = $entityManager;
        $this->iriConverter  = $iriConverter;
    }

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

        $iriBook = $request->request->get('book');
        $book    = null;
        if ($iriBook) {
            /**
             * @var Book $book
             */
            $book = $this->iriConverter->getItemFromIri($iriBook);
        }

        $mediaObject       = new Media();
        $mediaObject->file = $uploadedFile;
        $mediaObject->setBook($book);

        return $mediaObject;
    }
//..

Если это ваш случай, то никаких дальнейших действий (DataPersist) требуется.

Далее вам нужно go 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
    }
}

PS Я не проверяю этот код. Я пишу идею;) PSS $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;
    }
}

...