├── src ├── Contracts │ ├── Query.php │ └── Factory.php ├── Commands │ ├── stubs │ │ └── query.stub │ └── QueryMakeCommand.php ├── Query │ └── AbstractQuery.php ├── Traits │ └── WithSearchConsole.php ├── Facades │ └── SearchConsole.php ├── Concerns │ └── SearchConsole.php ├── Providers │ └── SearchConsoleServiceProvider.php └── SearchConsoleClient.php ├── docs ├── macro.md ├── trait.md └── workbench.md ├── LICENSE ├── composer.json └── README.md /src/Contracts/Query.php: -------------------------------------------------------------------------------- 1 | init(); 16 | } 17 | 18 | abstract public function init(): void; 19 | } 20 | -------------------------------------------------------------------------------- /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\SearchConsole\Facades\SearchConsole; 9 | 10 | public function boot() 11 | { 12 | SearchConsole::macro('submit', function (string $siteUrl, string $feedpath, array $optParams = []): object { 13 | return $this->getService()->sitemaps->submit($siteUrl, $feedpath, $optParams)->toSimpleObject(); 14 | }); 15 | } 16 | ``` 17 | 18 | ## Use somewhere 19 | ```php 20 | $response = SearchConsole::submit($siteUrl, $feedpath); 21 | ``` 22 | -------------------------------------------------------------------------------- /src/Contracts/Factory.php: -------------------------------------------------------------------------------- 1 | tokenForSearchConsole(); 20 | 21 | return Container::getInstance()->make(Factory::class)->setAccessToken($token); 22 | } 23 | 24 | /** 25 | * Get the Access Token. 26 | */ 27 | abstract protected function tokenForSearchConsole(): array|string; 28 | } 29 | -------------------------------------------------------------------------------- /src/Facades/SearchConsole.php: -------------------------------------------------------------------------------- 1 | tokenForSearchConsole(); 23 | 24 | return Container::getInstance()->make(Factory::class)->setAccessToken($token); 25 | } 26 | 27 | /** 28 | * Get the Access Token. 29 | * 30 | * @deprecated Use {@link WithSearchConsole} instead. 31 | */ 32 | abstract protected function tokenForSearchConsole(): array|string; 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 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 | -------------------------------------------------------------------------------- /src/Providers/SearchConsoleServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 19 | $this->commands([ 20 | QueryMakeCommand::class, 21 | ]); 22 | } 23 | } 24 | 25 | /** 26 | * Register the service provider. 27 | */ 28 | public function register(): void 29 | { 30 | $this->app->scoped(Factory::class, SearchConsoleClient::class); 31 | } 32 | 33 | /** 34 | * Get the services provided by the provider. 35 | */ 36 | public function provides(): array 37 | { 38 | return [Factory::class]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Commands/QueryMakeCommand.php: -------------------------------------------------------------------------------- 1 | $this->access_token, 32 | 'refresh_token' => $this->refresh_token, 33 | 'expires_in' => 3600, 34 | 'created' => $this->updated_at->getTimestamp(), 35 | ]; 36 | } 37 | } 38 | ``` 39 | 40 | Add `tokenForSearchConsole()`(abstract) for access_token. 41 | 42 | Trait has `searchconsole()` that returns `SearchConsole` instance. 43 | 44 | ```php 45 | public function __invoke(Request $request) 46 | { 47 | $sites = $request->user() 48 | ->searchconsole() 49 | ->listSites(); 50 | 51 | $sites = $sites->rows ?? []; 52 | 53 | return view('sites.index')->with(compact('sites')); 54 | } 55 | ``` 56 | 57 | ## Already searchconsole() exists 58 | 59 | ```php 60 | use WithSearchConsole { 61 | WithSearchConsole::searchconsole as sc; 62 | } 63 | ``` 64 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "revolution/laravel-google-searchconsole", 3 | "description": "Google SearchConsole API for Laravel", 4 | "keywords": [ 5 | "google", 6 | "searchconsole", 7 | "laravel" 8 | ], 9 | "license": "MIT", 10 | "require": { 11 | "php": "^8.2", 12 | "illuminate/support": "^11.0||^12.0", 13 | "revolution/laravel-google-sheets": "^7.0" 14 | }, 15 | "require-dev": { 16 | "orchestra/testbench": "^10.0", 17 | "laravel/pint": "^1.22" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "Revolution\\Google\\SearchConsole\\": "src/" 22 | } 23 | }, 24 | "autoload-dev": { 25 | "psr-4": { 26 | "Tests\\": "tests/", 27 | "Workbench\\App\\": "workbench/app/", 28 | "Workbench\\Database\\Factories\\": "workbench/database/factories/", 29 | "Workbench\\Database\\Seeders\\": "workbench/database/seeders/" 30 | } 31 | }, 32 | "authors": [ 33 | { 34 | "name": "kawax", 35 | "email": "kawaxbiz@gmail.com" 36 | } 37 | ], 38 | "extra": { 39 | "laravel": { 40 | "providers": [ 41 | "Revolution\\Google\\SearchConsole\\Providers\\SearchConsoleServiceProvider" 42 | ] 43 | } 44 | }, 45 | "scripts": { 46 | "post-autoload-dump": [ 47 | "@clear", 48 | "@prepare" 49 | ], 50 | "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", 51 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 52 | "build": "@php vendor/bin/testbench workbench:build --ansi", 53 | "serve": [ 54 | "Composer\\Config::disableProcessTimeout", 55 | "@build", 56 | "@php vendor/bin/testbench serve --ansi" 57 | ], 58 | "lint": [ 59 | "@php vendor/bin/pint --ansi" 60 | ], 61 | "test": [ 62 | "@clear", 63 | "@php vendor/bin/phpunit" 64 | ] 65 | } 66 | } -------------------------------------------------------------------------------- /src/SearchConsoleClient.php: -------------------------------------------------------------------------------- 1 | service = $service; 24 | 25 | return $this; 26 | } 27 | 28 | public function getService(): Webmasters 29 | { 30 | return $this->service ?? Google::make('Webmasters'); 31 | } 32 | 33 | /** 34 | * set access_token and set new service. 35 | */ 36 | public function setAccessToken(array|string $token): static 37 | { 38 | Google::getCache()->clear(); 39 | 40 | Google::setAccessToken($token); 41 | 42 | if (isset($token['refresh_token']) and Google::isAccessTokenExpired()) { 43 | Google::fetchAccessTokenWithRefreshToken(); 44 | } 45 | 46 | return $this->setService(Google::make('Webmasters')); 47 | } 48 | 49 | public function getAccessToken(): array 50 | { 51 | return $this->getService()->getClient()->getAccessToken(); 52 | } 53 | 54 | public function query(string $url, Query|SearchAnalyticsQueryRequest $query): object 55 | { 56 | return $this->serviceSearchAnalytics()->query($url, $query)->toSimpleObject(); 57 | } 58 | 59 | protected function serviceSearchAnalytics(): Searchanalytics 60 | { 61 | return $this->getService()->searchanalytics; 62 | } 63 | 64 | public function listSites(array $optParams = []): object 65 | { 66 | return $this->serviceSites()->listSites($optParams)->toSimpleObject(); 67 | } 68 | 69 | protected function serviceSites(): Sites 70 | { 71 | return $this->getService()->sites; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /docs/workbench.md: -------------------------------------------------------------------------------- 1 | # Orchestra Testbench Workbench Development Guide 2 | 3 | This guide provides comprehensive documentation for developing Laravel packages using Orchestra Testbench Workbench, based on real-world examples and development patterns from the Laravel ecosystem. 4 | 5 | ## Overview 6 | 7 | Orchestra Testbench Workbench provides a complete Laravel application environment for package development, allowing you to: 8 | 9 | - **Test your package in a real Laravel application context** 10 | - **Develop and preview package functionality interactively** 11 | - **Create demo applications and examples** 12 | - **Build comprehensive test suites with realistic scenarios** 13 | - **Serve your package with a full Laravel application for development** 14 | 15 | Workbench creates a complete Laravel application structure within your package, enabling you to develop, test, and demonstrate your package functionality in an isolated environment. 16 | 17 | ## Installation and Setup 18 | 19 | ### 1. Install Orchestra Testbench 20 | 21 | Add Orchestra Testbench to your package's development dependencies: 22 | 23 | ```bash 24 | composer require --dev orchestra/testbench 25 | ``` 26 | 27 | ### 2. Install Workbench 28 | 29 | Use the built-in installation command to set up workbench automatically: 30 | 31 | ```bash 32 | # Install workbench (creates directory structure and updates composer.json) 33 | vendor/bin/testbench workbench:install 34 | ``` 35 | 36 | This command will: 37 | - Create the complete `workbench/` directory structure 38 | - Automatically update your `composer.json` with the necessary autoload-dev configuration 39 | - Set up the required composer scripts for workbench development 40 | 41 | #### Installation Options 42 | 43 | ```bash 44 | # Force overwrite existing files 45 | vendor/bin/testbench workbench:install --force 46 | 47 | # Skip routes and discovers installation (basic setup) 48 | vendor/bin/testbench workbench:install --basic 49 | 50 | # Install with DevTool support 51 | vendor/bin/testbench workbench:install --devtool 52 | ``` 53 | 54 | #### Manual Composer Configuration (Alternative) 55 | 56 | If you prefer to configure manually or need custom settings, you can update your `composer.json`: 57 | 58 | ```json 59 | { 60 | "autoload-dev": { 61 | "psr-4": { 62 | "Tests\\": "tests/", 63 | "Workbench\\App\\": "workbench/app/", 64 | "Workbench\\Database\\Factories\\": "workbench/database/factories/", 65 | "Workbench\\Database\\Seeders\\": "workbench/database/seeders/" 66 | } 67 | }, 68 | "scripts": { 69 | "post-autoload-dump": [ 70 | "@clear", 71 | "@prepare" 72 | ], 73 | "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", 74 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 75 | "build": "@php vendor/bin/testbench workbench:build --ansi", 76 | "serve": [ 77 | "Composer\\Config::disableProcessTimeout", 78 | "@build", 79 | "@php vendor/bin/testbench serve --ansi" 80 | ] 81 | } 82 | } 83 | ``` 84 | 85 | ### 3. Initialize Workbench 86 | 87 | After installation, run the following commands to set up your workbench environment: 88 | 89 | ```bash 90 | # Clear any existing skeleton 91 | composer clear 92 | 93 | # Discover and prepare the package 94 | composer prepare 95 | 96 | # Build the workbench application 97 | composer build 98 | ``` 99 | 100 | ## Configuration with testbench.yaml 101 | 102 | The `testbench.yaml` file is the heart of workbench configuration. Create this file in your package root: 103 | 104 | ### Basic Configuration 105 | 106 | ```yaml 107 | providers: 108 | - Laravel\Socialite\SocialiteServiceProvider 109 | - Your\Package\Providers\YourServiceProvider 110 | - Workbench\App\Providers\WorkbenchServiceProvider 111 | 112 | migrations: 113 | - workbench/database/migrations 114 | 115 | workbench: 116 | start: '/' 117 | install: true 118 | health: false 119 | discovers: 120 | web: true 121 | api: false 122 | commands: false 123 | components: false 124 | views: false 125 | config: true 126 | build: 127 | - asset-publish 128 | - create-sqlite-db 129 | - db-wipe 130 | - migrate-fresh: 131 | --seed: true 132 | --seeder: Workbench\Database\Seeders\DatabaseSeeder 133 | assets: 134 | - laravel-assets 135 | sync: [] 136 | ``` 137 | 138 | ### Configuration File Management 139 | 140 | The `testbench.yaml` file is specified in `.gitignore` and is not included in the repository. This allows developers to have personalized configurations without committing sensitive or environment-specific settings. 141 | 142 | To use workbench configuration: 143 | 144 | 1. Copy the example configuration file: 145 | ```bash 146 | cp testbench.yaml.example testbench.yaml 147 | ``` 148 | 149 | 2. Customize your local `testbench.yaml` file as needed for your development environment 150 | 151 | 3. The `testbench.yaml.example` file serves as a template and should be committed to the repository to help other developers set up their workbench 152 | 153 | ### Configuration Options Explained 154 | 155 | #### Providers 156 | List all service providers that should be loaded in the workbench environment: 157 | - Third-party service providers (like Socialite) 158 | - Your package's service providers 159 | - Custom workbench service providers 160 | 161 | #### Environment Variables 162 | ```yaml 163 | env: 164 | - APP_ENV=testing 165 | - APP_KEY=base64:your-app-key 166 | - DB_CONNECTION=sqlite 167 | - DB_DATABASE=:memory: 168 | - YOUR_PACKAGE_CONFIG=value 169 | ``` 170 | 171 | #### Migrations 172 | Specify directories containing migrations to run: 173 | ```yaml 174 | migrations: 175 | - workbench/database/migrations 176 | - database/migrations 177 | ``` 178 | 179 | #### Workbench Settings 180 | - `start`: The default route when serving the workbench application 181 | - `install`: Whether to run package installation during build 182 | - `health`: Enable/disable health checks 183 | - `discovers`: Control Laravel's package auto-discovery features 184 | - `config: true`: Load configuration files from `workbench/config/` directory 185 | 186 | #### Build Process 187 | Define commands to run during workbench build: 188 | ```yaml 189 | build: 190 | - asset-publish # Publish package assets 191 | - create-sqlite-db # Create SQLite database 192 | - db-wipe # Clean database 193 | - migrate-fresh: # Run fresh migrations 194 | --seed: true 195 | --seeder: Workbench\Database\Seeders\DatabaseSeeder 196 | ``` 197 | 198 | ## Workbench Directory Structure 199 | 200 | When you build your workbench, it creates a complete Laravel application structure: 201 | 202 | ``` 203 | workbench/ 204 | ├── app/ 205 | │ ├── Http/ 206 | │ │ └── Controllers/ 207 | │ ├── Models/ 208 | │ └── Providers/ 209 | │ └── WorkbenchServiceProvider.php 210 | ├── bootstrap/ 211 | ├── config/ 212 | ├── database/ 213 | │ ├── factories/ 214 | │ ├── migrations/ 215 | │ └── seeders/ 216 | │ └── DatabaseSeeder.php 217 | ├── public/ 218 | ├── resources/ 219 | │ ├── views/ 220 | │ └── js/ 221 | ├── routes/ 222 | │ ├── web.php 223 | │ ├── api.php 224 | │ └── console.php 225 | └── storage/ 226 | ``` 227 | 228 | ## Creating Workbench Components 229 | 230 | ### 1. Workbench Service Provider 231 | 232 | Create a custom service provider for workbench-specific functionality: 233 | 234 | ```php 235 | app->singleton(DemoService::class); 249 | } 250 | 251 | public function boot(): void 252 | { 253 | // Register package extensions or demo functionality 254 | SomeClass::register('demo', DemoService::class); 255 | 256 | // Load workbench routes 257 | $this->loadRoutesFrom(__DIR__.'/../../routes/web.php'); 258 | 259 | // Load workbench views 260 | $this->loadViewsFrom(__DIR__.'/../../resources/views', 'workbench'); 261 | } 262 | } 263 | ``` 264 | 265 | ### 2. Database Seeders 266 | 267 | Create seeders to populate test data: 268 | 269 | ```php 270 | count(10)->create(); 283 | 284 | // Seed package-specific data 285 | $this->call([ 286 | PackageDataSeeder::class, 287 | ]); 288 | } 289 | } 290 | ``` 291 | 292 | ### 3. Model Factories 293 | 294 | Create factories for testing and demo data: 295 | 296 | ```php 297 | $this->faker->name(), 312 | 'email' => $this->faker->unique()->safeEmail(), 313 | 'email_verified_at' => now(), 314 | 'password' => bcrypt('password'), 315 | ]; 316 | } 317 | } 318 | ``` 319 | 320 | ### 4. Controllers and Routes 321 | 322 | Create controllers to demonstrate package functionality: 323 | 324 | ```php 325 | json([ 344 | 'package_version' => YourPackage::version(), 345 | 'features' => YourPackage::getFeatures(), 346 | ]); 347 | } 348 | } 349 | ``` 350 | 351 | Routes in `workbench/routes/web.php`: 352 | 353 | ```php 354 | comment(Inspiring::quote()); 375 | })->purpose('Display an inspiring quote'); 376 | ``` 377 | 378 | Run workbench console commands using the testbench command wrapper: 379 | 380 | ```bash 381 | # Execute workbench console commands 382 | vendor/bin/testbench inspire 383 | 384 | # List all available commands 385 | vendor/bin/testbench list 386 | ``` 387 | 388 | ## Testing with Workbench 389 | 390 | ### Using WithWorkbench Trait 391 | 392 | Integrate workbench into your test suite: 393 | 394 | ```php 395 | set('database.default', 'testing'); 417 | $app['config']->set('your-package.key', 'test-value'); 418 | } 419 | } 420 | ``` 421 | 422 | ### Feature Tests with Workbench 423 | 424 | ```php 425 | get('/'); 436 | 437 | $response->assertStatus(200); 438 | $response->assertViewIs('workbench::demo'); 439 | } 440 | 441 | public function test_package_api_integration() 442 | { 443 | $response = $this->get('/api/demo'); 444 | 445 | $response->assertStatus(200); 446 | $response->assertJsonStructure([ 447 | 'package_version', 448 | 'features', 449 | ]); 450 | } 451 | } 452 | ``` 453 | 454 | ## Development Workflow 455 | 456 | ### 1. Daily Development 457 | 458 | ```bash 459 | # Start development server 460 | composer serve 461 | 462 | # Build workbench after changes 463 | composer build 464 | 465 | # Run tests 466 | composer test 467 | 468 | # Lint code 469 | composer lint 470 | ``` 471 | 472 | ### 2. Package Development Cycle 473 | 474 | 1. **Develop**: Write your package code in `src/` 475 | 2. **Configure**: Update `testbench.yaml` with required providers and settings 476 | 3. **Build**: Run `composer build` to create workbench application 477 | 4. **Test**: Use workbench routes and controllers to test functionality 478 | 5. **Iterate**: Make changes and rebuild as needed 479 | 480 | ### 3. Serving the Workbench 481 | 482 | The workbench serves as a complete Laravel application: 483 | 484 | ```bash 485 | # Serve with automatic building 486 | composer serve 487 | 488 | # Or manually 489 | composer build 490 | vendor/bin/testbench serve 491 | ``` 492 | 493 | Access your workbench at `http://localhost:8000` (or configured port). 494 | 495 | ## Advanced Configuration 496 | 497 | ### Custom Environment Files 498 | 499 | Create environment-specific configurations: 500 | 501 | ```yaml 502 | # testbench.yaml 503 | env: 504 | - APP_ENV=testing 505 | - DB_CONNECTION=sqlite 506 | - DB_DATABASE=:memory: 507 | 508 | # For different environments 509 | workbench: 510 | start: '/' 511 | install: true 512 | discovers: 513 | web: true 514 | api: true 515 | commands: true 516 | ``` 517 | 518 | ### Asset Management 519 | 520 | Handle package assets in workbench: 521 | 522 | ```yaml 523 | workbench: 524 | assets: 525 | - laravel-assets 526 | - package-assets 527 | build: 528 | - asset-publish 529 | - npm-install 530 | - npm-run-build 531 | ``` 532 | 533 | ### Database Configuration 534 | 535 | Configure different database setups: 536 | 537 | ```yaml 538 | env: 539 | - DB_CONNECTION=mysql 540 | - DB_HOST=127.0.0.1 541 | - DB_PORT=3306 542 | - DB_DATABASE=workbench_test 543 | - DB_USERNAME=root 544 | - DB_PASSWORD= 545 | 546 | migrations: 547 | - workbench/database/migrations 548 | - vendor/other-package/migrations 549 | ``` 550 | 551 | ## Best Practices 552 | 553 | ### 1. Workbench Organization 554 | 555 | - Keep workbench code separate from package code 556 | - Use workbench for demonstrations and integration testing 557 | - Create realistic test scenarios in workbench controllers 558 | - Document workbench setup in your package README 559 | 560 | ### 2. Configuration Management 561 | 562 | - Use environment variables for sensitive configuration 563 | - Provide example configurations in `testbench.yaml.example` 564 | - Document required environment variables 565 | - Use sensible defaults for development 566 | 567 | ### 3. Testing Strategy 568 | 569 | - Use workbench for integration testing 570 | - Test package functionality through workbench routes 571 | - Create comprehensive seeders for test data 572 | - Verify package behavior in realistic scenarios 573 | 574 | ### 4. Development Efficiency 575 | 576 | - Set up composer scripts for common tasks 577 | - Use workbench for rapid prototyping 578 | - Create demo pages to showcase package features 579 | - Automate workbench building in your development workflow 580 | 581 | ## Common Patterns 582 | 583 | ### API Package Development 584 | 585 | For packages providing API functionality: 586 | 587 | ```yaml 588 | workbench: 589 | discovers: 590 | api: true 591 | build: 592 | - create-sqlite-db 593 | - migrate-fresh 594 | ``` 595 | 596 | ### UI Component Packages 597 | 598 | For packages with frontend components: 599 | 600 | ```yaml 601 | workbench: 602 | discovers: 603 | views: true 604 | components: true 605 | assets: 606 | - laravel-assets 607 | - npm-install 608 | - npm-run-build 609 | ``` 610 | 611 | ### Service Integration Packages 612 | 613 | For packages integrating external services: 614 | 615 | ```yaml 616 | env: 617 | - SERVICE_API_KEY=test-key 618 | - SERVICE_ENDPOINT=https://api.example.com 619 | 620 | workbench: 621 | health: true 622 | build: 623 | - service-health-check 624 | ``` 625 | 626 | ## Troubleshooting 627 | 628 | ### Common Issues 629 | 630 | 1. **Workbench not building**: Check `testbench.yaml` syntax and provider configuration 631 | 2. **Routes not loading**: Verify route file paths and service provider registration 632 | 3. **Database issues**: Ensure migration paths are correct and database is configured 633 | 4. **Asset problems**: Check asset publishing configuration and build scripts 634 | 635 | ### Debug Commands 636 | 637 | ```bash 638 | # Clear and rebuild 639 | composer clear && composer prepare && composer build 640 | 641 | # Check package discovery 642 | vendor/bin/testbench package:discover --ansi 643 | 644 | # Verify configuration 645 | vendor/bin/testbench about 646 | 647 | # Check routes 648 | vendor/bin/testbench route:list 649 | ``` 650 | 651 | ## Conclusion 652 | 653 | Orchestra Testbench Workbench provides a powerful environment for Laravel package development. By following these patterns and best practices, you can create comprehensive development and testing environments that make package development more efficient and reliable. 654 | 655 | The key to successful workbench usage is treating it as a complete Laravel application that showcases and tests your package functionality in realistic scenarios. This approach leads to better package design, more thorough testing, and clearer documentation for your package users. 656 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google SearchConsole API for Laravel 2 | 3 | [![Maintainability](https://qlty.sh/badges/9fb434a4-1cd3-4203-af92-efca4ca98a81/maintainability.svg)](https://qlty.sh/gh/invokable/projects/laravel-google-searchconsole) 4 | [![Code Coverage](https://qlty.sh/badges/9fb434a4-1cd3-4203-af92-efca4ca98a81/test_coverage.svg)](https://qlty.sh/gh/invokable/projects/laravel-google-searchconsole) 5 | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/invokable/laravel-google-searchconsole) 6 | 7 | https://developers.google.com/webmaster-tools 8 | 9 | ## Overview 10 | 11 | This Laravel package provides a comprehensive PHP wrapper for the Google Search Console API, enabling you to seamlessly integrate Search Console functionality into your Laravel applications. With this package, you can: 12 | 13 | - **Query Search Analytics Data**: Retrieve search performance metrics including impressions, clicks, CTR, and average position 14 | - **Analyze Website Performance**: Get detailed insights about your website's performance in Google Search results 15 | - **Filter by Dimensions**: Query data by page, query, country, device, and more 16 | - **Manage Site Properties**: List and manage your Search Console properties 17 | - **Flexible Authentication**: Support for both OAuth 2.0 (user-based) and Service Account (server-to-server) authentication 18 | - **Laravel Integration**: Built specifically for Laravel with facades, service providers, and Artisan commands 19 | 20 | The package leverages the powerful `revolution/laravel-google-sheets` dependency for Google API client management and authentication, providing automatic token refresh and robust error handling. 21 | 22 | **Perfect for**: SEO tools, analytics dashboards, automated reporting, website monitoring, and any application that needs to access Google Search Console data programmatically. 23 | 24 | ## Requirements 25 | - PHP >= 8.2 26 | - Laravel >= 11.0 27 | 28 | ## Installation 29 | 30 | ```bash 31 | composer require revolution/laravel-google-searchconsole 32 | 33 | php artisan vendor:publish --tag="google-config" 34 | ``` 35 | 36 | ### Uninstall 37 | ``` 38 | composer remove revolution/laravel-google-searchconsole 39 | ``` 40 | 41 | ## Sample project 42 | 43 | https://github.com/invokable/search-console-project 44 | 45 | ## Configuration 46 | 47 | ### Authentication Methods 48 | 49 | This package supports two authentication methods for accessing the Google Search Console API: 50 | 51 | - **OAuth 2.0**: Recommended for user-based access when you need to access data on behalf of individual Google users 52 | - **Service Account**: Ideal for server-to-server applications with automated access to specific properties 53 | - **Note**: API Key authentication is NOT supported by Google Search Console API 54 | 55 | ### Prerequisites 56 | 57 | 1. Go to the [Google Cloud Console](https://console.cloud.google.com/) 58 | 2. Create a new project or select an existing one 59 | 3. Enable the **Google Search Console API** 60 | 4. Create credentials based on your chosen authentication method 61 | 62 | ## OAuth 2.0 Setup (using Socialite) 63 | 64 | For OAuth authentication, this package works seamlessly with Laravel Socialite's official Google driver. 65 | 66 | ### 1. Install Laravel Socialite 67 | 68 | ```bash 69 | composer require laravel/socialite 70 | ``` 71 | 72 | ### 2. Create OAuth 2.0 Credentials 73 | 74 | In Google Cloud Console: 75 | 1. Go to "Credentials" → "Create Credentials" → "OAuth 2.0 Client IDs" 76 | 2. Set application type to "Web application" 77 | 3. Add your authorized redirect URIs (e.g., `https://yourapp.com/auth/google/callback`) 78 | 79 | ### 3. Configure OAuth Settings 80 | 81 | Add to `config/services.php`: 82 | 83 | ```php 84 | 'google' => [ 85 | 'client_id' => env('GOOGLE_CLIENT_ID'), 86 | 'client_secret' => env('GOOGLE_CLIENT_SECRET'), 87 | 'redirect' => env('GOOGLE_REDIRECT_URI'), 88 | ], 89 | ``` 90 | 91 | Add to `config/google.php`: 92 | 93 | ```php 94 | 'client_id' => env('GOOGLE_CLIENT_ID'), 95 | 'client_secret' => env('GOOGLE_CLIENT_SECRET'), 96 | 'redirect_uri' => env('GOOGLE_REDIRECT_URI'), 97 | 'scopes' => [\Google\Service\Webmasters::WEBMASTERS], 98 | 'access_type' => 'offline', 99 | 'prompt' => 'consent select_account', 100 | ``` 101 | 102 | ### 4. Environment Variables for OAuth 103 | 104 | ```env 105 | GOOGLE_CLIENT_ID=your_oauth_client_id 106 | GOOGLE_CLIENT_SECRET=your_oauth_client_secret 107 | GOOGLE_REDIRECT_URI=https://yourapp.com/auth/google/callback 108 | ``` 109 | 110 | ## Service Account Setup 111 | 112 | Service Accounts provide server-to-server authentication without user interaction. The Service Account email must be added as an owner or user in Search Console for each property you want to access. 113 | 114 | ### 1. Create Service Account Credentials 115 | 116 | In Google Cloud Console: 117 | 1. Go to "Credentials" → "Create Credentials" → "Service Account" 118 | 2. Fill in the service account details 119 | 3. Create and download the JSON key file 120 | 121 | ### 2. Configure Service Account 122 | 123 | The package automatically uses Service Account authentication when configured through the `laravel-google-sheets` dependency. Place your service account JSON file in your Laravel storage directory. 124 | 125 | Even for service accounts, scopes must be set in `config/google.php`: 126 | 127 | ```php 128 | 'scopes' => [\Google\Service\Webmasters::WEBMASTERS], 129 | ``` 130 | 131 | ### 3. Environment Variables for Service Account 132 | 133 | #### Option 1: Using File Path 134 | 135 | ```env 136 | GOOGLE_SERVICE_ENABLED=true 137 | GOOGLE_SERVICE_ACCOUNT_JSON_LOCATION=/path/to/service-account.json 138 | ``` 139 | 140 | #### Option 2: Using JSON String 141 | 142 | `GOOGLE_SERVICE_ACCOUNT_JSON_LOCATION` can be an array other than a file path, so you can set it as a JSON string in `.env` and decode it to an array in `config/google.php`. 143 | 144 | ```php 145 | // config/google.php 146 | 'service' => [ 147 | 'file' => json_decode(env('GOOGLE_SERVICE_ACCOUNT_JSON_LOCATION', ''), true), 148 | ], 149 | ``` 150 | 151 | ```env 152 | // .env 153 | GOOGLE_SERVICE_ACCOUNT_JSON_LOCATION='{"type": "service_account", "project_id": "your-project", "private_key_id": "...", "private_key": "...", "client_email": "...", "client_id": "...", "auth_uri": "...", "token_uri": "...", "auth_provider_x509_cert_url": "...", "client_x509_cert_url": "..."}' 154 | ``` 155 | 156 | This method is particularly well-suited for GitHub Actions deployment, as it allows the entire service account credentials to be stored as a single secret. 157 | 158 | ### 4. Add Service Account to Search Console 159 | 160 | 1. Go to [Google Search Console](https://search.google.com/search-console) 161 | 2. Select your property 162 | 3. Go to Settings → Users and permissions 163 | 4. Add your service account email as a user with "Full" permissions 164 | 165 | ## Creating Custom Queries 166 | 167 | All query classes extend `Revolution\Google\SearchConsole\Query\AbstractQuery`, which is a subclass of `Google\Service\Webmasters\SearchAnalyticsQueryRequest`. This provides access to all Google Search Console API query parameters. 168 | 169 | ### Generate Query Classes 170 | 171 | Use the Artisan command to create new query classes in `app/Search`: 172 | 173 | ```bash 174 | php artisan make:search:query YourQueryName 175 | ``` 176 | 177 | ### Query Structure 178 | 179 | Each query class must implement the `init()` method where you define your query parameters: 180 | 181 | ```php 182 | setStartDate('2024-01-01'); // Start date (YYYY-MM-DD) 204 | $this->setEndDate('2024-01-31'); // End date (YYYY-MM-DD) 205 | $this->setStartDate(now()->subDays(30)->toDateString()); // Dynamic dates 206 | ``` 207 | 208 | #### Dimensions (group results by) 209 | ```php 210 | $this->setDimensions(['query']); // Group by search queries 211 | $this->setDimensions(['page']); // Group by pages 212 | $this->setDimensions(['country']); // Group by countries 213 | $this->setDimensions(['device']); // Group by device types 214 | $this->setDimensions(['searchAppearance']); // Group by search appearance 215 | $this->setDimensions(['query', 'page']); // Multiple dimensions 216 | ``` 217 | 218 | #### Filters 219 | ```php 220 | // Filter by specific queries 221 | $this->setDimensionFilterGroups([ 222 | [ 223 | 'filters' => [ 224 | [ 225 | 'dimension' => 'query', 226 | 'operator' => 'contains', 227 | 'expression' => 'laravel' 228 | ] 229 | ] 230 | ] 231 | ]); 232 | 233 | // Filter by specific pages 234 | $this->setDimensionFilterGroups([ 235 | [ 236 | 'filters' => [ 237 | [ 238 | 'dimension' => 'page', 239 | 'operator' => 'equals', 240 | 'expression' => 'https://example.com/page' 241 | ] 242 | ] 243 | ] 244 | ]); 245 | ``` 246 | 247 | #### Additional Options 248 | ```php 249 | $this->setRowLimit(100); // Limit results (max 25,000) 250 | $this->setStartRow(0); // Pagination offset 251 | $this->setAggregationType(['auto']); // Aggregation type 252 | $this->setDataState('final'); // 'final' or 'fresh' data 253 | ``` 254 | 255 | ### Example Query Classes 256 | 257 | #### Top Pages Query 258 | ```php 259 | setStartDate(now()->subDays(30)->toDateString()); 270 | $this->setEndDate(now()->toDateString()); 271 | $this->setDimensions(['page']); 272 | $this->setRowLimit(50); 273 | $this->setDataState('final'); 274 | } 275 | } 276 | ``` 277 | 278 | #### Mobile Search Queries 279 | ```php 280 | setStartDate(now()->subDays(7)->toDateString()); 291 | $this->setEndDate(now()->toDateString()); 292 | $this->setDimensions(['query']); 293 | 294 | // Filter for mobile devices only 295 | $this->setDimensionFilterGroups([ 296 | [ 297 | 'filters' => [ 298 | [ 299 | 'dimension' => 'device', 300 | 'operator' => 'equals', 301 | 'expression' => 'mobile' 302 | ] 303 | ] 304 | ] 305 | ]); 306 | 307 | $this->setRowLimit(100); 308 | } 309 | } 310 | ``` 311 | 312 | #### Country Performance Query 313 | ```php 314 | setStartDate(now()->subMonths(3)->toDateString()); 325 | $this->setEndDate(now()->toDateString()); 326 | $this->setDimensions(['country', 'query']); 327 | $this->setRowLimit(200); 328 | $this->setAggregationType(['auto']); 329 | } 330 | } 331 | ``` 332 | 333 | ## Basic Usage 334 | 335 | ### Using OAuth 2.0 Authentication 336 | 337 | When using OAuth, you'll need to obtain access tokens through the Socialite authentication flow, then use `setAccessToken()` to authenticate your requests. 338 | 339 | #### 1. OAuth Authentication Flow (Controller Example) 340 | 341 | ```php 342 | scopes(config('google.scopes')) 357 | ->with([ 358 | 'access_type' => config('google.access_type'), 359 | 'prompt' => config('google.prompt'), 360 | ]) 361 | ->redirect(); 362 | } 363 | 364 | public function handleGoogleCallback() 365 | { 366 | $user = Socialite::driver('google')->user(); 367 | 368 | // Store tokens in your database 369 | auth()->user()->update([ 370 | 'google_access_token' => $user->token, 371 | 'google_refresh_token' => $user->refreshToken, 372 | ]); 373 | 374 | return redirect()->route('dashboard'); 375 | } 376 | 377 | public function getSearchConsoleData(Request $request) 378 | { 379 | $token = [ 380 | 'access_token' => auth()->user()->google_access_token, 381 | 'refresh_token' => auth()->user()->google_refresh_token, 382 | ]; 383 | 384 | // Create and execute query 385 | $query = new TopPagesQuery(); 386 | $result = SearchConsole::setAccessToken($token) 387 | ->query('https://example.com/', $query); 388 | 389 | return response()->json($result); 390 | } 391 | } 392 | ``` 393 | 394 | #### 2. Using OAuth with Model Integration 395 | 396 | You can use the `WithSearchConsole` trait to integrate Search Console functionality directly into your models: 397 | 398 | ```php 399 | $this->google_access_token, 418 | 'refresh_token' => $this->google_refresh_token, 419 | ]; 420 | } 421 | } 422 | ``` 423 | 424 | Then use it in your application: 425 | 426 | ```php 427 | use App\Search\TopPagesQuery; 428 | 429 | $user = User::find(1); 430 | $query = new TopPagesQuery(); 431 | 432 | // The searchconsole() method automatically handles authentication 433 | $result = $user->searchconsole()->query('https://example.com/', $query); 434 | $sites = $user->searchconsole()->listSites(); 435 | ``` 436 | 437 | ### Using Service Account Authentication 438 | 439 | With Service Account authentication, the package automatically handles authentication through the GoogleApiClient included in `laravel-google-sheets`. No manual token management is required. 440 | 441 | #### Service Account Usage Example 442 | 443 | ```php 444 | $siteUrl]); 483 | } 484 | } 485 | ``` 486 | 487 | #### Using Service Account in Commands 488 | 489 | ```php 490 | argument('site_url'); 506 | $query = new TopPagesQuery(); 507 | 508 | try { 509 | $result = SearchConsole::query($siteUrl, $query); 510 | 511 | $this->info("Found {$result->rowCount} results:"); 512 | 513 | foreach ($result->rows as $row) { 514 | $this->line("Page: {$row->keys[0]}"); 515 | $this->line(" Clicks: {$row->clicks}"); 516 | $this->line(" Impressions: {$row->impressions}"); 517 | $this->line(" CTR: " . round($row->ctr * 100, 2) . "%"); 518 | $this->line(" Position: " . round($row->position, 1)); 519 | $this->line(""); 520 | } 521 | } catch (\Exception $e) { 522 | $this->error("Error fetching data: " . $e->getMessage()); 523 | } 524 | } 525 | } 526 | ``` 527 | 528 | ### Quick Reference 529 | 530 | #### Available Facade Methods 531 | 532 | ```php 533 | use Revolution\Google\SearchConsole\Facades\SearchConsole; 534 | 535 | // Set access token (OAuth only) 536 | SearchConsole::setAccessToken($token); 537 | 538 | // Execute a query 539 | SearchConsole::query($siteUrl, $queryObject); 540 | 541 | // List all sites 542 | SearchConsole::listSites(); 543 | 544 | // List sites with parameters 545 | SearchConsole::listSites(['siteUrl' => 'https://example.com/']); 546 | ``` 547 | 548 | #### Response Structure 549 | 550 | The API returns objects with the following structure: 551 | 552 | ```php 553 | $result = SearchConsole::query($siteUrl, $query); 554 | 555 | // Access the data 556 | echo $result->rowCount; // Number of results 557 | echo $result->responseAggregationType; // Aggregation type used 558 | 559 | foreach ($result->rows as $row) { 560 | print_r($row->keys); // Array of dimension values 561 | echo $row->clicks; // Number of clicks 562 | echo $row->impressions; // Number of impressions 563 | echo $row->ctr; // Click-through rate (0-1) 564 | echo $row->position; // Average position (1+) 565 | } 566 | ``` 567 | 568 | #### Laravel Helper Functions for Response Handling 569 | 570 | The response objects work seamlessly with Laravel's helper functions, making data manipulation more convenient: 571 | 572 | **Using `data_get()` for Safe Property Access** 573 | 574 | ```php 575 | $result = SearchConsole::query($siteUrl, $query); 576 | 577 | // Safely access properties with default values 578 | $rowCount = data_get($result, 'rowCount', 0); 579 | $aggregationType = data_get($result, 'responseAggregationType', 'unknown'); 580 | 581 | // Access nested properties safely 582 | foreach ($result->rows as $row) { 583 | $clicks = data_get($row, 'clicks', 0); 584 | $impressions = data_get($row, 'impressions', 0); 585 | $ctr = data_get($row, 'ctr', 0.0); 586 | $position = data_get($row, 'position', 0.0); 587 | 588 | // Handle optional or nested properties 589 | $firstKey = data_get($row, 'keys.0', 'N/A'); 590 | } 591 | ``` 592 | 593 | **Using `collect()` for Enhanced Data Processing** 594 | 595 | ```php 596 | $result = SearchConsole::query($siteUrl, $query); 597 | 598 | // Convert rows to a Laravel Collection for powerful data manipulation 599 | $rows = collect($result->rows); 600 | 601 | // Filter rows with high CTR 602 | $highCtrRows = $rows->filter(fn($row) => $row->ctr > 0.05); 603 | 604 | // Sort by impressions (descending) 605 | $sortedRows = $rows->sortByDesc('impressions'); 606 | 607 | // Get top 10 pages by clicks 608 | $topPages = $rows->sortByDesc('clicks')->take(10); 609 | 610 | // Transform data structure 611 | $pageData = $rows->map(function ($row) { 612 | return [ 613 | 'page' => data_get($row, 'keys.0', 'Unknown'), 614 | 'metrics' => [ 615 | 'clicks' => $row->clicks, 616 | 'impressions' => $row->impressions, 617 | 'ctr' => round($row->ctr * 100, 2) . '%', 618 | 'position' => round($row->position, 1), 619 | ] 620 | ]; 621 | }); 622 | 623 | // Group by device type (if querying by device dimension) 624 | $deviceGroups = $rows->groupBy(fn($row) => data_get($row, 'keys.0', 'unknown')); 625 | 626 | // Calculate totals 627 | $totalClicks = $rows->sum('clicks'); 628 | $totalImpressions = $rows->sum('impressions'); 629 | $averagePosition = $rows->average('position'); 630 | ``` 631 | 632 | ## LICENSE 633 | MIT 634 | --------------------------------------------------------------------------------