Skip to content

Storage drivers

Alejandro Glejberman edited this page Apr 13, 2023 · 1 revision

Widestand utiliza el disco para el almacenamiento de assets. Para esto, utiliza una carpeta llamada storage, que se symlinkea a public para acceder a los assets públicos.

Pero es posible modificar el almacenamiento de los assets, para por ejemplo utilizar S3 u otro sistema de almacenamiento. Para esto es necesario definir un driver específico que se encargue de las tareas relacionadas con el almacenamiento de ficheros. Este driver debe implementar la clase WS\Core\Library\Storage\StorageDriverInterface y debemos indicar en el proyecto que se utilizará este driver:

parameters:
    storage.driver: 'local'

Ejemplo de driver para S3

La clase que implementa el driver de S3 puede tener cualquier nombre y ubicarse en cualquier sitio del proyecto. Un namespace sugerido es App\Library\Storage.

El método getName debe devolver un identificador de tipo string que luego se usará en la configuración del proyecto para indicar que usaremos dicho driver. Para este ejemplo, el fichero services.yaml ser vería así:

parameters:
    storage.driver: 'awss3'
    storage.configuration:
        cdn: 'https://cdn.xxx/'
        bucket: '<bucket-name>'
        region: '%env(default::AWS_REGION)%'
        key: '%env(default::AWS_ACCESS_KEY_ID)%'
        secret: '%env(default::AWS_SECRET_ACCESS_KEY)%'
<?php

namespace App\Library\Storage;

use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Aws\S3\S3Client;
use WS\Core\Library\Storage\StorageDriverException;
use WS\Core\Library\Storage\StorageDriverInterface;

class AwsS3Driver implements StorageDriverInterface
{
    private ?S3Client $s3Client = null;
    private string $cdn;
    private string $bucket;
    private ?string $prefix;
    private string $region;
    private ?string $key;
    private ?string $secretKey;

    public function __construct(private ParameterBagInterface $params)
    {
    }

    public function getName(): string
    {
        return 'awss3';
    }

    public function setConfiguration(): void
    {
        if (!$this->params->has('storage.configuration')) {
            throw new StorageDriverException('AWS S3 driver requires configuration to be defined');
        }

        if (!isset($this->params->get('storage.configuration')['cdn'])) {
            throw new StorageDriverException('AWS S3 driver requires cdn domain to be defined');
        }

        if (!isset($this->params->get('storage.configuration')['bucket'])) {
            throw new StorageDriverException('AWS S3 driver requires bucket name to be defined');
        }

        $this->cdn = $this->params->get('storage.configuration')['cdn'];
        $this->bucket = $this->params->get('storage.configuration')['bucket'];
        $this->prefix = $this->params->get('storage.configuration')['prefix'] ?? null;
        $this->region = $this->params->get('storage.configuration')['region'];
        $this->key = $this->params->get('storage.configuration')['key'] ?? null;
        $this->secretKey = $this->params->get('storage.configuration')['secret'] ?? null;
    }

    public function getStorageMetadata(): array
    {
        return [
            'bucket' => $this->bucket,
            'prefix' => $this->prefix,
        ];
    }

    public function save(string $filePath, string $content, string $context): void
    {
        $this->getS3Client()->putObject([
            'ACL' => 'private',
            'Bucket' => $this->bucket,
            'ContentType' => (new \finfo(\FILEINFO_MIME_TYPE))->buffer($content),
            'Key' => $this->getStorageKey($filePath, ['prefix' => $this->prefix]),
            'Body' => $content,
        ]);
    }

    public function get(string $filePath, string $context, array $options): string
    {
        return $this->getS3Client()->getObject([
            'Bucket' => $this->bucket,
            'Key' => $this->getStorageKey($filePath, $options)
        ])->get('Body');
    }

    public function exists(string $filePath, string $context, array $options): bool
    {
        return $this->getS3Client()->doesObjectExist($this->bucket, $this->getStorageKey($filePath, $options));
    }

    public function getPublicUrl(string $filePath, array $options): string
    {
        return sprintf('%s/%s', rtrim($this->cdn, '/'), $this->getStorageKey($filePath, $options));
    }

    private function getStorageKey(string $filePath, array $options): string
    {
        $key = $filePath;
        if (isset($options['prefix']) && null !== $options['prefix']) {
            $key = sprintf('%s/%s', $options['prefix'], $filePath);
        }

        return $key;
    }

    private function getS3Client(): S3Client
    {
        if ($this->s3Client === null) {
            $options = [
                'region' => $this->region,
                'version' => '2006-03-01',
            ];
            if (null !== $this->key) {
                $options['credentials'] = [
                    'key' => $this->key,
                    'secret' => $this->secretKey,
                ];
            }
            $this->s3Client = new S3Client($options);
        }

        return $this->s3Client;
    }
}