Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why I set itemUriTemplate is not work? #2337

Closed
bhtomming opened this issue Dec 4, 2022 · 2 comments
Closed

Why I set itemUriTemplate is not work? #2337

bhtomming opened this issue Dec 4, 2022 · 2 comments

Comments

@bhtomming
Copy link

bhtomming commented Dec 4, 2022

api-platform/core: ^3.0
I want to upload images through object mapping, but I find that only images can be uploaded, and the mapping cannot be updated to your content or changed. Because the PUT method does not support uploading images, I can find a way to make the POST method have two routes to upload images. When I look up the content in the operations section, I find that there is a sub route that can be set, but when I set it as described in the tutorial, and then upload files to this sub route, The error message that the route does not exist is displayed. How should the sub route be set to take effect?
My few

// api/src/Entity/MediaObject.php
<?php


namespace App\Entity;

use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiFilter;
use App\Controller\CreateMediaObjectAction;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
 * @ORM\Entity
 * @Vich\Uploadable
 */
#[ApiResource(
    types: ['http://schema.org/MediaObject'],
    operations: [
        new Get(),
        new GetCollection(),
        new Post(
            controller: CreateMediaObjectAction::class,
            openapiContext: [
                'requestBody' => [
                    'content' => [
                        'multipart/form-data' => [
                            'schema' => [
                                'type' => 'object',
                                'properties' => [
                                    'file' => ['type' => 'string',
                                        'format' => 'binary'
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
            ],
            validationContext: ['groups' => ['Default', 'media_object_create']],
            deserialize: false,
        )
    ],
    normalizationContext: ['groups' => ['media_object:read']]
)]
class MediaObject
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     * @ORM\Id
     */
    private ?int $id = null;

    #[ApiProperty(iris: ['http://schema.org/contentUrl'])]
    #[Groups(['media_object:read'])]
    public ?string $contentUrl = null;

    /**
     * @Vich\UploadableField(mapping="media_object", fileNameProperty="filePath")
     */
    #[Assert\NotNull(groups: ['media_object_create'])]
    public ?File $file = null;

    /**
     * @ORM\Column(nullable=true)
     */
    public ?string $filePath = null;

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

the Slider file is

// api/src/Entity/Slider.php
<?php

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use App\Repository\SliderRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Nette\Utils\DateTime;
use phpDocumentor\Reflection\DocBlock\Tags\Method;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
 * @ORM\Entity(repositoryClass=SliderRepository::class)
 * @Vich\Uploadable
 */
#[ApiResource(
    denormalizationContext: ['groups' => ['slider:write']],
    types: ['https://schema.org/Slider'],  
)]
#[GetCollection] // auto-generated path will be /users
#[Get] // auto-generated path will be /users/{id}
#[GetCollection(itemUriTemplate: '/sliders/{id}/image'/*, ... */)]
#[Post(inputFormats: ['multipart' => ['multipart/form-data']],itemUriTemplate: '/sliders/{id}/image'/*, ... */)]
#[Get(uriTemplate: '/sliders/{id}'/*, ... */)]
class Slider
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="MediaObject")
     * @ORM\JoinColumn(nullable=true)
     */
    #[ApiProperty(types: ['https://schema.org/mediaObject'])]
    public ?MediaObject $image = null;

    /**
     * @ORM\Column(type="string", length=255)
     */
    #[Groups(['slider:write'])]
    private $title;


    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    #[Groups(['slider:write'])]
    private $topic;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    #[Groups(['slider:write'])]
    private $description;

    /**
     * @ORM\Column(type="string", length=50)
     */
    #[Groups(['slider:write'])]
    private $state;

    /**
     * @ORM\Column(type="datetime")
     */
    private $createdAt;

    /**
     * @ORM\Column(type="datetime")
     */
    private $updatedAt;

    public function __construct()
    {
        $this->setCreatedAt(new DateTime('now'));
        $this->setUpdatedAt(new DateTime('now'));
    }

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

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): self
    {
        $this->title = $title;

        return $this;
    }


    public function getTopic(): ?string
    {
        return $this->topic;
    }

    public function setTopic(?string $topic): self
    {
        $this->topic = $topic;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): self
    {
        $this->description = $description;

        return $this;
    }

    public function getState(): ?string
    {
        return $this->state;
    }

    public function setState(string $state=null): self
    {
        if(!$state)
        {
            $this->state = 1;
        }
        $this->state = $state;

        return $this;
    }

    public function getCreatedAt(): ?\DateTimeInterface
    {
        return $this->createdAt;
    }

    public function setCreatedAt(\DateTimeInterface $createdAt): self
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    public function getUpdatedAt(): ?\DateTimeInterface
    {
        return $this->updatedAt;
    }

    public function setUpdatedAt(\DateTimeInterface $updatedAt): self
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }

}
<?php
// api/src/Controller/CreateMediaObjectAction.php

<?php

namespace App\Controller;

use App\Entity\MediaObject;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

#[AsController]
final class CreateMediaObjectAction extends AbstractController
{
    public function __invoke(Request $request): MediaObject
    {
        $uploadedFile = $request->files->get('file');
        if (!$uploadedFile) {
            throw new BadRequestHttpException('"file" is required');
        }

        $mediaObject = new MediaObject();
        $mediaObject->file = $uploadedFile;

        return $mediaObject;
    }
}
<?php
// api/src/Serializer/MediaObjectNormalizer.php

<?php


namespace App\Serializer;

use App\Entity\MediaObject;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Vich\UploaderBundle\Storage\StorageInterface;

final class MediaObjectNormalizer implements ContextAwareNormalizerInterface,  NormalizerAwareInterface
{
    use NormalizerAwareTrait;

    private const ALREADY_CALLED = 'MEDIA_OBJECT_NORMALIZER_ALREADY_CALLED';

    public function __construct(private StorageInterface $storage)
    {
    }

    public function normalize($object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
    {
        $context[self::ALREADY_CALLED] = true;

        $object->contentUrl = $this->storage->resolveUri($object, 'file');

        return $this->normalizer->normalize($object, $format, $context);
    }

    public function supportsNormalization($data, ?string $format = null, array $context = []): bool
    {
        if (isset($context[self::ALREADY_CALLED])) {
            return false;
        }

        return $data instanceof MediaObject;
    }
}

When I use the default route POST, I can create Slider and MediaObject, but I cannot update any information.
When I use/sliders/{id}/image or any custom uri template route with id, I can update the information of the slider or MediaObject, but I cannot create new objects.
When the route is set to the itemUriTemplate, it does not work. The php bin/console debug: route does not display the route set by the itemUriTemplate

@vbarrier
Copy link

vbarrier commented Sep 9, 2023

Hi, did you find any workaround?

When I use the property uriTemplate on a POST operation vich uploader doesn't do its job.

@vbarrier
Copy link

vbarrier commented Sep 9, 2023

Hi, did you find any workaround?

When I use the property uriTemplate on a POST operation vich uploader doesn't do its job.

Found my problem, with Vich Uploader you must also "dirty" a entity property (like a lastUpdate field) to trigger persistence

@soyuka soyuka closed this as completed Sep 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants