DoeTheFourth
DoeTheFourth

Reputation: 405

Add Laravel storage disk/filesystem on the fly

I keep multiple FTP accounts in a database table and would like each one to be available as a storage disk in Laravel.

Normally, Laravel's disks are defined in config/filesystems.php. But instead of hardcoding my FTP accounts there, I would like to define them on the fly (in a middleware).

Is this possible? How do I accomplish that?

Upvotes: 19

Views: 11757

Answers (5)

crocodile2u
crocodile2u

Reputation: 413

Laravel's FilesystemManager has methods to create most common disks.

<?php
use Illuminate\Filesystem\FilesystemManager;
$fsMgr = new FilesystemManager(app());
// local disk
$localDisk = $fsMgr->createLocalDriver(['root' => "/path/to/root"]);
// FTP disk
$ftpDisk = $fsMgr->createFtpDriver([/* FTP options */]);
// SFTP disk
$sftpDisk = $fsMgr->createSftpDriver([/* SFTP options */]);
// S3 disk
$s3Disk = $fsMgr->createS3Driver([/* S3 options */]);

Apart from that, you can create any filesystem supported by your league/flysystem installation, you only need to wrap it in Illuminate\Filesystem\FilesystemAdapter:

<?php
use League\Flysystem\FilesystemInterface;
// create any Flysystem instance and wrap it in Laravel's adapter
$myDisk = new FilesystemAdapter(/* \League\Flysystem\FilesystemInterface */ $filesystem);

This way you create your disks on-the-fly, without the need to modify the application config, handy if you have lots of connections, for example, to partners' FTP/SFTP servers, GCloud or S3 buckets.

Upvotes: 12

kireeti9
kireeti9

Reputation: 288

Create a new service provider php artisan make:provider FileSystemServiceProvider

Register the provider in the config/app.php If you open the config/app.php file included with Laravel, you will see a provider's array. register the created service provider there.

In The Boot Method of the created service provider use the below-mentioned code to register the disks Assuming fileSystemsDetails is the model name

public function boot() {
    DomainSettings::all()->each(function(DomainSettings $myDisk) {

        if (!is_null($myDisk->driver_name) && !is_null($myDisk->driver_type)
            && !is_null($myDisk->driver_host) && !is_null($myDisk->driver_username) && !is_null($myDisk->driver_password)) {

            $this->app['config']["filesystems.disks.{$myDisk->driver_name}"] =
                [   'driver' => $myDisk->driver_type,
                    'host' => $myDisk->driver_host,
                    'username' => $myDisk->driver_username,
                    'password' => $myDisk->driver_password,
                    'root' => $myDisk->driver_root,
                ];
        }
    });
}

Upvotes: 7

Heinz
Heinz

Reputation: 39

I solved the same issue by adding some vanilla PHP to the config file (in my case the database config). The point is that the laravel config files are still just "normal" PHP files so nothing prevents you from getting at your database in here, you just cant use Eloquent since the config is processed first.

So with that approach - you can just add your disks to the configuration array after reading them from the database via PDO.

The config file unedited returns an array of objects (so in your case also 'disks')

  1. Change the filesystems config and add the current array into a variable called something sensible like $staticConfig

    $staticConfig = 
    [
     /*the current default contents of filesystems.php
    ]
    
  2. Set up a PDO to your database (real setting would be in your .env file)

    function get_db_pdo()
    {
       $host = env('DB_HOST', '127.0.0.1');
       $db   = env('DB_DATABASE', '3306');
       $user = env('DB_USERNAME', 'your username');
       $pass = env('DB_PASSWORD', '');
       $charset = 'utf8mb4';
       $dsn = "mysql:host=$host;dbname=$db;charset=$charset";
       $opt = [
         PDO::ATTR_ERRMODE            => PDO::ERRMODE_WARNING,
         PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
         PDO::ATTR_EMULATE_PREPARES   => false,
       ];
       return new PDO($dsn, $user, $pass, $opt);
     }
    
  3. Add a function to fetch your disks from your DB (as you stated in your question it is in the DB) - something like this (using FTP as an example):

    function fetchMyFTPs() {
      $db = get_db_pdo();
      $ftpList = [];
      $sql = 'SELECT * FROM myftpservers';
      foreach ($db->query($sql) as $row) {
        $newConnection = [
          'driver' => 'ftp',
          'host' => $row['ftpserver'],
          'username' => $row['ftpusername'],
          'password' => $row['ftppassword']
        ];
        $ftpList[$row['my_ft_connection_name']] = $newConnection;
      }
      return $ftpList;
    }
    
  4. Last thing to do is to add your dynamic config to the "static" config and return the array like so:

    $dynamicFTPS = fetchMyFTPs();
    $staticConfig['disks'] = array_merge($staticConfig['disks'],$dynamicFTPS);
    return $staticConfig
    

This way you can still add config as per normal to the static config array, and just merge in your dynamic disks from db.

** Please note this will most definitely have some effect on performance - the literal configuration is preferred. However, in some cases (like I also had on the DB side) there seems to be no other practical way. The "results" of this call are cached anyway so I don't think the DB code will execute on every call.

Upvotes: -2

DoeTheFourth
DoeTheFourth

Reputation: 405

I created a service provider MyDiskServiceProdiver with this method:

public function boot()
{
    MyDisk::all()->each(function(MyDisk $myDisk) {
        $this->app['config']["filesystems.disks.{$myDisk->name}"] = ['driver' => $myDisk->driver] + $myDisk->config;
    });
}

Where the config attribute is of type json in database and holds several driver-specific attributes.

Upvotes: 8

piyush
piyush

Reputation: 85

You can store your FTP accounts configurations in a csv file in your project and then use each accounts as a storage disk using core php in laravel . you have some idea to upload file from this link: https://cloudinary.com/blog/file_upload_with_php.

in this $currentDir = getcwd(); $uploadDirectory = "/uploads/"; you can get these two fileds from CSV file. May be this can help you after implementing correct logic.

Upvotes: -3

Related Questions