I have a Symfony project made up of several microservices, each in a different repo, and a microservice that acts as an API Gateway, through which the fronts make their calls in order to verify user authentication via a token and access rights to the resource in question before making a request with Guzzle.
I'm currently in charge of the microservice linked to document management (upload, download, etc.) on an S3 bucket via minIO.
This is the Controller of my microservice, and the service it calls:
<?php
namespace App\Controller;
use App\Service\UploadFileService;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class UploadFileController extends AbstractController
{
private UploadFileService $uploadFileService;
public function __construct(UploadFileService $uploadFileService)
{
$this->uploadFileService = $uploadFileService;
}
#[Route('/api/v1/upload_files', name: 'upload_files', methods:['POST'])]
public function uploadFiles(Request $request): JsonResponse
{
return $this->uploadFileService->uploadFiles($request);
}
}
<?php
namespace App\Service;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Aws\S3\S3Client as AmazonS3;
class UploadFileService extends AbstractController
{
public function uploadFiles(Request $request): JsonResponse
{
try {
$files = $request->files->all('files');
$urls = $request->request->all('urls');
//On vérifie qu'il y a des fichiers, et qu'il y a autant d'url que de fichiers
if (empty($files) || count($files) !== count($urls)) {
return $this->json(['error' => 'Invalid files or URLs.']);
}
//On se connecte à minIo
$s3 = new AmazonS3([
'version' => 'latest',
'region' => 'eu',
'endpoint' => $_ENV['S3_URL'],
'use_path_style_endpoint' => true,
'credentials' => [
'key' => $_ENV['S3_ACCESS_KEY'],
'secret' => $_ENV['S3_SECRET_KEY']
],
]);
$results = [];
foreach ($files as $index => $file) {
//On vérifie la validité du fichier
if (!$file instanceof UploadedFile || !$file->isValid()) {
return $this->json(['error' => 'Invalid file.']);
}
$fileContent = fopen($file->getPathname(), 'rb');
// On utilise finfo pour détecter le type MIME pour l'enregistrer sous le bon format
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file->getPathname());
//On l'enregistre
$result = $s3->putObject([
'Bucket' => 'documents',
'Key' => $urls[$index],
'Body' => $fileContent,
'ContentType' => $mimeType,
]);
fclose($fileContent);
$results[] = $result['@metadata']['statusCode'];
}
return $this->json(['results' => $results]);
} catch (\Exception $ex) {
return $this->json(['Exception' => $ex->getMessage()], 400);
}
}
}
This code is functional, with a direct Postman call of this type:

The problem now arises when I try to use the Gateway API, whose code is as follows:
<?php
namespace App\Controller\DocumentService;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use App\Security\AccessControl;
#[Route('/api/v1/syndicatDashboard/{coproprieteId}/upload', name:'app_document_upload', methods:['POST'])]
class UploadFileController extends AbstractController
{
private AccessControl $accessControl;
public function __construct(AccessControl $accessControl)
{
$this->accessControl = $accessControl;
}
public function __invoke(string $coproprieteId, Request $request): JsonResponse
{
try {
$this->accessControl->getVoteCopropriete($this->getUser()->getId(),$this->getUser()->getRoles(), $coproprieteId);
$url = $_ENV['GESTION_DOCUMENTS']."/api/v1/upload_files";
$client = new Client();
$response = $client->post($url, [
'body' => $request->getContent(),
'headers' => $request->headers->all(),
]);
return $this->json(json_decode($response->getBody()->getContents()), 200);
} catch (GuzzleException $e) {
if (str_contains($e->getMessage(), "cURL error 7")) {
throw new \Exception($e->getMessage());
}
return $this->json(json_decode($e->getResponse()->getBody()->getContents()), $e->getCode());
} catch (\Throwable $e) {
return $this->json(["Exception" => $e->getMessage()], $e->getCode());
}
}
}
I'm doing a check linked to the token and the $coproprieteId string, but for the "body" I'd just like to retrieve it and send it to my microservice, but if I return $request-\>getContent() it's empty.
I've also tried sending $request-\>request-\>all('urls') directly and it works, but I can't send $request-\>files-\>all('files'): if I display it in my API gateway I've got my files, but on the microservice side the value becomes null.
So my question is: how do I get the body and send it to my microservice?
Check guzzle multipart documentation for sending files with guzzle.
Simple example