localfr/uberall-bundle
A Symfony bundle for integrating the Uberall location marketing platform into PHP applications. It provides configuration and service wrappers for managing business listings, locations, and related Uberall API data. Use it to connect your app to Uberall APIs for local SEO and directory management.
The first hour of evaluation should confirm what this package actually is: a Symfony bundle, not a Laravel package. There is no composer require → php artisan path. Open the repo and read, in this order:
composer.json — confirm the Symfony constraint (symfony/framework-bundle:^5.4|^6|^7) and whether there is a separate Uberall API client dependency (e.g. localfr/uberall-api or uberall/php-sdk). The bundle is almost certainly a thin DI/config wrapper; the client is where the value sits.DependencyInjection/UberallExtension.php and Resources/config/services.yaml — this is the real documentation. It enumerates the services the bundle registers, the config tree (account_id, private_key, base_uri, etc.), and how the client is constructed.ClientInterface) or ContainerAware. Framework-agnostic classes can be lifted into Laravel; ContainerAware ones cannot.For a Laravel codebase: do not install the bundle. Use the repo as a reference implementation and build a single App\Services\Uberall\UberallClient over Laravel's Http facade. Bind it as a singleton in App\Providers\UberallServiceProvider, config under config/services.php → uberall key, credentials in .env (UBERALL_PRIVATE_KEY, UBERALL_ACCOUNT_ID).
For a Symfony codebase: composer require localfr/uberall-bundle, register the bundle in config/bundles.php, publish config to config/packages/uberall.yaml, inject the client. This is the only stack where the bundle scaffolding pays for itself.
First use case — always read-only. Call the list-locations endpoint and dump the response. This validates three things in one shot: credentials are wired correctly, base URI and API version are what you expect, and the wrapper shapes pagination the way Uberall's docs describe. Do this before writing a single line of sync logic.
Wrap, don't leak. Define your own UberallClientInterface with typed methods (locations(): array, upsertLocation(LocationDto $dto): LocationDto, listings(string $locationId): array). Implement it over the bundle's extracted client, or Http::withToken(...)->baseUrl(...). Every call site depends on your interface, not the third-party class — this is what makes the eventual fork-and-replace cheap.
Config-cache friendly setup. Credentials go in .env; everything else (base URI, timeout, retry counts, queue name) goes in config/services.php. Never read env() outside a config file — php artisan config:cache will null it out in production.
Queue every write. Uberall propagates mutations to Google, Apple, Facebook, Yelp and friends. A bad payload is externally visible and slow to unwind. Everything that is not a GET goes behind a job:
final class SyncLocationToListings implements ShouldQueue
{
public int $tries = 5;
public array $backoff = [10, 30, 60, 180, 600];
public function __construct(public readonly string $locationId) {}
}
Use a dedicated uberall queue with its own worker and concurrency cap so a bulk location import doesn't starve interactive traffic or trip 429s. Attach an idempotency key to each job payload and log per-location outcomes.
DTOs at the boundary. Hydrate responses into LocationDto, ListingDto, ReviewDto with explicit field mapping. Log unknown keys at debug level so upstream schema drift shows up in logs before it shows up as a null-pointer.
Read-then-write sequencing. Ship in this order: locations → listings → insights → reviews → writes. Validate data parity against whatever process you're replacing before enabling any POST/PUT. Shadow-run reads against production data for a few days.
Testing. If you extracted the client behind your interface, bind a fake in the container and assert job dispatch. If you wrote it over Http, use Http::fake(['api.uberall.com/*' => Http::response($fixture)]). Never let the real API into the test suite.
Pagination helper. A small paginate(callable $fetch) over Uberall's page/offset semantics saves repeating the loop in every resource method.
Kernel, no DependencyInjection\Extension execution. Any tutorial assuming Symfony boot will silently fail. Confirm this before estimating effort — it's a one-line finding that changes the whole plan.symfony/framework-bundle:^5.4|^6, you cannot install it alongside Laravel without dragging in framework-bundle and creating an upgrade tax. Check composer why-not before you argue about code quality.ContainerAware services don't port. If the bundle's service classes resolve collaborators from the container or use ContainerAwareInterface, extraction to Laravel means rewriting them anyway. Only constructor-injected, framework-free classes are worth copying.attempted_at, last_error, succeeded_at so you can replay failures without re-running the batch.verifyListings() job that re-reads directory status and alerts on drift.internals/ in composer.json (VCS or path repo) so patching is a normal PR, not a composer.json hack that your next developer inherits as archaeology.How can I help you explore Laravel packages today?