Flysystem en PHP: abstracción de sistemas de ficheros para local, S3 y SFTP

Flysystem es una librería de abstracción del sistema de ficheros que permite trabajar con almacenamiento local, Amazon S3, SFTP y otros backends con exactamente la misma interfaz. Si cambias de almacenamiento, solo cambias el adaptador.

Instalación

# Core
composer require league/flysystem

# Adaptador S3 (requiere aws-sdk-php)
composer require league/flysystem-aws-s3-v3

# Adaptador SFTP
composer require league/flysystem-sftp-v3

Adaptador local

<?php
use LeagueFlysystemFilesystem;
use LeagueFlysystemLocalLocalFilesystemAdapter;

$adapter    = new LocalFilesystemAdapter('/var/www/almacenamiento');
$filesystem = new Filesystem($adapter);

// Escribir un fichero
$filesystem->write('documentos/factura.txt', 'Contenido de la factura');

// Leer
$contenido = $filesystem->read('documentos/factura.txt');

// Comprobar existencia
if ($filesystem->fileExists('documentos/factura.txt')) {
    echo "El fichero existen";
}

// Eliminar
$filesystem->delete('documentos/factura.txt');

// Copiar y mover
$filesystem->copy('original.jpg', 'copia.jpg');
$filesystem->move('copia.jpg',    'nueva-ubicacion/copia.jpg');
?>

Subida de imágenes con adaptador local

<?php
// En un endpoint de subida de ficheros
function subirImagen(SplFileInfo $ficheroSubido, string $destino): string
{
    $extension = strtolower($ficheroSubido->getExtension());
    $nombre    = bin2hex(random_bytes(16)) . '.' . $extension;
    $ruta      = "imagenes/usuarios/$nombre";

    $stream = fopen($ficheroSubido->getPathname(), 'r');
    $filesystem->writeStream($ruta, $stream);
    fclose($stream);

    return $ruta;
}
?>

Adaptador Amazon S3

<?php
use AwsS3S3Client;
use LeagueFlysystemAwsS3V3AwsS3V3Adapter;

$cliente = new S3Client([
    'region'      => 'eu-west-1',
    'version'     => 'latest',
    'credentials' => [
        'key'    => getenv('AWS_ACCESS_KEY_ID'),
        'secret' => getenv('AWS_SECRET_ACCESS_KEY'),
    ],
]);

$adapter    = new AwsS3V3Adapter($cliente, 'mi-bucket', 'prefijo/');
$filesystem = new Filesystem($adapter);

// La misma API que con el adaptador local
$filesystem->write('facturas/2024/enero.pdf', $contenidoPdf);
?>

Adaptador SFTP

<?php
use LeagueFlysystemPhpseclibV3SftpAdapter;
use LeagueFlysystemPhpseclibV3SftpConnectionProvider;

$adapter = new SftpAdapter(
    SftpConnectionProvider::fromArray([
        'host'       => 'sftp.proveedor.com',
        'username'   => 'usuario',
        'privateKey' => '/home/web/.ssh/id_rsa',
        'port'       => 22,
    ]),
    '/var/datos/entrada'  // ruta raíz en el servidor SFTP
);

$filesystem = new Filesystem($adapter);
$ficheros   = $filesystem->listContents('pedidos/')->toArray();

foreach ($ficheros as $fichero) {
    if ($fichero['type'] === 'file') {
        $contenido = $filesystem->read($fichero['path']);
        procesarPedido($contenido);
        $filesystem->move($fichero['path'], 'pedidos/procesados/' . basename($fichero['path']));
    }
}
?>

Listar contenidos de un directorio

<?php
// Solo el primer nivel
$items = $filesystem->listContents('facturas/')->toArray();

// Recursivo
$items = $filesystem->listContents('facturas/', true)->toArray();

foreach ($items as $item) {
    echo $item['type'] . ': ' . $item['path'] . "n";
    if ($item['type'] === 'file') {
        echo '  Tamaño: ' . $item['file_size'] . " bytesn";
    }
}
?>

Testing con InMemoryFilesystemAdapter

<?php
use LeagueFlysystemInMemoryInMemoryFilesystemAdapter;

class SubidaImagenTest extends PHPUnitFrameworkTestCase
{
    public function testSubidaCorrecta(): void
    {
        $adapter    = new InMemoryFilesystemAdapter();
        $filesystem = new Filesystem($adapter);

        $servicio = new ServicioImagenes($filesystem);
        $servicio->subir('foto.jpg', 'contenido de prueba');

        $this->assertTrue($filesystem->fileExists('imagenes/foto.jpg'));
    }
}
?>

Errores comunes

  • UnableToWriteFile con S3: verifica que el bucket existe, que las credenciales tienen el permiso s3:PutObject y que la región es correcta.
  • Permisos en el adaptador local: el directorio raíz debe ser escribible por el proceso PHP. Flysystem no crea el directorio raíz automáticamente.
  • Streams no cerrados: siempre llama a fclose() después de writeStream()/readStream(); dejar streams abiertos agota los descriptores de fichero.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP