Skip to content

Conversation

@emmanuel-averty
Copy link
Contributor

@emmanuel-averty emmanuel-averty commented Dec 15, 2022

As mention in #5136 and #5464, EasyAdmin is tight linked to local filesystem. This is an issue when one wants to use a s3 bucket to store images uploaded through FileUploadType.
Instead of override upload_new and upload_delete closure to move the file to the bucket (tip from SymfonyCast), you can use the stream wrapper of s3 client :

  • call registerStreamWrapper after instantiate S3 client
  • set ImageField uploadDir to s3://bucket-name/`
  • no need to set upload_new nor upload_delete type options

But doing this isn't enough because EasyAdmin check uploadDir existence prepended with project directory (in order to have an absolute directory name).

I propose with this PR to not prepend project directory when the uploadDir is an url.

@javiereguiluz javiereguiluz added this to the 4.x milestone Dec 15, 2022
@javiereguiluz javiereguiluz merged commit 5d40afe into EasyCorp:4.x Dec 15, 2022
@javiereguiluz
Copy link
Collaborator

Emmanuel, thanks for this contribution. I don't know much about this because I don't use services like S3, so thanks for explaining things and for providing a fix for it.

@rogercbe
Copy link
Contributor

rogercbe commented Sep 14, 2023

Could you provide an example of this? Seems way cleaner than the solution that our team came up with but I can't get it to work:

As you mentioned "Instead of override upload_new and upload_delete closure to move the file to the bucket (tip from SymfonyCast)", that's the approach we took, but it feels a little hacky

ImageField::new('photoUrl')
    ->setUploadDir('var/tmp') // hack to make it work on prod environments
    ->setUploadedFileNamePattern([name].[extension]')
    ->setFormTypeOptions([
        'upload_new' => function (UploadedFile $uploadedFile) {
            return $this->fileStorer->saveAs(
                $uploadedFile,
                $uploadedFile->getClientOriginalName(),
                'path/to/bucket'
            );
        },
    ])

@emmanuel-averty
Copy link
Contributor Author

Hi @rogercbe,

My use case is to save a product image into a S3 bucket.

I've created a ProductHelper class which contains a method that returns the URL of the target directory (I'm aware that the method name is not accurate) :

class ProductHelper
{
    public function __construct(
    ) {
        // …
        private readonly S3Client $productBucketClient // @phpstan-ignore-line
    }

    // …

    public function getImageStreamUrl(): string
    {
        return 's3://my-bucket/
    }
}

Here is the service definition in service.yaml :

    bucket_client.product_image:
        class: Aws\S3\S3Client
        autowire: false
        arguments:
            -
                version: '2006-03-01'
                region: '%env(AWS_S3_REGION)%'
                use_path_style_endpoint: '%env(bool:AWS_S3_USE_PATH_STYLE_ENDPOINT)%'
                endpoint: '%env(AWS_S3_ENDPOINT)%'
                credentials:
                    key: '%env(AWS_S3_KEY_BUCKET_PRODUCT_IMAGE)%'
                    secret: '%env(AWS_S3_SECRET_BUCKET_PRODUCT_IMAGE)%'
        calls:
            - registerStreamWrapper: []
    

    App\Service\ProductHelper:
        arguments:
            #
            $productBucketClient: '@bucket_client.product_image'

In the ProductCrudController here is my field definition :

        yield ImageField::new('imageFile')
            ->setBasePath('')
            ->setUploadDir($this->productHelper->getImageStreamUrl())
            // ->set…

The trick is to let Symfony call the S3Client::registerStreamWrapper method when it instantiate ProductHelper. That's why the S3Client is a «fake dependency» of ProductHelper. I had to add the // @phpstan-ignore-line to avoid phpstan errors

All that stuff is quite ugly, that's why I've proposed #5555.

Hope this helps

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants