PHP - przeliczanie walut dla wielu produktów

0

Cześć,
mam po około 30 produktów na stronę w sklepie. Klient ma do wyboru pięć walut. Może je dowolnie zmieniać. W bazie przechowuję wszystko w złotówkach (dokładnie w groszach). Do przeliczania wykorzystuję taki kod.

public static function convert(string $fromCurrency, ?string $toCurrency, ?int $amount): float|null
    {
        if (!$amount)
            return null;

        if (!$toCurrency)
            $toCurrency = 'PLN';

        $req_url = 'https://api.exchangerate-api.com/v4/latest/'.$fromCurrency;

        $response_json = file_get_contents($req_url);
        if(false !== $response_json) {
            try {
                $response_object = json_decode($response_json);
                return round(($amount * $response_object->rates->$toCurrency), 2);
            }
            catch(Exception $e) {
                
            }
        }
    }

kod działa jednak kiedy na stronie jest powiedzmy 30 produktów nie jest to raczej wydajne rozwiązanie ponieważ za każdym razem cena jest przeliczana. Czasem też jest błąd

file_get_contents(https://api.exchangerate-api.com/v4/latest/PLN): Failed to open stream: HTTP request failed! HTTP/1.1 429 Too Many Requests

Moje pytanie: jak zrobić to wydajnie, tak aby nie było problemu i zbędnego obciążenia?

Dzięki

4

Jak często się ceny zmieniają? Jeśli nie musi to być robione w real-time po 300x dziennie to może lepiej po prostu dodać do każdego produktu parę kolumn z cenami w pozostałych walutach, a potem to pobierać z bazy - a nie przeliczać? A przy zmianie kursu robisz jakiegoś update, który z automatu przeliczy wartości wyrażone w innych pieniądzach niż PLN?

5

Ja bym zrobil tak ze po godzinie 12 w nocy np o 12:01 pobierana jest cena waluty z dnia poprzedniego , bo tak tez liczy sie faktury, i wtedy za kazdym razem gdy przeliczasz nie laczysz sie z serwerem walut bo po co? a u ciebie wyglada jakby pokazanie 30 produktow musialo 30 razy sciagac kurs z api ?

4
  1. Zapisuj w bazie danych aktualny kurs waluty i odświeżaj go raz dziennie. Dzięki temu, nie będziesz uderzał setki razy do API, gdy tylko ktoś wejdzie na produkt.
  2. Wartość każdego produktu w danej walucie zapisywałbym po przeliczeniu do bazy danych i odświeżał te ceny cronem raz dziennie. Dzięki temu wyświetlasz od razu gotową cenę z bazy danych, a nie przeliczasz za każdym razem na podstawie kursu.
  3. Po json_decode robisz catch Exception, ale nic z tym nie robisz. Nawet nie będziesz wiedział, kiedy wystąpi błąd. Zrób obsługę błędów. Skorzystaj też funkcji json_last_error (https://www.php.net/manual/en/function.json-last-error.php)

Obecne Twoje rozwiązanie poza problemami wydajnościowymi ma jeszcze jeden zasadniczy problem. Gdy przestanie działać API, nie będziesz miał przeliczonych cen produktów w innych walutach.

2
Mjuzik napisał(a):
  1. Zapisuj w bazie danych aktualny kurs waluty i odświeżaj go raz dziennie. Dzięki temu, nie będziesz uderzał setki razy do API, gdy tylko ktoś wejdzie na produkt.

pobieraj z bazy danych produkt z przeliczoną ceną ( na podstawi pola z pkt 1 i ceny w PLN mnożenie w treści SQLa)

4

No musisz sobie zdać pytanie, czy prawdziwa cena produktów to cena w złotówkach, a te ceny "przeliczane" są jakby "mniej prawdziwe", czy te ceny które pokazujesz klientom są prawdziwe tak jak PLN.

Musisz wiedzieć że stosunek np PLN do EUR, a potem EUR do USD, to nie jest to samo co stosunek USD do PLN. To są tzw. forex-pairs, i to znaczy że żadna waluta nie ma "absolutnej" wartości, jedynie możesz sprawdzać walutę względem innych walut.

Także, poważna decyzja - czy źródłem prawdy jest PLN, czy wszystkie. Jeśli wszystkie, to bracie masz dużo roboty, bo jeśli będziesz trzymał jedną cenę, to ta cena będzie zależna od aktualnego kursu Twojej waluty, tzn jeśli wybrałeś trzymanie w bazie złotówek, to ta jednostka będzie zależna np od polskiej inflacji.

Albo możesz zrobić tak jak mówi @cerrato - trzymać różne ceny w różnych walutach. Dzięki temu możesz manewrować cenami, np jak Ci za coś wyjdzie €98.12, to masz możliwość zmienić na €97.99 albo €98.99, zależy jakim typem osoby jesteś.

1

Ten kod aż się prosi o cache. Nie musisz do tego organizować osobnej tabeli w bazie ani wiele zmieniać, po prostu w tym miejscu

$response_json = file_get_contents($req_url);

Dajesz odczyt/zapis cache czy to statycznego, Redisa czy memcache bez znaczenia. Nie wiem czy jakiegoś Frameworka tam używasz, ale np. w przypadku np. Laravela twój problem rozwiązałbyś dosłownie jedna linią kodu.

Mam bardzo podobną funkcję w jednym serwisie i lata to tak bez problemów od lat, więc sprawdzone ;)

Edit: A jednak widzę w tagach że to akurat Laravel, więc użyj funkcji remember i gotowe.
https://laravel.com/docs/8.x/cache#retrieve-store

0

Tak jak kolega wyżej pisał cache byłby tutaj najlepszym rozwiązaniem, bo skoro reguły biznesowe z API się sprawdzały to raczej biznesowo wszystko się spina, więc nie ma sensu zmieniać teraz bazy danych i przerabiać połowy systemu. Przeliczanie cen w bazie ma też ten minus, że jeśli kursy mogą się często zmieniać to będzie sporo operacji zapisów + pytanie czy nie ma tam jakiejś dalszej logiki uzależnionej od cen itd. Reasumując - dodać cache z TTL typu 1-5 minut + zabezpieczeniem na wypadek padu API i po problemie. Można to ogarnąć w 10 minut i nie rozwalamy całej logiki biznesowej.

Oczywiście tak czy siak warto sobie odpowiedzieć na pytania np. @TomRiddle bo może jest błąd w samym myśleniu, ale z punktu widzenia rozwiązania problemu cache będzie najszybszym i najbezpieczniejszym wyjściem.

1 użytkowników online, w tym zalogowanych: 0, gości: 1