【问题标题】:File not found exception on Symfony uploadSymfony 上传时找不到文件异常
【发布时间】:2018-08-14 14:25:27
【问题描述】:

我正在使用 Symfony 3.4 开发一个简单的 REST API 微服务。使用 HTTP API 和文件上传时,找不到太多资源。我正在按照文档中的一些说明进行操作,但我发现了一堵墙。

我想要做的是将上传文件的相对路径存储在实体字段上,但似乎验证期望该字段是完整路径。

这是我的一些代码:

<?php
// BusinessClient.php
namespace DemoBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use ApiBundle\Entity\BaseEntity;
use ApiBundle\Entity\Client;
use JMS\Serializer\Annotation as Serializer;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints;

/**
 * Class BusinessClient
 * @package DemoBundle\Entity
 * @ORM\Entity(repositoryClass="DemoBundle\Repository\ClientRepository")
 * @ORM\Table(name="business_client")
 * @Serializer\ExclusionPolicy("all")
 * @Serializer\AccessorOrder("alphabetical")
 */
class BusinessClient extends BaseEntity
{
    /**
     * @Constraints\NotBlank()
     * @ORM\ManyToOne(targetEntity="ApiBundle\Entity\Client", fetch="EAGER")
     * @ORM\JoinColumn(name="client_id", referencedColumnName="oauth2_client_id", nullable=false)
     */
    public $client;

    /**
     * @Constraints\NotBlank()
     * @ORM\Column(type="string", length=255, nullable=false)
     * @Serializer\Expose
     */
    protected $name;

    /**
     * @Constraints\Image(minWidth=100, minHeight=100)
     * @ORM\Column(type="text", nullable=true)
     */
    protected $logo;

    /**
     * One Business may have many brands
     * @ORM\OneToMany(targetEntity="DemoBundle\Entity\Brand", mappedBy="business")
     * @Serializer\Expose
     */
    protected $brands;

    /**
     * BusinessClient constructor.
     */
    public function __construct()
    {
        $this->brands = new ArrayCollection();
    }

    /**
     * Set the links property for the resource response
     *
     * @Serializer\VirtualProperty(name="_links")
     * @Serializer\SerializedName("_links")
     */
    public function getLinks()
    {
        return [
            "self" => "/clients/{$this->getId()}",
            "brands" => "/clients/{$this->getId()}/brands"
        ];
    }

    /**
     * Get the name of the business client
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set the name of the business client
     *
     * @param string $name
     */
    public function setName($name): void
    {
        $this->name = $name;
    }

    /**
     * Get the logo
     *
     * @Serializer\Expose
     * @Serializer\VirtualProperty(name="logo")
     * @Serializer\SerializedName("logo")
     */
    public function getLogo()
    {
        return $this->logo;
    }

    /**
     * Set the logo field
     *
     * @param string|File|UploadedFile $logo
     */
    public function setLogo($logo): void
    {
        $this->logo = $logo;
    }

    /**
     * Get the client property
     *
     * @return Client
     */
    public function getClient()
    {
        return $this->client;
    }

    /**
     * Set the client property
     *
     * @param Client $client
     */
    public function setClient($client): void
    {
        $this->client = $client;
    }
}

上传服务:

<?php

namespace DemoBundle\Services;


use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
 * Class FileUploader
 * @package DemoBundle\Services
 */
class FileUploader
{
    /** @var string $uploadDir The directory where the files will be uploaded */
    private $uploadDir;

    /**
     * FileUploader constructor.
     * @param $uploadDir
     */
    public function __construct($uploadDir)
    {
        $this->uploadDir = $uploadDir;
    }

    /**
     * Upload a file to the specified upload dir
     * @param UploadedFile $file File to be uploaded
     * @return string The unique filename generated
     */
    public function upload(UploadedFile $file)
    {
        $fileName = md5(uniqid()).'.'.$file->guessExtension();

        $file->move($this->getTargetDirectory(), $fileName);

        return $fileName;
    }

    /**
     * Get the base dir for the upload files
     * @return string
     */
    public function getTargetDirectory()
    {
        return $this->uploadDir;
    }
}

我已经注册了服务:

services:
  # ...
  public: false
  DemoBundle\Services\FileUploader:
    arguments:
      $uploadDir: '%logo_upload_dir%'

最后是控制器:

<?php

namespace DemoBundle\Controller;

use ApiBundle\Exception\HttpException;
use DemoBundle\Entity\BusinessClient;
use DemoBundle\Services\FileUploader;
use FOS\RestBundle\Controller\Annotations as REST;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Swagger\Annotations as SWG;
use Symfony\Component\Validator\Constraints\ImageValidator;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\ConstraintViolationInterface;


/**
 * Class BusinessClientController
 * @package DemoBundle\Controller
 */
class BusinessClientController extends BaseController
{

    /**
     * Create a new business entity and persist it in database
     *
     * @REST\Post("/clients")
     * @SWG\Tag(name="business_clients")
     * @SWG\Response(
     *     response="201",
     *     description="Create a business client and return it's data"
     * )
     * @param Request $request
     * @param FileUploader $uploader
     * @return Response
     * @throws HttpException
     */
    public function createAction(Request $request, FileUploader $uploader, LoggerInterface $logger)
    {
        $entityManager = $this->getDoctrine()->getManager();
        $oauthClient = $this->getOauthClient();

        $data = $request->request->all();
        $client = new BusinessClient();
        $client->setName($data["name"]);
        $client->setClient($oauthClient);

        $file = $request->files->get('logo');

        if (!is_null($file)) {
            $fileName = $uploader->upload($file);
            $client->setLogo($fileName);
        }

        $errors = $this->validate($client);
        if (count($errors) > 0) {
            $err = [];
            /** @var ConstraintViolationInterface $error */
            foreach ($errors as $error) {
                $err[] = [
                    "message" => $error->getMessage(),
                    "value" => $error->getInvalidValue(),
                    "params" => $error->getParameters()
                ];
            }
            throw HttpException::badRequest($err);
        }

        $entityManager->persist($client);
        $entityManager->flush();

        $r = new Response();

        $r->setContent($this->serialize($client));
        $r->setStatusCode(201);
        $r->headers->set('Content-type', 'application/json');

        return $r;
    }

    /**
     * Get data for a single business client
     *
     * @REST\Get("/clients/{id}", requirements={"id" = "\d+"})
     * @param $id
     * @return Response
     * @SWG\Tag(name="business_clients")
     * @SWG\Response(
     *     response="200",
     *     description="Get data for a single business client"
     * )
     */
    public function getClientAction($id)
    {
        $object = $this->getDoctrine()->getRepository(BusinessClient::class)
            ->find($id);
        $j = new Response($this->serialize($object));
        return $j;
    }
}

当我尝试将徽标设置为文件基本名称字符串时,请求将失败。找不到文件(基本名称)的错误。这在某种程度上是有道理的。

否则,如果我尝试设置的不是字符串,而是具有新上传文件的有效路径的文件,则请求将成功,但表中的字段将替换为完整的系统路径。当我放置有效的系统路径而不是文件时,也会发生同样的情况。

<?php

// Controller 
.....


// This works
if (!is_null($file)) {
    $fileName = $uploader->upload($file);
    $client->setLogo($this->getParameter("logo_upload_dir")."/$fileName");
}

上传目录参数:

parameters:
  logo_upload_dir: '%kernel.project_dir%/web/uploads/logos'

我没有使用任何表单,因为这是第三方 API,我主要直接使用请求对象来处理数据。大多数文档都使用 Forms 来处理这个问题。此外,我所有的回复都是 JSON 格式。

如果能提供任何帮助,我将不胜感激。否则我将不得不存储完整路径,这不是一个好主意而且非常不切实际。

提前致谢。

【问题讨论】:

    标签: php symfony doctrine-orm fosrestbundle


    【解决方案1】:

    对此有一个想法:验证方法,而不是验证您计划作为图像相对路径的属性。可能是这样的:

    class BusinessClient extends BaseEntity
    {
        public static $basePath;
    
        // ....
        /**
         * Get the logo
         *
         * @Constraints\Image(minWidth=100, minHeight=100)
         */
        public function getAbsolutePathLogo()
        {
            return self::$basePath . '/' . $this->logo;
        }
    

    因此,从您的 logo 成员中删除验证,添加一个新方法(我将其命名为 getAbsolutePathLogo 购买,您可以选择任何东西)并在其之上设置验证。

    这样,您的徽标将作为相对路径保留,并且验证应该有效。然而,现在的挑战是确定设置静态$basePath 的正确时机。实际上,这个甚至不需要是类静态成员,而是可以是全局的:

    return MyGlobalPath::IMAGE_PATH . '/' . $this->logo;
    

    这有意义吗?

    希望对你有所帮助...

    【讨论】:

      猜你喜欢
      • 2018-07-03
      • 1970-01-01
      • 1970-01-01
      • 2015-02-11
      • 2023-03-25
      • 2020-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多