В этом уроке я покажу вам, как можно создать автозаполняемый адрес с помощью API google place. Вы могли видеть на некоторых сайтах при заполнении формы адреса он автоматически предлагает адрес и при нажатии на него заполняет остальные поля, такие как street line1, street line2, city, state, postcode
и т.д.
Итак, что нам нужно, чтобы следовать этому руководству?
- Настройте проект laravel:
composer create-project laravel/laravel googleautocomplete
. - Установите Guzzle:
composer require guzzlehttp/guzzle
. - Действительный ключ API google
- Ознакомьтесь с документами API google Place Autocomplete: Google Place Autocomplete Api
Давайте создадим контроллер с методом index
, который возвращает форму
php artisan make:controller GoogleAutocompleteController
GoogleAutocompleteController.php
<?php
namespace AppHttpControllers;
use IlluminateViewView;
use IlluminateHttpRequest;
class GoogleAutocompleteController extends Controller
{
public function index(): View
{
return view('form');
}
}
Внутри resources/views/form.blade.php
Я создал простую форму с помощью bootstrap, в которой есть поле адрес улицы, поле города, поле штата, поле почтового индекса и поле страны
.
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Google autocomplete</div>
<div class="card-body">
<div class="form-group">
<label for="streetaddress">Street address</label>
<input type="text" class="form-control ui-widget autocomplete-google" id="street_address_1">
</div>
<div class="form-group">
<label for="streetaddress2">Street address 2</label>
<input type="text" class="form-control" id="street_address_2">
</div>
<div class="form-group">
<label for="city">City</label>
<input type="text" class="form-control" id="city">
</div>
<div class="form-group">
<label for="state">State</label>
<input type="text" class="form-control" id="state">
</div>
<div class="form-group">
<label for="postcode">Postcode</label>
<input type="text" class="form-control" id="postcode">
</div>
<div class="form-group">
<label for="country">Country</label>
<input type="text" class="form-control" id="country">
</div>
</div>
</div>
</div>
</div>
</div>
В приведенной выше форме я добавил класс
ui-widget
в поле адреса улицы, потому что я использую библиотеку jQuery autocomplete для всплытия адреса, возвращаемого google API, когда пользователь начинает вводить адрес в поле адреса улицы. Этот класс включит наше автозаполнение.
Наконец, мы зарегистрируем маршрут в web.php
для просмотра нашей формы.
Route::get('/autocomplete',[GoogleAutocompleteController::class,'index']);
Теперь, когда вы перейдете по ссылке /autocomplete
в браузере, вы увидите эту форму
Часть 2
В этой части мы будем использовать библиотеку jQuery autocomplete и API места. Google place API вернет идентификатор места, и на основе этого идентификатора мы заполним все поля формы.
Шаг 1: перейдите к jQuery autocomplete
скопируйте ссылки jquery-ui.js
и jquery-ui.css
и вставьте их в ваш app.blade.php
.
Теперь мой layouts/app.blade.php
выглядит следующим образом
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<link rel="stylesheet" href="//code.jquery.com/ui/1.13.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto">
<!-- Authentication Links -->
@guest
@if (Route::has('login'))
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@endif
@if (Route::has('register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@endif
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }}
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
<main class="py-4">
@yield('content')
@yield('script')
</main>
</div>
</body>
</html>
Как вы можете видеть, я добавил
@yield('script')
выше, что означает, что все, что я обернул в script, будет инжектировано сюда. Позже мы обернем javascript в@section('script')//..js код @endsection
вform.blade.php
.
Шаг 2: перейдите к Google Place API
Вы можете увидеть типы, Вы можете ограничить результаты запроса автозаполнения Place определенным типом, передав параметр types. Этот параметр указывает тип или коллекцию типов, как указано в разделе Типы мест. Если ничего не указано, возвращаются все типы.
Нам нужны типы address
.
Итак, скопируйте этот URL и откройте ваш почтальон или браузер, вставьте его и нажмите кнопку отправить.
Вы должны получить ответ, подобный этому:
Примечание: нас интересует place_id
ответа. Это потому, что с помощью place_id
мы можем получить country, street addres, post code, city
и т.д.
Шаг 3: Откройте ваш config/services.php
и установите ключ
'googlekey'=>[
'key'=> env('GOOGLE_KEY', null),
],
и в вашем файле .env
.
GOOGLE_KEY=YOUR_GOOGLE_KEY
Шаг 4: Теперь давайте сделаем еще один маршрут в web.php
.
Route::get('/placeid',[ GoogleAutocompleteController::class,'getPlaceId'])->name('placeid');
и GoogleAutocompleteController.php
будет выглядеть следующим образом
<?php
namespace AppHttpControllers;
use IlluminateViewView;
use IlluminateHttpRequest;
class GoogleAutocompleteController extends Controller
{
public function index(): View
{
return view('form');
}
public function getPlaceId(Request $request)
{
//get user typed address via ajax request
}
}
Шаг 5: В нашем form.blade.php
добавим код jQuery для включения выпадающего списка автозаполнения, ajax запрос для отправки введенного пользователем адреса в конечную точку /placeId
.
<script>
$(document).ready(function() {
$(".autocomplete-google").autocomplete({
source: function(request, response) {
$.ajax({
url: '/placeid',
type: 'GET',
dataType: "json",
data: {
inputData: request.term,
},
success: function(data) {
response(data);
}
});
},
});
});
</script>
Теперь наш полный resources/form.blade.php
выглядит следующим образом
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Google autocomplete</div>
<div class="card-body">
<div class="form-group">
<label for="streetaddress">Street address</label>
<input type="text" class="form-control ui-widget autocomplete-google" id="street_address_1">
</div>
<div class="form-group">
<label for="streetaddress2">Street address 2</label>
<input type="text" class="form-control" id="street_address_2">
</div>
<div class="form-group">
<label for="city">City</label>
<input type="text" class="form-control" id="city">
</div>
<div class="form-group">
<label for="state">State</label>
<input type="text" class="form-control" id="state">
</div>
<div class="form-group">
<label for="postcode">Postcode</label>
<input type="text" class="form-control" id="postcode">
</div>
<div class="form-group">
<label for="country">Country</label>
<input type="text" class="form-control" id="country">
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('script')
<script>
$(document).ready(function() {
$(".autocomplete-google").autocomplete({
source: function(request, response) {
$.ajax({
url: '/placeid',
type: 'GET',
dataType: "json",
data: {
inputData: request.term,
},
success: function(data) {
response(data);
}
});
},
});
});
</script>
@endsection
Шаг 6: Теперь давайте создадим специальный класс-обработчик для автозаполнения. Мы могли бы написать весь код в getPlaceId()
из GoogleAutocompleteController
, но для очистки кода, я думаю, будет хорошей идеей перенести всю логику в другой класс PHP.
Давайте сделаем AutocompleteHandler.php
внутри папки Integration
в каталоге app.
<?php
namespace AppIntegration;
use GuzzleHttpClient;
class AutocompleteHandler
{
public const BASE_URL = "https://maps.googleapis.com/maps/api/place";
public string $key;
public function __construct()
{
$this->key = config('services.googlekey.key');
}
public function placeId(string $address)
{
//
}
}
В приведенном выше коде я установил базовые URL
и key
, которые мы будем использовать позже. В приведенном выше методе placeId(string $address)
мы получаем адрес в качестве строкового параметра, который мы получим из метода GoogleAutocompleteController getPlaceId()
, как показано ниже.
Шаг 7: Теперь внедрим AutocompleteHandler.php
в GoogleAutocompleteController.php
и передадим адрес из getPlaceId()
из GoogleAutocompleteController.php
в placeId()
из AutocompleteHandler.php
.
Теперь наш GoogleAutocompleteController.php
выглядит следующим образом
<?php
namespace AppHttpControllers;
use IlluminateViewView;
use IlluminateHttpRequest;
use SymfonyComponentHttpFoundationJsonResponse;
use AppIntegrationAutocompleteHandler;
class GoogleAutocompleteController extends Controller
{
public $googleAutocomplete;
public function __construct(AutocompleteHandler $googleAutocomplete)
{
$this->googleAutocomplete = $googleAutocomplete;
}
public function index(): View
{
return view('form');
}
public function getPlaceId(Request $request): JsonResponse
{
return $this->googleAutocomplete->placeId($request->inputData);
}
}
Выше, мы получаем адрес как inputData
и передаем этот inputData
как address
параметр в placeId()
.
Шаг 8: Теперь давайте поработаем над AutocompleteHandler.php
. Здесь я укажу шаги, которые мы будем выполнять
<?php
namespace AppIntegration;
use GuzzleHttpClient;
class AutocompleteHandler {
public const BASE_URL = "https://maps.googleapis.com/maps/api/place";
public string $key;
public function __construct()
{
$this->key = config('services.googlekey.key');
}
public function placeId(string $address)
{
$url= https://maps.googleapis.com/maps/api/place/autocomplete/json?input=kathmandu&types=address&key=YOUR_API_KEY
// build the readable url with http_build_query and sprintf()
try {
// step1: instantiate GuzzleHttp client
// step2: hit the url
// step3: get json response
// step4: convert json to array
// step5: loop over the predictions array of response
// step6: and return place_id as id and description as label
} catch (Exception $e) {
//catch error
}
}
}
Итак, наш окончательный вариант AutocompleteHandler.php
выглядит следующим образом
<?php
namespace AppIntegration;
use SymfonyComponentHttpFoundationJsonResponse;
use SymfonyComponentHttpFoundationResponse;
use GuzzleHttpClient;
class AutocompleteHandler
{
public const BASE_URL = "https://maps.googleapis.com/maps/api/place";
public string $key;
public function __construct()
{
$this->key = config('services.googlekey.key');
}
public function placeId(string $address): JsonResponse
{
$url = sprintf(
'%s/autocomplete/json?%s',
self::BASE_URL,
http_build_query([
'input' => $address,
'types' => 'address',
'key' => $this->key,
])
);
try {
$client = new Client();
$response = $client->request('get', $url);
$responseJson = $response->getBody()->getContents();
$responseArray = json_decode($responseJson, true);
return response()->json(collect($responseArray['predictions'])->map(
fn ($value) =>
[
'id' => $value['place_id'],
'label' => $value['description'],
]
));
} catch (Exception $e) {
return response()->json([
'error' => $e->getMessage(),
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
}
Выше я преобразовал $responseArray
в коллекцию с помощью collect()
и отобразил его, чтобы вернуть place_id
и description
.
Теперь, когда вы начнете вводить форму, она начнет предлагать вам адрес, как показано ниже.
Часть 3
Это последняя часть автозаполнения google place. В этой части мы заполним остальные поля формы, когда пользователь выберет один адрес из выпадающего списка.
Теперь нам нужен place_id
из предыдущего ответа, и если вы нажмете на этот URL: https://maps.googleapis.com/maps/api/place/details/json?place_id=EhtOZXBhbCBUYXIsIEthdGhtYW5kdSwgTmVwYWwiLiosChQKEgk9yaxKzhjrORHvFQWGXi5RGhIUChIJv6p7MIoZ6zkR6rGN8Rt8E7U&key=YOUR_KEY
.
Вы получите ответ в таком формате.
Таким образом, мы получаем результат в виде ответа, а все необходимые данные, такие как адрес, город, почтовый индекс
и т.д. находятся в компонентах адреса
.
Шаг 1:В приведенном выше примере мы возвращаем place_id
в качестве id, и теперь, когда пользователь нажмет на адрес, мы сделаем еще один ajax запрос, чтобы получить все детали места на основе place_id
.
Итак, после исходного текста мы можем добавить select, как показано ниже
select: function(event, ui) {
var placeId = ui.item.id;
console.log(placeId)
}
Таким образом, наш код jQuery выглядит следующим образом
<script>
$(document).ready(function() {
$(".autocomplete-google").autocomplete({
source: function(request, response) {
$.ajax({
url: '/placeid',
type: 'GET',
dataType: "json",
data: {
inputData: request.term,
},
success: function(data) {
response(data);
}
});
},
select: function(event, ui) {
var placeId = ui.item.id;
console.log(placeId)
}
});
});
</script>
если вы нажмете на выбранный адрес, он вернет идентификатор места в консоль.
Шаг2: давайте сделаем конечную точку, которая будет принимать идентификатор места и возвращать информацию о месте на основе идентификатора места
В web.php
Route::get('address',[ GoogleAutocompleteController::class,'findAddressBasedOnPlaceId'])->name('address');
Итак, теперь web.php
выглядит следующим образом
<?php
use IlluminateSupportFacadesRoute;
use AppHttpControllersGoogleAutocompleteController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/autocomplete',[ GoogleAutocompleteController::class,'index']);
Route::get('/placeid',[ GoogleAutocompleteController::class,'getPlaceId'])->name('placeid');
Route::get('address',[ GoogleAutocompleteController::class,'findAddressBasedOnPlaceId'])->name('address');
Auth::routes();
Route::get('/home', [AppHttpControllersHomeController::class, 'index'])->name('home');
Шаг2: давайте сделаем этот метод в GoogleAutocompleteController.php
public function findAddressBasedOnPlaceId(Request $request)
{
//get place_id via ajax request when user select the address from dropdown suggestions
}
Шаг3: Из приведенного выше ответа, полученного от API, мы видим, что наши данные находятся в address_components
, и все ответы могут возвращать или не возвращать почтовый индекс, название населенного пункта и т.д. для этого place_id
. Поэтому нам нужно вручную проверить, присутствуют ли необходимые ключи в ответе или нет, чтобы предотвратить ошибку на странице. Например, если в ответе присутствует почтовый индекс, то мы возвращаем почтовый индекс, и так далее. Поэтому вместо того, чтобы писать оператор if-else для каждого ключа, давайте сделаем вспомогательную функцию, которая возвращает нужный ключ, если он есть в ответе.
Теперь в AutocompleteHandler.php
я добавлю следующие проверки
public function getDataFromAddressComponent(array $addressComponents, string $searchFor): ?string
{
return collect($addressComponents)->map(fn ($addressComponent) => collect($addressComponent['types'])->contains($searchFor) ? $addressComponent['long_name'] : null)->filter()->first();
}
Выше я перебираю $addressComponents и проверяю, есть ли у нас $searchFor в этом массиве. Если true, то возвращается что-то, если нет, то возвращается null. Однако здесь я использовал коллекцию.
В качестве альтернативы мы можем сделать следующее
foreach ($addressComponents as $address) {
if (in_array($searchFor, $address['types'])) {
return $address['long_name'];
}
}
return null;
Шаг 4: В GoogleAutocompleteController.php
мы примем id места из ajax запроса и передадим его в метод addressBasedOnPlaceId()
в AutocompleteHandler.php
, который вернет детали места
public function findAddressBasedOnPlaceId(Request $request): JsonResponse
{
return $this->googleAutocomplete->addressBasedOnPlaceId($request->placeId);
}
Шаг 5: Отправим идентификатор места в метод findAddressBasedOnPlaceId()
через ajax
Мы добавим getAddressDetails(placeId)
в
select: function(event, ui) {
var placeId = ui.item.id;
getAddressDetails(placeId);
}
Функция getAddressDetails()
сделает ajax запрос к нашей конечной точке /address
.
Теперь наш resource/form.blade.php
выглядит следующим образом
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Google autocomplete</div>
<div class="card-body">
<div class="form-group">
<label for="streetaddress">Street number</label>
<input type="text" class="form-control ui-widget autocomplete-google" id="street_address_1">
</div>
<div class="form-group">
<label for="streetaddress2">Street address 2</label>
<input type="text" class="form-control" id="street_address_2">
</div>
<div class="form-group">
<label for="city">City</label>
<input type="text" class="form-control" id="city">
</div>
<div class="form-group">
<label for="state">State</label>
<input type="text" class="form-control" id="state">
</div>
<div class="form-group">
<label for="postcode">Postcode</label>
<input type="text" class="form-control" id="postcode">
</div>
<div class="form-group">
<label for="country">Country</label>
<input type="text" class="form-control" id="country">
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('script')
<script>
$(document).ready(function() {
$(".autocomplete-google").autocomplete({
source: function(request, response) {
$.ajax({
url: '/placeid',
type: 'GET',
dataType: "json",
data: {
inputData: request.term,
},
success: function(data) {
response(data);
}
});
},
select: function(event, ui) {
var placeId = ui.item.id;
getAddressDetails(placeId);
}
});
});
function getAddressDetails(placeId) {
$.ajax({
url: "/address",
type: 'GET',
dataType: "json",
data: {
placeId: placeId,
},
success: function(data) {
$('#country').val(data.country);
$('#city').val(data.locality);
$('#postcode').val(data.postal_code);
$('#state').val(data.state);
$('#street_address_1').val(data.streetNumber);
$('#street_address_2').val(data.streetName);
},
catch: function(error) {
console.log('error');
}
});
}
</script>
@endsection
Выше, при успехе, мы получаем результат и заполняем форму с id страны, города, почтового индекса и штата, хотя наш api не готов возвращать данные о месте на основе id места.
Шаг 6: В AutocompleteHandler.php
мы сделаем addressBasedOnPlaceId()
, который будет принимать Id места, отправленный из нашего findAddressBasedOnPlaceId()
из GoogleAutocompleteController.php
.
public function addressBasedOnPlaceId(string $placeId): JsonResponse
{
$url = $url = // build the readable url with http_build_query and sprintf()
https://maps.googleapis.com/maps/api/place/details/json?place_id=EhtOZXBhbCBUYXIsIEthdGhtYW5kdSwgTmVwYWwiLiosChQKEgk9yaxKzhjrORHvFQWGXi5RGhIUChIJv6p7MIoZ6zkR6rGN8Rt8E7U&key=YOUR_KEY
);
try {
// step1: instantiate GuzzleHttp client
// step2: hit the url
// step3: get json response
// step4: convert json to array
// step5: return required data such as street name, city,postcode, country etc
} catch (Exception $e) {
//catch error
}
}
Шаг 7: Окончательный вариант AutocompleteHandler.php
выглядит следующим образом
<?php
namespace AppIntegration;
use GuzzleHttpClient;
use SymfonyComponentHttpFoundationJsonResponse;
use SymfonyComponentHttpFoundationResponse;
class AutocompleteHandler
{
public const BASE_URL = "https://maps.googleapis.com/maps/api/place";
private $key;
public function __construct()
{
$this->key = config('services.googlekey.key');
}
public function getDataFromAddressComponent(array $addressComponents, string $searchFor): ?string
{
return collect($addressComponents)->map(fn ($addressComponent) => collect($addressComponent['types'])->contains($searchFor) ? $addressComponent['long_name'] : null)->filter()->first();
}
public function placeId(string $address): JsonResponse
{
$url = sprintf(
'%s/autocomplete/json?%s',
self::BASE_URL,
http_build_query([
'input' => $address,
'types' => 'address',
'key' => $this->key,
])
);
try {
$client = new Client();
$response = $client->request('get', $url);
$responseJson = $response->getBody()->getContents();
$responseArray = json_decode($responseJson, true);
return response()->json(collect($responseArray['predictions'])->map(
fn ($value) =>
[
'id' => $value['place_id'],
'label' => $value['description'],
]
));
} catch (Exception $e) {
return response()->json([
'error' => $e->getMessage(),
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function addressBasedOnPlaceId(string $placeId): JsonResponse
{
$url = sprintf(
'%s/details/json?%s',
self::BASE_URL,
http_build_query([
'place_id' => $placeId,
'key' => $this->key,
])
);
try {
$client = new Client();
$response = $client->request('get', $url);
$responseJson = $response->getBody()->getContents();
$responseArray = json_decode($responseJson, true);
return response()->json([
'streetNumber' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'street_number'),
'streetName' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'route'),
'locality' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'locality'),
'state' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'administrative_area_level_1'),
'administrative_area_level_2' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'administrative_area_level_2'),
'country' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'country'),
'postal_code' => $this->getDataFromAddressComponent($responseArray['result']['address_components'], 'postal_code')
]);
} catch (Exception $e) {
return response()->json(['error' => $e->getMessage(), 'exception' => get_class($e)], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
}
Итак, мы вернули в ответ StreetNumber, StreetName, locality, state, postcode и country и заполнили этими значениями остальные поля формы.
👉 Получить код