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

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:
Lösung
In etwa so, ungetestet:
Code:
    if ($decoded['success'] == 0) {
        echo "Keine Tarife zur Kombination vorhanden: $zipcode und $consumption \n";
        continue;
    } else {
        // loop through all results
        foreach ($decoded["data"]["result"] as $record) {
            //get desired fields
            $tariff_id = $record["tariff_id"];
            //Parameter übergeben
            mysqli_stmt_bind_param($stmt, "s",
                $tariff_id
            );
            //Ausführen
            mysqli_stmt_execute($stmt);
        }
    }
Habe mich teilweise geirrt. Das Datum muss doch als String im SQL-Datumsformat vorliegen. Aber ob ein String oder NULL kommt, ist egal
PHP: Vorbereitete Anweisungen (Prepared Statements) - Manual
PHP:
if($created_date == '' or !isset($created_date)) $created_date = null;
$insert_data = <<<SQL
        INSERT INTO {$destination_table_name}(created_date)
        VALUES(?)
        SQL;
//SQL Statement vorbereiten
$stmt = mysqli_prepare($db, $insert_data);
//Parameter übergeben
mysqli_stmt_bind_param($stmt, "s", $created_date);
//Ausführen
mysqli_stmt_execute($stmt);[/php]
 
Ok, danke dir.

das funzt soweit. Was ich auf den ersten Blick recht "nervig" finde und bei vielen Feldern unübersichtlich wird ist dass ich hier: mysqli_stmt_bind_param($stmt, "is", $order_id, $created_date); die typenangabe für jedes feld festlegen muss. Die hab ich ja eigtl. schon in der DB selbst festgelegt.

"issiisssi":
PHP:
mysqli_stmt_bind_param($stmt, "issiisssi", $feld1, $feld2, $feld3, $feld4, $feld5, $feld6, $feld7, $feld8, $feld9)

Ich habe 63 felder die ich importieren muss, gibts da irgendwie eine "übersichtlichere" Variante?
 
Nimm PDO anstelle von mysqli. Dann kannst du mit benannten Parametern arbeiten.
PHP: Prepared Statements und Stored Procedures - Manual
PHP: PDOStatement::execute - Manual

Musst halt auch die db-anbindung auf PDO umstellen. Ich finde PDO eh besser las mysqli
PHP:
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour');
$sth->bindValue(':calories', $calories, PDO::PARAM_INT);
$sth->bindValue(':colour', $colour, PDO::PARAM_STR);

//oder alles in einem. Geht auch ohne Typenzuweisung.
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour');
$sth->execute(array(':calories' => $calories, ':colour' => $colour));
 
Hallo nochmal ihr Lieben,

ich hänge wieder an einer Stelle fest.

Ausgangslage:
Den API-Endpunkt mit verschiedenen URL-Parametern aufrufen und alle Ergebnisse pro Request in eine MariaDB speichern.
Es handelt sich hier um Tarifdaten, die abhängig von Verbrauch und Postleitzahl abgefragt werden müssen. Verbrauch und PLZ hole ich mir dabei aus einer vorliegenden DB-Tabelle.
Jeder erfolgreiche Call liefert ~50+ Ergebnisse.

Das Zusammensetzen der Request-URL und das speichern in die Datenbank funktioniert bereits, allerdings wird immer nur das erste Ergebnis jedes Calls in die DB geschrieben.
Angenommen jeder Call liefert 50 Ergebnisse und ich habe 2 Verbrauchs- und Postleitzaheln Einträge vorliegen, so müsste ich eigtl. 100 Einträge in der DB erhalten.

Ich vermute ich habe wieder mal was in der Schleife versemmelt, sodass ich nur je 1 Eintrag erhalte?

Hier mein Code:
PHP:
<?php
$headers = array('Content-Type: application/json', 'Accesskey:' . $accesskey, 'Token:' . $accesstoken, 'charset=utf8mb4');

// destination table to import data
$destination_table_name = "table_name";

/* API url*/
$baseurl = 'https://api-provider.com/api/tariffs';

// querry param_values and put in array
$query_select ="SELECT zipcode, consumption FROM tariff_params";
$result_select = mysqli_query($db,$query_select);

$rows = [];
while($row = mysqli_fetch_array($result_select))
    $rows[] = $row;

$data=array();
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

/* Assign parameter values here */
foreach($rows as $row) {
    $zipcode              = $row['zipcode'];
    $consumption_primary  = $row['consumption'];

    /* $_GET Parameters to Send */
    $params = array(
        'zipcode'             =>   $zipcode,
        'consumption_primary' =>   $consumption
    );

    /* Update URL to container Query String of Paramaters */
    $url = urldecode($baseurl . '?' . http_build_query($params));
    echo($url);
    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) {
      echo "Keine Tarife zur Kombination vorhanden: $zipcode und $consumption \n";
      continue;
    } else {
          //get desired fields
          $tariff_id = $decoded["data"]["result"][0]["tariff_id"];

          //insert into mysql table
          $insert_data = <<<SQL
                INSERT INTO {$destination_table_name} (
                  tariff_id
                )
                  VALUES (?)
                SQL;
          //SQL Statement vorbereiten
          $stmt = mysqli_prepare($db, $insert_data);
          //Parameter übergeben
          mysqli_stmt_bind_param($stmt, "s",
                                $tariff_id
                                );
          //Ausführen
          mysqli_stmt_execute($stmt);
      }

}

curl_close($ch);
mysqli_close($db);

?>


var_dump($decoded); (hier stark eingekürzt) gibt mir auf jeden Fall alle Ergebnisse aus
Code:
array(2) {
  ["success"]=> int(1)
  ["data"]=> array(1) {
    ["result"]=> array(124) {
      [0]=> array(178) {
        ["tariff_id"]=> int(722571494)
Ich finde den Fehler nicht. Könnt ihr mir nochmal helfen bitte.


Grüße,
canju
 
Nimm PDO anstelle von mysqli. Dann kannst du mit benannten Parametern arbeiten.
PHP: Prepared Statements und Stored Procedures - Manual
PHP: PDOStatement::execute - Manual

Musst halt auch die db-anbindung auf PDO umstellen. Ich finde PDO eh besser las mysqli
PHP:
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour');
$sth->bindValue(':calories', $calories, PDO::PARAM_INT);
$sth->bindValue(':colour', $colour, PDO::PARAM_STR);

//oder alles in einem. Geht auch ohne Typenzuweisung.
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour');
$sth->execute(array(':calories' => $calories, ':colour' => $colour));
Danke dir, werde ich ggf. in einem der nächsten Projekte einsetzen, muss mich da erst reinarbeiten. Bin froh, dass ich erstmal halbwegs mit mysqli "klar komme". Aber man lernt nie aus.
 
Beim Durchlesen kann ich nicht erkennen, dass Du etwas in der Schleife versemmelt hättest. Um das weiter zu untersuchen, schlage ich vor, auch mal im Erfolgszweig ein echo einzubauen, damit Du siehst, was passiert:
Code:
    if($decoded['success'] == 0) {
      echo "Keine Tarife zur Kombination vorhanden: $zipcode und $consumption \n";
      continue;
    } else {
          echo "Daten erfolgreich geladen";
          //get desired fields
          $tariff_id = $decoded["data"]["result"][0]["tariff_id"];
 
Scheint zu funktionieren:
Code:
https://api-provider.com/api/tariffs?zipcode=08297&consumption=2500
Daten erfolgreich geladen
https://api-provider.com/api/tariffs?zipcode=18556&consumption=4000
Daten erfolgreich geladen
Die URL lasse ich mir weiter oben in der Schleife echo'n
 
Selber nicht so der PHP- und Datenbankexperte finde ich da dieses von jemand, der das gleiche Problem hatte wie Du:
PHP Prepare method not working when calling it twice?
Ich empfehle, das prepare nur einmal vor der Schleife zu machen und innerhalb der Schleife dann nur das bind_param und execute. Wenn ich das richtig verstehe, wäre das genau das was prepared statements vorsehen: Die Abfrage mit prepare einmal vorbereiten, dann kann sie mit unterschiedlichen Parametern mehrfach ausgeführt werden.
 
Hmm, bleibt dabei. weiterhin nur jeweils ein Eintrag des ersten Ergebnisses.
So meintest du oder?:
PHP:
<?php
$headers = array('Content-Type: application/json', 'Accesskey:' . $accesskey, 'Token:' . $accesstoken, 'charset=utf8mb4');

// destination table to import data
$destination_table_name = "table_name";

/* API url*/
$baseurl = 'https://api-provider.com/api/tariffs';

// querry param_values and put in array
$query_select ="SELECT zipcode, consumption FROM tariff_params";
$result_select = mysqli_query($db,$query_select);

$rows = [];
while($row = mysqli_fetch_array($result_select))
    $rows[] = $row;

$data=array();
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

          //insert into mysql table
          $insert_data = <<<SQL
                INSERT INTO {$destination_table_name} (
                  tariff_id
                )
                  VALUES (?)
                SQL;
          //SQL Statement vorbereiten
          $stmt = mysqli_prepare($db, $insert_data);
          //Parameter übergeben
          mysqli_stmt_bind_param($stmt, "s",
                                $tariff_id
                                );


/* Assign parameter values here */
foreach($rows as $row) {
    $zipcode              = $row['zipcode'];
    $consumption_primary  = $row['consumption'];

    /* $_GET Parameters to Send */
    $params = array(
        'zipcode'             =>   $zipcode,
        'consumption_primary' =>   $consumption
    );

    /* Update URL to container Query String of Paramaters */
    $url = urldecode($baseurl . '?' . http_build_query($params));
    echo($url);
    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) {
      echo "Keine Tarife zur Kombination vorhanden: $zipcode und $consumption \n";
      continue;
    } else {
          //get desired fields
          $tariff_id = $decoded["data"]["result"][0]["tariff_id"];

          //Ausführen
          mysqli_stmt_execute($stmt);
      }

}

curl_close($ch);
mysqli_close($db);

?>


Hier nochmal der var_dump($decoded); (stark eingekürzt)

Code:
https://api-provider.com/api/tariffs?zipcode=08297&consumption=2500
Daten erfolgreich geladen
array(2) {
  ["success"]=>  int(1)
  ["data"]=>  array(1) {
    ["result"]=> array(121) {
      [0]=> array(178) {
        ["tariff_id"]=> int(452171494)
      [1]=> array(178) {
        ["tariff_id"]=> int(654149933)
      [2]=>  array(176) {
        ["tariff_id"]=> int(214286138)
      [3]=> array(176) {
        ["tariff_id"]=> int(571337882)
      ...   

https://api-provider.com/api/tariffs?zipcode=18556&consumption=4000
Daten erfolgreich geladen

array(2) {
  ["success"]=> int(1)
  ["data"]=> array(1) {
    ["result"]=> array(114) {
      [0]=>  array(176) {
        ["tariff_id"]=> int(831499737)
      [1]=>  array(176) {
        ["tariff_id"]=> int(647263587)
      [2]=> array(176) {
        ["tariff_id"]=> int(124773076)
      [3]=> array(176) {
        ["tariff_id"]=> int(674553389)
      ...
 
Zurück