在Symfony上传中找不到文件异常

时间:2018-08-14 14:25:28

标签: php symfony doctrine-orm fosrestbundle

我正在使用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="bussiness_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="bussiness_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,而我主要是直接使用request对象来处理数据。大多数文档使用表格来处理。另外,我所有的回复都使用JSON。

在此方面,我将不胜感激。否则,我将不得不存储完整路径,这不是一个好主意,而且非常不切实际。

谢谢。

1 个答案:

答案 0 :(得分: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;

这有意义吗?

希望这会有所帮助...