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 应用程序中上传文件。