jeffersongoncalves/laravel-g2
Laravel G2 is a lightweight package that integrates the G2 platform into your Laravel applications, offering simple configuration and convenient helpers for pulling G2 review data into your site. Quick to install via Composer and easy to customize.
Install the package and publish its config, then wire the token into the environment:
composer require <vendor>/g2-api
php artisan vendor:publish --tag=g2-config
G2_API_TOKEN=your-token-here
First place to look is the package's config/g2.php (base URI, timeout, retry settings) and the resource classes it exposes: reviews, products, reports, competitors, categories, tracking. Resolve the client out of the container rather than instantiating it — app(G2Client::class) or the provided facade — so you can swap it in tests.
The fastest first use case is pulling reviews for a single known product and dumping them to confirm the shape before modelling anything:
use Vendor\G2\Facades\G2;
$reviews = G2::reviews()->forProduct($productId)->get();
foreach ($reviews as $review) {
dump($review); // inspect JSON:API attributes/relationships
}
Do this in php artisan tinker before writing migrations — the JSON:API resource shape (attributes vs. relationships, included resources) dictates your schema more than any docs page will.
Wrap the client in your own service, don't call it from controllers. The package exposes a thin HTTP layer; your app should own the domain logic.
class G2ReviewImporter
{
public function __construct(private G2Client $g2) {}
public function import(Product $product): int
{
$count = 0;
foreach ($this->g2->reviews()->forProduct($product->g2_id)->cursor() as $page) {
foreach ($page as $review) {
Review::updateOrCreate(
['g2_id' => $review->id],
[
'product_id' => $product->id,
'rating' => $review->attributes['rating'],
'body' => $review->attributes['comment'],
'submitted_at' => $review->attributes['submitted_at'],
]
);
$count++;
}
}
return $count;
}
}
Upsert on the G2 resource id — it's your only stable natural key, and it makes re-runs idempotent.
Drive all syncs from queued jobs and scheduled commands. Nothing in this package is cached or batched for you, so a sync across every product and every review page will be slow and will happily blow a web request's timeout.
// app/Console/Kernel.php
$schedule->job(new SyncAllG2Reviews)->dailyAt('03:00');
$schedule->job(new SyncG2Competitors)->weekly();
Inside SyncAllG2Reviews, dispatch one SyncProductReviews($product) job per product so a single failure doesn't kill the batch and you get per-product retry visibility in Horizon.
Handle G2Exception at the job boundary, not inside the import loop. Let it bubble out of the importer, catch it in the job, log with context, and decide between release() and fail():
public function handle(G2ReviewImporter $importer): void
{
try {
$importer->import($this->product);
} catch (G2Exception $e) {
Log::warning('G2 sync failed', ['product' => $this->product->id, 'error' => $e->getMessage()]);
$this->release(300);
}
}
Cache aggressively for anything surfaced in the UI. Competitor tracking and review counts are read-heavy and change slowly. A Cache::remember with a 6–24 hour TTL in front of the client turns a per-page-load API call into a per-day one.
Model the JSON:API included payload deliberately. If you request related resources (e.g. reviews with their product), the response splits data across data and included. Write a small normaliser that indexes included by type/id instead of scattering array_filter calls through your importers.
Testing: fake the transport, not the package. In feature tests, bind a fake G2Client into the container (or use Laravel's Http::fake() if the package is built on the HTTP client) and assert your importer maps attributes to columns correctly. Keep one opt-in integration test against the real API behind an env flag so upstream shape changes are caught.
G2Exception on non-2xx loses granularity unless you unwrap it. Check whether the exception exposes the response/status code before assuming you can distinguish 401 (bad token), 404 (wrong resource id), and 429 (rate limited) in your handler. If it doesn't, log $e->getMessage() plus the trace and treat everything as retryable-but-throttled rather than retrying a 401 forever.RateLimited middleware to sync jobs or a simple usleep between page fetches, and treat 429 as a signal to increase your scheduled interval rather than just retrying.G2_API_TOKEN server-side only; don't proxy raw client output to the frontend, and remember config:cache — changing the env var requires a config recache in production.cursor()/paginate() handles JSON:API links.next for you or whether you must loop manually. If you only fetch the first page you'll get a plausible-looking partial dataset with no error.CarbonImmutable at the mapping boundary and store in UTC; don't let G2's timezone leak into comparisons with created_at.$review->attributes['x'] ?? null (or a DTO with defaults) rather than array access, which will throw on partially-populated records.first()-based assumption turn it into a null dereference.^1.0 and read release notes before minor bumps in case resource method signatures shift.G2Client and bind that in the container — it keeps the third-party surface isolated to one file when the API changes.How can I help you explore Laravel packages today?