Błąd PHP, który kosztował mnie 646 dolarów

Wczoraj podczas iteracji pętli while przy użyciu Google Places API — usługi internetowej, która kosztowała mnie jedyne 646 dolarów, dostałem bardzo cenną lekcję.
Tak, zgadza się, w ciągu jednej godziny spingowałem Places API 17 000 razy 🙄🤦♂️. Postanowiłem więc napisać ten artykuł, który udokumentuje mój błąd w nadziei, że nie popełni go ktoś inny. Zobaczcie więc o co chodziło.
Co takiego się wydarzyło?
No więc...buduję właśnie aplikację internetową i mobilną, która korzysta z Google Places API i potrzebowałem zwrócić wyniki, korzystając z pobliskiego wyszukiwania. Google API zwraca tylko 20 wyników, a maksymalnie 60, które można uzyskać, korzystając z jego wersji paginacji. Prześledźmy więc kod i zobaczmy, co poszło nie tak.
// This is the initial request that we send to get the initial 20 results.
$response = Http::get('https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=' . $request->lat .'%2C'. $request->lng.'&radius='.$request->radius.'&type=restaurant&key=' . env('GOOGLE_MAP_KEY'))->json();
// We push those results to our google_places collection
$this->google_places->push(...$response['results']);
// Here we check the status of the response, if it's ok we continue.
if ($response['status'] == 'OK') {
// If the response has a next page token we loop through until it doesn't, getting the next 20 results.
while ($response['next_page_token']) {
// The google places api has a delay from when the next_page_token is created and those results are actually available.
// So we need to create a delay before we try and grab the next results otherwise we will get an INVALID_REQUEST response.
sleep(2);
// Now we get the next 20 results.
$new_results = Http::get('https://maps.googleapis.com/maps/api/place/nearbysearch/json?pagetoken=' . $response['next_page_token'] . '&key=' . env('GOOGLE_MAP_KEY'))->json();
// Then we push those results to our collection.
$this->google_places->push(...$new_results['results']);
}
}
Udało Ci się ustalić, gdzie popełniłem błąd? Pętla while w PHP będzie działać tak długo, aż otrzyma z powrotem wartość false. Wartość, którą umieściłem w pętli while, zawsze zwraca wartość true, a więc powoduje, że pętla while staje się pętlą nieskończoną. Używałem $response['next_page_token']
z początkowego żądania, które zawsze będzie zwracać wartość true.
Jak sobie z tym poradziłem?
$response = Http::get('https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=' . $request->lat .'%2C'. $request->lng.'&radius='.$request->radius.'&type=restaurant&key=' . env('GOOGLE_MAP_KEY'))->json();
$this->google_places->push(...$response['results']);
// We check to see if the above response includes the key next_page_token, if it does we set the next_page_token property to true
// This is super important, this is how we're going to ensure our loop doesn't run infinitely.
if (array_key_exists('next_page_token', $response)) {
$this->next_page_token = true;
}
if ($response['status'] == 'OK') {
// For the while loop we check to see if the next_page_token is true, it will end when it returns false.
while ($this->next_page_token) {
sleep(2);
$new_results = Http::get('https://maps.googleapis.com/maps/api/place/nearbysearch/json?pagetoken=' . $response['next_page_token'] . '&key=' . env('GOOGLE_MAP_KEY'))->json();
$this->google_places->push(...$new_results['results']);
// This is also super important, here we check to see if next_page_token does not exist or if we're getting an invalid request response.
// If we do we want to set the next_page_token to false so that the loop ends.
if (!array_key_exists('next_page_token', $new_results) || $new_results['status'] == 'INVALID_REQUEST') {
$this->next_page_token = false;
}
}
}
// Being that the Google API only returns 60 results that means the loop should only have to run a max of 2 times.
// So you could create a $count variable and add that like so while($this->next_page_token || $count <= 2) and increment the count each time.
// This will act as a safety in case you accidentally throw a forever truthy statement in there like I did.
Wniosek
Bądź ostrożny podczas pracy z interfejsami API, które pobierają opłaty i aktywują je w pętli. Na wszelki wypadek możesz zawsze utworzyć zmienną $count i zatrzymać pętlę przy ustalonej liczbie, na wypadek, gdybyś miał popełnić błąd.
Od tej pory tak właśnie będę robił, aby mieć pewność, że więcej się to nie powtórzy. Mam nadzieję, że pomoże to Tobie lub komuś innemu uniknąć popełnienia tego samego błędu.
Oryginał tekstu w języku angielskim przeczytasz tutaj.