PHP/MySQL: Verarbeitung / Durchiterieren von json response aus API-Endpunkt

Status
Dieses Thema wurde gelöst! Zur Lösung gehen…

canju

Erfahrenes Mitglied
Hallo ihr Lieben, ich bins mal wieder :)
Ich habe zwei Anliegen, bei denen ich wieder eure Hilfe gebrauchen kann.

Es geht um das Herunterladen von .json files über einen API-Endpunkt via PHP (7.4.3) und das anschließende Importieren in eine MariaDB (10.3).
Hierzu verwende ich zwei PHP Scripte, das eine Lädt die json Datei herunter und speichert diese auf dem Server ab, das andere kümmert sich dann um den Import in die MariaDB.

Das Herunterladen und speichern der json response in eine .json datei klappt schon einwandfrei, hierzu bräuchte ich eure Unterstützung bei einer Erweitertung unter Punkt 2.

1. Beim Import in die MariaDB erhalte ich immer die PHP Notice:

Bash:
PHP Notice:  Trying to access array offset on value of type int in /path/to/import/script.php on line 25
PHP Notice:  Trying to access array offset on value of type null in /path/to/import/script.php on line 25

Die order_number wird zwar in die MariaDB importiert, aber die beiden Notices stören mich.

Die order_number ($order_number = $row['res']['no'];) scheint aber als int vorzuliegen, weil mir var_dump($order_number); die folgende Augabe gibt: int(123456).

Die json Datei enthält folgendes:
JSON:
{
    "success": 1,
    "data":
    {
        "res":
        {
            "no": 123456,
            "completed_date": "2020-10-03",
            "external_incoming_order_id": "659874g23643",
            "customstatus_id": 4,
            "customstatus": null,
            "unifiedstatus_code": "TRANSMITTED",
            "country": "DE"
        }
    }
}

Mein Code für den Import:

PHP:
<?php
// Load the database configuration file
include_once 'db_config.php';
$table_name = 'tabellen_name';

$script_start_time = microtime(true);
$current_datetime = date("Y-m-d H:i:s");
$script_name = $_SERVER['SCRIPT_NAME'];

$qryTruncateTable = <<<SQL
TRUNCATE TABLE $table_name
SQL;
$db->query($qryTruncateTable) or die($current_datetime . " - FAILED - " . $script_name . " - Failed to truncate table an_cads_transactions");
echo ($current_datetime . " - SUCCESS - " . $script_name . " - Truncate table  $table_name  successfull \n");

//read the json file contents
$jsondata = file_get_contents('/path/to/response_example.json');


//convert json object to php associative array
$data = json_decode($jsondata, true);

foreach ($data as $row) {
  //get data details
  $order_number = $row['res']['no'];

  // replace empty fields with null
  if($order_number == '' OR !isset($order_number) ){$order_number = null;};

  //insert into mysql table
    $insert_data = <<<SQL
    INSERT INTO $table_name(
                  order_number
                )
                VALUES(
                  '$order_number'
                  )
    SQL;
    $result = mysqli_query($db, $insert_data);
}

if ($result == false) {
    echo "Error description: " . mysqli_error($db) . "\n";
} else {echo ($current_datetime . " - SUCCESS - " . $script_name . " - Data import successfull");}

mysqli_close($db);
$script_runtime = microtime(true) - $script_start_time;
$total_script_duration = "'(' . sprintf('%.5f', $script_runtime) . ' sec)'";

echo '(' . sprintf('%.5f', $script_runtime) . ' sec)' . "\n";
var_dump($data);
var_dump($order_number);
?>

Ausgabe von var_dump($data); (Habe nur die ersten paar Zeilen hier eingefügt)
PHP:
array(2) {
  ["success"]=>
  int(1)
  ["data"]=>
  array(1) {
    ["res"]=>
    array(64) {
      ["no"]=>
      int(123456)
      ["completed_date"]=>
      string(10) "2020-10-03"
      ["external_incoming_order_id"]=>
      string(12) "659874g23643"
      ["customstatus_id"]=>
      int(4)

...

Ausgabe von var_dump($row); (Habe nur die ersten paar Zeilen hier eingefügt)
PHP:
array(1) {
  ["res"]=>
  array(64) {
    ["no"]=>
    int(123456)
    ["completed_date"]=>
    string(10) "2020-10-03"
    ["external_incoming_order_id"]=>
    string(12) "659874g23643"
    ["customstatus_id"]=>
    int(4)

...

Ich habe gelesen, dass einige das PHP Error-Reporting umstellen, sodass PHP Notice einfach nur unterdrückt werden. Das scheint mir aber keine Lösung zu sein und möchte das nicht machen.

Was mache ich hier falsch, wie bekomme ich die PHP Notice weg?



2. Erweiterung des Download-Scripts

Bei meinem zweiten Anliegen geht es darum, die Request-URL anhand einer order_number liste durhzuiterieren und dann als ergebnis in eine .json Datei zu speichern. Der Endpunkt lässt leider keinen Call zu, der mir alle orders auf einmal zurückgibt, daher ein Call pro order_number notwendig.

Die order_ids habe ich in einer MariaDB Tabelle vorliegen.

SQL:
SELECT
    order_id
FROM orders

569872
569873
569874
569875
569876
569877
569878
569879

Ich verwende diesen Code zum herunterladen, der für das Abfragen eine order id auch wunderbar funktioniert:
PHP:
<?php
$script_start_time = microtime(true);

// GET ACCESS TOKEN
include_once 'get_accesstoken.php';

/*** DOWNLOAD FILE FROM CURL ****/
//API KEY AND PASSWORD
$headers = array('Content-Type: application/json', 'Accept:application/json', 'Accesskey:' . $accesskey, 'Token:' . $accesstoken);

$current_datetime = date("Y-m-d H:i:s");
$script_name = $_SERVER['SCRIPT_NAME'];


//URL
$url='https://api-endpoint.de/api/v1/orders?order_id=123456';

//FILE NAME
$filename = 'orders.json';

//DOWNLOAD PATH
$path = '/path/to/download/directory/'.$filename;

//FOLDER PATH
$fp = fopen($path, 'w');

//SETTING UP CURL REQUEST
$ch = curl_init($url) or die($current_datetime . " - FAILED - ". $script_name . " - Couldnt reach endpoint \n");
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$data = curl_exec($ch);

//CONNECTION CLOSE
curl_close($ch);
fclose($fp);
echo $current_datetime . " - SUCCESS - ". $script_name . " - File download successfull \n";

$script_runtime = microtime(true) - $script_start_time;
$total_script_duration = "'(' . sprintf('%.5f', $script_runtime) . ' sec)'";

echo '(' . sprintf('%.5f', $script_runtime) . ' sec)' . "\n";

?>

Ich weiß leider nicht wie ich das Durchiterieren umsetzen soll, sodass dann am Schluss eine .json Datei mit allen Orders asu der orders tabelle als Ergebnis gespeichert wird.

Hoffe ihr könnt mir hierbei wieder helfen.


Beste Grüße,
canju
 
Zuletzt bearbeitet:
Zur 1. Frage: Beim Dekodieren deines JSON erhältst Du ein ass. Array mit zwei Einträgen:
1. Schlüssel "success" und Wert 1 (integer)
2. Schlüssel "data" mit einem ass. Array als Wert, wie von dir erwartet.
Beim Iterieren mit foreach bearbeitest Du auch den 1. Eintrag und dabei kommt der Fehler zu Stande, denn Du greifst mit diesem:
$row['res']['no']
nicht auf ein Array sondern auf einen integer zu.

Außerdem frage ich mich, warum Du bei dem ganzen Vorgang den Umweg über eine Datei nimmst. Du kannst das Ergebnis doch auch in eine Variable eintragen und sofort verarbeiten? Oder brauchst Du die JSON-Datei für andere Zwecke?
 
Hey Sempervivum,
danke für deine schnelle Antwort.

Das war ein sehr hilfreicher Tipp:
Beim Iterieren mit foreach bearbeitest Du auch den 1. Eintrag
Das hat mich zur Funktion array_slice() geführt, mit dem ich in der foreach schleife jetzt den "data" Schlüssel verarbeiten kann: foreach (array_slice($data, 1) as $row) {.
Ich bekomme jetzt keine PHP Notice mehr ausgegeben.
Einziger Nachteil wird denke ich sein, sobald der api anbieter einen weiteren schlüssel oberhalb von "data" einführt. Dann muss ich nur daran denken den Slice zu ändern.

Hierfür:
Außerdem frage ich mich, warum Du bei dem ganzen Vorgang den Umweg über eine Datei nimmst.
habe ich eigtl. 2 Gründe:
Es handelt sich hier um Daten, die ich permanent neu reinholen muss, da sich diese bis zu 6+ Monate rückwirkend ändern/aktualisieren.
  1. Ich sichere 1x täglich die json Datei auf einen gesonderten Server um jeweils den Stand zu diesem Zeitpunkt "dokumentiert" zu haben.
  2. Offen gesagt bin ich heil froh, dass ich das mit der json Datei soweit hinbekommen habe (hier hattest du mir ja auch schon einmal bei geholfen) und hab daher wieder dieses Verfahren gewählt.
 
Noch nicht, ich bin zwar ein Stück weitergekommen, aber hänge an einer Stelle fest.
Ich poste dir meinen aktuellen Stand zu Punkt 2 morgen. Vielleicht kannst du mir dann noch mal Unterstützung geben.

Grüße,
canju
 
Hey Sempervivum,

wie versprochen nun mein aktueller Stand.

Das Speichern der order_ids in ein Array aus der vorliegenden MariaDB Tabelle habe ich hinbekommen.
Die Request-URL mit dem entsprechenden GET-Parametern baue ich zusammen mit http_build_query() (Credits: PHP looping on cURL API call). Finde ich sehr gut, da ich später noch Endpunkte ansprechen muss in denen ich mehrere GET-Parameter übermitteln muss um eine valide Anfrage zu stellen. So kann ich die Request-URL recht komfortabel um weitere Parameter ergänzen, bin aber auch offen für Alternativen.

Ich hänge nun an diesen Stellen fest bei denen ich Unterstützung bräuchte:

1. Die Datei orders_all.json soll ja am Ende alle Datensätze enthalten. Aktuell wird pro order_id die gesamte response gespeichert inkl. den nerivgen escape Backslashes (JSON_UNESCAPED_SLASHES hat hier leider nicht geholfen), auch Umlaute werden codiert dargestellt. Das sieht aktuell so aus (ich habe die Response stark eingekürzt, da es sehr große Datensätze mit sehr vielen verschachtelten json Objekten sind):

Code:
[
    "{\"success\":1,\"data\":{\"res\":{\"no\":1234,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1545\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}",
    "{\"success\":1,\"data\":{\"res\":{\"no\":1235,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1546\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}",
    "{\"success\":1,\"data\":{\"res\":{\"no\":1236,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1547\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}",
    "{\"success\":1,\"data\":{\"res\":{\"no\":1237,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1548\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}",
    "{\"success\":1,\"data\":{\"res\":{\"no\":1238,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1549\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}",
    "{\"success\":1,\"data\":{\"res\":{\"no\":1239,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1544\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}"
]

Die Daten, die ich gesammelt in der finalen json haben möchte befinden sich in "data" -> "res"
Vorzugsweise, sollte das Endformat dann so aussehen orders_all.json:

JSON:
{
    "success": 1,
    "data":
    {
        "res":
        {
            "no": 123456,
            "completed_date": "2020-10-03",
            "external_incoming_order_id": "659874g23643",
            "customstatus_id": 4,
            "customstatus": null,
            "unifiedstatus_code": "TRANSMITTED",
            "country": "DE"
        }
    }
}

in dem alle Datensätze unter res gelistet werden


2. Sollte eine order_id nicht mehr im Endpunkt vorhanden sein, dann darf dies den vorgang nicht abbrechen, sondern soll fortfahren und die restlichen vorhandenen order_ids abarbeiten.

Hier "mein" Code den ich soweit habe:

PHP:
<?php

// Load the database configuration file
include_once 'db_config.php';

$headers = array('Content-Type: application/json', 'Accept:application/json', 'Accesskey:' . $accesskey, 'Token:' . $accesstoken);

$qrySelectOrderIds= <<<SQL
SELECT
  order_id
FROM orders
SQL;

 $get_order_ids = mysqli_query($db,$qrySelectOrderIds);

 $result = [];
 while ($array = mysqli_fetch_array($get_order_ids)) {
     $result[] = $array['no'];
 }

/* API url*/
$baseurl = 'https://api-endpoint/api/orders';

$orders = $result;
$data=array();

$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
//curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
//curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );

/* Assign parameter values here */
foreach( $orders as $order ) {
    $order_id = $order;

    /* $_GET Parameters to Send */
    $params = array(
        'order_id'               =>   $order_id
    );

    /* Update URL to container Query String of Paramaters */
    $url = $baseurl . '?' . http_build_query($params);

    curl_setopt( $ch, CURLOPT_URL, $url );
    $curl_response = curl_exec( $ch );

    $decoded = json_decode($curl_response, true);

    /* store all responses for later consumption */
    $data[]=$curl_response;
    /* for debug, show responses */
    //echo json_encode( $decoded, JSON_PRETTY_PRINT );
}

  curl_close($ch);

  if( !empty( $data ) ) print_r( $data );

mysqli_close($db);

//print_r($data)

// write array to file in JSON format:
file_put_contents('/destination/path/to/store/combined_response/orders_all.json', json_encode($data, JSON_PRETTY_PRINT))
  or die($current_datetime . " - FAILED - " . $script_name . " - File creation failed");

?>

Grüße,
canju
 
Als Nachtrag zu Punkt 2 "order id existiert im Endpunkt nicht mehr".
Ich habe gerade mal simuliert was passiert wenn eine order id nicht mehr im Endpunkt vorhanden ist.

success: 0 mit lehrem Data Objekt:

Code:
[
    "{\"success\":0,\"data\":[]}",
    "{\"success\":1,\"data\":{\"res\":{\"no\":1234,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1545\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}",
    "{\"success\":1,\"data\":{\"res\":{\"no\":1235,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1546\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}",
    "{\"success\":1,\"data\":{\"res\":{\"no\":1236,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1547\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}",
    "{\"success\":1,\"data\":{\"res\":{\"no\":1237,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1548\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}",
    "{\"success\":1,\"data\":{\"res\":{\"no\":1238,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1549\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}",
    "{\"success\":1,\"data\":{\"res\":{\"no\":1239,\"completed_date\":\"2020-09-03\",\"external_incoming_order_id\":\"1544\",\"customstatus_id\":3,\"customstatus\":{\"name\":\"\\u00fcbermittelt\",\"code\":\"123\"},\"data_key\":\"res\"}}"
]
 
Ohne da ins Detail einzusteigen erst Mal eine grundsätzliche Empfehlung: Ich rate davon ab, zweigleisig zu fahren mit der Datenbank auf der einen und den JSON-Dateien auf der anderen Seite. Das ist immer eine potenzielle Fehlerquelle z. B. wenn das auseinander läuft. Mit einem Problem bist Du ja schon konfrontiert: Die Sache mit dem Escaping. Also besser alles in der Datenbank halten und dort bei Bedarf abfragen. Auch wenn Du es später im JSON-Format brauchen solltest, kannst Du es ebenfalls auslesen und entspr. kodieren.
 
Ok, verstehe und leuchtet ein.
Ich habe jetzt die Schleife umgebaut, sodass ich jeweils den entsprechenden Feldwert direkt aus der response in die DB schreibe. Das funzt auch soweit (kann es kaum glauben :))

PHP:
/* Assign parameter values here */
foreach( $orders as $order ) {
    $order_id = $order;

    /* $_GET Parameters to Send */
    $params = array(
        'order_id'               =>   $order_id
    );

    /* Update URL to container Query String of Paramaters */
    $url = $baseurl . '?' . http_build_query($params);

    curl_setopt( $ch, CURLOPT_URL, $url );
    $curl_response = curl_exec( $ch );

    // convert response into ass. array
    $decoded = json_decode($curl_response, true);

    if($decoded['success'] == 0) {
      continue;
    } else {
        //get desired fields
        $order_number = $decoded['data']['res']['no'];

        // replace empty fields with null
        if($order_number == '' OR !isset($order_number) ){$order_number = null;};
        //insert into mysql table
        $insert_data = <<<SQL
        INSERT INTO $table_name(
                      order_number
                    )
                    VALUES(
                      '$order_number'
                      )
        SQL;
        $result = mysqli_query($db, $insert_data);
      }
var_dump($order_number);
}

Den Fall einer fehlenden order_id im Endpunkt "überspringe" ich aktuell mit:
PHP:
    if($decoded['success'] == 0) {
      continue;

Ich würde mir aber gerne ausgeben lassen, welche order_id denn aus dem Endpunkt verschwunden ist und diese später in ein logfile schreiben wollen.
Hast du ein Tipp / Snippet, wie ich diese ausgeben lassen kann?
 
Status
Dieses Thema wurde gelöst! Zur Lösung gehen…
Zurück