├── LICENSE ├── README.md ├── UPGRADING.md ├── composer.json ├── config └── google.php ├── docs ├── google-client.md ├── macro.md ├── oauth.md ├── test.md └── trait.md ├── lib └── google │ ├── Exceptions │ └── UnknownServiceException.php │ ├── Facades │ └── Google.php │ ├── GoogleApiClient.php │ └── Providers │ └── GoogleServiceProvider.php └── src ├── Concerns ├── SheetsCollection.php ├── SheetsDrive.php ├── SheetsProperties.php └── SheetsValues.php ├── Contracts └── Factory.php ├── Facades └── Sheets.php ├── Providers └── SheetsServiceProvider.php ├── SheetsClient.php └── Traits └── GoogleSheets.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 kawax 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Sheets API v4 for Laravel 2 | 3 | [![packagist](https://badgen.net/packagist/v/revolution/laravel-google-sheets)](https://packagist.org/packages/revolution/laravel-google-sheets) 4 | [![Maintainability](https://qlty.sh/badges/483a3481-fbc3-4e43-a83f-a843a25a45d8/maintainability.svg)](https://qlty.sh/gh/invokable/projects/laravel-google-sheets) 5 | [![Code Coverage](https://qlty.sh/badges/483a3481-fbc3-4e43-a83f-a843a25a45d8/test_coverage.svg)](https://qlty.sh/gh/invokable/projects/laravel-google-sheets) 6 | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/invokable/laravel-google-sheets) 7 | 8 | ## Requirements 9 | - PHP >= 8.2 10 | - Laravel >= 11.0 11 | 12 | ## Installation 13 | 14 | ### Composer 15 | ``` 16 | composer require revolution/laravel-google-sheets 17 | ``` 18 | 19 | ### Laravel 20 | 21 | 1. Run `php artisan vendor:publish --tag="google-config"` to publish the google config file 22 | 23 | // config/google.php 24 | 25 | // OAuth 26 | 'client_id' => env('GOOGLE_CLIENT_ID', ''), 27 | 'client_secret' => env('GOOGLE_CLIENT_SECRET', ''), 28 | 'redirect_uri' => env('GOOGLE_REDIRECT', ''), 29 | 'scopes' => [\Google\Service\Sheets::DRIVE, \Google\Service\Sheets::SPREADSHEETS], 30 | 'access_type' => 'online', 31 | 'approval_prompt' => 'auto', 32 | 'prompt' => 'consent', //"none", "consent", "select_account" default:none 33 | 34 | // or Service Account 35 | 'file' => storage_path('credentials.json'), 36 | 'enable' => env('GOOGLE_SERVICE_ENABLED', true), 37 | 38 | 2. Get API Credentials from https://developers.google.com/console 39 | Enable `Google Sheets API`, `Google Drive API`. 40 | 41 | 3. Configure .env as needed 42 | 43 | GOOGLE_APPLICATION_NAME= 44 | GOOGLE_CLIENT_ID= 45 | GOOGLE_CLIENT_SECRET= 46 | GOOGLE_REDIRECT= 47 | GOOGLE_DEVELOPER_KEY= 48 | GOOGLE_SERVICE_ENABLED= 49 | GOOGLE_SERVICE_ACCOUNT_JSON_LOCATION= 50 | 51 | ## Demo 52 | - https://github.com/kawax/google-sheets-project 53 | - https://sheets.kawax.biz/ 54 | 55 | Another Google API Series. 56 | - https://github.com/kawax/laravel-google-photos 57 | - https://github.com/kawax/laravel-google-searchconsole 58 | 59 | ## Select auth type 60 | You must select an authentication type and configure it appropriately. 61 | 62 | - Service Account : Access to only your own spreadsheets. 63 | - OAuth : Access to user's spreadsheets. 64 | - API key: Access to public spreadsheets. 65 | 66 | ## Usage 67 | 68 | | id | name | mail | 69 | |-----|-------|-------| 70 | | 1 | name1 | mail1 | 71 | | 2 | name2 | mail2 | 72 | 73 | https://docs.google.com/spreadsheets/d/{spreadsheetID}/... 74 | 75 | ### Basic Laravel Usage 76 | ```php 77 | use Revolution\Google\Sheets\Facades\Sheets; 78 | 79 | $user = $request->user(); 80 | 81 | $token = [ 82 | 'access_token' => $user->access_token, 83 | 'refresh_token' => $user->refresh_token, 84 | 'expires_in' => $user->expires_in, 85 | 'created' => $user->updated_at->getTimestamp(), 86 | ]; 87 | 88 | // all() returns array 89 | $values = Sheets::setAccessToken($token)->spreadsheet('spreadsheetId')->sheet('Sheet 1')->all(); 90 | // [ 91 | // ['id', 'name', 'mail'], 92 | // ['1', 'name1', 'mail1'], 93 | // ['2', 'name1', 'mail2'] 94 | // ] 95 | ``` 96 | 97 | ### Basic Non-Laravel Usage 98 | 99 | ```php 100 | use Google\Client; 101 | use Revolution\Google\Sheets\SheetsClient; 102 | 103 | $client = new Client(); 104 | $client->setScopes([Google\Service\Sheets::DRIVE, Google\Service\Sheets::SPREADSHEETS]); 105 | // setup Google Client 106 | // ... 107 | 108 | $service = new \Google\Service\Sheets($client); 109 | 110 | $sheets = new SheetsClient(); 111 | $sheets->setService($service); 112 | 113 | $values = $sheets->spreadsheet('spreadsheetID')->sheet('Sheet 1')->all(); 114 | ``` 115 | 116 | ### Get a sheet's values with the header as the key 117 | ```php 118 | use Revolution\Google\Sheets\Facades\Sheets; 119 | 120 | // get() returns Laravel Collection 121 | $rows = Sheets::sheet('Sheet 1')->get(); 122 | 123 | $header = $rows->pull(0); 124 | $values = Sheets::collection(header: $header, rows: $rows); 125 | $values->toArray() 126 | // [ 127 | // ['id' => '1', 'name' => 'name1', 'mail' => 'mail1'], 128 | // ['id' => '2', 'name' => 'name2', 'mail' => 'mail2'] 129 | // ] 130 | ``` 131 | 132 | Blade 133 | ```php 134 | @foreach($values as $value) 135 | {{ data_get($value, 'name') }} 136 | @endforeach 137 | ``` 138 | 139 | ### Using A1 Notation 140 | ```php 141 | use Revolution\Google\Sheets\Facades\Sheets; 142 | 143 | $values = Sheets::sheet('Sheet 1')->range('A1:B2')->all(); 144 | // [ 145 | // ['id', 'name'], 146 | // ['1', 'name1'], 147 | // ] 148 | ``` 149 | 150 | ### Updating a specific range 151 | ```php 152 | use Revolution\Google\Sheets\Facades\Sheets; 153 | 154 | Sheets::sheet('Sheet 1')->range('A4')->update([['3', 'name3', 'mail3']]); 155 | $values = Sheets::range('')->all(); 156 | // [ 157 | // ['id', 'name', 'mail'], 158 | // ['1', 'name1', 'mail1'], 159 | // ['2', 'name1', 'mail2'], 160 | // ['3', 'name3', 'mail3'] 161 | // ] 162 | ``` 163 | 164 | ### Append a set of values to a sheet 165 | ```php 166 | use Revolution\Google\Sheets\Facades\Sheets; 167 | 168 | // When we don't provide a specific range, the sheet becomes the default range 169 | Sheets::sheet('Sheet 1')->append([['3', 'name3', 'mail3']]); 170 | $values = Sheets::all(); 171 | // [ 172 | // ['id', 'name', 'mail'], 173 | // ['1', 'name1', 'mail1'], 174 | // ['2', 'name1', 'mail2'], 175 | // ['3', 'name3', 'mail3'] 176 | // ] 177 | ``` 178 | 179 | ### Append a set of values with keys 180 | ```php 181 | use Revolution\Google\Sheets\Facades\Sheets; 182 | 183 | // When providing an associative array, values get matched up to the headers in the provided sheet 184 | Sheets::sheet('Sheet 1')->append([['name' => 'name4', 'mail' => 'mail4', 'id' => 4]]); 185 | $values = Sheets::all(); 186 | // [ 187 | // ['id', 'name', 'mail'], 188 | // ['1', 'name1', 'mail1'], 189 | // ['2', 'name1', 'mail2'], 190 | // ['3', 'name3', 'mail3'], 191 | // ['4', 'name4', 'mail4'], 192 | // ] 193 | ``` 194 | 195 | ### Add a new sheet 196 | ```php 197 | use Revolution\Google\Sheets\Facades\Sheets; 198 | 199 | Sheets::spreadsheetByTitle($title)->addSheet('New Sheet Title'); 200 | ``` 201 | 202 | ### Deleting a sheet 203 | ```php 204 | use Revolution\Google\Sheets\Facades\Sheets; 205 | 206 | Sheets::spreadsheetByTitle($title)->deleteSheet('Old Sheet Title'); 207 | ``` 208 | 209 | ### Specifying query parameters 210 | ```php 211 | use Revolution\Google\Sheets\Facades\Sheets; 212 | 213 | $values = Sheets::sheet('Sheet 1')->majorDimension('DIMENSION_UNSPECIFIED') 214 | ->valueRenderOption('FORMATTED_VALUE') 215 | ->dateTimeRenderOption('SERIAL_NUMBER') 216 | ->all(); 217 | ``` 218 | https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get#query-parameters 219 | 220 | ## Use original Google_Service_Sheets 221 | ```php 222 | use Revolution\Google\Sheets\Facades\Sheets; 223 | 224 | $sheets->spreadsheets->... 225 | $sheets->spreadsheets_sheets->... 226 | $sheets->spreadsheets_values->... 227 | 228 | Sheets::getService()->spreadsheets->... 229 | 230 | ``` 231 | see https://github.com/google/google-api-php-client-services/blob/master/src/Google/Service/Sheets.php 232 | 233 | ## LICENSE 234 | MIT 235 | Copyright kawax 236 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | # UPGRADING 2 | 3 | ## 6.x to 7.0 4 | - Require PHP>=8.2 and Laravel>=11.x 5 | - Change Google Client namespace `Revolution\Google\Client`. Move to `lib/google/`. If you only use "Sheets", there will be little effect. 6 | - Remove `Sheets` short Facade alias. Always recommended to use the full namespace. 7 | 8 | ```php 9 | use Revolution\Google\Sheets\Facades\Sheets; 10 | ``` 11 | 12 | ## 5.x to 6.0 13 | - Require PHP>=8.0 and Laravel>=8.x 14 | 15 | ## 4.x to 5.0 16 | - require `PHP>=7.2` 17 | 18 | ## 3.x to 4.0 19 | - require `PHP>=7.1.3` and Laravel 5.8 20 | 21 | ## 2.x to 3.0 22 | - require `PHP>=7.0` and Laravel 5.5 23 | - Change namespace to `Revolution\Google\Sheets\`. It will auto resolved by Package discovery. 24 | - composer.json 25 | ``` 26 | "revolution/laravel-google-sheets": "^3.0" 27 | ``` 28 | 29 | ## 1.0.x to 2.0 30 | - Remove "repositories" from composer.json 31 | ```json 32 | "repositories": [ 33 | { 34 | "type": "vcs", 35 | "url": "https://github.com/kawax/google-apiclient" 36 | } 37 | ], 38 | ``` 39 | - Change composer.json 40 | Bump version 41 | ```json 42 | "require": { 43 | "revolution/laravel-google-sheets": "^2.0" 44 | } 45 | ``` 46 | Remove 47 | ``` 48 | "pulkitjalan/google-apiclient": "^3.0", 49 | ``` 50 | - Remove "vendor" dir. 51 | - Remove composer.lock 52 | - Clear composer cache. `composer clear-cache` 53 | - `composer install` 54 | - Change config/google.php 55 | ``` 56 | 'service' => [ 57 | /* 58 | | Enable service account auth or not. 59 | */ 60 | 'enabled' => false, 61 | 62 | /* 63 | | Path to service account json file 64 | */ 65 | 'file' => '', 66 | ], 67 | ``` 68 | https://github.com/pulkitjalan/google-apiclient#usage 69 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "revolution/laravel-google-sheets", 3 | "description": "Google Sheets API v4", 4 | "keywords": [ 5 | "google", 6 | "sheets", 7 | "laravel" 8 | ], 9 | "license": "MIT", 10 | "require": { 11 | "php": "^8.2", 12 | "illuminate/support": "^11.0||^12.0", 13 | "google/apiclient": "^2.16" 14 | }, 15 | "require-dev": { 16 | "orchestra/testbench": "^10.0", 17 | "pulkitjalan/google-apiclient": "^6.2", 18 | "laravel/pint": "^1.22" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Revolution\\Google\\Sheets\\": "src/", 23 | "Revolution\\Google\\Client\\": "lib/google/" 24 | } 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "Tests\\": "tests/" 29 | } 30 | }, 31 | "authors": [ 32 | { 33 | "name": "kawax", 34 | "email": "kawaxbiz@gmail.com" 35 | } 36 | ], 37 | "scripts": { 38 | "pre-autoload-dump": "Google\\Task\\Composer::cleanup" 39 | }, 40 | "extra": { 41 | "laravel": { 42 | "providers": [ 43 | "Revolution\\Google\\Sheets\\Providers\\SheetsServiceProvider", 44 | "Revolution\\Google\\Client\\Providers\\GoogleServiceProvider" 45 | ], 46 | "google/apiclient-services": [ 47 | "Drive", 48 | "Sheets" 49 | ] 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /config/google.php: -------------------------------------------------------------------------------- 1 | env('GOOGLE_APPLICATION_NAME', ''), 10 | 11 | /* 12 | |---------------------------------------------------------------------------- 13 | | Google OAuth 2.0 access 14 | |---------------------------------------------------------------------------- 15 | | 16 | | Keys for OAuth 2.0 access, see the API console at 17 | | https://developers.google.com/console 18 | | 19 | */ 20 | 'client_id' => env('GOOGLE_CLIENT_ID', ''), 21 | 'client_secret' => env('GOOGLE_CLIENT_SECRET', ''), 22 | 'redirect_uri' => env('GOOGLE_REDIRECT', ''), 23 | 'scopes' => [], 24 | 'access_type' => 'online', 25 | 'approval_prompt' => 'auto', 26 | 27 | /* 28 | |---------------------------------------------------------------------------- 29 | | Google developer key 30 | |---------------------------------------------------------------------------- 31 | | 32 | | Simple API access key, also from the API console. Ensure you get 33 | | a Server key, and not a Browser key. 34 | | 35 | */ 36 | 'developer_key' => env('GOOGLE_DEVELOPER_KEY', ''), 37 | 38 | /* 39 | |---------------------------------------------------------------------------- 40 | | Google service account 41 | |---------------------------------------------------------------------------- 42 | | 43 | | Set the credentials JSON's location to use assert credentials, otherwise 44 | | app engine or compute engine will be used. 45 | | 46 | */ 47 | 'service' => [ 48 | /* 49 | | Enable service account auth or not. 50 | */ 51 | 'enable' => env('GOOGLE_SERVICE_ENABLED', false), 52 | 53 | /* 54 | * Path to service account json file. You can also pass the credentials as an array 55 | * instead of a file path. 56 | */ 57 | 'file' => env('GOOGLE_SERVICE_ACCOUNT_JSON_LOCATION', ''), 58 | ], 59 | 60 | /* 61 | |---------------------------------------------------------------------------- 62 | | Additional config for the Google Client 63 | |---------------------------------------------------------------------------- 64 | | 65 | | Set any additional config variables supported by the Google Client 66 | | Details can be found here: 67 | | https://github.com/google/google-api-php-client/blob/master/src/Google/Client.php 68 | | 69 | | NOTE: If client id is specified here, it will get over written by the one above. 70 | | 71 | */ 72 | 'config' => [], 73 | ]; 74 | -------------------------------------------------------------------------------- /docs/google-client.md: -------------------------------------------------------------------------------- 1 | # Use original Google api client 2 | 3 | This package includes `pulkitjalan/google-apiclient` to support the latest Laravel. 4 | 5 | If you want to use the original package, you can change it with AppServiceProvider. 6 | 7 | ## Install package 8 | ```shell 9 | composer require pulkitjalan/google-apiclient 10 | ``` 11 | 12 | ## AppServiceProvider 13 | 14 | ```php 15 | use PulkitJalan\Google\Client as GoogleClient; 16 | 17 | class AppServiceProvider extends ServiceProvider 18 | { 19 | public function register(): void 20 | { 21 | $this->app->alias(GoogleClient::class, 'google-client'); 22 | } 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/macro.md: -------------------------------------------------------------------------------- 1 | # Macroable 2 | 3 | Extend any method by your self. 4 | 5 | ## Register in AppServiceProvider.php 6 | 7 | ```php 8 | use Revolution\Google\Sheets\Facades\Sheets; 9 | 10 | public function boot() 11 | { 12 | Sheets::macro('my', function () { 13 | return $this->getService()->spreadsheets->... 14 | }); 15 | } 16 | ``` 17 | 18 | ## Use somewhere 19 | ```php 20 | use Revolution\Google\Sheets\Facades\Sheets; 21 | 22 | $values = Sheets::sheet('Sheet 1')->my(); 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/oauth.md: -------------------------------------------------------------------------------- 1 | # OAuth by Socialite 2 | 3 | https://github.com/kawax/google-sheets-project/blob/6.x/app/Http/Controllers/LoginController.php 4 | 5 | ```php 6 | public function redirect() 7 | { 8 | return Socialite::driver('google') 9 | ->scopes(config('google.scopes')) 10 | ->with([ 11 | 'access_type' => config('google.access_type'), 12 | 'approval_prompt' => config('google.approval_prompt'), 13 | ]) 14 | ->redirect(); 15 | } 16 | 17 | /** 18 | * 19 | * @return \Illuminate\Http\Response 20 | */ 21 | public function callback() 22 | { 23 | if (!request()->has('code')) { 24 | return redirect('/'); 25 | } 26 | 27 | /** 28 | * @var \Laravel\Socialite\Two\User $user 29 | */ 30 | $user = Socialite::driver('google')->user(); 31 | 32 | /** 33 | * @var \App\User $loginUser 34 | */ 35 | $loginUser = User::updateOrCreate( 36 | [ 37 | 'email' => $user->email, 38 | ], 39 | [ 40 | 'name' => $user->name, 41 | 'email' => $user->email, 42 | 'access_token' => $user->token, 43 | 'refresh_token' => $user->refreshToken, 44 | 'expires_in' => $user->expiresIn, 45 | ]); 46 | 47 | auth()->login($loginUser, false); 48 | 49 | return redirect('/home'); 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/test.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | ## Facade 4 | ```php 5 | use Revolution\Google\Sheets\Facades\Sheets; 6 | 7 | $spreadsheets = Sheets::setAccessToken($token) 8 | ->spreadsheetList(); 9 | ``` 10 | 11 | test 12 | ```php 13 | Sheets::shouldReceive('setAccessToken->spreadsheetList')->once()->andReturn([]); 14 | ``` 15 | 16 | ## trait 17 | 18 | ```php 19 | $spreadsheets = $request->user() 20 | ->sheets() 21 | ->spreadsheetList(); 22 | ``` 23 | 24 | test 25 | ```php 26 | Sheets::shouldReceive('setAccessToken->spreadsheetList')->once()->andReturn([]); 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/trait.md: -------------------------------------------------------------------------------- 1 | # GoogleSheets Trait 2 | Like a Laravel Notifications. 3 | 4 | Add `GoogleSheets` trait to User model. 5 | 6 | ```php 7 | $this->access_token, 30 | 'refresh_token' => $this->refresh_token, 31 | 'expires_in' => $this->expires_in, 32 | 'created' => $this->created->timestamp, 33 | ]; 34 | } 35 | } 36 | ``` 37 | 38 | Add `sheetsAccessToken()`(abstract) for access_token. 39 | 40 | Trait has `sheets()` that returns Sheets instance. 41 | 42 | ```php 43 | use Revolution\Google\Client\Facades\Google; 44 | use Revolution\Google\Sheets\Facades\Sheets; 45 | 46 | public function __invoke(Request $request) 47 | { 48 | // Facade 49 | // $token = $request->user()->access_token; 50 | // 51 | // Google::setAccessToken($token); 52 | // 53 | // $spreadsheets = Sheets::setService(Google::make('sheets')) 54 | // ->setDriveService(Google::make('drive')) 55 | // ->spreadsheetList(); 56 | 57 | // GoogleSheets Trait 58 | $spreadsheets = $request->user() 59 | ->sheets() 60 | ->spreadsheetList(); 61 | 62 | return view('sheets.index')->with(compact('spreadsheets')); 63 | } 64 | ``` 65 | 66 | ## Already sheets() exists 67 | 68 | ```php 69 | use GoogleSheets { 70 | GoogleSheets::sheets as googlesheets; 71 | } 72 | ``` 73 | -------------------------------------------------------------------------------- /lib/google/Exceptions/UnknownServiceException.php: -------------------------------------------------------------------------------- 1 | config = $config; 19 | 20 | // create an instance of the google client for OAuth2 21 | $this->client = new GoogleClient(Arr::get($config, 'config', [])); 22 | 23 | // set application name 24 | $this->client->setApplicationName(Arr::get($config, 'application_name', '')); 25 | 26 | // set oauth2 configs 27 | $this->client->setClientId(Arr::get($config, 'client_id', '')); 28 | $this->client->setClientSecret(Arr::get($config, 'client_secret', '')); 29 | $this->client->setRedirectUri(Arr::get($config, 'redirect_uri', '')); 30 | $this->client->setScopes(Arr::get($config, 'scopes', [])); 31 | $this->client->setAccessType(Arr::get($config, 'access_type', 'online')); 32 | $this->client->setApprovalPrompt(Arr::get($config, 'approval_prompt', 'auto')); 33 | 34 | // set developer key 35 | $this->client->setDeveloperKey(Arr::get($config, 'developer_key', '')); 36 | 37 | // auth for service account 38 | if (Arr::get($config, 'service.enable', false)) { 39 | $this->auth($userEmail); 40 | } 41 | } 42 | 43 | /** 44 | * Getter for the google client. 45 | */ 46 | public function getClient(): GoogleClient 47 | { 48 | return $this->client; 49 | } 50 | 51 | /** 52 | * Setter for the google client. 53 | */ 54 | public function setClient(GoogleClient $client): self 55 | { 56 | $this->client = $client; 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * Getter for the google service. 63 | * 64 | * @throws UnknownServiceException|\ReflectionException 65 | */ 66 | public function make(string $service): mixed 67 | { 68 | $service = 'Google\\Service\\'.ucfirst($service); 69 | 70 | if (class_exists($service)) { 71 | $class = new \ReflectionClass($service); 72 | 73 | return $class->newInstance($this->client); 74 | } 75 | 76 | throw new UnknownServiceException($service); 77 | } 78 | 79 | /** 80 | * Setup correct auth method based on type. 81 | */ 82 | protected function auth(string $userEmail = ''): void 83 | { 84 | // see (and use) if user has set Credentials 85 | if ($this->useAssertCredentials($userEmail)) { 86 | return; 87 | } 88 | 89 | // fallback to compute engine or app engine 90 | $this->client->useApplicationDefaultCredentials(); 91 | } 92 | 93 | /** 94 | * Determine and use credentials if user has set them. 95 | * 96 | * @return bool used or not 97 | */ 98 | protected function useAssertCredentials(string $userEmail = ''): bool 99 | { 100 | $serviceJsonUrl = Arr::get($this->config, 'service.file', ''); 101 | 102 | if (empty($serviceJsonUrl)) { 103 | return false; 104 | } 105 | 106 | $this->client->setAuthConfig($serviceJsonUrl); 107 | 108 | if (! empty($userEmail)) { 109 | $this->client->setSubject($userEmail); 110 | } 111 | 112 | return true; 113 | } 114 | 115 | /** 116 | * Magic call method. 117 | * 118 | * @return mixed 119 | * 120 | * @throws BadMethodCallException 121 | */ 122 | public function __call(string $method, array $parameters) 123 | { 124 | if (method_exists($this->client, $method)) { 125 | return $this->client->{$method}(...array_values($parameters)); 126 | } 127 | 128 | throw new BadMethodCallException(sprintf('Method [%s] does not exist.', $method)); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/google/Providers/GoogleServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__.'/../../../config/google.php', 'google'); 16 | 17 | $this->app->scoped(GoogleApiClient::class, fn ($app) => new GoogleApiClient($app['config']['google'])); 18 | 19 | $this->app->alias(GoogleApiClient::class, 'google-client'); 20 | } 21 | 22 | /** 23 | * Boot the service provider. 24 | */ 25 | public function boot(): void 26 | { 27 | if ($this->app->runningInConsole()) { 28 | $this->publishes([ 29 | __DIR__.'/../../../config/google.php' => config_path('google.php'), 30 | ], 'google-config'); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Concerns/SheetsCollection.php: -------------------------------------------------------------------------------- 1 | all(); 12 | 13 | return Collection::make($values); 14 | } 15 | 16 | public function collection(array $header, array|Collection $rows): Collection 17 | { 18 | return Collection::make($rows)->map(function ($item) use ($header) { 19 | $row = Collection::make($item)->pad(count($header), ''); 20 | 21 | return Collection::make($header)->combine($row); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Concerns/SheetsDrive.php: -------------------------------------------------------------------------------- 1 | getDriveService() 17 | ->files 18 | ->listFiles( 19 | [ 20 | 'q' => "mimeType = 'application/vnd.google-apps.spreadsheet'", 21 | ], 22 | ) 23 | ->getFiles(); 24 | 25 | foreach ($files as $file) { 26 | $list[$file->id] = $file->name; 27 | } 28 | 29 | return $list; 30 | } 31 | 32 | public function setDriveService(mixed $drive): static 33 | { 34 | $this->drive = $drive; 35 | 36 | return $this; 37 | } 38 | 39 | public function getDriveService(): Drive 40 | { 41 | return $this->drive ??= Google::make('drive'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Concerns/SheetsProperties.php: -------------------------------------------------------------------------------- 1 | getService() 10 | ->spreadsheets 11 | ->get($this->getSpreadsheetId()) 12 | ->getProperties() 13 | ->toSimpleObject(); 14 | } 15 | 16 | public function sheetProperties(): object 17 | { 18 | $sheets = $this->getService() 19 | ->spreadsheets 20 | ->get($this->getSpreadsheetId(), ['ranges' => $this->sheet]) 21 | ->getSheets(); 22 | 23 | return $sheets[0]->getProperties()->toSimpleObject(); 24 | } 25 | 26 | public function getSpreadsheetId(): string 27 | { 28 | return $this->spreadsheetId ?? ''; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Concerns/SheetsValues.php: -------------------------------------------------------------------------------- 1 | query(); 28 | 29 | $sheets = $this->serviceValues()->batchGet($this->getSpreadsheetId(), $query); 30 | 31 | $values = $sheets->getValueRanges()[0]->getValues(); 32 | 33 | return $values ?? []; 34 | } 35 | 36 | public function first(): array 37 | { 38 | $values = $this->all(); 39 | 40 | $first = head($values); 41 | 42 | return $first ?: []; 43 | } 44 | 45 | public function update(array $value, string $valueInputOption = 'RAW'): BatchUpdateValuesResponse 46 | { 47 | $range = $this->ranges(); 48 | 49 | $batch = new BatchUpdateValuesRequest; 50 | $batch->setValueInputOption($valueInputOption); 51 | 52 | $valueRange = new ValueRange; 53 | $valueRange->setValues($value); 54 | $valueRange->setRange($range); 55 | 56 | $batch->setData($valueRange); 57 | 58 | return $this->serviceValues()->batchUpdate($this->getSpreadsheetId(), $batch); 59 | } 60 | 61 | public function clear(): ?ClearValuesResponse 62 | { 63 | $range = $this->ranges(); 64 | 65 | $clear = new ClearValuesRequest; 66 | 67 | return $this->serviceValues()->clear($this->getSpreadsheetId(), $range, $clear); 68 | } 69 | 70 | public function append(array $values, string $valueInputOption = 'RAW', string $insertDataOption = 'OVERWRITE'): AppendValuesResponse 71 | { 72 | $range = $this->ranges(); 73 | $orderedValues = $this->orderAppendables($values); 74 | 75 | $valueRange = new ValueRange; 76 | $valueRange->setValues($orderedValues); 77 | $valueRange->setRange($range); 78 | 79 | $optParams = [ 80 | 'valueInputOption' => $valueInputOption, 81 | 'insertDataOption' => $insertDataOption, 82 | ]; 83 | 84 | return $this->serviceValues()->append($this->getSpreadsheetId(), $range, $valueRange, $optParams); 85 | } 86 | 87 | public function orderAppendables(array $values): array 88 | { 89 | // The array has integer keys, so just append 90 | if (! Arr::isAssoc(head($values) ?: [])) { 91 | return $values; 92 | } 93 | 94 | // The array has keys, which we want to map to headers and order 95 | $header = $this->first(); 96 | 97 | $ordered = []; 98 | // Gets just the values of an array that has been re-ordered to match the header order 99 | foreach ($values as $value) { 100 | $ordered[] = array_values(array_replace(array_flip($header), $value)); 101 | } 102 | 103 | // Replaces null values with empty strings to work with Google's API 104 | return array_map(function ($row) { 105 | $notNull = []; 106 | foreach ($row as $key => $value) { 107 | // If key is the same as value, that's because the user 108 | // didn't specify a header that exists in the sheet. 109 | if (empty($value) || $key === $value) { 110 | $notNull[] = ''; 111 | } else { 112 | $notNull[] = $value; 113 | } 114 | } 115 | 116 | return $notNull; 117 | }, $ordered); 118 | } 119 | 120 | public function ranges(): ?string 121 | { 122 | // If no range is provided, we get the sheet automatically 123 | if (blank($this->range)) { 124 | return $this->sheet; 125 | } 126 | 127 | // If we only provide part of the range, we get the full proper range 128 | if (! Str::contains($this->range, '!')) { 129 | return $this->sheet.'!'.$this->range; 130 | } 131 | 132 | // If we provide the full range, it returns accurately 133 | return $this->range; 134 | } 135 | 136 | public function range(string $range): static 137 | { 138 | $this->range = $range; 139 | 140 | return $this; 141 | } 142 | 143 | public function majorDimension(string $majorDimension): static 144 | { 145 | $this->majorDimension = $majorDimension; 146 | 147 | return $this; 148 | } 149 | 150 | public function valueRenderOption(string $valueRenderOption): static 151 | { 152 | $this->valueRenderOption = $valueRenderOption; 153 | 154 | return $this; 155 | } 156 | 157 | public function dateTimeRenderOption(string $dateTimeRenderOption): static 158 | { 159 | $this->dateTimeRenderOption = $dateTimeRenderOption; 160 | 161 | return $this; 162 | } 163 | 164 | protected function serviceValues(): SpreadsheetsValues 165 | { 166 | return $this->getService()->spreadsheets_values; 167 | } 168 | 169 | protected function query(): array 170 | { 171 | $query = []; 172 | 173 | $ranges = $this->ranges(); 174 | 175 | if (! empty($ranges)) { 176 | $query['ranges'] = $ranges; 177 | } 178 | 179 | if (! empty($this->majorDimension)) { 180 | $query['majorDimension'] = $this->majorDimension; 181 | } 182 | 183 | if (! empty($this->valueRenderOption)) { 184 | $query['valueRenderOption'] = $this->valueRenderOption; 185 | } 186 | 187 | if (! empty($this->dateTimeRenderOption)) { 188 | $query['dateTimeRenderOption'] = $this->dateTimeRenderOption; 189 | } 190 | 191 | return $query; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/Contracts/Factory.php: -------------------------------------------------------------------------------- 1 | app->scoped(Factory::class, SheetsClient::class); 18 | } 19 | 20 | /** 21 | * Get the services provided by the provider. 22 | * 23 | * @codeCoverageIgnore 24 | */ 25 | public function provides(): array 26 | { 27 | return [Factory::class]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/SheetsClient.php: -------------------------------------------------------------------------------- 1 | service = $service; 36 | 37 | return $this; 38 | } 39 | 40 | public function getService(): GoogleSheets 41 | { 42 | return $this->service ??= Google::make('sheets'); 43 | } 44 | 45 | /** 46 | * set access_token and set new service. 47 | */ 48 | public function setAccessToken(#[\SensitiveParameter] array|string $token): static 49 | { 50 | Google::getCache()->clear(); 51 | 52 | Google::setAccessToken($token); 53 | 54 | if (isset($token['refresh_token']) && Google::isAccessTokenExpired()) { 55 | Google::fetchAccessTokenWithRefreshToken(); 56 | } 57 | 58 | return $this->setService(Google::make('sheets')) 59 | ->setDriveService(Google::make('drive')); 60 | } 61 | 62 | public function getAccessToken(): ?array 63 | { 64 | return $this->getService()->getClient()->getAccessToken(); 65 | } 66 | 67 | public function spreadsheet(string $spreadsheetId): static 68 | { 69 | $this->spreadsheetId = $spreadsheetId; 70 | 71 | return $this; 72 | } 73 | 74 | public function spreadsheetByTitle(string $title): static 75 | { 76 | $list = $this->spreadsheetList(); 77 | 78 | $this->spreadsheetId = Arr::get(array_flip($list), $title); 79 | 80 | return $this; 81 | } 82 | 83 | public function sheet(string $sheet): static 84 | { 85 | $this->sheet = $sheet; 86 | 87 | return $this; 88 | } 89 | 90 | public function sheetById(string $sheetId): static 91 | { 92 | $list = $this->sheetList(); 93 | 94 | $this->sheet = Arr::get($list, $sheetId); 95 | 96 | return $this; 97 | } 98 | 99 | public function sheetList(): array 100 | { 101 | $list = []; 102 | 103 | $sheets = $this->getService()->spreadsheets->get($this->getSpreadsheetId())->getSheets(); 104 | 105 | foreach ($sheets as $sheet) { 106 | $list[$sheet->getProperties()->getSheetId()] = $sheet->getProperties()->getTitle(); 107 | } 108 | 109 | return $list; 110 | } 111 | 112 | public function addSheet(string $sheetTitle): BatchUpdateSpreadsheetResponse 113 | { 114 | $body = new BatchUpdateSpreadsheetRequest( 115 | [ 116 | 'requests' => [ 117 | 'addSheet' => [ 118 | 'properties' => [ 119 | 'title' => $sheetTitle, 120 | ], 121 | ], 122 | ], 123 | ], 124 | ); 125 | 126 | return $this->getService()->spreadsheets->batchUpdate($this->getSpreadsheetId(), $body); 127 | } 128 | 129 | public function deleteSheet(string $sheetTitle): BatchUpdateSpreadsheetResponse 130 | { 131 | $list = $this->sheetList(); 132 | $id = Arr::get(array_flip($list), $sheetTitle); 133 | 134 | $body = new BatchUpdateSpreadsheetRequest( 135 | [ 136 | 'requests' => [ 137 | 'deleteSheet' => [ 138 | 'sheetId' => $id, 139 | ], 140 | ], 141 | ], 142 | ); 143 | 144 | return $this->getService()->spreadsheets->batchUpdate($this->getSpreadsheetId(), $body); 145 | } 146 | 147 | /** 148 | * @throws InvalidArgumentException 149 | */ 150 | public function __get(string $property) 151 | { 152 | if (property_exists($this->getService(), $property)) { 153 | return $this->getService()->{$property}; 154 | } 155 | 156 | throw new InvalidArgumentException(sprintf('Property [%s] does not exist.', $property)); 157 | } 158 | 159 | /** 160 | * Magic call method. 161 | * 162 | * @throws BadMethodCallException 163 | */ 164 | public function __call($method, $parameters) 165 | { 166 | if (method_exists($this->getService(), $method)) { 167 | return $this->getService()->{$method}(...array_values($parameters)); 168 | } 169 | 170 | return $this->macroCall($method, $parameters); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Traits/GoogleSheets.php: -------------------------------------------------------------------------------- 1 | sheetsAccessToken(); 16 | 17 | return Container::getInstance()->make(Factory::class)->setAccessToken($token); 18 | } 19 | 20 | /** 21 | * Get the Access Token. 22 | * 23 | * @return string|array 24 | */ 25 | abstract protected function sheetsAccessToken(); 26 | } 27 | --------------------------------------------------------------------------------