Hướng dẫn lưu trữ file trên S3 với laravel

  • June 16, 2020
  • 2102
Amazon S3 (Simple Storage Service) là một dịch vụ tuyệt vời để lưu trữ tệp tin sử dụng công nghệ điện toán đám mây. Bài viết này, mình xin giới thiệu với các bạn cách sử dụng AWS S3 để lưu trữ file trong các dự án sử dụng Laravel

Nếu bạn sử dụng phiên bản laravel mới lớn hơn 5.7 thì đọc bài viết bên dưới nhé!

Hướng dẫn upload file to s3 với laravel storage

Cài đặt thư viện

Theo document của laravel thì trước khi sử dụng S3 driver cần cài đặt packge league/flysystem-aws-s3-v3 ~1.0 thông qua composer


composer require league/flysystem-aws-s3-v3

Cấu hình file .env


AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=
AWS_BUCKET=

Viết lại thư viện ở các phiên bản cũ < 5.7

Viết một file helper dùng chung (App/Helpers/Common.php)


<?php

namespace App\Helpers;

class Common
{
    /**
     * Ensure env is local
     *
     * @return bool
     */
    public static function isLocalEnv(): bool
    {
        return config('app.env') === 'local';
    }
}

Viết một trait để xử lý các vấn đề liên quan đến file trên s3(App/Traits/MediaTrait.php)


<?php

namespace App\Traits;

use App\Helpers\Common;
use Aws\S3\S3Client;
use League\Flysystem\Adapter\Local;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
use League\Flysystem\Filesystem;
use Illuminate\Support\Facades\Storage;
use Log;
use Exception;

trait MediaTrait
{
    protected $local;
    protected $s3;
    protected $rootLocalUpload = 'app/public';

    public function __construct()
    {
        if (Common::isLocalEnv()) {
            $this->initLocal();
        } else {
            $this->initS3();
        }
    }

    public function initLocal()
    {
        $adapter     = new Local(storage_path($this->rootLocalUpload));
        $this->local = new Filesystem($adapter);
    }

    public function initS3()
    {
        try {
            $client = new S3Client(
                [
                    'region'  => config('filesystems.disks.s3.region'),
                    'version' => 'latest',
                    'signature' => 'v4',
                ]
            );
            $adapter = new AwsS3Adapter(
                $client,
                config('filesystems.disks.s3.bucket')
            );

            $this->s3 = new Filesystem($adapter);
        } catch (Exception $e) {
            Log::error('[ERROR_S3_INIT] => '.$e->getMessage());
        }
    }

    /**
     * Upload file role private.
     *
     * @param object $file
     * @param string $path
     * @param boolean $isRename
     *
     * @return boolean|string
     */
    public function uploadImage(object $file, string $path, bool $isRename = true)
    {
        try {
            $fileName = $this->getFileName($file, $isRename);
            $key = $this->getUploadKey($path, $fileName);
            if (Common::isLocalEnv()) {
                $this->local->put($key, file_get_contents($file));

                return $fileName;
            }

            $this->s3->put($key, file_get_contents($file));

            return $fileName;
        } catch (Exception $e) {
            Log::error('[ERROR_S3_UPLOAD_IMAGE] =>' . $e->getMessage());

            return false;
        }//end try
    }

    /**
     * Upload file to s3 role public.
     *
     * @param object $file
     * @param string $path
     * @param boolean $isRename
     *
     * @return boolean|string
     */
    public function uploadS3Public(object $file, string $path, bool $isRename = true)
    {
        try {
            $fileName = $this->getFileName($file, $isRename);
            $key = $this->getUploadKey($path, $fileName);
            if (Common::isLocalEnv()) {
                $this->local->put($key, file_get_contents($file));

                return $fileName;
            }

            Storage::disk('s3')->put($key, file_get_contents($file), 'public');

            return $fileName;
        } catch (Exception $e) {
            Log::error('[ERROR_S3_UPLOAD_ROLE_PUBLIC] =>' . $e->getMessage());

            return false;
        }//end try
    }

    /**
     * Get file name.
     *
     * @param object $file
     * @param boolean $isRename
     *
     * @return string
     */
    public function getFileName(object $file, bool $isRename = true): string
    {
        $fileName = $file->getClientOriginalName();
        if ($isRename) {
            $fileName = encrypt_file_name(
                random_st(),
                $file->getClientOriginalExtension()
            );
        }
        
        return $fileName;
    }

    /**
     * Get public url.
     *
     * @param string $name
     * @param string $path
     *
     * @return null|string
     */
    public function getPublicUrl(string $name, string $path)
    {
        try {
            $key = $this->getUploadKey($path, $name);

            if (Common::isLocalEnv()) {
                return Storage::disk('local')->url($key);
            }

            if (!$this->s3) {
                return null;
            }
           
            $client = $this->s3->getAdapter()->getClient();
            if (!$this->isExistS3($key, $client)) {
                Log::error('[ERROR_S3_URL_NOT_EXIST][name='.$name.'][type='.$path.'] => '.'Image not found in S3');

                return null;
            }

            return Storage::disk('s3')->url($key);
        } catch (Exception $e) {
            Log::error('ERROR_S3_GET_PUBLIC_URL:'. $e->getMessage());

            return null;
        }//end try
    }


    /**
     * Get url.
     *
     * @param string $name
     * @param string $path
     *
     * @return null|string
     */
    public function getUrl(string $name, string $path)
    {
        try {
            $key = $this->getUploadKey($path, $name);
            if (Common::isLocalEnv()) {
                return Storage::disk('local')->url($key);
            }

            if (!$this->s3) {
                return null;
            }
           
            $client = $this->s3->getAdapter()->getClient();
            if (!$this->isExistS3($key, $client)) {
                Log::error('[ERROR_S3_URL_NOT_EXIST][name='.$name.'][type='.$path.'] => '.'Image not found in S3');

                return null;
            }

            return $this->generateS3Url($key, $client);
        } catch (Exception $e) {
            Log::error('ERROR_S3_GET_URL:'. $e->getMessage());

            return null;
        }//end try
    }

    /**
     * Check file exist s3
     *
     * @param string   $key
     * @param S3Client $client
     *
     * @return bool
     */
    public function isExistS3($key, $client): bool
    {
        return $client->doesObjectExist(
            config('filesystems.disks.s3.bucket'),
            $key
        );
    }

    /**
     * Check file exist s3
     *
     * @param string $key
     *
     * @return boolean
     */
    public function isFileExist(string $key): bool
    {
        try {
            $client = $this->s3->getAdapter()->getClient();

            return $this->isExistS3($key, $client);
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * Generate s3 url.
     *
     * @param string $key
     * @param object $client
     *
     * @return string
     */
    public function generateS3Url($key, $client): string
    {
        $cmd = $client->getCommand('GetObject', [
            'Bucket' => config('filesystems.disks.s3.bucket'),
            'Key'    => $key,
        ]);
        $request = $client->createPresignedRequest($cmd, config('filesystems.disks.s3.time_url'));

        return (string) $request->getUri();
    }

    /**
     * Delete image.
     *
     * @param string $name
     * @param string $path
     *
     * @return boolean
     */
    public function deleteImage(string $name, string $path): bool
    {
        try {
            $key = $this->getUploadKey($path, $name);
            if (Common::isLocalEnv()) {
                return $this->local->delete($key);
            }

            return $this->s3->delete($key);
        } catch (Exception $e) {
            Log::error('[ERROR_S3_DELETE_IMAGE] =>' . $e->getMessage());

            return false;
        }
    }

    /**
     * Copy file from S3 to local.
     *
     * @param string $s3Path path s3.
     * @param string $localPath path local.
     *
     * @return boolean
     */
    public function copyFileS3ToLocal(string $s3Path, string $localPath): bool
    {
        try {
            if (!$this->isFileExist($s3Path)) {
                Log::error('[ERROR_S3_COPY_FILE_S3_TO_LOCAL][s3Path='.$s3Path.'] => '.'Image not found in S3');
                
                return false;
            }
            
            $client = $this->s3->getAdapter()->getClient();
            $urlS3 = $this->generateS3Url($s3Path, $client);
            Storage::disk('local')->put($localPath, file_get_contents($urlS3));

            return true;
        } catch (Exception $e) {
            Log::error('ERROR_S3_COPY_FILE_S3_TO_LOCAL:'. $e->getMessage());

            return false;
        }
    }

    /**
     * Copy file from Local to S3.
     *
     * @param string $localPath path local.
     * @param string $s3Path path s3.
     *
     * @return boolean
     */
    public function copyFileLocalToS3(string $localPath, string $s3Path): bool
    {
        try {
            if (!Storage::disk('local')->exists($localPath)) {
                Log::error('[ERROR_S3_COPY_FILE_Local_TO_S3][s3Path='.$s3Path.'] => '.'Image not found in local');
                
                return false;
            }
            $localFile = Storage::disk('local')->get($localPath);
            $this->s3->put($s3Path, $localFile);

            return true;
        } catch (Exception $e) {
            Log::error('ERROR_S3_COPY_FILE_Local_TO_S3:'. $e->getMessage());

            return false;
        }
    }

    /**
     * Get upload key to s3.
     *
     * @param string $path path.
     * @param string $name name.
     *
     * @return string
     */
    public function getUploadKey(string $path, string $name): string
    {
        return sprintf('%s/%s', $path, $name);
    }

    public function moveObject($path, $newPath)
    {
        try {
            $client = $this->s3->getAdapter()->getClient();
            if ($this->isExistS3($path, $client)) {
                if ($this->s3->getAdapter()->copy($path, $newPath)) {
                    return $this->s3->getAdapter()->delete($path);
                }

                return false;
            }

            return false;
        } catch (Exception $e) {
            Log::error('[MOVE_OBJECT] => '.$e->getMessage());

            return false;
        }
    }

    public function copyObject($sourcePath, $destinationPath)
    {
        try {
            $client = $this->s3->getAdapter()->getClient();
            if ($this->isExistS3($sourcePath, $client)) {
                if ($this->s3->getAdapter()->copy($sourcePath, $destinationPath)) {
                    return true;
                }
            }

            return false;
        } catch (Exception $e) {
            Log::error('[COPY_OBJECT] => '.$e->getMessage());

            return false;
        }
    }

    /**
     * Copy file from S3 to local.
     *
     * @param string $s3Path    path s3.
     * @param string $localPath path local.
     *
     * @return boolean
     */
    public function copyFileS3ToLocal(string $s3Path, string $localPath)
    {
        try {
            if (!$this->isFileExist($s3Path)) {
                Log::error('[ERROR_S3_COPY_FILE_S3_TO_LOCAL][s3Path='.$s3Path.'] => '.'Image not found in S3');

                return false;
            }
            $client = $this->s3->getAdapter()->getClient();
            $urlS3 = $this->generateS3Url($s3Path, $client);
            Storage::disk('local')->put($localPath, file_get_contents($urlS3));

            return true;
        } catch (Exception $e) {
            Log::error('ERROR_S3_COPY_FILE_S3_TO_LOCAL:'.$e->getMessage());

            return false;
        }
    }

    /**
     * Copy file from Local to S3.
     *
     * @param string $localPath path local.
     * @param string $s3Path    path s3.
     *
     * @return boolean
     */
    public function copyFileLocalToS3(string $localPath, string $s3Path): bool
    {
        $files = Storage::disk('local')->allFiles($localPath);
        if ($files) {
            foreach ($files as $gif) {
                $localFile = Storage::disk('local')->path($gif);
                $newS3Path = $s3Path.'/'.basename($gif);
                $this->s3->put($newS3Path, fopen($localFile, 'r+'));
            }
        }

        return true;
    }

}

Viết một mediaHelper(App/Helpers/MediaHelper.php)


<?php

namespace App\Helpers;
use App\Traits\MediaTrait;

class MediaHelper
{
    use MediaTrait {
        MediaTrait::__construct as private __fhConstruct;
    }

    public function __construct()
    {
        $this->__fhConstruct();
    }

   /**
     * Get url.
     *
     * @param string $name
     * @param string $path
     *
     * @return null|string
     */
    public static function getS3Url(string $name, string $path)
    {
        $helper = new self();
        return $helper->getUrl($name, $path);
    }
}

Tham khảo tài liệu league/flysystem-aws-s3-v3