Google uses unique IDs for each folder and file. This makes it difficult to integrate with other storage services which use normal paths.
This Flysystem adapter works around that problem by seamlessly translating paths from "display paths" to "virtual paths", and vice versa.
For example: virtual path /Xa3X9GlR6EmbnY1RLVTk5VUtOVkk/0B3X9GlR6EmbnY1RLVTk5VUtOVkk
becomes /My Nice Dir/myFile.ext
and all ID handling is hidden.
composer require masbug/flysystem-google-drive-ext
Please follow Google Docs to obtain your client ID, client secret & refresh token
.
In addition you can also check these easy-to-follow tutorial by @ivanvermeyen
$client = new \Google_Client();
$client->setClientId([client_id]);
$client->setClientSecret([client_secret]);
$client->refreshToken([refresh_token]);
$client->setApplicationName('My Google Drive App');
$service = new \Google_Service_Drive($client);
// variant 1
$adapter = new \Masbug\Flysystem\GoogleDriveAdapter($service, 'My_App_Root');
// variant 2: with extra options and query parameters
$adapter2 = new \Masbug\Flysystem\GoogleDriveAdapter(
$service,
'My_App_Root',
[
'useDisplayPaths' => true, /* this is the default */
/* These are global parameters sent to server along with per API parameters. Please see https://developers.google.com/drive/api/v3/query-parameters for more info. */
'parameters' => [
/* This example tells the remote server to perform quota checks per unique user id. Otherwise the quota would be per client IP. */
'quotaUser' => (string)$some_unique_per_user_id
]
]
);
// variant 3: connect to team drive
$adapter3 = new \Masbug\Flysystem\GoogleDriveAdapter(
$service,
'My_App_Root',
[
'teamDriveId' => '0GF9IioKDqJsRGk9PVA'
]
);
$fs = new \League\Flysystem\Filesystem($adapter, new \League\Flysystem\Config([\League\Flysystem\Config::OPTION_VISIBILITY => \League\Flysystem\Visibility::PRIVATE]));
// List selected root folder contents
$contents = $fs->listContents('', true /* is_recursive */);
// List specific folder contents
$contents = $fs->listContents('MyFolder', true /* is_recursive */);
// Upload a file
$local_filepath = '/home/user/downloads/file_to_upload.ext';
$remote_filepath = 'MyFolder/file.ext';
$localAdapter = new League\Flysystem\Local\LocalFilesystemAdapter();
$localfs = new \League\Flysystem\Filesystem($localAdapter, new \League\Flysystem\Config([\League\Flysystem\Config::OPTION_VISIBILITY => \League\Flysystem\Visibility::PRIVATE]));
try {
$time = Carbon::now();
$ret = $fs->writeStream($remote_filepath, $localfs->readStream($local_filepath));
if($ret) {
$speed = filesize($local_filepath) / (float)$time->diffInSeconds();
echo 'Elapsed time: '.$time->diffForHumans(null, true);
echo 'Speed: '. number_format($speed/1024,2) . ' KB/s';
} else {
echo 'Upload FAILED!';
}
} catch(\League\Flysystem\UnableToReadFile $e) {
echo 'Source doesn\'t exist!';
}
// NOTE: Remote folders are automatically created.
// Download a file
$remote_filepath = 'MyFolder/file.ext';
$local_filepath = '/home/user/downloads/file.ext';
$localAdapter = new League\Flysystem\Local\LocalFilesystemAdapter();
$localfs = new \League\Flysystem\Filesystem($localAdapter, new \League\Flysystem\Config([\League\Flysystem\Config::OPTION_VISIBILITY => \League\Flysystem\Visibility::PRIVATE])));
try {
$time = Carbon::now();
$ret = $localfs->writeStream($local_filepath, $fs->readStream($remote_filepath));
if($ret) {
$speed = filesize($local_filepath) / (float)$time->diffInSeconds();
echo 'Elapsed time: '.$time->diffForHumans(null, true);
echo 'Speed: '. number_format($speed/1024,2) . ' KB/s';
} else {
echo 'Downloaded FAILED!';
}
} catch(\League\Flysystem\UnableToReadFile $e) {
echo 'Source doesn\'t exist!';
}
$client = new \Google_Client();
$client->setClientId([client_id]);
$client->setClientSecret([client_secret]);
$client->refreshToken([refresh_token]);
$client->setApplicationName('My Google Drive App');
$service = new \Google_Service_Drive($client);
$drives = $service->teamdrives->listTeamdrives()->getTeamDrives();
foreach ($drives as $drive) {
echo 'TeamDrive: ' . $drive->name . PHP_EOL;
echo 'ID: ' . $drive->id . PHP_EOL;
}
Add the keys you created to your .env
file and set google
as your default cloud storage. You can copy the .env.example
file and fill in the blanks.
FILESYSTEM_CLOUD=google
GOOGLE_DRIVE_CLIENT_ID=xxx.apps.googleusercontent.com
GOOGLE_DRIVE_CLIENT_SECRET=xxx
GOOGLE_DRIVE_REFRESH_TOKEN=xxx
GOOGLE_DRIVE_FOLDER=
#GOOGLE_DRIVE_TEAM_DRIVE_ID=xxx
# you can use more accounts, only add more configs
#SECOND_GOOGLE_DRIVE_CLIENT_ID=xxx.apps.googleusercontent.com
#SECOND_GOOGLE_DRIVE_CLIENT_SECRET=xxx
#SECOND_GOOGLE_DRIVE_REFRESH_TOKEN=xxx
#SECOND_GOOGLE_DRIVE_FOLDER=backups
#SECOND_DRIVE_TEAM_DRIVE_ID=xxx
'disks' => [
// ...
'google' => [
'driver' => 'google',
'clientId' => env('GOOGLE_DRIVE_CLIENT_ID'),
'clientSecret' => env('GOOGLE_DRIVE_CLIENT_SECRET'),
'refreshToken' => env('GOOGLE_DRIVE_REFRESH_TOKEN'),
'folder' => env('GOOGLE_DRIVE_FOLDER'), // without folder is root of drive or team drive
//'teamDriveId' => env('GOOGLE_DRIVE_TEAM_DRIVE_ID'),
],
// you can use more accounts, only add more disks and configs on .env
// also you can use the same account and point to a diferent folders for each disk
/*'second_google' => [
'driver' => 'google',
'clientId' => env('SECOND_GOOGLE_DRIVE_CLIENT_ID'),
'clientSecret' => env('SECOND_GOOGLE_DRIVE_CLIENT_SECRET'),
'refreshToken' => env('SECOND_GOOGLE_DRIVE_REFRESH_TOKEN'),
'folder' => env('SECOND_GOOGLE_DRIVE_FOLDER'),
],*/
// ...
],
Example:
<?php
namespace App\Providers;
class AppServiceProvider extends \Illuminate\Support\ServiceProvider{ // can be a custom ServiceProvider
// ...
public function boot(){
// ...
try{
\Storage::extend('google', function($app, $config) {
$options = [];
if(!empty($config['teamDriveId']??null)) $options['teamDriveId']=$config['teamDriveId'];
$client = new \Google_Client();
$client->setClientId($config['clientId']);
$client->setClientSecret($config['clientSecret']);
$client->refreshToken($config['refreshToken']);
$service = new \Google_Service_Drive($client);
$adapter = new \Masbug\Flysystem\GoogleDriveAdapter($service,$config['folder']??'/', $options);
return new \League\Flysystem\Filesystem($adapter);
});
}catch(\Exception $e){ }
// ...
}
// ...
}
Now you can access the drives like so:
$googleDisk = Storage::disk('google');
//$secondDisk = Storage::disk('second_google'); //others disks
Keep in mind that there can only be one default cloud storage drive, defined by FILESYSTEM_CLOUD
in your .env
(or config) file. If you set it to main_google
, that will be the cloud drive:
Storage::cloud(); // refers to Storage::disk('google')
Using display paths as identifiers for folders and files requires them to be unique. Unfortunately Google Drive allows users to create files and folders with same (displayed) names. In such cases when unique path cannot be determined this adapter chooses the oldest (first) instance. In case the newer duplicate is a folder and user puts a unique file or folder inside the adapter will be able to reach it properly (because full path is unique).
Concurrent use of same Google Drive might lead to unexpected problems due to heavy caching of file/folder identifiers and file objects.
This adapter is based on wonderful flysystem-google-drive by Naoki Sawada.
It also contains an adaptation of Google_Http_MediaFileUpload by Google. I've added support for resumable uploads directly from streams (avoiding copying data to memory).
TeamDrive support was implemented by Maximilian Ruta - Deltachaos.