Api平台上传图片-未知错误如何解决?

Api Platform upload image – how to solve unknown error?

我正在使用 Symfony 4、ApiPlatform 1 和 VichUploaderBundle 1。

已按规定执行所有操作 in docs 但出现错误:Cannot validate values of type \"NULL\" automatically. Please provide a constraint.

我什至不知道如何调试它——错误和回溯是如此无用。从哪里开始搜索?请帮忙。


我的图像实体:

<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
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(repositoryClass="App\Repository\ImageRepository")
 * @ApiResource(iri="http://schema.org/MediaObject")
 * @Vich\Uploadable
 */
class Image
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     * @Groups({"user"})
     */
    private $id;

    /**
     * @var File|null
     * @Assert\NotNull()
     * @Vich\UploadableField(mapping="image", fileNameProperty="contentUrl")
     */
    public $file;

    /**
     * @var string|null
     * @ORM\Column(nullable=true)
     * @ApiProperty(iri="http://schema.org/contentUrl")
     * @Groups({"user"})
     */
    private $contentUrl;

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

    public function setContentUrl(string $contentUrl): void { $this->contentUrl = $contentUrl; }
}

路由yaml配置:

app_image_upload:
    methods : ['POST']
    path    : '/images'
    defaults:
        _controller        : App\Controller\CreateImageAction
        _api_resource_class: App\Entity\Image
        _api_receive       : false

为其自定义操作:

<?php

namespace App\Controller;

use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException;
use App\Entity\Image;
use App\Form\ImageType;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;

final class CreateImageAction
{
    private $validator;
    private $doctrine;
    private $factory;
    private $uploader;

    public function __construct(
        RegistryInterface $doctrine,
        FormFactoryInterface $factory,
        ValidatorInterface $validator,
        UploaderHelper $uploader
    ) {
        $this->validator = $validator;
        $this->doctrine  = $doctrine;
        $this->factory   = $factory;
        $this->uploader  = $uploader;
    }

    public function __invoke(Request $request): Image
    {
        $image = new Image();

        $form = $this->factory->create(ImageType::class, $image);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->doctrine->getManager();
            $em->persist($image);
            $em->flush();

            $image->setContentUrl($this->uploader->asset($image, 'file'));
            $em->merge($image);
            $em->flush();

            // Prevent the serialization of the file property
            $image->file = null;

            return $image;
        }

        // This will be handled by API Platform and returns a validation error.
        throw new ValidationException($this->validator->validate($image));
    }
}

和形式:

<?php

namespace App\Form;

use App\Entity\Image;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class ImageType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('file', FileType::class, [
                'label'    => 'label.file',
                'required' => false,
            ]);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class'      => Image::class,
            'csrf_protection' => false,
        ]);
    }

    public function getBlockPrefix(): string
    {
        return '';
    }
}

我的请求是这样的:

$ http -v --form 'api.example.com/api/images' @image.png
POST /api/images HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Bearer [API_TOKEN]
Connection: keep-alive
Content-Length: 48888
Content-Type: multipart/form-data; boundary=aedf69840efb693f8ef3f566bc1be656
Host: api.example.com
User-Agent: HTTPie/1.0.2

+-----------------------------------------+
| NOTE: binary data not shown in terminal |
+-----------------------------------------+

并回复:

HTTP/1.1 500 Internal Server Error
Cache-Control: no-cache, private
Connection: keep-alive
Content-Type: application/ld+json; charset=utf-8
Date: Sat, 29 Dec 2018 04:10:27 GMT
Link: <http://api.example.com/api/docs.jsonld>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: deny

{
    "@context": "/api/contexts/Error",
    "@type": "hydra:Error",
    "hydra:description": "Cannot validate values of type \"NULL\" automatically. Please provide a constraint.",
    "hydra:title": "An error occurred",
    "trace": [
        {
            "args": [],
            "class": "",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/symfony/validator/Validator/RecursiveContextualValidator.php",
            "function": "",
            "line": 166,
            "namespace": "",
            "short_class": "",
            "type": ""
        },
        {
            "args": [
                [
                    "null",
                    null
                ],
                [
                    "null",
                    null
                ],
                [
                    "array",
                    [
                        [
                            "string",
                            "Default"
                        ]
                    ]
                ]
            ],
            "class": "Symfony\Component\Validator\Validator\RecursiveContextualValidator",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/symfony/validator/Validator/RecursiveValidator.php",
            "function": "validate",
            "line": 100,
            "namespace": "Symfony\Component\Validator\Validator",
            "short_class": "RecursiveContextualValidator",
            "type": "->"
        },
        {
            "args": [
                [
                    "null",
                    null
                ],
                [
                    "null",
                    null
                ],
                [
                    "null",
                    null
                ]
            ],
            "class": "Symfony\Component\Validator\Validator\RecursiveValidator",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/symfony/validator/Validator/TraceableValidator.php",
            "function": "validate",
            "line": 66,
            "namespace": "Symfony\Component\Validator\Validator",
            "short_class": "RecursiveValidator",
            "type": "->"
        },
        {
            "args": [
                [
                    "null",
                    null
                ],
                [
                    "null",
                    null
                ],
                [
                    "null",
                    null
                ]
            ],
            "class": "Symfony\Component\Validator\Validator\TraceableValidator",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/api-platform/core/src/Bridge/Symfony/Validator/Validator.php",
            "function": "validate",
            "line": 61,
            "namespace": "Symfony\Component\Validator\Validator",
            "short_class": "TraceableValidator",
            "type": "->"
        },
        {
            "args": [
                [
                    "null",
                    null
                ],
                [
                    "array",
                    {
                        "groups": [
                            "null",
                            null
                        ]
                    }
                ]
            ],
            "class": "ApiPlatform\Core\Bridge\Symfony\Validator\Validator",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/api-platform/core/src/Validator/EventListener/ValidateListener.php",
            "function": "validate",
            "line": 59,
            "namespace": "ApiPlatform\Core\Bridge\Symfony\Validator",
            "short_class": "Validator",
            "type": "->"
        },
        {
            "args": [
                [
                    "object",
                    "Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent"
                ],
                [
                    "string",
                    "kernel.view"
                ],
                [
                    "object",
                    "Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher"
                ]
            ],
            "class": "ApiPlatform\Core\Validator\EventListener\ValidateListener",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/symfony/event-dispatcher/Debug/WrappedListener.php",
            "function": "onKernelView",
            "line": 111,
            "namespace": "ApiPlatform\Core\Validator\EventListener",
            "short_class": "ValidateListener",
            "type": "->"
        },
        {
            "args": [
                [
                    "object",
                    "Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent"
                ],
                [
                    "string",
                    "kernel.view"
                ],
                [
                    "object",
                    "Symfony\Component\EventDispatcher\EventDispatcher"
                ]
            ],
            "class": "Symfony\Component\EventDispatcher\Debug\WrappedListener",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/symfony/event-dispatcher/EventDispatcher.php",
            "function": "__invoke",
            "line": 212,
            "namespace": "Symfony\Component\EventDispatcher\Debug",
            "short_class": "WrappedListener",
            "type": "->"
        },
        {
            "args": [
                [
                    "array",
                    [
                        [
                            "object",
                            "Symfony\Component\EventDispatcher\Debug\WrappedListener"
                        ],
                        [
                            "object",
                            "Symfony\Component\EventDispatcher\Debug\WrappedListener"
                        ],
                        [
                            "object",
                            "Symfony\Component\EventDispatcher\Debug\WrappedListener"
                        ],
                        [
                            "object",
                            "Symfony\Component\EventDispatcher\Debug\WrappedListener"
                        ],
                        [
                            "object",
                            "Symfony\Component\EventDispatcher\Debug\WrappedListener"
                        ],
                        [
                            "object",
                            "Symfony\Component\EventDispatcher\Debug\WrappedListener"
                        ],
                        [
                            "object",
                            "Symfony\Component\EventDispatcher\Debug\WrappedListener"
                        ]
                    ]
                ],
                [
                    "string",
                    "kernel.view"
                ],
                [
                    "object",
                    "Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent"
                ]
            ],
            "class": "Symfony\Component\EventDispatcher\EventDispatcher",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/symfony/event-dispatcher/EventDispatcher.php",
            "function": "doDispatch",
            "line": 44,
            "namespace": "Symfony\Component\EventDispatcher",
            "short_class": "EventDispatcher",
            "type": "->"
        },
        {
            "args": [
                [
                    "string",
                    "kernel.view"
                ],
                [
                    "object",
                    "Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent"
                ]
            ],
            "class": "Symfony\Component\EventDispatcher\EventDispatcher",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php",
            "function": "dispatch",
            "line": 142,
            "namespace": "Symfony\Component\EventDispatcher",
            "short_class": "EventDispatcher",
            "type": "->"
        },
        {
            "args": [
                [
                    "string",
                    "kernel.view"
                ],
                [
                    "object",
                    "Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent"
                ]
            ],
            "class": "Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/symfony/http-kernel/HttpKernel.php",
            "function": "dispatch",
            "line": 155,
            "namespace": "Symfony\Component\EventDispatcher\Debug",
            "short_class": "TraceableEventDispatcher",
            "type": "->"
        },
        {
            "args": [
                [
                    "object",
                    "Symfony\Component\HttpFoundation\Request"
                ],
                [
                    "integer",
                    1
                ]
            ],
            "class": "Symfony\Component\HttpKernel\HttpKernel",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/symfony/http-kernel/HttpKernel.php",
            "function": "handleRaw",
            "line": 67,
            "namespace": "Symfony\Component\HttpKernel",
            "short_class": "HttpKernel",
            "type": "->"
        },
        {
            "args": [
                [
                    "object",
                    "Symfony\Component\HttpFoundation\Request"
                ],
                [
                    "integer",
                    1
                ],
                [
                    "boolean",
                    true
                ]
            ],
            "class": "Symfony\Component\HttpKernel\HttpKernel",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/vendor/symfony/http-kernel/Kernel.php",
            "function": "handle",
            "line": 198,
            "namespace": "Symfony\Component\HttpKernel",
            "short_class": "HttpKernel",
            "type": "->"
        },
        {
            "args": [
                [
                    "object",
                    "Symfony\Component\HttpFoundation\Request"
                ]
            ],
            "class": "Symfony\Component\HttpKernel\Kernel",
            "file": "/Users/SOMEUSER/Projects/MYPROJECT/www/public/index.php",
            "function": "handle",
            "line": 37,
            "namespace": "Symfony\Component\HttpKernel",
            "short_class": "Kernel",
            "type": "->"
        }
    ]
}

更新:MWE on GitHub

通过更准确地遵循文档,一切正常:您只是忘记了正确的 @ApiResource 注释和 Image.php

中的 use 语句
<?php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use App\Controller\CreateImageAction;

/**
 * @ORM\Entity(repositoryClass="App\Repository\ImageRepository")
 * @ApiResource(iri="http://schema.org/MediaObject", collectionOperations={
 *     "get",
 *     "post"={
 *         "method"="POST",
 *         "path"="/images",
 *         "controller"=CreateImageAction::class,
 *         "defaults"={"_api_receive"=false},
 *     },
 * })
 * @Vich\Uploadable
 */
class Image
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     * @Groups({"user"})
     */
    private $id;

    /**
     * @var File|null
     * @Vich\UploadableField(mapping="image", fileNameProperty="contentUrl")
     */
    public $file;

    /**
     * @var string|null
     * @ORM\Column(nullable=true)
     * @ApiProperty(iri="http://schema.org/contentUrl")
     * @Groups({"user"})
     */
    private $contentUrl;

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

    public function setContentUrl(?string $contentUrl = null): void { $this->contentUrl = $contentUrl; }
}