Symfony无法识别通过Guzzle multipart / form-data请求上传的多个文件

Wor*_*orp 4 php symfony guzzle6

环境:Guzzle 6 Symfony 2.3

通过Guzzle POST请求上载多个文件应该与multipart请求一起完成。所以我这样配置$ options数组:

Array
(
[multipart] => Array
    (
        [0] => Array
            (
                [name] => filename-0
                [contents] => Resource id #440
                [filename] => filename-0
            )

        [1] => Array
            (
                [name] => filename-1
                [contents] => Resource id #441
                [filename] => filename-1
            )

        [2] => Array
            (
                [name] => filename-2
                [contents] => Resource id #442
                [filename] => filename-2
            )

    )

[headers] => Array
    (
        [Accept] => application/json
        [Content-Type] => multipart/form-data
        [Accept-Language] => de
    )

[allow_redirects] =>
[http_errors] =>
Run Code Online (Sandbox Code Playgroud)

multipart数组中的资源是fopen()的结果。

并使用发送请求

$response = $this->client->post(
    '/someUrl/someAction',
    $options
);
Run Code Online (Sandbox Code Playgroud)

使用已经创建的客户端。

在接受Symfony-Controllers方面,我无法发送文件:

var_dump($_FILES); // array(0) {}
var_dump($_POST);  // array(0) {}
var_dump(count($request->files->all())); // int(0)
Run Code Online (Sandbox Code Playgroud)

但是,这两个:

var_dump(file_get_contents("php://input"));
var_dump($request->getContent());
Run Code Online (Sandbox Code Playgroud)

在输入流上返回数据:

/myPath/FileController.php:xx:
string(601) "--e55f849feb078da4a9e35ba77da3ded02ec813a7
Content-Disposition: form-data; name="filename-0"; filename="filename-0"
Content-Length: 43

This is a testfile...
--e55f849feb078da4a9e35ba77da3ded02ec813a7
Content-Disposition: form-data; name="filename-1"; filename="filename-1"
Content-Length: 43

This is a testfile...
--e55f849feb078da4a9e35ba77da3ded02ec813a7
Content-Disposition: form-data; name="filename-2"; filename="filename-2"
Content-Length: 43

This is a testfile...
--e55f849feb078da4a9e35ba77da3ded02ec813a7--
"
Run Code Online (Sandbox Code Playgroud)

如何以Symfony方式进入接收控制器?

需要考虑的好奇心:管制员报告

var_dump($request->getContentType()); // NULL
Run Code Online (Sandbox Code Playgroud)

我的直觉说这可能很重要。

Phi*_*ber 8

When using the multipart option, you must not specify the Content-Type header yourself. Guzzle will take care of this and - more important for this issue - of the boundary that separates the content parts in the raw request body. The main Content-Type header defines that boundary like so:

Content-Type: multipart/form-data; boundary=unique-string-that-is-hopefully-not-used-in-the-real-content-because-it-separates-the-content-parts`
Run Code Online (Sandbox Code Playgroud)

Check Client.php#L308: Guzzle creates the request body as GuzzleHttp\Psr7\MultipartStream object when multipart option was used.

Then check the MultipartStream constructor at MultipartStream.php#L30: A random value is created to be used as the boundary: sha1(uniqid('', true))

Guzzle will then use the boundary to set the correct content type on its own at Client.php#L383.

但是由于Guzzle不会覆盖您已明确指定的选项,因此主标头指定了一个空边界,并且原始主体部分将由MultpartStream构造函数创建的边界分隔。在接收端,PHP无法再分离内容部分。

该代码对我有用:

(new GuzzleHttp\Client())->request(
    'POST',
    'http://localhost/upload',
    [
        'multipart' => [
            [
                'name' => 'filename-0',
                'contents' => fopen(__DIR__.'/sample-0.txt', 'rb'),
                'filename' => 'filename-0',
            ],
            [
                'name' => 'filename-1',
                'contents' => fopen(__DIR__.'/sample-1.txt', 'rb'),
                'filename' => 'filename-1',
            ],
            [
                'name' => 'filename-2',
                'contents' => fopen(__DIR__.'/sample-2.txt', 'rb'),
                'filename' => 'filename-2',
            ],
        ],
        'headers' => [
            # Do not override the Content-Type header here, Guzzle takes care of it
        ] ,
    ]
);
Run Code Online (Sandbox Code Playgroud)

请注意,所有这一切都是不Symfony的关系,即Symfony的只是创建它Request从它可以通过PHP的原生来源获取对象喜欢$_FILES$_POST等你能看到所有的原始请求体(内容$request->getContent()),因为它是像发送这样,但是PHP无法分割内容部分,因为它不知道正确的边界。