Symfony 文件上传
最后修改于 2020 年 7 月 5 日
Symfony 文件上传教程展示了如何在 Symfony 应用程序中上传文件。在示例中,我们使用普通表单发送文件;我们不使用表单构建器。
Symfony
Symfony 是一套可重用的 PHP 组件和一个用于 Web 项目的 PHP 框架。Symfony 于 2005 年发布为免费软件。Symfony 的原始作者是 Fabien Potencier。Symfony 的灵感主要来源于 Spring Framework 和 Ruby on Rails。
文件上传
要上传文件,form 必须将 enctype 设置为 multipart/form-data,并将 input 的类型设置为 file。
此外,在 PHP 的 php.ini 文件中,文件上传是通过 file_uploads 选项控制的。
Symfony 文件上传示例
在示例中,我们有一个简单的表单,其中包含一个输入字段:要上传的文件。表单提交后,我们验证 CSRF 令牌并加载图像,检索其名称,然后将文件存储在 var 目录中。
$ symfony new symupl $ cd symupl
我们创建一个新的 Symfony 项目并进入项目目录。
$ php bin/console --version Symfony 5.0.8 (env: dev, debug: true)
我们使用 Symfony 5.0.8。
$ composer require annot twig
我们安装以下包:annotations 和 twig。这些是创建路由和模板所必需的。
$ composer require symfony/security-csrf monolog
symfony/security-csrf 包用于防止跨站请求伪造,monolog 用于日志记录。
$ composer require maker profiler --dev
在开发阶段,我们还安装了 maker 和 profiler。
构建 Symfony 应用程序
我们定义将上传图像的目录。
parameters:
upload_dir: '../var/uploads'
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, ...
bind:
$uploadDir: '%upload_dir%'
...
我们定义了一个包含图像上传目录名称的参数。upload_dir 参数绑定到 $uploadDir 变量,该变量可以被注入。
$ php bin/console make:controller HomeController
我们创建一个 HomeController。该控制器将表单发送给客户端。
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class HomeController extends AbstractController
{
/**
* @Route("/", name="home")
*/
public function index()
{
return $this->render('home/index.html.twig');
}
}
这是一个简单的控制器,它将包含 Web 表单的视图发送给用户。
{% extends 'base.html.twig' %}
{% block title %}Upload file{% endblock %}
{% block body %}
<form action="{{ path('do-upload') }}" method="post" enctype="multipart/form-data">
<input type="hidden" name="token" value="{{ csrf_token('upload') }}" />
<div>
<label for="myfile">File to upload:</label>
<input type="file" name="myfile" id="myfile">
</div>
<button type="submit">Send</button>
</form>
{% endblock %}
此视图创建一个表单。它定义了 multipart/form-data enctype 和 file 输入。此外,它还有一个 CSRF 隐藏输入令牌。
$ php bin/console make:controller UploadController
我们创建一个 UploadController 来响应表单提交。此控制器不需要生成的 twig 模板;因此,我们将其删除。
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Service\FileUploader;
use Psr\Log\LoggerInterface;
class UploadController extends AbstractController
{
/**
* @Route("/doUpload", name="do-upload")
* @param Request $request
* @param string $uploadDir
* @param FileUploader $uploader
* @param LoggerInterface $logger
* @return Response
*/
public function index(Request $request, string $uploadDir,
FileUploader $uploader, LoggerInterface $logger): Response
{
$token = $request->get("token");
if (!$this->isCsrfTokenValid('upload', $token))
{
$logger->info("CSRF failure");
return new Response("Operation not allowed", Response::HTTP_BAD_REQUEST,
['content-type' => 'text/plain']);
}
$file = $request->files->get('myfile');
if (empty($file))
{
return new Response("No file specified",
Response::HTTP_UNPROCESSABLE_ENTITY, ['content-type' => 'text/plain']);
}
$filename = $file->getClientOriginalName();
$uploader->upload($uploadDir, $file, $filename);
return new Response("File uploaded", Response::HTTP_OK,
['content-type' => 'text/plain']);
}
}
在 UploadController 中,我们检查 CSRF 令牌,从请求中获取文件,并调用 uploader 服务 upload() 方法。
public function index(Request $request, string $uploadDir,
FileUploader $uploader, LoggerInterface $logger): Response
{
我们注入请求对象、上传目录参数、FileUploader 服务和日志记录器。
$token = $request->get("token");
if (!$this->isCsrfTokenValid('upload', $token))
{
$logger->info("CSRF failure");
return new Response("Operation not allowed", Response::HTTP_BAD_REQUEST,
['content-type' => 'text/plain']);
}
我们检索令牌并使用 isCsrfTokenValid() 方法对其进行验证。如果验证失败,我们会记录事件并发送一个纯文本响应“Operation not allowed”和 Response::HTTP_BAD_REQUEST 响应代码。
$file = $request->files->get('myfile');
if (empty($file))
{
return new Response("No file specified", Response::HTTP_UNPROCESSABLE_ENTITY,
['content-type' => 'text/plain']);
}
我们使用 empty() 方法检查用户是否在表单中指定了任何文件。如果输入字段为空,我们会将纯文本“No file specified”发送回客户端,响应代码为 Response::HTTP_UNPROCESSABLE_ENTITY。
$filename = $file->getClientOriginalName();
我们使用 getClientOriginalName() 获取文件名。
$uploader->upload($uploadDir, $file, $filename);
我们调用 uploader 服务 upload() 方法,该方法将文件移动到选定的目录。我们将目录名称、文件数据和文件名传递给该方法。
return new Response("File uploaded", Response::HTTP_OK,
['content-type' => 'text/plain']);
如果一切顺利,我们会将纯文本消息“File uploaded”发送回客户端,响应代码为 Response::HTTP_OK。
<?php
namespace App\Service;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Psr\Log\LoggerInterface;
class FileUploader
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function upload($uploadDir, $file, $filename)
{
try {
$file->move($uploadDir, $filename);
} catch (FileException $e){
$this->logger->error('failed to upload image: ' . $e->getMessage());
throw new FileException('Failed to upload file');
}
}
}
FileUploader 服务使用 move() 将文件移动到上传目录。操作失败时,我们会抛出 FileException。这将导致生成错误页面。
{% extends "base.html.twig" %}
{% block title %}
Problem detected
{% endblock %}
{% block body %}
<div>
<p>
There was a problem: {{ exception.message }}
</p>
</div>
{% endblock %}
我们覆盖 Twig 的 error.html.twig 模板。我们需要在 templates 目录中创建这个确切的目录路径:bundles/TwigBundle/Exception/。当发生 FileException 且环境设置为 production 时,将为用户生成此错误视图。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>
这是基本的 Twig 模板。
在本教程中,我们展示了如何在 Symfony 应用程序中上传文件。