Skip to content

PHP klient ​

Oficiálna knižnica pre Search API a Catalog API. Rieši dávkovanie, čiastočné zlyhania, opakovanie požiadaviek a bezpečný full sync.

Klient nemá frameworkovú závislosť: HTTP prebieha cez PSR-18 klienta a PSR-17 factory — buď tie, ktoré dodáte vy, alebo tie, ktoré si nájde discovery vo vašom projekte — a logovanie je voliteľné cez PSR-3. Vyžaduje PHP 8.1 alebo novšie.

Inštalácia ​

bash
composer require getmetty/metty-php

Ak projekt zatiaľ neobsahuje PSR-18 klienta, doinštalujte ľubovoľnú implementáciu:

bash
composer require symfony/http-client nyholm/psr7

Nastavenie ​

php
use Metty\Client\MettyClient;

$client = MettyClient::create(
    publicKey: '<PUBLIC_API_KEY>',   // čítanie
    secretKey: '<SECRET_API_KEY>',   // zápis katalógu; iba na serveri
);

Adresy oboch API sú predvolené, uvádzať ich nemusíte. Postačí kľúč, ktorý skutočne potrebujete: klient s pk_ dokáže iba čítať, klient s sk_ iba zapisovať. Zámenu kľúčov klient odmietne už pri vytvorení, aby sa secret nedostal do URL.

create() si PSR-18 klienta aj PSR-17 factory nájde cez discovery a discovery timeout nezaručuje. V aplikácii, ktorá ich už má v kontajneri, si ich zapojte sami — nič potom nezávisí od toho, čo discovery náhodou nájde, a zároveň viete odovzdať PSR-3 logger:

php
use Metty\Client\Configuration;
use Metty\Client\MettyClient;

$client = new MettyClient(
    new Configuration(
        publicKey: '<PUBLIC_API_KEY>',
        secretKey: '<SECRET_API_KEY>',
        maxRetries: 3,
    ),
    $httpClient,       // Psr\Http\Client\ClientInterface
    $requestFactory,   // Psr\Http\Message\RequestFactoryInterface
    $streamFactory,    // Psr\Http\Message\StreamFactoryInterface
    $logger,           // Psr\Log\LoggerInterface, voliteľné
);

Každý argument za konfiguráciou je voliteľný a bez neho nastupuje discovery, takže viete odovzdať napríklad iba logger. Bez loggera klient nič neloguje.

Timeouty ​

Autodiscovery (php-http/discovery) vyberie ktoréhokoľvek klienta, ktorého nájde, a žiadna z implementácií timeout nezaručuje — s nevhodnou môže požiadavka visieť, kým PHP proces niekto nezabije. Timeout na spojenie aj na celú požiadavku si nastavte sami a klienta odovzdajte hotového:

php
use Metty\Client\Configuration;
use Metty\Client\MettyClient;
use Nyholm\Psr7\Factory\Psr17Factory;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Psr18Client;

$psr17 = new Psr17Factory();
$httpClient = new Psr18Client(
    HttpClient::create(['timeout' => 10, 'max_duration' => 30]),
    $psr17,
    $psr17,
);

$client = new MettyClient(
    new Configuration('<PUBLIC_API_KEY>', '<SECRET_API_KEY>'),
    $httpClient,
    $psr17,
    $psr17,
);

timeout je timeout nečinnosti spojenia, max_duration strop pre celú požiadavku; Guzzle tú istú dvojicu volá connect_timeout a timeout. Obmedzujú jeden pokus — opakovaná požiadavka navyše čaká medzi pokusmi, najviac 60 s na pokus, keď server pošle Retry-After, inak krátky backoff. search() a suggest() 429 nikdy neopakujú; vyhodia ho hneď.

Vyhľadávanie ​

php
use Metty\Client\Search\SearchQuery;

$response = $client->search()->search(
    SearchQuery::for('vŕtačka')
        ->facet('farba', 'modrá')
        ->facet('farba', 'čierna')          // rovnaké pole dvakrát = ALEBO
        ->category('Náradie > Vŕtačky')
        ->priceRange(100, 200)
        ->sortBy('price_asc')
        ->withSections('facets', 'categories', 'suggestions')
        ->perPage(24)
        ->page(2),
);

foreach ($response->products as $product) {
    echo $product->id, ' ', $product->name, ' ', $product->price, PHP_EOL;
}

echo $response->total, ' výsledkov na ', $response->pages, ' stránkach';

Vlastnosti facets, categories, priceRange a suggestions sú naplnené iba pri použití withSections(). Pole highlight prichádza zo servera vrátane značiek []; klient zvýraznenie nedopočítava.

Klient pozná okno 200 výsledkov: hasNextPage() ho rešpektuje a dotaz mimo okna zlyhá ešte pred odoslaním požiadavky, nie až chybou 422.

php
foreach ($client->search()->searchAll(SearchQuery::for('vŕtačka')) as $product) {
    echo $product->name, PHP_EOL;
}

Našepkávanie:

php
$suggest = $client->search()->suggest('vŕta', limit: 8);

$suggest->suggestions;  // [['query' => 'vŕtačka', 'count' => 41], …]
$suggest->products;     // najviac 5 skrátených produktov

Zápis katalógu ​

php
use Metty\Client\Catalog\CatalogProduct;

$result = $client->catalog()->replace([
    CatalogProduct::create('sku-1', 'Príklepová vŕtačka', 'https://eshop.sk/vrtacka',
        price: 129.9, inStock: true, brand: 'Bosch', category: 'Náradie > Vŕtačky',
        params: ['farba' => 'modrá', 'príkon' => '800 W']),
    CatalogProduct::create('sku-2', 'Uhlová brúska', 'https://eshop.sk/bruska', price: 89.5),
]);

if ($result->hasFailures()) {
    foreach ($result->failures() as $failure) {
        echo $failure->id, ': ', $failure->error, ' — ', $failure->message, PHP_EOL;
    }
}

Katalóg môžete odovzdať naraz bez ohľadu na veľkosť — klient ho rozdelí na dávky po 100 produktoch a výsledky zlúči.

replace() produkt úplne nahradí, patch() zmení iba uvedené polia a delete(['sku-1']) produkty odstráni. Pri patch() sa rozlišuje vynechané pole od poľa s hodnotou null, preto sa vymazanie zapisuje explicitne:

php
$client->catalog()->patch([
    new CatalogProduct('sku-1', ['price' => 99.0, 'brand' => null]),
]);

Bezpečný full sync ​

php
$outcome = $client->catalog()->synchronize($products);

echo $outcome['commit']['removed'];  // koľko starých produktov zmizlo

Klient otvorí sync, nahrá pod ním celý katalóg a až potom ho commitne. Ak sa niektorý produkt nepodarí nahrať, sync necommitne — commit by odstránil práve tie produkty, ktoré neprešli — a vyhodí SyncIncompleteException. Sync zostáva otvorený, takže ho možno dokončiť:

php
use Metty\Client\Exception\SyncIncompleteException;

try {
    $client->catalog()->synchronize($products);
} catch (SyncIncompleteException $exception) {
    $retry = $client->catalog()->replace($products, $exception->syncId);

    if ($retry->hasFailures()) {
        foreach ($retry->failures() as $failure) {
            echo $failure->id, ': ', $failure->error, ' — ', $failure->message, PHP_EOL;
        }

        throw $exception;
    }

    $client->catalog()->commit($exception->syncId);
}

replace() nad jedným produktom nikdy nevyhodí výnimku, takže opakovanie treba skontrolovať presne tak ako prvé nahrávanie: commitnite iba WriteResult bez chýb. Commit syncu, v ktorom niečo znova zlyhalo, by odstránil práve tie produkty, ktoré sa nepodarilo nahrať. Necommitnutý sync nemaže nič, takže nechať ho otvorený a dokončiť pri ďalšom behu je bezpečný výsledok.

Najjednoduchšie opakovanie je poslať celý snapshot znova, lebo produkt zapísaný dvakrát pod tým istým syncom zostáva jedným produktom. Predpokladá to, že $products sa dá prejsť druhýkrát — generátor, ktorý synchronize() už spotreboval, nevráti nič, takže zdroj na opakovanie načítajte nanovo. Ak chcete poslať iba to, čo zlyhalo, vezmite si id z $exception->result->failures() — každý ItemResult nesie id, error aj message.

Prázdny snapshot klient odmietne vždy. Parameter force: true sa týka výhradne serverovej poistky, ktorá odmieta snapshot pokrývajúci menej než polovicu katalógu.

Export ​

php
foreach ($client->catalog()->export() as $product) {
    echo $product['id'], PHP_EOL;
}

Klient stránkuje automaticky, takže cyklus prejde celý katalóg.

Prehľad funkcií ​

  • dávkovanie podľa limitu servera, teda 100 produktov na dávku
  • spracovanie čiastočného zlyhania — stav každého produktu samostatne, nie jedna výnimka na celú dávku
  • kontrola hraníc servera — neznáme radenie, sekcia alebo stránka mimo okna zlyhajú lokálne
  • opakovanie požiadaviek — 429 pri volaniach katalógu, s rešpektovaním Retry-After; search() a suggest() vyhodia 429 hneď (ApiException::isRateLimited()); chyba servera alebo siete iba pri metódach, ktoré je bezpečné zopakovať; ostatné 4xx nikdy
  • poistka pri full syncu — neúplný snapshot sa necommitne
  • bezpečné logovanie — hlavička Authorization sa nedostane do logu ani do výnimky

Idempotency kľúč nie je potrebný: server zapisuje podľa id, takže zopakovaná dávka nevytvorí duplicity.

Chyby ​

výnimkakedy
ConfigurationExceptionneplatné nastavenie alebo dotaz, ktorý by server odmietol
ApiExceptionserver vrátil chybu; obsahuje statusCode a errorCode
SyncIncompleteExceptionfull sync sa nedokončil celý a nebol commitnutý
TransportExceptionsieťová chyba alebo odpoveď, ktorá sa nedá spracovať

Všetky implementujú Metty\Client\Exception\MettyException, takže sa dajú zachytiť spoločne.

Kompletné príklady použitia nájdete v sekcii Príklady.

Dokumentácia Metty