Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP Client Multipart with multidimensional array inputs #37174

Closed
omzy opened this issue Apr 29, 2021 · 12 comments
Closed

HTTP Client Multipart with multidimensional array inputs #37174

omzy opened this issue Apr 29, 2021 · 12 comments

Comments

@omzy
Copy link

omzy commented Apr 29, 2021

  • Laravel Version: 8.37.0
  • PHP Version: 7.3.13
  • Database Driver & Version: MySQL 5.7.32

Description:

The fix applied in #32058 does not work for multidimensional array inputs.

Steps To Reproduce:

HTML:

<form method="post" action="" enctype="multipart/form-data">
    <input type="text" name="data[Product][user_id]">
    <input type="text" name="data[Product][supplier_id]">
    <input type="file" name="file">
</form>

PHP:

public function index(Request $request)
{
    $url = 'my-url';
    $post_data = $request->post();

    // Example of how $post_data will look
    $post_data = [
        'data[Product][user_id]' => 123,
        'data[Product][supplier_id]' => 456,
    ];

    $contents = fopen($request->file('file'), 'r');

    return Http::attach('file', $contents)
        ->post($url, $post_data);
}

This produces an error: A 'contents' key is required.

A print_r() of $laravelData (line 714 src/Illuminate/Http/Client/PendingRequest.php) outputs the following:

Array
(
    [0] => Array
        (
            [Product] => Array
                (
                    [user_id] => 123
                    [supplier_id] => 456
                )
        )

    [1] => Array
        (
            [name] => file
            [contents] => Resource id #439
        )
)

As you can see, it does not format the input variables into name and contents pairs, like it has done with the file input.

@omzy
Copy link
Author

omzy commented Apr 29, 2021

In the meantime, I have used the code in this answer here to reformat the POST array: https://stackoverflow.com/a/53731825/372630

It would be great if we could integrate something like this in to the library, as the error isn't helpful and it took me a while to figure out the problem.

@omzy
Copy link
Author

omzy commented Apr 29, 2021

Any potential solution would need to take in to account:

  • POST array containing a mixture of standard inputs and multidimensional array inputs
  • POST array containing a numerically indexed array, e.g:
<input type="text" name="data[categories][]" value="1">
<input type="text" name="data[categories][]" value="2">

@taylorotwell
Copy link
Member

Are you able to submit a PR?

@driesvints
Copy link
Member

Closing this issue because it's inactive, already solved, old or not relevant anymore. Feel free to reply if you're still experiencing this issue and we'll re-open this issue.

@nickfls
Copy link

nickfls commented Jun 15, 2021

This is definitely happening:

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\File;

$path = storage_path('file.txt');

Http::acceptJson()
    ->attach('attachments[]', File::get($path), File::basename($path))
    ->post($destination, [
        'name' => 'John Smith',
        'providers' => [1,2,3]
]);

with throw InvalidArgumentException with message 'A 'contents' key is required'
where as will be just fine

Http::acceptJson()
    ->attach('attachments[]', File::get($path), File::basename($path))
    ->post($destination, [
        'name' => 'John Smith',
        'providers' => '1'
]);

@renwarkurd
Copy link

Same issue here.

@mikecummings10
Copy link

This is still an issue. Please re-open.

@codoffer
Copy link

@mikecummings10 @renwarkurd @nickfls Have you found any fix for this issue?

@Astriel
Copy link

Astriel commented Jan 25, 2024

Still bugging for me on Laravel 10.

@spider4216
Copy link

the same in Laravel 10

@bernardwiesner
Copy link
Contributor

still happening, no one seems to care about fixing this one...

@RomainMazB
Copy link

RomainMazB commented Oct 7, 2024

still happening, no one seems to care about fixing this one...

I made a PR to fix it but it sounds like it won't be merged.

After more investigations on what happens and what would be the cleaner/easier way to tackle this. Here is my final thoughts.

Why

When using Laravel client to upload a file or with the asMultipart method, Laravel internally uses the Guzzle multipart option, which states:

The value of multipart is an array of associative arrays, each containing the following key value pairs:

  • name: (string, required) the form field name
  • contents: (StreamInterface/resource/string, required) The data to use in the form element.
  • headers: (array) Optional associative array of custom headers to use with the form element.
  • filename: (string) Optional string to send as the filename in the part.

As you can see, the contents parameter can't handle an array. And Laravel won't convert your fields to a Guzzle well-formatted format. If you pass a value as an array into Laravel, the value is passed "as is" due to this modification

How

Now this is known, you understand that it's up to us to prepare the nested array to the Guzzle format.
Happily, Symfony's FormDataPart class can do most of the job for us

// The data with nested array
$data = [
  'products' => [
    [
      'id' => '123',
      'quantity' => '1',
    ],
    [
      'id' => '321',
      'name' => '2',
    ]
  ]
];

// Use the FormDataPart from Symfony to flatten our nested array values into a one level array
$formParts = (new \Symfony\Component\Mime\Part\Multipart\FormDataPart($data))->getParts();

// Convert each part to the name/contents Guzzle format
$guzzleData = array_map(fn ($field) => [
    'name' => $field->getName(),
    'contents' => $field->bodyToString()
], $formParts);

$request = Http::asMultipart()->post($url, $guzzleData);

Requirements

There is only one requirement that I faced: Symfony's class only accepts a string as value.
All the values in your nested array should be strings, boolean should be converted to "true", integer 1 should be converted to a string value "1" and so on...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests