saloon: File Uploads Not Working

I’m using version 2.

Scenario: I need to POST to an API endpoint the following values:

  • reference_id : String
  • files: Array of Files

Here is my request file, with redactions:

<?php

namespace {redacted}\{redacted}\Requests;

use Saloon\Contracts\Body\HasBody;
use Saloon\Data\MultipartValue;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;
use Saloon\Traits\Body\HasMultipartBody;

class CreateClientRequest extends Request implements HasBody
{
    use HasMultipartBody;

    protected Method $method = Method::POST;

    public function __construct(public string|int $reference_id, public array $files = [])
    {
        //
    }

    public function resolveEndpoint(): string
    {
        return '/clients';
    }

    protected function defaultHeaders(): array
    {
        $boundary = md5(uniqid());

        return [
            'Content-Type' => "multipart/form-data; boundary=$boundary; charset=utf-8"
        ];
    }

    protected function buildMultipartValues(): array
    {
        $multipart_values = [];

        foreach($this->files as $file)
        {
            $multipart_values[] = new MultipartValue(
                    name: $file['name'],
                   value: file_get_contents($file['path']),
                filename: $file['filename'],
                 headers: [
                    'Content-Type' => 'text/plain'
                ]
            );
        }

        return $multipart_values;
    }

    protected function defaultBody(): array
    {
        $default = [new MultipartValue(name: 'reference_id', value: $this->reference_id)];
        $files   = $this->buildMultipartValues();

        return $default + $files;
    }
}


I’m following the documentation at: https://docs.saloon.dev/v/2/the-basics/request-body-data/multipart-form-body

The documentation states:

Saloon will automatically send the Content-Type: multipart/form-data header for you when using the HasMultipartBody trait, however, if you would like to overwrite this behaviour then you can use the defaultHeaders method on the request or modify the headers before the request is sent.

However, the receiving API does not show a multipart/form-data header which is why you’ll see my use of defaultHeaders().

The receiving API, a Laravel application, is validating the request with the following rules:

$request->validate([
          'reference_id' => [
              'required',
              'string',
              'max:255',
              Rule::unique('api_clients', 'reference_id')
                  ->where(
                      'api_organization_id',
                      $request->organization()->id
                  )
          ],
          'files'   => 'required|array',
          'files.*' => 'file|mimes:txt',
     ]);

Upon sending the request I receive:

  "message" => "The reference id field is required. (and 1 more error)"
  "errors" => array:2 [
    "reference_id" => array:1 [
      0 => "The reference id field is required."
    ]
    "files" => array:1 [
      0 => "The files field is required."
    ]
  ]
]

I’ve tried variations of the request (using different traits, defaultQuery(), etc).

Not sure if this helps but this is the guzzle sample Postman provided me when I successfully issued my API request with Postman:

<?php
$client = new Client();
$headers = [
  '{redacted}-Api-Key' => '{redacted}',
  'Accept' => 'application/json'
];
$options = [
  'multipart' => [
    [
      'name' => 'reference_id',
      'contents' => '123-456-789'
    ],
    [
      'name' => 'files[0]',
      'contents' => Utils::tryFopen('/Users/jordandalton/Code/Commercial/{redacted}/packages/package-skeleton-php/tests/tmp/test-file.txt', 'r'),
      'filename' => '/Users/jordandalton/Code/Commercial/{redacted}/packages/package-skeleton-php/tests/tmp/test-file.txt',
      'headers'  => [
        'Content-Type' => '<Content-type header>'
      ]
    ]
]];
$request = new Request('POST', 'https://{redacted}.test/api/v1/clients', $headers);
$res = $client->sendAsync($request, $options)->wait();
echo $res->getBody();

About this issue

  • Original URL
  • State: closed
  • Created 8 months ago
  • Comments: 18

Most upvoted comments

@ManukMinasyan Please can you open a separate issue.

I’d need to pull in the v3 branch to test. I was developing against v2.