Gaufrette (with AWS) & Symfony 4 - Read a picture

1k Views Asked by At

Good afternoon,

I try to display an image from AWS S3 storage. I use the gaufrette file configuration which works when I upload file in AWS S3 storage.

Gaufrette configuration:

knp_gaufrette:
    adapters:
        images:
            aws_s3:
                service_id: "infra.aws_s3.client"
                bucket_name: "%amazon_s3.bucket_name%"
                detect_content_type: true
                options:
                    directory: "%amazon_s3.directories.images%"
                    acl: public-read
        documents:
            aws_s3:
                service_id: "infra.aws_s3.client"
                bucket_name: "%amazon_s3.bucket_name%"
                detect_content_type: true
                options:
                    directory: "%amazon_s3.directories.documents%"
                    acl: public-read
        imports:
            aws_s3:
                service_id: "infra.aws_s3.client"
                bucket_name: "%amazon_s3.bucket_name%"
                detect_content_type: true
                options:
                    directory: "%amazon_s3.directories.imports%"
                    acl: public-read
    filesystems:
        images:
            adapter: images
            alias: images_filesystem
        documents:
            adapter: documents
            alias: documents_filesystem
        imports:
            adapter: imports
            alias: imports_filesystem

Controller code:

<?php

namespace App\Application\Controller;

use App\Application\Controller\Rest\RestController;
use App\Domain\Entity\BuildingMedia;
use App\Domain\Entity\PropertyPicture;
use App\Infrastructure\Command\Command\CommandBus;
use App\Infrastructure\Repository\BuildingMediaRepository;
use App\Infrastructure\Repository\PropertyPictureRepository;
use Gaufrette\Filesystem;
use Gaufrette\StreamWrapper;
use JMS\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Swagger\Annotations as SWG;
use Nelmio\ApiDocBundle\Annotation\Operation;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;

class MediaController extends RestController
{
    /**
     * @var PropertyPictureRepository
     */
    private $propertyPictureRepository;

    /**
     * @var BuildingMediaRepository
     */
    private $buildingMediaRepository;

    /**
     * @var Filesystem
     */
    private $awsS3;

    /**
     * MediaController constructor.
     *
     * @param PropertyPictureRepository $propertyPictureRepository
     * @param BuildingMediaRepository   $buildingMediaRepository
     * @param Filesystem                $awsS3
     * @param SerializerInterface       $serializer
     * @param CommandBus                $commandBus
     */
    public function __construct(
        PropertyPictureRepository $propertyPictureRepository,
        BuildingMediaRepository $buildingMediaRepository,
        Filesystem $awsS3,
        SerializerInterface $serializer,
        CommandBus $commandBus
    ) {
        $this->propertyPictureRepository = $propertyPictureRepository;
        $this->buildingMediaRepository = $buildingMediaRepository;
        $this->awsS3 = $awsS3;

        parent::__construct($serializer, $commandBus);
    }

    /**
     * @Operation(
     *     tags={"Media"},
     *     summary="Read a property's picture.",
     *     @SWG\Response(
     *         response="200",
     *         description="OK"
     *     ),
     *     @SWG\Response(
     *         response="400",
     *         description="Bad request"
     *     ),
     *     @SWG\Response(
     *         response="401",
     *         description="Unauthorized"
     *     ),
     *     @SWG\Response(
     *         response="403",
     *         description="Access denied"
     *     ),
     *     @SWG\Response(
     *         response="404",
     *         description="Entity not found"
     *     ),
     *     @SWG\Response(
     *         response="500",
     *         description="Internal server error"
     *     )
     * )
     *
     * @ParamConverter("propertyPicture", converter="property_picture")
     *
     * @param PropertyPicture $propertyPicture
     *
     * @return BinaryFileResponse
     */
    public function propertyPictureReadAction(PropertyPicture $propertyPicture): BinaryFileResponse
    {
        $adapter = $this->awsS3->getAdapter();

        $filePath = $adapter->read(
            'group'.DIRECTORY_SEPARATOR.
            $this->getUser()->getGroupId().DIRECTORY_SEPARATOR.
            'property'.DIRECTORY_SEPARATOR.
            $propertyPicture->getProperty()->getIdValue().DIRECTORY_SEPARATOR.
            $propertyPicture->getFilename()
        );

        $response = new BinaryFileResponse($filepath);
        $response->headers->set('Content-Type', 'image/jpeg');
        $response->setContentDisposition(
            ResponseHeaderBag::DISPOSITION_INLINE,
            $propertyPicture->getFilename()
        );

        return $response;
    }
}

If I try to use getLink() from $propertyPicture (the method returns the file URL save in database), BinaryFileResponse not found the file from given URL.

I need to have the file URL from Gaufrette to display the file in API response.

1

There are 1 best solutions below

0
On BEST ANSWER

After try many possible solution, I have find this one for pictures (and it is working perfecly, even if you use a private bucket in AWS S3):

<?php

namespace App\Application\Controller;

use App\Application\Controller\Rest\RestController;
use App\Domain\Entity\BuildingMedia;
use App\Domain\Entity\PropertyPicture;
use App\Infrastructure\Command\Command\CommandBus;
use App\Infrastructure\Repository\BuildingMediaRepository;
use App\Infrastructure\Repository\PropertyPictureRepository;
use Gaufrette\Filesystem;
use JMS\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\Response;
use Swagger\Annotations as SWG;
use Nelmio\ApiDocBundle\Annotation\Operation;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpKernel\Exception\HttpException;

class MediaController extends RestController
{
    /**
     * @var PropertyPictureRepository
     */
    private $propertyPictureRepository;

    /**
     * @var BuildingMediaRepository
     */
    private $buildingMediaRepository;

    /**
     * @var Filesystem
     */
    private $awsS3;

    /**
     * MediaController constructor.
     *
     * @param PropertyPictureRepository $propertyPictureRepository
     * @param BuildingMediaRepository   $buildingMediaRepository
     * @param Filesystem                $awsS3
     * @param SerializerInterface       $serializer
     * @param CommandBus                $commandBus
     */
    public function __construct(
        PropertyPictureRepository $propertyPictureRepository,
        BuildingMediaRepository $buildingMediaRepository,
        Filesystem $awsS3,
        SerializerInterface $serializer,
        CommandBus $commandBus
    ) {
        $this->propertyPictureRepository = $propertyPictureRepository;
        $this->buildingMediaRepository = $buildingMediaRepository;
        $this->awsS3 = $awsS3;

        parent::__construct($serializer, $commandBus);
    }

    /**
     * @Operation(
     *     tags={"Media"},
     *     summary="Read a property's picture.",
     *     @SWG\Response(
     *         response="200",
     *         description="OK"
     *     ),
     *     @SWG\Response(
     *         response="400",
     *         description="Bad request"
     *     ),
     *     @SWG\Response(
     *         response="401",
     *         description="Unauthorized"
     *     ),
     *     @SWG\Response(
     *         response="403",
     *         description="Access denied"
     *     ),
     *     @SWG\Response(
     *         response="404",
     *         description="Entity not found"
     *     ),
     *     @SWG\Response(
     *         response="500",
     *         description="Internal server error"
     *     )
     * )
     *
     * @ParamConverter("propertyPicture", converter="property_picture")
     *
     * @param PropertyPicture $propertyPicture
     *
     * @return Response
     */
    public function propertyPictureReadAction(PropertyPicture $propertyPicture): Response
    {
        $adapter = $this->awsS3->getAdapter();

        $content = $adapter->read(
            'group'.DIRECTORY_SEPARATOR.
            $this->getUser()->getGroupId().DIRECTORY_SEPARATOR.
            'property'.DIRECTORY_SEPARATOR.
            $propertyPicture->getProperty()->getIdValue().DIRECTORY_SEPARATOR.
            $propertyPicture->getFilename()
        );

        return new Response($content, 200, array(
                'Content-Type' => 'image/jpeg'
        ));
    }
}

The read() method from Gaufrette returns the file content, not the real path. So, I have just change the returns value from BinaryFileResponse to Response and set manually the Content-Type.

The next upgrade, if you have the same processing is to set the type MIME in database when file is save and returns it when you need to display it like with a similar route from your API.