ifha / bee-maps
SDK Laravel multi-provider de geolocalizacao (Google Maps, HERE)
Requires
- php: ^8.3
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.5|^11.0
Suggests
- google/apiclient: Necessario apenas para a estrategia fleet_routing de RouteOptimization (OAuth de service account). A estrategia default, matrix_tsp, nao precisa dela.
Provides
None
Conflicts
None
Replaces
None
README
SDK Laravel multi-provider de geolocalização. Google Maps e HERE atrás dos mesmos contratos, com respostas tipadas — trocar de provedor não muda o código que consome.
Status:
0.x. Os seis contratos da Fase 1 estão implementados nos dois provedores: autocomplete, geocoding, busca de lugares, rotas, matriz de rotas e otimização de paradas. A API pública pode mudar antes do1.0.0.
Requisitos
- PHP >= 8.3
- Laravel 10, 11 ou 12
Instalação
composer require ifha/bee-maps php artisan vendor:publish --tag=bee-maps-config
GOOGLE_MAPS_KEY= HERE_API_KEY=
Uso
O provedor é sempre explícito. O pacote nunca decide por você, não cacheia e não guarda estado entre chamadas.
use BeeDelivery\BeeMaps\MapServiceFactory; use BeeDelivery\BeeMaps\Enums\Provider; use BeeDelivery\BeeMaps\DTOs\Requests\AutocompleteRequest; final class BuscaDeEndereco { public function __construct(private MapServiceFactory $maps) {} public function buscar(string $texto): void { $sugestoes = $this->maps->autocomplete(Provider::Here) ->suggest(new AutocompleteRequest($texto, countries: ['BR'])); foreach ($sugestoes as $sugestao) { echo $sugestao->mainText; } } }
A MapServiceFactory é injetável. Há também uma facade, para uso pontual:
use BeeDelivery\BeeMaps\Facades\BeeMaps; $sugestoes = BeeMaps::autocomplete(Provider::Google)->suggest($request);
Escolhendo o provedor em tempo de execução
O pacote não conhece a sua regra de negócio. Se a escolha depende de algum dado seu — cliente, região, teste A/B —, resolva no seu código e passe o Provider já escolhido:
$provider = $this->flags->providerPara($contaId, Service::Autocomplete); $this->maps->autocomplete($provider)->suggest($request);
Autocomplete
new AutocompleteRequest( query: 'Av Paulista 1000', near: new Coordinates(-23.5615, -46.6562), // opcional radiusMeters: 3000, // default 50000 countries: ['BR'], // default: região do config language: 'pt-BR', // default: idioma do config );
Cada Suggestion traz:
| Campo | Tipo | |
|---|---|---|
place |
?PlaceReference |
pode ser nulo — ver abaixo |
description |
string |
endereço completo |
mainText |
string |
linha principal — rua e número, ou o nome do lugar |
secondaryText |
string |
complemento (bairro, cidade, UF, CEP) |
isEstablishment |
bool |
é estabelecimento, não endereço |
O HERE tem dois endpoints de autocomplete, e a escolha depende do near
O Google resolve autocomplete com um endpoint só. O HERE tem dois, e nenhum faz o que o outro faz:
/autosuggest |
/autocomplete |
|
|---|---|---|
| Devolve POI | sim | não — só endereço e área administrativa |
| Busca sem foco espacial | não — 400 sem at/in=circle/in=bbox/in=ring |
sim |
in=countryCode sozinho |
recusado | aceito |
isEstablishment |
reflete o resultado | sempre false |
place nulo |
acontece (chainQuery) |
nunca |
A restrição que separa os dois está no spec do GS7: em /autosuggest (e em /discover) o in=countryCode "must be accompanied by exactly one of at, in=circle or in=bbox". Ou seja, só o /autocomplete consegue buscar no país inteiro — o caso de quem cadastra um endereço em outro estado e não tem ponto de referência nenhum.
Por isso o default é autocomplete_strategy = 'auto', que escolhe pela requisição:
// tem referência (a loja, o cliente) → /autosuggest, com POI $maps->autocomplete(Provider::Here) ->suggest(new AutocompleteRequest('Av Paulista', new Coordinates(-23.5615, -46.6562))); // não tem referência → /autocomplete, Brasil inteiro $maps->autocomplete(Provider::Here) ->suggest(new AutocompleteRequest('Rua Blumenau'));
Para fixar um dos dois, veja autocomplete_strategy. O bee-maps.here.autosuggest_center não influencia o modo auto: se influenciasse, um centro esquecido no config viraria um recorte de 50 km em volta dele numa busca que pediu alcance nacional.
place pode ser nulo — verifique antes do lookup()
O Autosuggest do HERE devolve também itens chainQuery e categoryQuery (ex.: "Postos Shell"), que são refinamentos de busca, não lugares, e não podem ser resolvidos. No Google todo resultado tem identificador, então lá o campo nunca é nulo. No /autocomplete do HERE o campo também nunca é nulo.
$sugestao = $sugestoes->first(); if ($sugestao->place !== null) { $endereco = $this->maps->geocoding(Provider::Here)->lookup($sugestao->place); }
Geocoding
$geocoding = $this->maps->geocoding(Provider::Google); $geocoding->geocode('Av Paulista 1000', new GeocodeFilters(city: 'São Paulo')); $geocoding->reverse(new Coordinates(-23.5615, -46.6562)); $geocoding->lookup($placeReference); // devolve ?GeocodeResult
Cada GeocodeResult traz address (Address), coordinates, partial, matchScore e place.
Identificadores de lugar não atravessam provedores
Um place_id do Google não significa nada para o HERE. PlaceReference registra quem o emitiu, e lookup() recusa referências de outro provedor em vez de responder "não encontrado":
$doGoogle = new PlaceReference(Provider::Google, 'ChIJ...'); $maps->geocoding(Provider::Here)->lookup($doGoogle); // PlaceReferenceProviderMismatchException
Se você grava identificadores no banco, grave também o provedor — ou re-geocodifique por texto ao trocar.
Busca de lugares
use BeeDelivery\BeeMaps\DTOs\Requests\PlaceSearchRequest; $lugares = BeeMaps::placeSearch(Provider::Google)->search( new PlaceSearchRequest('farmacia', new Coordinates(-23.5615, -46.6562)), ); foreach ($lugares as $lugar) { echo $lugar->name, ' — ', $lugar->address->formatted, PHP_EOL; }
Cada Place traz place (?PlaceReference), name, address (Address estruturado nos dois provedores) e coordinates.
O HERE exige contexto geográfico. O discover não aceita busca sem at ou in: passe near, ou region, ou deixe bee-maps.defaults.region preenchido. Sem nenhum dos três, o pacote lança InvalidRequestException antes de sair para a rede, em vez de deixar o HERE responder 400. No Google os três são opcionais.
Rota
use BeeDelivery\BeeMaps\DTOs\Requests\RouteRequest; use BeeDelivery\BeeMaps\Enums\TravelMode; $rota = BeeMaps::routing(Provider::Here)->route(new RouteRequest( origin: new Coordinates(-23.5615, -46.6562), destination: new Coordinates(-23.5505, -46.6425), intermediates: [new Coordinates(-23.5580, -46.6500)], mode: TravelMode::TwoWheeler, optimizeIntermediates: true, includePolyline: true, includeLegs: true, )); echo $rota->distance->kilometers(), ' km em ', $rota->duration->minutes(), ' min', PHP_EOL; // raw() para repassar ao front; coordinates() decodifica sob demanda. $pontos = $rota->polyline?->coordinates() ?? $rota->legs[0]->polyline->coordinates();
polyline, legs e optimizedOrder são opt-in: sem includePolyline, includeLegs e optimizeIntermediates, os dois provedores devolvem null, [] e []. Pedir geometria encarece o field mask do Google e infla a resposta do HERE, então nada disso vem sem você pedir.
Rotas alternativas
alternatives pede rotas adicionais para o mesmo trajeto, de 0 a 6. O padrão é 0 — uma rota só, como antes.
$rota = BeeMaps::routing($provider)->route(new RouteRequest( origin: new Coordinates(-23.5615, -46.6562), destination: new Coordinates(-23.5505, -46.6333), alternatives: 3, )); // A rota devolvida é a preferida do provedor; as outras vêm em `alternatives`. $maisCurta = min(array_map( fn (Route $r) => $r->distance->meters, [$rota, ...$rota->alternatives], ));
O pacote não escolhe entre elas. "Mais curta" ou "mais rápida" é regra de quem chama, e as duas respostas são legítimas: a preferida é a que o motorista provavelmente fará, a mais curta é a de menor distância cobrada. Escolher aqui dentro seria decidir política de cobrança pelo consumidor.
É um pedido, não uma garantia, e a assimetria é do upstream, não do pacote:
| parâmetro | quantidade | |
|---|---|---|
computeAlternativeRoutes: true |
a critério dele — o número que você passou é ignorado | |
| HERE | alternatives=N |
honra o N, devolve até N+1 rotas |
O teto de 6 vem do HERE (alternatives=7 responde 400) e é validado no RouteRequest, para que o mesmo valor seja recusado do mesmo jeito nos dois provedores, antes de qualquer chamada. Custa uma chamada upstream, não N. Alternativas não aninham alternativas.
TravelMode tem quatro casos, traduzidos para o vocabulário de cada API:
TravelMode |
HERE | |
|---|---|---|
Drive |
DRIVE |
car |
TwoWheeler |
TWO_WHEELER |
scooter |
Bicycle |
BICYCLE |
bicycle |
Walk |
WALK |
pedestrian |
Matriz de rotas
use BeeDelivery\BeeMaps\DTOs\Requests\RouteMatrixRequest; $matriz = BeeMaps::routeMatrix(Provider::Google)->matrix(new RouteMatrixRequest( origins: [$origemA, $origemB], destinations: [$destinoA, $destinoB], )); // Busque por par — nunca por posição: o Google devolve os elementos fora de ordem. $entrada = $matriz->entry(originIndex: 0, destinationIndex: 1); if ($entrada->reachable) { echo $entrada->distance->kilometers(), ' km', PHP_EOL; }
Otimização de paradas
Este contrato responde em que ordem visitar as paradas — não o traçado. Para geometria, use Rota.
use BeeDelivery\BeeMaps\DTOs\Requests\OptimizeWaypointsRequest; use BeeDelivery\BeeMaps\Enums\OptimizationObjective; $resultado = BeeMaps::routeOptimization(Provider::Here)->optimize( new OptimizeWaypointsRequest($origem, $destino, $paradas), ); // $order indexa $paradas, e é sempre permutação completa dela. $reordenadas = array_map(fn (int $i) => $paradas[$i], $resultado->order); echo $resultado->distance->kilometers(), ' km em ', $resultado->duration->minutes(), ' min', PHP_EOL;
Os três formatos de tour saem do campo destination
// Fim fixo: sai da origem, passa pelas paradas, termina em $destino. new OptimizeWaypointsRequest($origem, $destino, $paradas); // Volta ao ponto de partida: basta o destino ser a origem. new OptimizeWaypointsRequest($origem, $origem, $paradas); // Tour aberto: termina na ultima parada que a otimizacao escolher. new OptimizeWaypointsRequest($origem, null, $paradas);
$order nunca contém origem nem destino — são pontos fixos, não resultado da otimização. E nunca vem parcial: uma ordem com buraco apagaria paradas no array_map acima, então índice repetido, fora de faixa ou contagem errada viram ProviderRequestException.
Objetivo e estratégia
O chamador pede o quê; o provedor decide como e informa o caminho em strategy:
new OptimizeWaypointsRequest($origem, $destino, $paradas, objective: OptimizationObjective::MinDistance); $resultado->strategy; // 'google.matrix_tsp'
strategy |
Quando | O que usa |
|---|---|---|
google.routes |
MinTravelTime |
computeRoutes com optimizeWaypointOrder |
google.matrix_tsp |
MinDistance (default) |
matriz de rotas + TSP local (vizinho-mais-próximo + 2-opt) |
google.fleet_routing |
MinDistance, se configurado |
Cloud Fleet Routing (optimizeTours) |
here.findsequence |
sempre | /v8/findsequence2 com improveFor |
MinTravelTime é o default. Qual API atende o MinDistance no Google sai de:
BEE_MAPS_GOOGLE_MIN_DISTANCE_API=matrix_tsp # ou fleet_routing
Um valor desconhecido cai no default em vez de derrubar a chamada — um typo no .env não deve tirar a otimização do ar.
fleet_routing exige dependência e credencial a mais
A Cloud Fleet Routing é o único endpoint do pacote que não aceita chave de API: precisa de OAuth de service account. Por isso o google/apiclient é sugerido, não exigido — ele arrasta google/auth, firebase/php-jwt e Guzzle, e a estratégia default não usa nada disso.
composer require google/apiclient
BEE_MAPS_GOOGLE_MIN_DISTANCE_API=fleet_routing BEE_MAPS_GOOGLE_PROJECT_ID= BEE_MAPS_GOOGLE_RO_PRIVATE_KEY= BEE_MAPS_GOOGLE_RO_CLIENT_EMAIL=
Pedir fleet_routing sem o pacote instalado lança ConfigurationException dizendo o comando e que o default matrix_tsp não precisa dele. Credencial incompleta lança MissingCredentialsException nomeando a chave que falta.
Códigos de país
O pacote aceita ISO 3166-1 alpha-2 (BR) ou alpha-3 (BRA) e converte para o formato que cada API exige — o Google quer alpha-2, o HERE quer alpha-3. Um código inválido lança InvalidRequestException em vez de produzir um filtro que a API ignora em silêncio.
new AutocompleteRequest('Av Paulista', countries: ['BR']); // ok new AutocompleteRequest('Av Paulista', countries: ['BRA']); // ok new AutocompleteRequest('Av Paulista', countries: ['XX']); // InvalidRequestException
partial e matchScore
Os dois provedores respondem perguntas diferentes sobre a qualidade do resultado:
- Google devolve
partial_match, um booleano: "não casei tudo que você pediu". - HERE devolve
scoring.queryScore, um número de 0 a 1: "casei assim de bem".
GeocodeResult carrega os dois. matchScore é o número cru (null no Google) e partial é derivado dele pelo limiar bee-maps.here.partial_threshold.
O limiar default
1.0não está calibrado. É o valor mais conservador possível: marca como parcial até um resultado com score 0.97, que na prática é um endereço ótimo. Consequência: o HERE reportapartial = truecom muito mais frequência que o Google.Se o seu código ramifica nesse campo, calibre o limiar contra endereços reais antes de trocar de provedor — rode o mesmo conjunto nos dois e compare a distribuição do
queryScorecom opartial_match. Quando precisar de precisão, leia omatchScorediretamente.
BEE_MAPS_HERE_PARTIAL_THRESHOLD=1.0
Erros
| Exceção | Quando |
|---|---|
ProviderNotSupportedException |
provedor fora de bee-maps.providers |
ServiceNotSupportedByProviderException |
provedor não implementa aquele serviço |
MissingCredentialsException |
chave de API ausente no config |
InvalidRequestException |
entrada inválida (coordenada fora de faixa, código de país desconhecido) |
PlaceReferenceProviderMismatchException |
referência de lugar de outro provedor |
ProviderAuthenticationException |
401 / 403 |
ProviderRateLimitException |
429 |
ProviderUnavailableException |
5xx ou timeout |
Todas descendem de BeeMapsException, e as três últimas expõem provider(), service(), httpStatus() e providerCode().
Nenhum resultado não é erro. Buscas devolvem coleção vazia; lookup() devolve null. Quem decide se isso é um problema é o seu caso de uso.
Credenciais não vazam para o log. Mensagens de erro de conexão do cURL incluem a URL completa, e o HERE envia a chave na query string. O pacote redige esses valores (apiKey=[REDACTED]) antes de construir a exceção, e não encadeia a exceção original — que carregaria a mensagem crua para o seu rastreador de erros.
Observabilidade
Cada chamada dispara MapRequestCompleted:
use BeeDelivery\BeeMaps\Support\Events\MapRequestCompleted; Event::listen(MapRequestCompleted::class, function (MapRequestCompleted $e) { Log::info('maps', [ 'provider' => $e->provider->value, 'service' => $e->service->value, 'status' => $e->httpStatus, // 0 quando houve falha de conexão 'ms' => $e->durationMs, 'chamadas' => $e->upstreamCalls, ]); });
O evento dispara também em falha e em timeout — sem isso, um provedor que estoura tempo em 30% das chamadas seria indistinguível de um que nunca foi chamado.
Configuração
'defaults' => [ 'language' => env('BEE_MAPS_LANGUAGE', 'pt-BR'), 'region' => env('BEE_MAPS_REGION', 'BR'), // usada quando countries vem vazio ], 'http' => [ 'timeout' => 10, 'connect_timeout' => 3, 'attempts' => 2, // TOTAL de tentativas, incluindo a primeira 'retry_delay_ms' => 200, ],
Só 429 e 5xx são repetidos. Falhas de autenticação (401/403) não são — repeti-las dobraria a carga exatamente quando o provedor está bloqueando você.
Todo endpoint é sobrescrevível por quem publica o config, o que permite apontar para um proxy ou corrigir uma URL sem esperar release do pacote:
'google' => ['endpoints' => [ 'autocomplete' => 'https://places.googleapis.com/v1/places:autocomplete', 'geocoding' => 'https://maps.googleapis.com/maps/api/geocode/json', 'place_search' => 'https://places.googleapis.com/v1/places:searchText', 'routing' => 'https://routes.googleapis.com/directions/v2:computeRoutes', ]], 'here' => ['endpoints' => [ 'autosuggest' => 'https://autosuggest.search.hereapi.com/v1/autosuggest', 'autocomplete' => 'https://autocomplete.search.hereapi.com/v1/autocomplete', 'geocode' => 'https://geocode.search.hereapi.com/v1/geocode', 'revgeocode' => 'https://revgeocode.search.hereapi.com/v1/revgeocode', 'lookup' => 'https://lookup.search.hereapi.com/v1/lookup', 'discover' => 'https://discover.search.hereapi.com/v1/discover', 'routing' => 'https://router.hereapi.com/v8/routes', 'findsequence' => 'https://wps.hereapi.com/v8/findsequence2', ]],
Estratégia de autocomplete do HERE
Qual dos dois endpoints atende autocomplete() (ver o porquê de serem dois):
BEE_MAPS_HERE_AUTOCOMPLETE_STRATEGY=auto
| Valor | Comportamento |
|---|---|
auto (default) |
/autosuggest quando a requisição traz near, /autocomplete quando não traz |
autosuggest |
sempre /autosuggest. Tem POI, mas exige foco espacial |
autocomplete |
sempre /autocomplete. Cobre o país inteiro, mas isEstablishment é sempre false |
O HERE tem ainda uma chave que não é endpoint: o foco espacial de fallback do Autosuggest, usado quando AutocompleteRequest::$near vem vazio. Ele só tem efeito na estratégia autosuggest — em auto, uma busca sem near vai para o /autocomplete e não é recortada por ele. Na estratégia autosuggest, sem esta chave e sem near, o pacote lança InvalidRequestException (ver Limitações conhecidas). O formato é o mesmo de Coordinates::toString():
BEE_MAPS_HERE_AUTOSUGGEST_CENTER="-23.5615,-46.6562"
'here' => [ 'autocomplete_strategy' => env('BEE_MAPS_HERE_AUTOCOMPLETE_STRATEGY', 'auto'), // "latitude,longitude", ou null para exigir `near` na estratégia autosuggest. 'autosuggest_center' => env('BEE_MAPS_HERE_AUTOSUGGEST_CENTER'), ],
Um valor malformado em qualquer uma das duas é ConfigurationException, não InvalidRequestException: quem precisa agir é quem fez o deploy, não quem fez a chamada.
Serviços disponíveis
| Serviço | HERE | |
|---|---|---|
| Autocomplete | places:autocomplete |
/v1/autosuggest ou /v1/autocomplete, conforme a estratégia |
| Geocoding | Geocoding API | /v1/geocode, /v1/revgeocode, /v1/lookup |
| PlaceSearch | places:searchText |
/v1/discover |
| Routing | directions/v2:computeRoutes |
/v8/routes (+ /v8/findsequence2 quando otimiza) |
| RouteMatrix | distanceMatrix/v2:computeRouteMatrix |
/v8/matrix?async=false |
| RouteOptimization | computeRoutes, computeRouteMatrix + TSP local, ou optimizeTours |
/v8/findsequence2 |
Limitações conhecidas
A normalização de endereços é ajustada ao Brasil. Revise antes de usar fora do país:
- O hífen do CEP é removido (
01310-100→01310100). No Brasil ele é cosmético, mas em Portugal, Polônia e Japão faz parte do código. - No Google,
administrative_area_level_2é mapeado paracity. No Brasil esse nível é o município; nos Estados Unidos é o county, e o HERE devolveria a cidade — os dois provedores discordariam.
No HERE, busca nacional e POI são mutuamente exclusivos. O /autosuggest devolve POI mas recusa a chamada sem um de at/in=bbox/in=circle/in=ring — filtro de país não conta como foco. O /autocomplete aceita in=countryCode sozinho mas não tem resultType de lugar, então isEstablishment é sempre false ali. O modo auto alterna entre os dois pela presença de near, mas não existe combinação que dê as duas coisas: uma busca sem ponto de referência no HERE não vai encontrar estabelecimento pelo nome. O Google entrega POI nos dois casos, então esta é uma diferença real de provider, não do pacote.
Na estratégia autosuggest forçada, o autocomplete do HERE exige foco espacial. Informe AutocompleteRequest::$near ou configure bee-maps.here.autosuggest_center; sem nenhum dos dois, o pacote lança InvalidRequestException em vez de deixar o HTTP 400 vazar.
O autocomplete do HERE pede show=details no Autosuggest. Sem esse parâmetro o address da resposta vem só com label, e não há como separar mainText de secondaryText: o title do Autosuggest vem igual ao endereço completo em todo resultado que não é um lugar nomeado, então usá-lo colapsaria a linha inteira em mainText. Isso importa além da estética — quem conta vírgulas em mainText para saber se falta o número da casa (é o caso do entregame) aceitaria uma rua sem número. A doc do HERE avisa que show pode envolver chamadas adicionais e aumentar a latência; é o preço dos dois campos corretos.
Uma resposta malformada do HERE devolve coleção vazia em vez de erro de mapeamento, pela mesma regra de "nenhum resultado não é erro".
Route::polyline é nulo no HERE quando a rota tem waypoints intermediários. O HERE devolve uma polyline por trecho e não existe forma válida de concatenar duas flexible polylines como string. Nesse caso a geometria vive em RouteLeg::polyline. O Google sempre devolve a polyline da rota inteira.
PlaceSearch no Google usa o SKU Enterprise do Text Search. O field mask pede places.addressComponents para que Place::address venha estruturado como no HERE. Quem preferir o SKU Basic remove o campo do GooglePlaceSearchRequestMapper::fieldMask() e passa a receber Address apenas com formatted.
Rota otimizada no HERE custa duas chamadas upstream. O /v8/routes não reordena waypoints; a ordem vem da Waypoints Sequence API (/v8/findsequence2). As duas chamadas são agrupadas em um único MapRequestCompleted com upstreamCalls: 2, para que a comparação de latência contra o Google não atribua ao HERE uma lentidão sem causa visível. O endpoint fica em bee-maps.here.endpoints.findsequence porque a documentação do HERE é ambígua entre findsequence2 e findsequence.json.
Sobre a matriz de rotas:
- O Google limita a matriz a 625 elementos (origens × destinos); o pacote lança
MatrixTooLargeExceptionantes da chamada, com o tamanho pedido e o limite. O HERE síncrono aguenta muito mais (25.000 elementos verificados), então uma matriz que funciona no HERE pode ser recusada no Google — divida em lotes se precisar de paridade. - Pares sem rota vêm com
reachable = falsee medidas zeradas, nunca ausentes. Uma matriz com buracos é mais difícil de consumir do que uma completa. - Só o modo síncrono. O modo assíncrono do HERE (submit → poll → download) depende de job e não cabe dentro de uma request HTTP.
- Matriz incompleta é erro, não resultado parcial. Se o provider interromper o cálculo
no meio (o
computeRouteMatrixdo Google faz isso com HTTP 200, anexando o erro ao final do stream) ou responder sem a matriz, o pacote lançaProviderRequestExceptionem vez de devolver uma coleção com buracos.
Sobre a otimização de paradas:
- O TSP local é vizinho-mais-próximo com refino 2-opt, e a rota devolvida nunca é mais
longa que a ordem que você mandou. A
google.matrix_tspcontinua sendo heurística — devolve uma boa rota, não a ótima —, mas a rota do guloso passa por 2-opt e é comparada com a ordem de entrada: vence a menor, e o empate mantém a sua ordem intacta. Sem essa comparação a "otimização" podia devolver rota mais longa que a rota sem otimização nenhuma, o que vira cobrança maior onde a taxa sai da distância (BEE-12720 no pacote legado, corrigido lá na 1.3.5). Held-Karp continua fora: o custo exponencial do exato não se paga com o teto de 25 paradas. - No HERE o
modeé semprefastest, nuncashortest. Oshortestmuda como cada perna é roteada e não tem equivalente nocomputeRouteMatrixdo Google — usá-lo faria a comparação entre provedores medir perguntas diferentes. O objetivo entra porimproveFor. - Uma parada só não é erro. A ordem é trivialmente
[0], mas os totais não são, e recusar quebraria quem itera sobre pedidos e às vezes encontra um de uma parada só.
A suíte não roda contra Laravel 10. O orchestra/testbench ^8.0 só casa com versões pontuais do Laravel 10 bloqueadas por advisories de segurança. Isso afeta só o desenvolvimento do pacote — consumidores em Laravel 10 instalam normalmente (verificado por resolução do Composer com plataforma forçada).
Atualizando o pacote
Se você publicou config/bee-maps.php, saiba que o pacote mescla o config dele com o
seu em profundidade: chaves novas (endpoints de serviços novos, limites) aparecem
automaticamente, e o que você definiu continua valendo. A única exceção são listas — como
providers —, que são substituídas inteiras pela sua versão, para que um item removido de
propósito não volte sozinho.
Se você usa
php artisan config:cache, reconstrua o cache depois de atualizar o pacote. Com a configuração cacheada o pacote não mescla nada — é o mesmo comportamento domergeConfigFromdo Laravel, e existe porque nesse modo o.envnão é carregado —, então chaves novas só aparecem depois de rodarphp artisan config:cachede novo.
Migrando do beedelivery/google-maps
Três mudanças observáveis, além dos tipos:
- Retornos são DTOs, não arrays.
$resposta['addresses'][0]['lat']vira$resultado->coordinates->latitude. - Erro é exceção, não chave no array. Some o
isset($response['code']); some também a exceção para "nenhum resultado", que agora é coleção vazia. - O autocomplete do Google passa a ser restrito à região configurada quando
countriesnão é informado. O pacote antigo buscava no mundo todo. Foi o preço decountries: []significar a mesma coisa nos dois provedores — "irrestrito nos dois" é impossível, porque o Autosuggest do HERE exigeatouin. Passecountriesexplicitamente se quiser outro comportamento.
Testes
composer install vendor/bin/phpunit # suite completa, sem rede vendor/bin/phpunit --group live # smoke contra as APIs reais (exige chaves, custa dinheiro)
Nenhum teste da suíte padrão toca a rede — Http::preventStrayRequests() está ativo, então uma requisição não simulada falha em vez de sair.
Os testes live são a exceção e por isso ficam excluídos por padrão no phpunit.xml. Eles precisam de GOOGLE_MAPS_KEY e HERE_API_KEY no ambiente; sem elas, se marcam como skipped sem tentar a chamada. Servem para responder o que nenhum Http::fake() responde: o endpoint existe e a chave tem acesso a ele?
Licença
MIT. Ver LICENSE.