Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel G2 Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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.

Implementation Patterns

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.

Gotchas and Tips

  • 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.
  • Rate limits are your problem, not the package's. There's no built-in backoff. Add 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.
  • Never let the token reach a browser. Keep 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.
  • Pagination is the most common source of silent data loss. Confirm whether the client's 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.
  • Timestamps arrive as ISO 8601 strings with offsets. Cast to CarbonImmutable at the mapping boundary and store in UTC; don't let G2's timezone leak into comparisons with created_at.
  • Attribute keys can be absent on some resources. Use $review->attributes['x'] ?? null (or a DTO with defaults) rather than array access, which will throw on partially-populated records.
  • Empty pages are not errors. An empty review set for a new product should short-circuit the loop cleanly; don't let a first()-based assumption turn it into a null dereference.
  • Bulk backfills deserve their own queue. Give initial historical imports a dedicated queue and low concurrency so a full backfill doesn't starve your transactional jobs.
  • Version pin loosely but watch the changelog. At 1.0.0 the public surface is still settling; pin ^1.0 and read release notes before minor bumps in case resource method signatures shift.
  • Extension point to lean on: if you need behaviour the package doesn't ship (custom retry, logging middleware, response caching), wrap your own class around G2Client and bind that in the container — it keeps the third-party surface isolated to one file when the API changes.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource