How to upload a file to Google Drive using Service Account in PHP?

半腔热情 提交于 2019-12-22 22:46:09

问题


Quickstart: Run a Drive App in PHP seems to be using an outdated version of [google-api-php-client][2], also it doesn't provide example on how to upload a file using Service Account.

In my project I'll need to add a file (spreadsheet) using Service Account, add a spreadsheet, share the spreadsheet with another user and add rows to the spreadsheet.

I'm able to get an empty list of spreadsheets, but I couldn't figure it out how to upload a file using Service Account.

The PHP Class google-api-php-client/examples/spreadsheet.php:

<?php
    use Google\Spreadsheet\DefaultServiceRequest;
    use Google\Spreadsheet\ServiceRequestFactory;
    use Google\Spreadsheet\SpreadsheetService;
    define('JPATH_PLATFORM',1);

    require_once realpath(dirname(__FILE__) . '/../autoload.php');


    class GoogleSpreadSheet
    {
        protected $data;
        protected $client_id            = null ; //Client ID
        protected $service_account_name = null; //Email Address
        protected $key_file_location    = null; //key.p12
        protected $client               = null;
        protected $aplication_name      = null;
        protected $_errors = array();
        protected $scopes = array(    'https://www.googleapis.com/auth/drive'
                                    , 'https://www.googleapis.com/auth/drive.appdata'
                                    , 'https://www.googleapis.com/auth/drive.apps.readonly'
                                    , 'https://spreadsheets.google.com/feeds'
                                );

        /**
         * Class constructor
         * @param   mixed  $properties  Either and associative array or another
         *                              object to set the initial properties of the object.
         */
        public function __construct($properties = null){
            if ($properties !== null)
            {
                $this->setProperties($properties);
            }

            $this->data = new stdClass();
            $this->getClient();
        }
        /**
         * Get Google_Client object
         * @return mixed Google_Client object if client ID and application_name are set OR null
         */
        public function getClient(){
            if(empty($this->client) AND $this->get('client_id') AND $this->get('aplication_name')){

                $this->client = new Google_Client();
                $this->client->setApplicationName($this->get('aplication_name'));
                $this->client->setClientId($this->get('client_id'));
            }
            return $this->client;
        }
        /**
         * Get Service AccessToken
         * @return AccessToken
         */
        public function getToken()
        {
            $key = file_get_contents($this->get('key_file_location'));
            $cred = new Google_Auth_AssertionCredentials(
                    $this->service_account_name,
                    $this->get('scopes', array()),
                    $key
            );

            $this->client->setAssertionCredentials($cred);


            if($this->client->getAuth()->isAccessTokenExpired()) {
                $this->client->getAuth()->refreshTokenWithAssertion($cred);
            }

            $service_token = json_decode($this->client->getAccessToken());
            return $service_token->access_token;
        }

        /**
         * Get Available Spreadsheets
         */
        public function getSpreadsheetsList()
        {

            $accessToken        = $this->getToken();
            $serviceRequest     = new DefaultServiceRequest($accessToken);
            ServiceRequestFactory::setInstance($serviceRequest);


            $spreadsheetService = new SpreadsheetService();
            $spreadsheetFeed    = $spreadsheetService->getSpreadsheets();

            echo '<pre>'; print_r($spreadsheetFeed);

            foreach($spreadsheetFeed as $item) {
                $spreadsheets[basename($item->getId())] = $item->getTitle();
            }

            $this->data->spreadsheets = isset($spreadsheets) ? $spreadsheets : false;
        }
        /**
         * Insert new file.
         *
         * @param string $title Title of the file to insert, including the extension.
         * @param string $description Description of the file to insert.
         * @param string $parentId Parent folder's ID.
         * @param string $mimeType MIME type of the file to insert.
         * @param string $filename Filename of the file to insert.
         * @return Google_DriveFile The file that was inserted. NULL is returned if an API error occurred.
         */
        public function insertFile($title, $description, $mimeType='application/vnd.google-apps.spreadsheet', $filename=null, $parentId=null) {
            //Google_DriveService $service Drive API service instance.
            $service = new Google_Service_Drive($this->getClient());

            $file = new Google_Service_Drive_DriveFile();
            $file->setTitle($title);
            $file->setDescription($description);
            $file->setMimeType($mimeType);

            // Set the parent folder.
            if ($parentId != null) {
                $parent = new Google_ParentReference();
                $parent->setId($parentId);
                $file->setParents(array($parent));
            }

            try {
                if(!empty($filename)){
                    $data = file_get_contents($filename);
                }else{
                    $data = '';
                }

                $createdFile = $service->files->insert($file, array(
                        'data' => $data,
                        'mimeType' => $mimeType,
                ));

                // Uncomment the following line to print the File ID
                // print 'File ID: %s' % $createdFile->getId();

                return $createdFile;
            } catch (Exception $e) {
                $this->setError($e->getMessage());
                print "An error occurred: " . $e->getMessage();
            }
        }

        /**
         * Sets a default value if not alreay assigned
         * @param   string  $property  The name of the property.
         * @param   mixed   $default   The default value.
         * @return  mixed
         */
        public function def($property, $default = null)
        {
            $value = $this->get($property, $default);
            return $this->set($property, $value);
        }

        /**
         * Returns a property of the object or the default value if the property is not set.
         * @param   string  $property  The name of the property.
         * @param   mixed   $default   The default value.
         * @return  mixed    The value of the property.
         */
        public function get($property, $default = null)
        {
            if (isset($this->$property))
            {
                return $this->$property;
            }
            return $default;
        }

        /**
         * Returns an associative array of object properties.
         * @param   boolean  $public  If true, returns only the public properties.
         * @return  array
         */
        public function getProperties($public = true)
        {
            $vars = get_object_vars($this);
            if ($public)
            {
                foreach ($vars as $key => $value)
                {
                    if ('_' == substr($key, 0, 1))
                    {
                        unset($vars[$key]);
                    }
                }
            }
            return $vars;
        }

        /**
         * Get the most recent error message.
         * @param   integer  $i         Option error index.
         * @param   boolean  $toString  Indicates if JError objects should return their error message.
         * @return  string   Error message
         */
        public function getError($i = null, $toString = true)
        {
            // Find the error
            if ($i === null)
            {
                // Default, return the last message
                $error = end($this->_errors);
            }
            elseif (!array_key_exists($i, $this->_errors))
            {
                // If $i has been specified but does not exist, return false
                return false;
            }
            else
            {
                $error = $this->_errors[$i];
            }

            // Check if only the string is requested
            if ($error instanceof Exception && $toString)
            {
                return (string) $error;
            }

            return $error;
        }

        /**
         * Return all errors, if any.
         * @return  array  Array of error messages or JErrors.
         */
        public function getErrors()
        {
            return $this->_errors;
        }

        /**
         * Modifies a property of the object, creating it if it does not already exist.
         * @param   string  $property  The name of the property.
         * @param   mixed   $value     The value of the property to set.
         * @return  mixed  Previous value of the property.
         */
        public function set($property, $value = null)
        {
            $previous = isset($this->$property) ? $this->$property : null;
            $this->$property = $value;
            return $previous;
        }

        /**
         * Set the object properties based on a named array/hash.
         * @param   mixed  $properties  Either an associative array or another object.
         * @return  boolean
         */
        public function setProperties($properties)
        {
            if (is_array($properties) || is_object($properties))
            {
                foreach ((array) $properties as $k => $v)
                {
                    // Use the set function which might be overridden.
                    $this->set($k, $v);
                }
                return true;
            }

            return false;
        }

        /**
         * Add an error message.
         * @param   string  $error  Error message.
         * @return  void
         */
        public function setError($error)
        {
            array_push($this->_errors, $error);
        }
    }

    $opt = array();
    $opt['key_file_location']   = realpath(dirname(__FILE__)).'/key_ss.p12';
    $opt['client_id']           = '*********.apps.googleusercontent.com';
    $opt['service_account_name']= '*********@developer.gserviceaccount.com';
    $opt['aplication_name']     = 'MY APPLICATION NAME';
    $GoogleSS = new GoogleSpreadSheet($opt);
    $GoogleSS->insertFile('test', 'description');
    $GoogleSS->getSpreadsheetsList();

I also have a modified version of /google-api-php-client/autoload.php:

<?php
    function google_api_php_client_autoload($className) {
      $classPath = explode('_', $className);
      if ($classPath[0] != 'Google') {
        return;
      }
      if (count($classPath) > 3) {
        // Maximum class file path depth in this project is 3.
        $classPath = array_slice($classPath, 0, 3);
      }
      $filePath = dirname(__FILE__) . '/src/' . implode('/', $classPath) . '.php';
      if (file_exists($filePath)) {
        require_once($filePath);
      }
    }
    function google_spreadsheet_api_php_client_autoload($className) {

        $classPath = explode('\\', $className);
        if (count($classPath) == 1) {
            $filePath = dirname(__FILE__) . '/src/Google/Spreadsheet/' .  $className . '.php';
        }else{
            $filePath = dirname(__FILE__) . '/src/' .  implode('/', $classPath) . '.php';
        } 

        if (file_exists($filePath)) {
            require_once($filePath);
        }
    }

    spl_autoload_register('google_api_php_client_autoload');
    spl_autoload_register('google_spreadsheet_api_php_client_autoload');

When I execute the code I get:

Notice: Undefined index: uploadType in /var/www/google-api-php-client/src/Google/Service/Resource.php on line 175

Notice: Undefined index: uploadType in /var/www/google-api-php-client/src/Google/Service/Resource.php on line 175
An error occurred: Error calling POST https://www.googleapis.com/drive/v2/files: (401) Login Required
Google\Spreadsheet\SpreadsheetFeed Object
( .... )

In order to reproduce the problem you will also need to download php-google-spreadsheet-client and upload the the Spreadsheet directory to /google-api-php-client/src/Google/

I've tried to provide as much details as possible, but please let me know if something is unclear.


回答1:


I think your problem is access token something like this : setAccessToken($accessToken) , check your credentials.

Also i have added google drive upload script. I am using this script.

Regards.

<?php

/*******************************************************************************
 * file:        google_drive_upload.php
 * author:      ben servoz
 * description: script to upload files to Google Drive
 * notes:       instructions at http://ben.akrin.com/?p=2080
*******************************************************************************/

// configuration
//   google oauth
$client_id = "your client ID here" ;
$client_secret = "your client secret here" ;
$refresh_token = "your refresh token here" ;
//   chunk size (this HAS to be a multiple of 256KB so only change the last integer)
$chunk_size = 256 * 1024 * 400 ; // this will upload files 100MB at a time
//   miscellaneous
$verbose = true ;   
$file_binary = "/usr/bin/file" ; // used for detecting mime type
//   md5
$check_md5_after_upload = true ;
$md5sum_binary = "/usr/bin/md5sum" ;


// todo: grant URL follow thingy if it hasn't been done already

if( count($argv)<2 || count($argv)>3 || in_array("-h", $argv) || in_array("--help", $argv) ) {
    echo "usage: {$argv[0]} <file_name> [folder_id]\n\n    where <file_name> is the full path to the file that you want to upload to Google Drive.\n      and [folder_id] is the the folder where you want to upload the file (optional, defaults to root)\n\n" ;
    exit( 1 ) ;
}

$file_name = $argv[1] ;
if( !file_exists($file_name) ) {
    echo "ERROR: {$file_name} is not found on the filesystem\n" ;
    exit( 1 ) ;
}

$mime_type = get_mime_type( $file_name ) ;
if( $verbose ) { echo " > mime type detected: {$mime_type}\n" ; }

$folder_id = "" ;
if( count($argv)>2 ) {
    $folder_id = $argv[2] ;
}

// retrieving current access token
$access_token = get_access_token() ;

// we create the file that we will be uploading to with google
if( $verbose ) { echo "> creating file with Google\n" ; }
$location = create_google_file( $file_name ) ;

$file_size = filesize( $file_name ) ;
if( $verbose ) { echo "> uploading {$file_name} to {$location}\n" ; }
if( $verbose ) { echo ">   file size: " . (string)($file_size / pow(1024, 2)) . "MB\n" ; }
if( $verbose ) { echo ">   chunk size: " . (string)($chunk_size / pow(1024, 2)) . "MB\n\n" ; }

$last_response_code = false ;
$final_output = null ;
$last_range = false ;
$transaction_counter = 0 ;
$average_upload_speed = 0 ;
$do_exponential_backoff = false ;
$exponential_backoff_counter = 0 ;
while( $last_response_code===false || $last_response_code=='308' ) {
    $transaction_counter++ ;
    if( $verbose ) { echo "> request {$transaction_counter}\n" ; }

    if( $do_exponential_backoff ) {
        $sleep_for = pow( 2, $exponential_backoff_counter ) ;
        if( $verbose ) { echo ">    exponential backoff kicked in, sleeping for {$sleep_for} and a bit\n" ; }
        sleep( $sleep_for ) ;
        usleep( rand(0, 1000) ) ;
        $exponential_backoff_counter++ ;
        if( $exponential_backoff_counter>5 ) {
            // we've waited too long as per Google's instructions
            echo "ERROR: reached time limit of exponential backoff\n" ;
            exit( 1 ) ;
        }
    }

    // determining what range is next
    $range_start = 0 ;
    $range_end = min( $chunk_size, $file_size - 1 ) ;
    if( $last_range!==false ) {
        $last_range = explode( '-', $last_range ) ;
        $range_start = (int)$last_range[1] + 1 ;
        $range_end = min( $range_start + $chunk_size, $file_size - 1 ) ;
    }
    if( $verbose ) { echo ">   range {$range_start}-{$range_end}/{$file_size}\n" ; }

    $ch = curl_init() ;
    curl_setopt( $ch, CURLOPT_URL, "{$location}" ) ;
    curl_setopt( $ch, CURLOPT_PORT , 443 ) ;
    curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "PUT" ) ;
    curl_setopt( $ch, CURLOPT_BINARYTRANSFER, 1 ) ;
    // grabbing the data to send
    $to_send = file_get_contents( $file_name, false, NULL, $range_start, ($range_end - $range_start + 1) ) ;
    curl_setopt( $ch, CURLOPT_POSTFIELDS, $to_send ) ;
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1) ;
    curl_setopt( $ch, CURLOPT_HEADER, true ) ;
    curl_setopt( $ch, CURLOPT_HTTPHEADER, array("Authorization: Bearer {$access_token}",
                                                "Content-Length: " . (string)($range_end - $range_start + 1),
                                                "Content-Type: {$mime_type}",
                                                "Content-Range: bytes {$range_start}-{$range_end}/{$file_size}") ) ;
    $response = parse_response( curl_exec($ch) ) ;
    $post_transaction_info = curl_getinfo( $ch ) ;
    curl_close( $ch ) ;

    $do_exponential_backoff = false ;
    if( isset($response['code']) ) {
        // checking for expired credentials
        if( $response['code']=="401" ) { // todo: make sure that we also got an invalid credential response
            if( $verbose ) { echo ">   access token expired, getting a new one\n" ; }
            $access_token = get_access_token( true ) ;
            $last_response_code = false ;
        } else if( $response['code']=="308" ) {
            $last_response_code = $response['code'] ;
            $last_range = $response['headers']['range'] ;
            // todo: verify x-range-md5 header to be sure, although I can't seem to find what x-range-md5 is a hash of exactly...
            $exponential_backoff_counter = 0 ;
        } else if( $response['code']=="503" ) { // Google's letting us know we should retry
            $do_exponential_backoff = true ;
            $last_response_code = false ;
        } else if( $response['code']=="200" ) { // we are done!
            $last_response_code = $response['code'] ;
            $final_output = $response ;
        } else {
            echo "ERROR: I have no idea what to do so here's a variable dump & have fun figuring it out.\n" ;
            echo "post_transaction_info\n" ;
            print_r( $post_transaction_info ) ;
            echo "response\n" ;
            print_r( $response ) ;
            exit( 1 ) ;
        }

        $average_upload_speed += (int)$post_transaction_info['speed_upload'] ;
        if( $verbose ) { echo ">   uploaded {$post_transaction_info['size_upload']}B\n" ; }

    } else {
        $do_exponential_backoff = true ;
        $last_response_code = false ;
    }
}

if( $last_response_code!="200" ) {
    echo "ERROR: there's no way we should reach this point\n" ;
    exit( 1 ) ;
}
if( $verbose ) { echo "\n> all done!\n" ; }
$average_upload_speed /= $transaction_counter ;
if( $verbose ) { echo "\n> average upload speed: " . (string)($average_upload_speed / pow(1024, 2)) . "MB/s\n" ; }

$final_output =json_decode( $final_output['body'] ) ;

if( $check_md5_after_upload ) {
    if( $verbose ) { echo "> md5 hash verification " ; }
    $result = exec( "{$md5sum_binary} {$file_name}" ) ;
    $result = trim( $result ) ;
    $result = explode( " ", $result ) ;
    $result = $result[0] ;
    if( $result!=$final_output->md5Checksum ) {
        if( $verbose ) { echo "FAIL\n" ; }
        echo "ERROR: md5 mismatch; local:{$result}, google:{$final_output->md5Checksum}\n" ;
        exit( 1 ) ;
    } else {
        if( $verbose ) { echo "OK\n" ; }
    }
}

echo $final_output->selfLink, "\n" ;

// we made it
exit( 0 )  ;


function get_mime_type( $file_name ) {
    global $file_binary ;

    $result = exec( "{$file_binary} -i -b {$file_name}" ) ;
    $result = trim( $result ) ;
    $result = explode( ";", $result ) ;
    $result = $result[0] ;

    return $result ;
}


function parse_response( $raw_data ) {
    $parsed_response = array( 'code'=>-1, 'headers'=>array(), 'body'=>"" ) ;

    $raw_data = explode( "\r\n", $raw_data ) ;

    $parsed_response['code'] = explode( " ", $raw_data[0] ) ;
    $parsed_response['code'] = $parsed_response['code'][1] ;

    for( $i=1 ; $i<count($raw_data) ; $i++ ) {
        $raw_datum = $raw_data[$i] ;

        $raw_datum = trim( $raw_datum ) ;
        if( $raw_datum!="" ) {
            if( substr_count($raw_datum, ':')>=1 ) {
                $raw_datum = explode( ':', $raw_datum, 2 ) ;
                $parsed_response['headers'][strtolower($raw_datum[0])] = trim( $raw_datum[1] ) ;
            }  else {
                echo "ERROR: we're in the headers section of parsing an HTTP section and no colon was found for line: {$raw_datum}\n" ;
                exit( 1 ) ;
            }
        } else {
            // we've moved to the body section
            if( ($i+1)<count($raw_data) ) {
                for( $j=($i+1) ; $j<count($raw_data) ; $j++ ) {
                    $parsed_response['body'] .= $raw_data[$j] . "\n" ;
                }
            }

            // we don't need to continue the $i loop
            break ;
        }
    }

    return $parsed_response ;
}


function get_access_token( $force_refresh=false ) {
    global $client_id, $client_secret, $refresh_token, $verbose ;

    if( $verbose ) { echo "> retrieving access token\n" ; }

    $token_filename = "/tmp/access_token_" . md5( $client_id . $client_secret . $refresh_token ) ;
    $access_token = "" ;
    if( !file_exists($token_filename) || $force_refresh===true ) {
        // no previous access token, let's get one
        if( $verbose ) { echo ">   getting new one\n" ; }

        $ch = curl_init() ;
        curl_setopt( $ch, CURLOPT_URL, "https://accounts.google.com/o/oauth2/token" ) ;
        curl_setopt( $ch, CURLOPT_PORT , 443 ) ;
        curl_setopt( $ch, CURLOPT_POST, 1 ) ;
        curl_setopt( $ch, CURLOPT_POSTFIELDS, "client_id={$client_id}&client_secret={$client_secret}&refresh_token={$refresh_token}&grant_type=refresh_token" ) ;
        curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ) ;
        curl_setopt( $ch, CURLOPT_HEADER, true ) ;
        curl_setopt( $ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded') ) ;

        $response = curl_exec( $ch ) ;
        $response = parse_response( $response ) ;

        // todo: make sure that we got a valid response before retrieving the access token from it

        $access_token = json_decode( $response['body'] ) ;
        $access_token = $access_token->access_token ;
        file_put_contents( $token_filename, $access_token ) ;
    } else {
        // we already have something cached, with some luck it's still valid
        $access_token = file_get_contents( $token_filename ) ;
        if( $verbose ) { echo ">   from cache\n" ; }
    }

    if( $access_token=="" ) {
        echo "ERROR: problems getting an access token\n" ;
        exit( 1 ) ;
    }

    return  $access_token ;
}


function create_google_file ( $file_name ) {
    global $access_token, $folder_id, $mime_type ;

    // todo: make mimeType universal
    if( $folder_id=="" ) {
        $post_fields = "{\"title\":\"{$file_name}\",
                         \"mimeType\":\"{$mime_type}\"}" ;
    } else {
        $post_fields = "{\"title\":\"{$file_name}\",
                         \"mimeType\":\"{$mime_type}\",
                         \"parents\": [{\"kind\":\"drive#fileLink\",\"id\":\"{$folder_id}\"}]}" ;
    }

    $ch = curl_init() ;
    curl_setopt( $ch, CURLOPT_URL, "https://www.googleapis.com/upload/drive/v2/files?uploadType=resumable" ) ;
    curl_setopt( $ch, CURLOPT_PORT , 443 ) ;
    curl_setopt( $ch, CURLOPT_POST, 1 ) ;
    curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_fields ) ;
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ) ;
    curl_setopt( $ch, CURLOPT_HEADER, true ) ;
    curl_setopt( $ch, CURLOPT_HTTPHEADER, array("Authorization: Bearer {$access_token}",
                                                "Content-Length: " . strlen($post_fields),
                                                "X-Upload-Content-Type: {$mime_type}",
                                                "X-Upload-Content-Length: " . filesize($file_name), 
                                                "Content-Type: application/json; charset=UTF-8") ) ;
    $response = curl_exec( $ch ) ;
    $response = parse_response( $response ) ;

    // access token expired, let's get a new one and try again
    if( $response['code']=="401" ) { // todo: make sure that we also got an invalid credential response
        $access_token = get_access_token( true ) ;
        return create_google_file( $file_name ) ; // todo: implement recursion depth limit so we don't rescurse to hell
    }

    // error checking
    if( $response['code']!="200" ) {
        echo "ERROR: could not create resumable file\n" ;
        print_r( $response ) ;
        exit( 1 ) ;
    }
    if( !isset($response['headers']['location']) ) {
        echo "ERROR: not location header gotten back\n" ;
        print_r( $response ) ;
        exit( 1 ) ;
    }

    return $response['headers']['location'] ;
}

?>



回答2:


You get a 401 error. So, you have a problem with your credentials. Look at the errors documentation for more details. Maybe your token has been expired, or it is invalid.



来源:https://stackoverflow.com/questions/27133141/how-to-upload-a-file-to-google-drive-using-service-account-in-php

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!