mitmario.dev

Ein kleines Werkzeug

PHP Sandbox 3 Min Lesezeit 4 BeispieleLektion 6 von 7

Diese Lektion bringt fast nichts Neues. Sie bringt zusammen, was in acht Abschnitten dazugekommen ist, und beantwortet die Frage, die spätestens ab jetzt jedes Mal ansteht: In welcher Form schreibt man so ein Programm auf?

Argumente von der Kommandozeile

Was in argv steht
<?php

// Dieses Beispiel wird aufgerufen als:  php index.php preis abwaerts

var_dump($argv);

echo "Anzahl:    ", $argc, "\n";
echo "Skript:    ", $argv[0], "\n";
echo "Erstes:    ", $argv[1], "\n";
echo "Zweites:   ", $argv[2], "\n";

Wenn du ein Skript mit php index.php preis abwaerts startest, landen die Wörter dahinter in einer Variablen namens $argv. Die musst du nicht anlegen, PHP füllt sie von sich aus.

Zwei Dinge daran überraschen beim ersten Mal:

  • $argv[0] ist der Dateiname, nicht das erste Argument. Deine Angaben fangen bei $argv[1] an.
  • $argc zählt den Dateinamen mit. Ohne Argumente steht dort 1 und nicht 0.

Damit hast du eine zweite Möglichkeit, einem Programm etwas mitzugeben, neben den Werten, die du direkt in den Code schreibst. Später kommen die Werte aus einem Formular, aus einer Adresse und aus einer Datenbank; das Muster bleibt dasselbe.

Das Beispiel bekommt seine beiden Wörter vom Prüfknopf mit. Im Terminal gibst du sie selbst: Tipp php index.php preis abwaerts, dann php index.php eins zwei drei, und sieh zu, wie $argv und $argc mitwachsen.

Was passiert, wenn nichts dahintersteht

Ein fehlendes Argument ist kein Absturz
<?php

// Diesmal ohne Argumente aufgerufen.

echo "Anzahl: ", $argc, "\n";

echo "\nUngeprueft, und das gibt eine Warnung:\n";
echo "Feld: ", $argv[1], "\n";

echo "\nMit Vorgabe:\n";
$feld = $argv[1] ?? "artikel";
echo "Sortiert wird nach: ", $feld, "\n";

echo "\nOder mit einer klaren Ansage:\n";
if ($argc < 2) {
    echo "Aufruf: php index.php <spalte>\n";
}

Ein $argv[1], das es nicht gibt, ist genau der Fall aus Lektion 5.1: Undefined array key 1 auf dem Fehlerkanal und null als Wert. Im Beispiel siehst du das an der ersten Zeile, die nach Feld: schlicht aufhört. Kein Absturz, aber ein Programm, das mit null weiterarbeitet.

Das Fragezeichenpaar ?? löst das in einem Zeichenpaar, und du hast damit gleich eine Vorgabe. Wenn das Argument dagegen wirklich nötig ist, sag es lieber deutlich: eine Zeile mit dem richtigen Aufruf hilft dem Benutzer mehr als eine leere Ausgabe.

Drei Aufgaben, drei Funktionen

Einlesen, umformen, ausgeben
<?php

function lies(string $pfad): array
{
    $datei = fopen($pfad, "r");
    $kopf = fgetcsv($datei, separator: ";", escape: "");

    $zeilen = [];
    while (($felder = fgetcsv($datei, separator: ";", escape: "")) !== false) {
        $zeilen[] = array_combine($kopf, $felder);
    }

    fclose($datei);

    return $zeilen;
}

function teuerste(array $zeilen): array
{
    usort($zeilen, fn(array $a, array $b) => alsZahl($b["preis"]) <=> alsZahl($a["preis"]));

    return $zeilen;
}

function alsZahl(string $wert): float
{
    return (float) str_replace(",", ".", $wert);
}

function teuersteAlsText(array $zeilen): array
{
    usort($zeilen, fn(array $a, array $b) => $b["preis"] <=> $a["preis"]);

    return $zeilen;
}

function gibAus(array $zeilen): void
{
    foreach ($zeilen as $nummer => $zeile) {
        echo $nummer + 1, ". ", $zeile["artikel"], " (", $zeile["preis"], ")\n";
    }
}

echo "Nach Preis, ueber alsZahl verglichen:\n";
gibAus(teuerste(lies("preise.csv")));

echo "\nUnd derselbe Vergleich auf dem Text:\n";
gibAus(teuersteAlsText(lies("preise.csv")));

Das ist der Aufbau, den fast jedes kleine Werkzeug hat, und er ist kein Schönheitswunsch:

  • Einlesen holt die Daten von dort, wo sie liegen, und gibt sie als Array zurück. Sonst nichts.
  • Umformen rechnet, sortiert, filtert. Es bekommt ein Array und gibt eines zurück, und es weiß nichts davon, woher die Daten kamen.
  • Ausgeben macht aus dem Array Text. Es rechnet nichts mehr.

Der Gewinn ist derselbe wie in Lektion 6.5: Jedes Stück lässt sich einzeln ansehen. Willst du die Daten aus einer Datenbank statt aus einer CSV holen, tauschst du eine Funktion aus und der Rest bleibt stehen. Willst du statt Text ein <table> ausgeben wie in Lektion 8.4, tauschst du die andere.

Sieh dir im Beispiel alsZahl() an, und daneben denselben Vergleich ohne sie. Zwei Texte vergleicht PHP Zeichen für Zeichen, und dann steht 47,30 vor 2,50 vor 12,45: Die 4 kommt vor der 2 und die vor der 1, und was danach folgt, sieht sich niemand mehr an. Der Schreibtisch steht damit zufällig richtig, das Notizbuch aber auf Platz zwei. Dasselbe Problem wie bei den Dateinamen in Lektion 8.5.

Übrigens steht alsZahl() im Beispiel unter der Funktion, die sie benutzt, und das geht: PHP sammelt die Funktionen einer Datei ein, bevor die erste Zeile läuft. Bei Variablen wäre das anders.

Warum die Ausgabe zuletzt kommt

Erst rechnen, dann ausgeben
<?php

$eingaben = ["12", "8", "acht", "20"];

echo "Sofort ausgeben:\n";
try {
    foreach ($eingaben as $eingabe) {
        if (!is_numeric($eingabe)) {
            throw new InvalidArgumentException("keine Zahl: " . $eingabe);
        }
        echo "  ", $eingabe, "\n";
    }
} catch (InvalidArgumentException $fehler) {
    echo "  Abbruch: ", $fehler->getMessage(), "\n";
}

echo "\nErst sammeln, dann ausgeben:\n";
try {
    $fertig = [];
    foreach ($eingaben as $eingabe) {
        if (!is_numeric($eingabe)) {
            throw new InvalidArgumentException("keine Zahl: " . $eingabe);
        }
        $fertig[] = $eingabe;
    }
    echo "  ", implode("\n  ", $fertig), "\n";
} catch (InvalidArgumentException $fehler) {
    echo "  Nichts ausgegeben, Abbruch: ", $fehler->getMessage(), "\n";
}

Eine Schleife, die rechnet und dabei ausgibt, ist bequem geschrieben und unangenehm, sobald etwas schiefgeht: Die halbe Ausgabe steht dann schon da, und dahinter klebt eine Fehlermeldung.

Im Beispiel siehst du beide Varianten mit denselben Daten. Der erste Durchgang gibt zwei Zeilen aus und bricht dann ab. Der zweite sammelt erst alles ein und gibt gar nichts aus, weil er nie fertig wird.

Welcher davon richtig ist, hängt vom Fall ab. Bei einem Programm, das eine Antwort baut, ist der zweite fast immer richtig: Entweder es kommt eine ganze Antwort heraus oder eine Fehlermeldung, aber kein halbes Ergebnis. Ab Abschnitt 10 wird das noch wichtiger, denn dort ist die Ausgabe eine Seite, und eine halbe Seite mit einer Fehlermeldung darin ist genau das, was Lektion 7.5 verhindern wollte.

Zum Mitnehmen

Nichts Neues mehr, außer einer Kleinigkeit: wie ein Skript erfährt, was hinter seinem Namen auf der Kommandozeile stand. Der Rest dieser Lektion ist die Frage, wie man aus acht Abschnitten ein Programm baut, das man in einem halben Jahr noch versteht.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.