Without the facade
The facade is the recommended default, not the only way in. In most packages there are three equivalent entry points — the facade, the manager injected through the constructor, and single-purpose action classes. google-places is a remote-API client, so it has no action classes: the client’s methods are the use cases. That leaves two:
- The GooglePlaces facade — the shortest form.
- The client, injected through the constructor — the same API with an explicit dependency and no static calls. The facade root is the RoundlyConsulting\GooglePlaces\Contracts\PlacesClient contract, bound as a singleton to the RoundlyConsulting\GooglePlaces\Places implementation.
Injecting the client
The facade and the contract resolve the same singleton, and GooglePlaces::fake() replaces both — so type-hint the contract, not the concrete Places class, in code you want to fake:
use RoundlyConsulting\GooglePlaces\Contracts\PlacesClient;
final class NearbyCafes
{
public function __construct(private PlacesClient $places) {}
public function __invoke(string $query): array
{
return $this->places->textSearch($query)->all();
}
}Handles built by an injected client call back into that client, so they are faked too:
use RoundlyConsulting\GooglePlaces\Contracts\PlacesClient;
final class PlacePhotos
{
public function __construct(private PlacesClient $places) {}
public function hero(string $placeId): ?string
{
$place = $this->places->details($placeId);
if ($place === null || $place->photos === []) {
return null;
}
return $this->places->photo($place->photos[0])->url();
}
}Facade method → client method
Every facade call maps one-to-one to the same method on PlacesClient:
| Method | Returns | Notes |
|---|---|---|
details(DetailsQuery|string) | ?Place | One place by id; null when Google answers 404. |
autocomplete(AutocompleteQuery|string) | Collection<AutocompletePrediction> | Place predictions while the user types. Never cached. |
session(?string $token = null) | PlacesSession | Autocomplete billing session — autocomplete(), details(), token(), isFinished(). |
textSearch(TextSearchQuery|string) | Collection<Place> | Free-text search, one page. |
nearbySearch(NearbySearchQuery) | Collection<Place> | Places within a radius of a point. |
textSearchPaginated(TextSearchQuery|string) | SearchPaginator | Follows nextPageToken for you. |
nearbySearchPaginated(NearbySearchQuery) | SearchPaginator | Same paginator shape over nearby search (single page). |
findPlace(string, ?Location) | ?Place | Best single match for a phrase, optionally biased. |
geocode(ReverseGeocodingQuery|Location|float, ?float) | Collection<ReverseGeocodingResult> | Coordinates → addresses. |
geocodeAddress(GeocodingQuery|string) | Collection<ReverseGeocodingResult> | Address → coordinates. |
distance(DistanceQuery) | Distance|MultipleDistances | Distance and travel time via the Routes API — one trip per destination. |
matrix(array $origins, array $destinations) | PendingMatrix | Fluent matrix builder — driving(), walking(), bicycling(), transit(). |
computeMatrix(MatrixQuery) | DistanceMatrix | Full origins × destinations matrix. |
photo(string, int = 1600, int = 1600) | PendingPhoto | Photo handle — url(), contents(), save(). |
photoUri(string, int = 1600, int = 1600) | string | Key-free media URL, one request. |
photoContents(string, int = 1600, int = 1600) | string | Raw image bytes, one request. |
photoUrl(string, int = 1600, int = 1600) | string | Keyed media URL built locally, no request — server-side only. |
check() | list<ApiCheckResult> | Is each Google API enabled and reachable with the configured key? |
GooglePlaces::fake() | GooglePlacesFake | Facade only — swap in the recording test fake. |
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.