SW6 REST-API Response: Was mach ich falsch?

Hallo,

habe nun dieses Beispiel ausprobiert https://docs.shopware.com/en/shopware-platform-dev-en/how-to/working-with-the-api-and-an-http-client?category=shopware-platform-dev-en/how-to

Im Beispiel werden die Auth-Daten ja in der Plugin-Config angegeben… ich habe dort also die Logindaten eines Admin-Users eingegeben. Ist das korrekt? Wieso wird hier nicht der API-Key aus dem Headless-Sales-Channel genutzt?

Das Beispiel ist wohl nur für die Admin-API ausgelegt… aber wie bekomm ich das für die Sales-Channel-API hin?

mit dem Response meines Admin-API-Calls kann ich ohnehin nix anfangen … 

**$service-\>request("GET", "product");**

object(GuzzleHttp\Psr7\Response)#3149 (6) {
  ["reasonPhrase":"GuzzleHttp\Psr7\Response":private]=>
  string(2) "OK"
  ["statusCode":"GuzzleHttp\Psr7\Response":private]=>
  int(200)
  ["headers":"GuzzleHttp\Psr7\Response":private]=>
  array(17) {
    ["Date"]=>
    array(1) {
      [0]=>
      string(29) "Wed, 27 May 2020 16:25:39 GMT"
    }
    ["Server"]=>
    array(1) {
      [0]=>
      string(6) "Apache"
    }
    ["Cache-Control"]=>
    array(1) {
      [0]=>
      string(7) "private"
    }
    ["Access-Control-Allow-Origin"]=>
    array(1) {
      [0]=>
      string(1) "*"
    }
    ["Access-Control-Allow-Methods"]=>
    array(1) {
      [0]=>
      string(25) "GET,POST,PUT,PATCH,DELETE"
    }
    ["Access-Control-Allow-Headers"]=>
    array(1) {
      [0]=>
      string(86) "Content-Type,Authorization,sw-context-token,sw-access-key,sw-language-id,sw-version-id"
    }
    ["sw-version-id"]=>
    array(1) {
      [0]=>
      string(0) ""
    }
    ["sw-language-id"]=>
    array(1) {
      [0]=>
      string(0) ""
    }
    ["sw-context-token"]=>
    array(1) {
      [0]=>
      string(0) ""
    }
    ["x-frame-options"]=>
    array(1) {
      [0]=>
      string(4) "deny"
    }
    ["X-Debug-Token"]=>
    array(1) {
      [0]=>
      string(6) "8fa1eb"
    }
    ["X-Debug-Token-Link"]=>
    array(1) {
      [0]=>
      string(38) "http://myshop.de/_profiler/8fa1eb";
    }
    ["X-Robots-Tag"]=>
    array(1) {
      [0]=>
      string(7) "noindex"
    }
    ["X-Symfony-Cache"]=>
    array(1) {
      [0]=>
      string(25) "GET /api/v1/product: miss"
    }
    ["Vary"]=>
    array(1) {
      [0]=>
      string(13) "Authorization"
    }
    ["Transfer-Encoding"]=>
    array(1) {
      [0]=>
      string(7) "chunked"
    }
    ["Content-Type"]=>
    array(1) {
      [0]=>
      string(24) "application/vnd.api+json"
    }
  }
  ["headerNames":"GuzzleHttp\Psr7\Response":private]=>
  array(17) {
    ["date"]=>
    string(4) "Date"
    ["server"]=>
    string(6) "Server"
    ["cache-control"]=>
    string(13) "Cache-Control"
    ["access-control-allow-origin"]=>
    string(27) "Access-Control-Allow-Origin"
    ["access-control-allow-methods"]=>
    string(28) "Access-Control-Allow-Methods"
    ["access-control-allow-headers"]=>
    string(28) "Access-Control-Allow-Headers"
    ["sw-version-id"]=>
    string(13) "sw-version-id"
    ["sw-language-id"]=>
    string(14) "sw-language-id"
    ["sw-context-token"]=>
    string(16) "sw-context-token"
    ["x-frame-options"]=>
    string(15) "x-frame-options"
    ["x-debug-token"]=>
    string(13) "X-Debug-Token"
    ["x-debug-token-link"]=>
    string(18) "X-Debug-Token-Link"
    ["x-robots-tag"]=>
    string(12) "X-Robots-Tag"
    ["x-symfony-cache"]=>
    string(15) "X-Symfony-Cache"
    ["vary"]=>
    string(4) "Vary"
    ["transfer-encoding"]=>
    string(17) "Transfer-Encoding"
    ["content-type"]=>
    string(12) "Content-Type"
  }
  ["protocol":"GuzzleHttp\Psr7\Response":private]=>
  string(3) "1.1"
  ["stream":"GuzzleHttp\Psr7\Response":private]=>
  object(GuzzleHttp\Psr7\Stream)#3401 (7) {
    ["stream":"GuzzleHttp\Psr7\Stream":private]=>
    resource(121) of type (stream)
    ["size":"GuzzleHttp\Psr7\Stream":private]=>
    NULL
    ["seekable":"GuzzleHttp\Psr7\Stream":private]=>
    bool(true)
    ["readable":"GuzzleHttp\Psr7\Stream":private]=>
    bool(true)
    ["writable":"GuzzleHttp\Psr7\Stream":private]=>
    bool(true)
    ["uri":"GuzzleHttp\Psr7\Stream":private]=>
    string(10) "php://temp"
    ["customMetadata":"GuzzleHttp\Psr7\Stream":private]=>
    array(0) {
    }
  }
}

 

Was mach ich falsch? Kann mir bitte jemand helfen?

weiß denn niemand Rat?

Scheint mir am auslesen deiner Response zu liegen. Was in deiner Response steht sind die Header-Informationen des Requests und nicht der eigentliche Body.

Als Statuscode erhälst du:

[“statusCode”:“GuzzleHttp\Psr7\Response”:private]=> int(200)

somit würde ich mal behaupten, dass die Anfrage erfolgreich durchgeht. 200 = Request ok.

danke für deine Antwort @Lukashwm‍

habe mich exakt an das Beispiel gehalten 

nur, dass ich die Methode request public gemacht habe damit ich von außen drauf komme

public function request(string $method, string $uri, ?array $body = null): ResponseInterface
    {
        if ($this->accessToken === null || $this->refreshToken === null || $this->expiresAt === null) {
            $this->getAdminAccess();
        }

        $bodyEncoded = json_encode($body);

        $request = $this->createShopwareApiRequest($method, $uri, $bodyEncoded);

        return $this->send($request, $uri);
    }

private function createShopwareApiRequest(string $method, string $uri, ?string $body = null): RequestInterface
    {
        return new Request(
            $method,
            getenv('APP_URL') . '/api/v1/' . $uri,
            [
                'Authorization' => 'Bearer ' . $this->accessToken,
                'Accept' => '*/*'
            ],
            $body
        );
    }

private function send(RequestInterface $request, string $uri)
    {
        if ($this->expiresAt <= (new \DateTime())) {
            $this->refreshAuthToken();

            $body = $request->getBody()->getContents();

            $request = $this->createShopwareApiRequest($request->getMethod(), $uri, $body);
        }

        return $this->restClient->send($request);
    }

und Zugriff von außen:

$service = new RestService($this->config);
$response = $service->request("GET", "product");

 

Versuch dir die Ausgabe mal so zu holen, das ist der gleiche Weg wie beim herausziehen des Oauth Tokens aus dem Beispiel:

$response_body = json_decode($response->getBody()->getContents(), true);

1 „Gefällt mir“

Hallo @Lukashwm‍

danke für den Tipp! Das sieht jetzt schon viel besser aus!

aber irgendwie ist zu jedem Feld nur ein weiterer API-Call angegeben, statt direkt die Daten:

["manufacturer"]=>
      array(2) {
        ["data"]=>
        NULL
        ["links"]=>
        array(1) {
          ["related"]=>
          string(82) "http://www.myshop.de/api/v1/product/017b939578874a8f92787bb3169019e3/manufacturer"
        }
      }

wieso steht hier nicht direkt der Herstellername?

["media"]=>
      array(2) {
        ["data"]=>
        array(0) {
        }
        ["links"]=>
        array(1) {
          ["related"]=>
          string(75) "http://sass.myc3.info/api/v1/product/017b939578874a8f92787bb3169019e3/media"
        }
      }

 

oder die Bilder?

Das ist ja übel umständlich, dann für alle Zusatzdaten nochmal nen Call zu machen…

laut Doku:

The manufacturer of a product can then be read over api/v1/product/{productId}/manufacturer

das kann doch nicht ernst gemeint sein? Da hab ich ja pro Produkt dann 10-12 Calls oder noch mehr…

 

und wieso kommen unsere übersetzten Texte nicht mit? Nur Deutschland kommt…

["translated"]=>
      array(7) {
        ["metaDescription"]=>
        NULL
        ["name"]=>
        string(45) "[DEMO] Lorem ipsum dolor sit amet, consetetur"
        ["keywords"]=>
        NULL
        ["description"]=>
        string(1221) "Lorem ipsum dolor sit "
        ["metaTitle"]=>
        NULL
        ["packUnit"]=>
        NULL
        ["customFields"]=>
        array(0) {
        }
      }

eigentlich gibts aber einen englischen Name

aber lasst mich raten, auch hier brauch ich nen extra Call?

["translations"]=>
      array(2) {
        ["data"]=>
        array(0) {
        }
        ["links"]=>
        array(1) {
          ["related"]=>
          string(82) "http://myshop.de/api/v1/product/fd7b62cb78414c518c32bd9f6fe72b41/translations"
        }
      }

 

Sende als Header “Accept: application/json”. Dadurch kommen die Daten ganz normal zurück und viel lesbarer :slight_smile:

https://docs.shopware.com/en/shopware-platform-dev-en/admin-api-guide/authentication?category=shopware-platform-dev-en/admin-api-guide

Hallo Flo,

grundsätzlich werden erstmal nur die Hauptdaten einer Entität ausgeliefert, die du anfragst. Heißt z.b. nur die Produktdaten. Zusätzliche Daten, die mit dem Produkt assoziert sind, müssen nicht mit einem extra Call geholt werden. Du kannst bei der Anfrage auch angeben, dass die mitgeladen werden sollen. Hier Shopware 6: Reading entities findest du ein Beispiel JSON, wie z.B. Produkte zu einer Category mitgeladen werden. Dies lässt sich analog auf Produkte und Hersteller übertragen. 

Es lässt sich jetzt sicherlich argumentieren, dass man doch immer den Hersteller zu einem Produkt braucht, aber wir haben das aus Performance Gründen bewusst so gestaltet. Es gibt eben halt auch immer Szenarien, wo dies nicht benötigt wird. 

Viele Grüße aus Schöppingen

cool Michael Telgmann

1 „Gefällt mir“

Hallo [@Michael Telgmann](http://forum.shopware.com/profile/17553/Michael Telgmann „Michael Telgmann“)‍,

muss ich das im Body des API-Requests mitgeben? Oder direkt in der URL?

Wir arbeiten schon mit der Sales-Channel-API und dort kann man das in der URL angeben… aber funktioniert mit der Admin-API offenbar nicht.

/sales-channel-api/v1/product/68b434488d23475596ac75fe1459fe42?associations[media][]&associations[properties][associations][group][]

aber ich verwende ja als Basis dieses Beispiel

return new Request(
            $method,
            getenv("APP_URL") . "/api/v1/" . $uri,
            [
                "Authorization" => "Bearer " . $this->accessToken,
                "Accept" => "application/json"
            ],
            $body
        );

das Beispiel von dir versteh ich nicht ganz, das hat weniger was mit meinem Fall zu tun. Leider ist die Doku hier echt dürftig… ich will kein Limit und auch keine Anzahl von Herstellern…

ich will einfach alle Daten in einem Call dabei haben

  • Hersteller
  • Artikelbilder
  • Eigenschaften
  • mehrsprachige Texte

Hallo,

genau, das musst du dann im Body als JSON String mitgeben. 

{
	"associations": {
		"manufacturer": {}
	}
}

Du musst die Associations ja nicht limitieren oder sonstiges. Ein leeres Objekt reicht an der Stelle dann. Du findest die Daten dann in der Response unter dem Key „included“.

Falls dir das json:api Schema nicht zusagt, kannst du auch einfach den Accept Header auf „application/json“ setzen. Shopware 6: Admin api authentication

Viele Grüße aus Schöppingen

cool Michael Telgmann

1 „Gefällt mir“

[@Michael Telgmann](http://forum.shopware.com/profile/17553/Michael Telgmann “Michael Telgmann”)‍

also entweder ich bin zu blöd, oder ich bekomm das mit dem Manufacturer einfach nicht hin…

{
    "associations": {
        "manufacturer": []
    }
}

ich parse das JSON dafür über PHP, deshalb ist da vermutlich ein und kein { }. Hier das PHP-Objekt:

$associations = ["associations" => [
            "manufacturer" => []
        ]];

habs aber auch direkt mit einem String mit { } versucht, selbes Ergebnis.

den Key “included” hab ich im Response gar nicht.

und das ist weiterhin NULL

["manufacturer"] => NULL

 

Hallo @FloC3‍

versuche mal folgendes, damit du da ein Objekt bekommst

$associations = ["associations" => [
    "manufacturer" => new \stdClass()
]];

Viele Grüße aus Schöppingen

cool Michael Telgmann

Hallo [@Michael Telgmann](http://forum.shopware.com/profile/17553/Michael Telgmann “Michael Telgmann”)‍,

danke für deinen Tipp. Leider ändert das am Resultat auch nichts. Manufacturer weiterhin NULL und kein “included” vorhanden.

das ist halt einfach das Problem, wenn die Doku quasi null Aussage hat dazu … 

[@Michael Telgmann](http://forum.shopware.com/profile/17553/Michael Telgmann „Michael Telgmann“)‍

bei Media und Co ist es das selbe… wäre echt dankbar für Hilfe! Ich will einfach ein komplette Produktobjekt haben mit allem Drum und Dran …

[@Michael Telgmann](http://forum.shopware.com/profile/17553/Michael Telgmann “Michael Telgmann”)‍ evtl. hast du noch einen Tipp? wäre sehr dankbar!!!

langsam hätte ich echt gern mal Hilfe dazu, wenn schon die Doku nix hergibt…

 

Moin,

du kannst nützliche Parameter, wie eben z.B. “associations” auf den API request anwenden, um den Response zu filtern oder mit zusätzlichen Daten zu bereichern. Schau sie dir mal an: https://docs.shopware.com/en/shopware-platform-dev-en/admin-api-guide/reading-entities?category=shopware-platform-dev-en/admin-api-guide

Soweit ich das verstehe, sind das aber Filter/Parameter für den search-Endpoint. Sprich jede Entity hat auch eine search Route, dafür machst Du anstelle eines

GET /api/v1/product

ein

POST /api/v1/search/product

Dann, wie schon beschrieben, mit mit associations als Payload im Body des POST Request und die manufacturers werden dann innerhalb _ included _ gelistet.

Das Ganze macht aber nicht so viel Sinn, wenn Du eine ungefilterte Produktliste aufrufst. _ included _ hängt nicht an jedem Produkt, sondern ist ein Key im Response (während die Produkte unter dem Key data gelistet sind). Sprich, während manufacturer nach wie vor am Produkt als “relationship” mit der ID als Referenz hängt, werden die manufacturer-Daten unter include gelistet. Damit brauchst du dann aber zumindest keinen extra Request mehr machen, sondern kannst dir alles aus einem Response zusammen suchen. Oder du verwendest zusätzliche Filter, um für jedes Produkt genau die Daten zu abzurufen, die du sehen willst - dafür dann natürlich mit mehreren Requests.

@jf43‍

hi, danke für deine Hilfe!

Ich hab das mit den Associations schon mal an anderer Stelle über einen Call in Javascript gemacht.

https://www.myshop.de/sales-channel-api/v1/product/77959cbb1e881ecd149fdd0eb86acdb8?associations[media][]&associations[properties][associations][group][]

Da hat das einwandfrei funktioniert, es direkt als URL-Parameter anzugeben…

aber in PHP über einen GET bekomm ich es einfach nicht hin. Geht das hier nicht bei einem GET? Oder unterscheiden sich hier die Sales-Channel-API und die Admin-API?

Ja, die beiden unterscheiden sich in Nutzung und Einsatzzweck. Wie die Name schon andeuten, hat die eine den Fokus auf´s Backend, die andere auf das Frontend:

The SalesChannel-API is part of our API family. It allows access to all sales channel operations, such as creating new customers, customer login and logout, various cart operations and a lot more.

It’s ideal if you want to build your own storefront. 

Das von dir verlinkte Howto behandelt die Shopware/Admin API. Für diese benötigt man den OAuth-Token zur Authentifizierung und wenn du die associations nutzen willst, dann nur per POST über den search Endpoint.

Die Implementierung eines Clients für die Sales-Channel-Api ist einfacher, da hier keine Authentifizierung nötig ist. Man brauch lediglich einen Header mit dem API Access Key des jeweiligen Sales-Channel für jeden Request

sw-access-key: API-ACCESS-KEY

siehe https://docs.shopware.com/en/shopware-platform-dev-en/sales-channel-api/sales-channel-api-authentication?category=shopware-platform-dev-en/sales-channel-api.

In PHP/Guzzle einen Request mit Parametern gegen die Sales-Channel-Api ist auch nicht kompliziert: http://docs.guzzlephp.org/en/stable/quickstart.html#query-string-parameters.

Wie auch immer, du kannst das Ganze auch in einer anderen Sprache implementieren. Das Beispiel behandelt PHP mit ein SW-Plugin, aber normalerweise rufst du die APIs nicht über ein SW-Plugin auf (selbst in dem Howto wird davon abgeraten, dass über ein Plugin zu machen.).

Hi @jf43‍,

wieso sollte man die API nicht von einem SW-Plugin aus aufrufen? Was spricht dagegen?

Wenn ich eine Anbindung an eine Warenwirtschaft machen muss, bleibt mir ja nichts anderes übrig. Außer ich hoste irgendwo ein externes “Tool”, was dann wiederum mit SW kommuniziert… Aber das ist eigentlich Blödsinn.

ps: mit dem Search-Endpoint hat das mit Associations nun geklappt… wusste nicht, dass das nicht für GET geht