{% raw %}

# Symfony 上传文件

> 原文： [http://zetcode.com/symfony/uploadfile/](http://zetcode.com/symfony/uploadfile/)

Symfony 上传文件教程显示了如何在 Symfony 应用中上传文件。 在示例中，我们使用普通形式发送文件； 我们不使用表单构建器。

## Symfony

Symfony 是一组可重用的 PHP 组件和一个用于 Web 项目的 PHP 框架。 Symfony 于 2005 年发布为免费软件。Symfony 的原始作者是 Fabien Potencier。 Symfony 受 Spring 框架和 Ruby on Rails 的极大启发。

## 上传文件

为了上传文件，`form`必须将`enctype`设置为`multipart/form-data`，并且`input`的类型设置为`file`。

同样，在 PHP 的`php.ini`中，文件上传由`file_uploads`选项控制。

## Symfony 文件上传示例

在示例中，我们有一个带有一个输入字段的简单表单：要上传的文件。 提交表单后，我们验证 CSRF 令牌并加载图像，检索其名称，并将文件存储在`var`目录中。

### 创建一个 Symfony 项目并安装包

composer 工具用于生成 Symfony 骨架项目并安装必要的包。

```php
$ composer create-project symfony/skeleton upload
$ cd upload

```

我们创建一个新的 Symfony 项目，然后转到项目目录。

```php
$ composer require maker annotations twig

```

我们为 Web 开发安装了三个基本的 Symfony 包：`annotations`，`maker`和`twig`。 这些是生成路由，控制器和模板所必需的。

```php
$ composer require symfony/security-csrf
$ composer require symfony/monolog-bundle

```

跨站点请求伪造需要`security-csrf`包，而日志记录则需要`monolog-bundle`包。

```php
$ composer require server --dev 
$ composer require symfony/profiler-pack --dev

```

在开发阶段，我们还安装了内置服务器和分析器。

### 构建 Symfony 应用

我们定义了要上传图像的目录。

`config/services.yaml`

```php
parameters:
    upload_dir: '../var/uploads'

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      
        autoconfigure: true 
        public: false       

        bind: 
            $uploadDir: '%upload_dir%'

```

我们定义一个参数，其中包含应将图像上传到的目录的名称。 `upload_dir`参数绑定到可以注入的`$uploadDir`变量。

```php
$ php bin/console make:controller HomeController

```

我们创建一个`HomeController`。 控制器将表单发送给客户端。

`src/Controller/HomeController.php`

```php
<?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 表单的视图发送给用户。

`templates/home/index.html.twig`

```php
{% extends 'base.html.twig' %}

{% block title %}Home page{% endblock %}

{% block body %}

<form action="doUpload" method="post" enctype="multipart/form-data">

    <input type="hidden" name="token" value="{{ csrf_token('upload') }}" />

    <div>
        <label>File to upload:</label>
        <input type="file" name="myfile">
    </div>

    <button type="submit">Send</button>

</form>

{% endblock %}

```

此视图创建一个表单。 它定义了`multipart/form-data`编码类型和`file`输入。 此外，它还具有 CSRF 隐藏输入令牌。

```php
$ php bin/console make:controller UploadController    

```

我们创建一个`UploadController`来响应表单提交。 我们不需要为该控制器生成的树枝模板； 因此，我们将其删除。

`src/Controller/UploadController.php`

```php
<?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="upload")
     */
     public function index(Request $request, string $uploadDir, 
             FileUploader $uploader, LoggerInterface $logger)
    {
        $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 令牌，从请求中获取文件，然后调用上载器服务`upload()`方法。

```php
public function index(Request $request, string $uploadDir, 
        FileUploader $uploader, LoggerInterface $logger)
{

```

我们注入了请求对象，上传目录参数，`FileUploader`服务和记录器。

```php
$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`响应代码。

```php
$file = $request->files->get('myfile');

if (empty($file)) 
{
    return new Response("No file specified",  Response::HTTP_UNPROCESSABLE_ENTITY, 
        ['content-type' => 'text/plain']);  
}     

```

我们检查用户是否使用`empty()`方法指定了格式的任何文件。 如果输入字段为空，我们将使用`Response::HTTP_UNPROCESSABLE_ENTITY`响应代码将纯文本`"No file specified"`发送回客户端。

```php
$filename = $file->getClientOriginalName();

```

我们使用`getClientOriginalName()`获得文件名。

```php
$uploader->upload($uploadDir, $file, $filename);

```

我们调用上载器服务`upload()`方法，该方法将文件移动到所选目录。 我们向该方法传递目录名，文件数据和文件名。

```php
return new Response("File uploaded",  Response::HTTP_OK, 
    ['content-type' => 'text/plain']);  

```

如果一切正常，我们将使用`Response::HTTP_OK`响应代码将简单的消息`"File uploaded"`发送回客户端。

`src/Service/FileUploader.php`

```php
<?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`。 这将导致生成错误页面。

`templates/home/index.html.twig`

```php
{% extends 'base.html.twig' %}

{% block title %}Home page{% endblock %}

{% block body %}

<form action="doUpload" method="post" enctype="multipart/form-data">

    <input type="hidden" name="token" value="{{ csrf_token('upload') }}" />

    <div>
        <label>File to upload:</label>
        <input type="file" name="myfile">
    </div>

    <button type="submit">Send</button>

</form>

{% endblock %}

```

该模板包含表单。

`templates/bundles/TwigBundle/Exception/error.html.twig`

```php
{% 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`时，将为用户生成此错误视图。

`templates/base.html.twig`

```php
<!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 应用中上传文件。

您可能也会对以下相关教程感兴趣： [Symfony 简介](/symfony/intro/)， [Symfony DBAL 教程](/symfony/dbal/)， [Symfony 表单教程](/symfony/form/)， [Symfony 服务教程](/symfony/service/) ， [Symfony 验证教程](/symfony/validation/)， [PHP 教程](/lang/php/)。

{% endraw %}