Astrarre il File System con Flysystem

Scopriamo insieme Flysystem, un package della League perfetto per gestire i propri file... in tutte le salse.
francesco
Francesco Malatesta
02/05/2014 in Package

Quante volte avere a che fare con il file system (soprattutto in progetti in cui la scalabilità è d’obbligo) può diventare fastidioso?

Flysystem è qui per risolvere il problema: un layer di astrazione che permette di lavorare allo stesso modo con i propri file in locale e in remoto (supporta FTP, Dropbox, Amazon S3 ed altri).

Layer di astrazione perché, per lavorare con un certo tipo di file system, basterà impostare adeguatamente alcune opzioni di configurazione. Nel momento in cui si avrà la necessità di fare uno switch basterà cambiare queste impostazioni: il codice del nostro progetto, invece, rimarrà invariato.

Interessante, vero?

La cosa ancora più interessante, inoltre, è scoprire che Flysystem cerca anche di uniformare tutti questi “tipi” diversi di file system. Non si parla solo di avere un API univoca: in situazioni in cui le cartelle non esistono (Amazon S3) alcune feature vengono addirittura emulate.

In questo articolo di overview vedremo velocemente come installarlo e come usarlo.

Installazione

Installare Flysystem è di una semplicità quasi disarmante.

Se usi composer basta aggiungere il nome del package tra le dipendenze del tuo progetto.

"require": {
        "league/flysystem": "0.2.*"
    }
}

Nulla, ovviamente, ti vieta di registrare una funzione di autoload nel caso non volessi usare alcun tool di dependency management.

spl_autoload_register(function($class) {
    if ( ! substr($class, 0, 17) === 'League\\Flysystem') {
        return;
    }

    $location = __DIR__ . 'path/to/league/flysystem/src/' . str_replace('\\', '/', $class) . '.php';

    if (is_file($location)) {
        require_once($location);
    }
});

La scelta è fondamentalmente tua. Una volta effettuata l’installazione non c’è bisogno di fare altro: si può passare direttamente alla configurazione del file system che vuoi usare.

Configurazione

Per lavorare con i diversi tipi di file system si deve inizializzare la classe corrispondente. Al momento in cui scrivo le opzioni sono le seguenti:

  • File system locale;
  • AWS S3;
  • Rackspace;
  • Dropbox;
  • FTP;
  • SFTP;
  • Zip;
  • WebDAV;

Negli esempi che seguono useremo l’oggetto $filesystem. Una volta inizializzato si potrà usare allo stesso modo per ogni configruazione, come vedremo nel capitolo successivo.

File system locale

use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local as Adapter;

$filesystem = new Filesystem(new Adapter(__DIR__.'/path/to/root'));

Amazon AWS S3

In questo caso viene usato l'AWS PHP SDK.

use Aws\S3\S3Client;
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\AwsS3 as Adapter;

$client = S3Client::factory(array(
    'key'    => '[your key]',
    'secret' => '[your secret]',
));

$filesystem = new Filesystem(new Adapter($client, 'bucket-name', 'optional-prefix'));

Rackspace

Flysystem usa Rackspace tramite il package Rackspace PHP Opencloud.

use OpenCloud\OpenStack;
use OpenCloud\Rackspace;
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Rackspace as Adapter;

$client = new OpenStack(Rackspace::UK_IDENTITY_ENDPOINT, array(
    'username' => ':username',
    'password' => ':password',
));

$store = $client->objectStoreService('cloudFiles', 'LON');
$container = $store->getContainer('flysystem');

$filesystem = new Filesystem(new Adapter($container));

Dropbox

use Dropbox\Client;
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Dropbox as Adapter;

$client = new Client($token, $appName);
$filesystem = new Filesystem(new Adapter($client, 'optional/path/prefix'));

FTP

use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Ftp as Adapter;

$filesystem = new Filesystem(new Adapter(array(
    'host' => 'ftp.example.com',
    'username' => 'username',
    'password' => 'password',

    /** optional config settings */
    'port' => 21,
    'root' => '/path/to/root',
    'passive' => true,
    'ssl' => true,
    'timeout' => 30,
)));

SFTP

In questo caso viene usata la libreria PHPSecLib per la gestione delle varie operazioni.

use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Sftp as Adapter;

$filesystem = new Filesystem(new Adapter(array(
    'host' => 'example.com',
    'port' => 21,
    'username' => 'username',
    'password' => 'password',
    'privateKey' => 'path/to/or/contents/of/privatekey',
    'root' => '/path/to/root',
    'timeout' => 10,
)));

Zip

Flysystem permette anche di lavorare con un archivio zip. In questo caso la sintassi da usare è la seguente.

use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Zip as Adapter;

$filesystem = new Filesystem(new Adapter(__DIR__.'/path/to/archive.zip'));

WebDAV

In caso di bisogno Flysystem ti permette di lavorare anche con WebDAV. Il package SabreDAV si occupa della logica sottostante.

$client = new Sabre\DAV\Client($settings);
$adapter = new League\Flysystem\Adapter\WebDav($client);
$filesystem = new League\Flysystem\Filesystem($adapter);

Insomma: la scelta non è niente male. Ricorda, inoltre, che l’oggetto di partenza con cui lavorerai sarà sempre e solo $filesystem.

Flysystem in azione

Ora che abbiamo visto come installarlo e configurarlo, vediamo come usare Flysystem per tutte le operazioni più comuni che coinvolgono i file.

Cominciamo dai task più semplici: lettura di un file, creazione, modifica e cancellazione.

Leggere un file

$contents = $filesystem->read('filename.txt');

Creare un file

$filesystem->write('filename.txt', 'contents');

Modificare un file

$filesystem->update('filename.txt', 'new contents');

Cancellare un file

$filesystem->delete('filename.txt');

Nel caso non si voglia fare distinzione fra create e update (lasciando così l’onere del controllo al sistema) si può usare il metodo put.

$filesystem->put('filename.txt', 'contents');

Tramite il metodo has si può verificare l’esistenza di un file.

$exists = $filesystem->has('filename.txt');

Per rinominare un file, invece, il metodo da usare è rename.

$filesystem->rename('filename.txt', 'newname.txt');

Creare una cartella

Creare una cartella è semplice: si usa createDir.

$filesystem->createDir('nested/directory');

Flysystem, inoltre, crea automaticamente tutte le cartelle necessarie in caso stiamo scrivendo in corrispondenza di un path non esistente. Ad esempio, in caso d’uso di

$filesystem->write('path/to/filename.txt', 'contents');

Le cartelle

  • path
  • path/to

verranno create automaticamente in caso non dovessero essere già presenti.

Cancellare una cartella

$filesystem->deleteDir('path/to/directory');

Ottenere il mimetype

$mimetype = $filesystem->getMimetype('filename.txt');

Ottenere i timestamp

$timestamp = $filesystem->getTimestamp('filename.txt');

Ottenere la dimensione di un file

$size = $filesystem->getSize('filename.txt');

Visibilità

Un altro aspetto molto interessante che si può gestire con Flysystem è la visibilità di un file, che di base consiste nell’astrazione dei vari permessi.

Un file può essere pubblico o privato. Vediamo innanzitutto come gestire la visibilità di un file al momento della sua creazione:

use League\Flysystem\AdapterInterface;

$filesystem->write('db.backup', $backup, [
   'visibility' => AdapterInterface::VISIBILITY_PRIVATE),
]);

// oppure

$filesystem->write('db.backup', $backup, ['visibility' => 'private']);

Il terzo parametro del metodo write accetta, nell’array, un elemento visibility il cui valore può essere una semplice stringa o un membro dell’interfaccia AdapterInterface. Ovviamente è possibile gestire senza problemi anche la visibilità di file già esistenti:

if ($filesystem->getVisibility('secret.txt') === 'private') {
   $filesystem->setVisibility('secret.txt', 'public');
}

Come se non fosse già abbastanza puoi specificare il valore di visibilità da usare di default in fase di configurazione:

$filesystem = new League\Flysystem\Filesystem($adapter, $cache, [
   'visibility' => AdapterInterface::VISIBILITY_PRIVATE
]);

Lista dei contenuti di una cartella

Ottenere la lista dei file in una cartella è semplice:

$contents = $filemanager->listContents();

Il “$contents” risultante consisterà in un array multidimensionale, contenente tutti i metadati dei file conosciuti in quel momento. Di default riceverai in output il path ed il tipo del file. In base all’adapter usato, inoltre, potresti ricevere anche altre informazioni.

Ecco un esempio:

foreach ($contents as $object) {
   echo $object['basename'].' is located at'.$object['path'].' and is a '.$object['type'];
}

Di default Flysystem non legge ricorsivamente all’interno delle sottocartelle. Se dovessi averne bisogno basta specificare true come secondo parametro aggiuntivo del metodo.

$contents = $flysystem->listContents('some/dir', true);

Hai bisogno dei percorsi? Basta usare listPaths.

$paths = $filemanager->listPaths();

foreach ($paths as $path) {
   echo $path;
}

Insomma, anche qui le opzioni sono tante.

Scrivere tramite stream

$stream = fopen('/path/to/database.backup', 'r+');
$flysystem->writeStream('backups/' . strftime('%G-%m-%d') . '.backup', $stream);

Scrivere tramite stream specificando direttamente la visibilità del file

$flysystem->writeStream('backups/' . strftime('%G-%m-%d') . '.backup', $stream, 'private');

Update di uno stream con specifica dei contenuti

$flysystem->updateStream('backups/' . strftime('%G-%m-%d') . '.backup', $stream);

Leggere da uno stream

$stream = $flysystem->readStream('something/is/here.ext');
$contents = stream_get_contents($stream);
fclose($stream);

Creare o sovrascrivere un file usando uno stream

$putStream = tmpfile();
fwrite($putStream, $contents);
rewind($putStream);
$filesystem->putStream('somewhere/here.txt', $putStream);
fclose($putStream);

Caching

Per incrementare ancora di più le performance, Flysystem ha un suo sistema di caching interno. In questo sistema tutti i vari dati e metadati vengono normalizzati e memorizzati, con il risultato di applicazioni più veloci e performanti nel tempo.

I metodi per lavorare con la cache, al momento, sono i seguenti:

  • Cache locale;
  • Memcached;
  • Redis;
  • Noop;

Per attivare la cache nel proprio adapter basta agire in fase di creazione dell’istanza dell’adapter stesso. Vediamo come, nel dettaglio.

Cache locale

use League\Flysystem\Filesystem;
use League\Flysystem\Cache\Memory as Cache;

$filesystem = new Filesystem($adapter, new Cache);

Memcached

use League\Flysystem\Filesystem;
use League\Flysystem\Cache\Memcached as Cache;

$memcached = new Memcached;
$memcached->addServer('localhost', 11211);
$filesystem = new Filesystem($adapter, new Cache($memcached, 'storageKey', 300));

Redis

Per lavorare con Redis viene usato Predis.

use League\Flysystem\Filesystem;
use League\Flysystem\Cache\Predis as Cache;

$filesystem = new Filesystem($adapter, new Cache);

// Or supply a client
$client = new Predis\Client;
$filesystem = new Filesystem($adapter, new Cache($client));

Noop

use League\Flysystem\Filesystem;
use League\Flysystem\Cache\Noop as Cache;

$filesystem = new Filesystem($adapter, new Cache);

Conclusioni

Flysystem è sicuramente un ottimo package che promette molto bene. Se dovessi avere problemi nel suo uso o vuoi lasciare un feedback fai pure tra i commenti di seguito, facci sentire la tua!