Abschnitt 8 · Lektion 5
Verzeichnisse
Bis hierhin hast du Dateien beim Namen genannt. Sobald ein Ordner Uploads enthält, Protokolle oder Bilder, geht das nicht mehr: Du weißt vorher nicht, was drinliegt, und musst fragen.
Eine Zeile in den Beispielen ist neu, und sie gehört nicht zum Thema: require "vorbereiten.php"
ganz oben. require holt eine andere PHP-Datei herein und führt sie aus, als stünde ihr Inhalt an
genau dieser Stelle. In vorbereiten.php legt der Kurs den Ordner daten/ samt Inhalt an, damit es
überhaupt etwas aufzulisten gibt; ohne die Zeile stünde derselbe Code in jedem Beispiel noch einmal.
Ausführlich kommt require in Lektion 10.6 dran, zusammen mit seinem leiseren Bruder include.
scandir gibt dir alles, wirklich alles
<?php
// Legt den Ordner daten/ mit ein paar Dateien an, damit es
// etwas aufzulisten gibt. Aendern musst du hier nichts.
$inhalte = [
"daten/anleitung.md" => "## So geht es",
"daten/einstellungen.json" => '{"sprache":"de"}',
"daten/liesmich.md" => "# Ordner daten",
"daten/liste.txt" => "Eins\nZwei\nDrei",
"daten/notizen.txt" => "Milch, Brot, Kaffee",
"daten/protokoll.txt" => "Start",
"daten/archiv/alt.txt" => "Alter Kram",
];
foreach ($inhalte as $pfad => $inhalt) {
if (!is_dir(dirname($pfad))) {
mkdir(dirname($pfad), recursive: true);
}
file_put_contents($pfad, $inhalt);
} <?php
require "vorbereiten.php";
var_dump(scandir("daten"));
echo "Eintraege insgesamt: ", count(scandir("daten")), "\n"; scandir() liefert eine Liste der Namen in einem Verzeichnis, alphabetisch sortiert. Nur Namen, keine
Pfade.
Und die ersten beiden Einträge sind immer . und ... Das sind keine Dateien, sondern das
Verzeichnis selbst und das darüber. Jedes Betriebssystem führt sie, und scandir verschweigt sie
nicht. Wer sie nicht aussortiert, versucht früher oder später, sie als Datei zu lesen. Die Shell
kennt die beiden auch: ls -a daten im Terminal zeigt sie an erster Stelle, ls daten lässt sie
weg, und genau diese Auswahl triffst du in PHP selbst.
Zwei Wege, sie loszuwerden. Der eine ist ein continue ganz oben in der Schleife, wie in Lektion
4.6. Der andere ist array_diff($liste, [".", ".."]): Die Funktion gibt dir die erste Liste ohne
alles zurück, was in der zweiten steht. Sie behält dabei die Schlüssel, deshalb steht in den
Beispielen array_values() daneben.
glob nimmt ein Muster
<?php
// Legt den Ordner daten/ mit ein paar Dateien an, damit es
// etwas aufzulisten gibt. Aendern musst du hier nichts.
$inhalte = [
"daten/anleitung.md" => "## So geht es",
"daten/einstellungen.json" => '{"sprache":"de"}',
"daten/liesmich.md" => "# Ordner daten",
"daten/liste.txt" => "Eins\nZwei\nDrei",
"daten/notizen.txt" => "Milch, Brot, Kaffee",
"daten/protokoll.txt" => "Start",
"daten/archiv/alt.txt" => "Alter Kram",
];
foreach ($inhalte as $pfad => $inhalt) {
if (!is_dir(dirname($pfad))) {
mkdir(dirname($pfad), recursive: true);
}
file_put_contents($pfad, $inhalt);
} <?php
require "vorbereiten.php";
echo "Nur die Textdateien:\n";
var_dump(glob("daten/*.txt"));
echo "\nAlles, was drinliegt:\n";
var_dump(glob("daten/*")); glob() beantwortet eine andere Frage: nicht „was liegt da”, sondern „was passt auf dieses Muster”.
Der Stern steht für beliebig viele Zeichen, das Fragezeichen für genau eines.
Drei Unterschiede zu scandir, und alle drei zählen im Alltag:
globgibt Pfade zurück, nicht bloße Namen.daten/liste.txtstattliste.txt. Das ist meist praktischer, denn damit kannst du sofort weiterarbeiten..und..sind nicht dabei. Das Muster passt nicht auf sie.- Ein Muster wie
*.txtsortiert Verzeichnisse gleich mit aus, jedenfalls solange keines auf.txtendet. Ohne Muster, also mitdaten/*, sind sie wieder dabei.
Das Muster ist übrigens dasselbe wie in der Shell: ls daten/*.txt im Terminal zeigt dieselben drei
Pfade, nur löst dort die Shell den Stern auf, bevor ls überhaupt läuft.
Ordner aussieben
<?php
// Legt den Ordner daten/ mit ein paar Dateien an, damit es
// etwas aufzulisten gibt. Aendern musst du hier nichts.
$inhalte = [
"daten/anleitung.md" => "## So geht es",
"daten/einstellungen.json" => '{"sprache":"de"}',
"daten/liesmich.md" => "# Ordner daten",
"daten/liste.txt" => "Eins\nZwei\nDrei",
"daten/notizen.txt" => "Milch, Brot, Kaffee",
"daten/protokoll.txt" => "Start",
"daten/archiv/alt.txt" => "Alter Kram",
];
foreach ($inhalte as $pfad => $inhalt) {
if (!is_dir(dirname($pfad))) {
mkdir(dirname($pfad), recursive: true);
}
file_put_contents($pfad, $inhalt);
} <?php
require "vorbereiten.php";
$dateien = 0;
$ordner = 0;
foreach (scandir("daten") as $eintrag) {
if ($eintrag === "." || $eintrag === "..") {
continue;
}
if (is_dir("daten/" . $eintrag)) {
$ordner++;
echo "Ordner: ", $eintrag, "\n";
continue;
}
$dateien++;
echo "Datei: ", $eintrag, "\n";
}
echo "\n", $dateien, " Dateien und ", $ordner, " Ordner\n"; Ob ein Eintrag ein Verzeichnis ist, beantwortet is_dir(). Wichtig dabei: Die Funktion braucht den
vollen Pfad, nicht den Namen aus scandir. is_dir("archiv") sucht im Arbeitsverzeichnis und
findet nichts, is_dir("daten/archiv") findet den Ordner.
Das ist der häufigste Fehler in dieser Lektion, und er ist besonders unangenehm, weil is_dir
einfach false sagt. Kein Fehler, keine Warning. Dein Programm behandelt den Ordner dann als Datei.
Wenn du beim Durchgehen mitzählst, hast du am Ende eine Zuordnung von Endung auf Anzahl, und die
willst du meistens nach der Anzahl sortiert ausgeben. Dafür gibt es arsort(), das Gegenstück zu
asort() aus Lektion 5.5: nach Wert sortiert, absteigend, und die Schlüssel bleiben dran. Genau das
brauchst du in der Challenge.
Zusammengesetzt wird ein Pfad mit dem normalen Punkt aus Lektion 3.2:
$ordner . "/" . $eintrag. Der Schrägstrich funktioniert auch unter Windows, ein Backslash
umgekehrt nicht überall.
Auf welche Reihenfolge du dich verlassen kannst
<?php
@mkdir("bilder");
foreach (["bild1.png", "bild2.png", "bild10.png", "bild20.png"] as $name) {
file_put_contents("bilder/" . $name, "x");
}
$namen = array_values(array_diff(scandir("bilder"), [".", ".."]));
echo "So sortiert scandir:\n";
echo implode(", ", $namen), "\n";
echo "\nRueckwaerts geht auch:\n";
$rueckwaerts = array_diff(scandir("bilder", SCANDIR_SORT_DESCENDING), [".", ".."]);
echo implode(", ", $rueckwaerts), "\n";
echo "\nUnd so, wie ein Mensch es erwartet:\n";
natsort($namen);
echo implode(", ", $namen), "\n"; scandir sortiert von sich aus alphabetisch, und mit SCANDIR_SORT_DESCENDING als zweitem Argument
andersherum. glob sortiert ebenfalls. Insofern ist die Reihenfolge verlässlich.
Nur ist alphabetisch nicht das, was ein Mensch erwartet: bild10.png steht vor bild2.png, weil die
1 vor der 2 kommt und danach nicht mehr weitergezählt wird. Sobald Zahlen in Dateinamen stecken,
brauchst du natsort(), das eine Ziffernfolge als Zahl liest. Die Shell hat dieselben zwei
Sortierungen: ls bilder im Terminal ordnet wie scandir, ls -v bilder wie natsort.
Namen zerlegen
<?php
$pfad = "daten/archiv/alt.txt";
echo "basename: ", basename($pfad), "\n";
echo "dirname: ", dirname($pfad), "\n";
echo "ohne Endung: ", basename($pfad, ".txt"), "\n\n";
var_dump(pathinfo($pfad));
echo "\nNur die Endung: ", pathinfo("archiv.tar.gz", PATHINFO_EXTENSION), "\n";
echo "Und bei einer Datei ganz ohne Punkt:\n";
var_dump(pathinfo("liesmich", PATHINFO_EXTENSION)); Drei Funktionen, die dir das Zerschneiden von Pfaden abnehmen:
basename()gibt den Dateinamen ohne Verzeichnis. Mit einem zweiten Argument schneidet es zusätzlich die Endung ab.dirname()gibt das Verzeichnis ohne Dateinamen. Genau das benutztvorbereiten.phpin den Beispielen, um den Ordner anzulegen.pathinfo()gibt alles auf einmal als Zuordnung:dirname,basename,extension,filename. Mit einer Konstante als zweitem Argument bekommst du nur ein Feld.
Zwei Dinge zur Endung, die man einmal gesehen haben muss: Bei archiv.tar.gz ist die Endung gz und
nicht tar.gz, denn pathinfo schneidet am letzten Punkt. Und bei einer Datei ganz ohne Punkt
bekommst du eine leere Zeichenkette zurück, keinen Fehler.
Und noch etwas Wichtiges: Eine Endung ist nur ein Teil des Namens. Sie sagt nichts darüber aus, was wirklich in der Datei steht. Wer sich beim Hochladen darauf verlässt, baut sich eine Lücke, und genau darum geht es in Abschnitt 15.
Zum Mitnehmen
Bisher kanntest du den Namen jeder Datei, die du gelesen hast. Ab jetzt fragst du das Verzeichnis, was überhaupt drinliegt, und musst dabei zwei Einträge aussortieren, die immer dabei sind.
Jetzt du
Basis Konto, kostenlosZu 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 5 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.