manuxi/google-reviews-bundle
manuxi/google-reviews-bundle integrates Google Reviews into PHP/Symfony applications, letting you fetch place reviews via the Google Places API and display ratings and review content in templates with configurable caching and credentials.
manuxi/google-reviews-bundle is a thin Symfony bundle that wraps the Google Places API to fetch reviews for a single place. It is not a Laravel package, so treat it as a small library you bridge into Laravel rather than something you php artisan vendor:publish.
Minimal steps
Install it, pinned to the new line:
composer require manuxi/google-reviews-bundle:^1.0
The package was re-released as v1.0.2. Because the release note is only "Initial release; should make getting Google reviews easier...", the 1.0.x line appears to be a versioning reset rather than an incremental patch on top of the older releases. Read the installed source before you write code against it — do not assume the class names or method signatures you may have seen in older blog posts still apply.
Laravel will not auto-discover a Symfony bundle. After install, open vendor/manuxi/google-reviews-bundle/ and read, in this order:
DependencyInjection/Configuration.php for the exact config keys (typically api_key and place_id).Service/ for the class that actually calls Google (this is the class you will instantiate from Laravel).composer.json inside the package for the PHP / Symfony version constraints — this is the fastest way to confirm the 1.0.x release actually targets your runtime.Wire it manually. The simplest path is a small service provider that binds the bundle's service class into the container with your credentials:
// app/Providers/GoogleReviewsServiceProvider.php
public function register(): void
{
$this->app->singleton(GoogleReviews::class, fn () => new GoogleReviews(
config('services.google_reviews.api_key'),
config('services.google_reviews.place_id'),
));
}
Put credentials in config/services.php and .env:
'google_reviews' => [
'api_key' => env('GOOGLE_PLACES_API_KEY'),
'place_id' => env('GOOGLE_PLACES_PLACE_ID'),
],
Where to look first
README.md for the supported config keys and the service ID.Resources/config/services.yml (if present) to see how dependencies are assembled; you'll replicate that wiring in your provider.First use case
Display a "What our customers say" block on a marketing page. Inject the service into a controller or Blade view composer, fetch reviews, pass them to the view, and render author_name, rating, text, relative_time_description. Cache the result before you ship it — see Gotchas.
Pattern 1 — Fetch with cache on render Wrap the raw fetch in your own repository/service so the bundle class never leaks into views:
class ReviewRepository
{
public function __construct(private GoogleReviews $client) {}
public function forPlace(): array
{
return Cache::remember('google_reviews:'.config('services.google_reviews.place_id'), now()->addHours(6), function () {
try {
return $this->client->getReviews(); // adjust to actual method name
} catch (\Throwable $e) {
report($e);
return [];
}
});
}
}
Views then depend only on ReviewRepository, and you can swap the bundle out later without touching Blade templates. This indirection matters more than usual here: it is the only thing that makes a future 1.0.x → 1.1 upgrade cheap.
Pattern 2 — Pull into the database on a schedule
For dashboards, search, or moderation, don't call Google on page render. Create an Artisan command and a queued job that fetches and upserts into a google_reviews table keyed by place_id + author_name + time (Google does not return a stable review ID from the Places endpoint). Schedule it:
// routes/console.php
Schedule::command('google-reviews:sync')->hourly();
This gives you historical data, filtering, and a single failure point. It also means a broken upgrade shows up as a failed scheduled command with a log entry, not as a blank marketing page.
Pattern 3 — Blade component / view composer Expose reviews via a dedicated component so designers can iterate without touching PHP:
<x-google-reviews :limit="5" />
The component pulls from ReviewRepository (cached), so it stays cheap on every page hit.
Pattern 4 — Normalize into a DTO
Map the raw array shape to a typed object (Review { author, rating, text, relativeTime, profilePhoto }) before passing to views or JSON responses. This insulates you from the bundle's return shape and makes it trivial to fake in tests. Given the 1.0.x line is effectively new, assume the array shape is unverified until you have printed one real response.
Pattern 5 — Fallback-first rendering Treat Google as unreliable. Always render a stale-cache fallback when the API fails, and log the error rather than surfacing a 500. A simple two-tier cache (fresh TTL + stale-forever fallback key) covers most outages.
Pattern 6 — Upgrade behind a smoke test When bumping within 1.0.x, run an integration check that asserts the response still contains the fields your DTO reads:
// tests/Feature/GoogleReviewsSmokeTest.php
public function test_the_client_returns_expected_fields(): void
{
$reviews = app(GoogleReviews::class)->getReviews(); // adjust to actual method name
$this->assertIsArray($reviews);
$this->assertArrayHasKey('author_name', $reviews[0] ?? []);
}
Mark it as a manual/CI-optional group so it does not run on every pull request against a billed API.
Symfony bundle, Laravel app
Versioning: 1.0.x is a reset, not a patch
v1.0.2. That combination strongly suggests the package was re-published on a new versioning line. Practically, that means:
composer update in an existing project may not pick it up at all (different constraint branch), and an unconstrained composer require may now resolve something structurally different from what older tutorials describe."manuxi/google-reviews-bundle": "^1.0" in composer.json, and commit composer.lock. Within ^1.0, a 1.1 could still change behavior; use 1.0.* if you want to be conservative.composer.json for minimum PHP.Google Places API returns at most 5 reviews This is a hard API limitation, not a package bug. If you need all reviews, you need the Google Business Profile API with OAuth, not Places. Plan your UI around "top 5" or use it purely as social proof. Do not assume a newer package release lifts this — it is a Google constraint, not a dependency constraint.
Maintenance signal is improving but thin
The prior 2021-era releases plus zero dependents justified assuming "low maintenance, expect to fork." A fresh 1.0.x line softens that — someone is still shipping — but a single vague release note is not evidence of an ongoing maintenance cadence. Keep your integration behind ReviewRepository / an interface so that:
app/Services/ in an afternoon.API key hygiene
Caching specifics
place_id in the cache key so multi-tenant or multi-location setups don't collide.Cache::forget() in your sync command so content refreshes predictably.composer update does not clear your cache. After upgrading the package, bust the review cache key manually, or you'll be debugging stale data that looks like a broken upgrade.Error handling
place_id, package version) so quota, auth, and post-upgrade shape changes are separable in your logs. Including the version in log context is cheap and pays off the first time an upgrade goes sideways.Extension points
Http-based replacement only by editing the class — expect to fork if you need real control over timeouts and retries.ReviewRepository (or the DTO mapper) rather than the bundle class. Keeping the bundle behind an interface is what makes this cheap — and is the single highest-value decision when the upstream versioning is this opaque.Upgrade path
If the 1.0.x line turns out to be cosmetic, or the bundle proves too thin, the same job can be done in ~50 lines with Laravel's Http facade against the Places API. The bundle's value is mostly that someone already mapped the fields — evaluate that against the cost of maintaining a Symfony dependency in a Laravel app. The new release does not change that calculus; it only removes "abandoned" from the list of reasons to avoid it.
How can I help you explore Laravel packages today?