Lekcja 13: Praca z Plikami i Katalogami w PHP
Witaj w trzynastej lekcji kursu PHP! Po opanowaniu obsługi błędów i wyjątków, przechodzimy do bardzo praktycznego i często wykorzystywanego aspektu programowania: interakcji z systemem plików. Aplikacje internetowe często muszą odczytywać dane z plików (np. pliki konfiguracyjne, szablony, dane CSV), zapisywać informacje (np. logi, dane użytkowników, cache), tworzyć, usuwać lub modyfikować pliki i katalogi. PHP dostarcza bogaty zestaw wbudowanych funkcji do wykonywania tych operacji. W tej lekcji nauczymy się, jak efektywnie pracować z plikami i katalogami.
Podstawowe Informacje o Plikach i Katalogach
Zanim przejdziemy do funkcji PHP, przypomnijmy kilka podstawowych pojęć:
- Plik (File): Nazwany zbiór danych przechowywany na nośniku pamięci (np. dysku twardym). Pliki mogą zawierać tekst, obrazy, kod programu, itp.
- Katalog (Directory / Folder): Specjalny typ pliku, który może zawierać inne pliki oraz katalogi, tworząc hierarchiczną strukturę systemu plików.
- Ścieżka (Path): Ciąg znaków określający lokalizację pliku lub katalogu w systemie plików. Może być:
- Absolutna (Absolute Path): Pełna ścieżka od katalogu głównego systemu plików (np.
/var/www/html/plik.txt
w Linuksie,C:\xampp\htdocs\plik.txt
w Windows). - Względna (Relative Path): Ścieżka określona względem bieżącego katalogu roboczego skryptu PHP (np.
dane/plik.txt
,../obrazy/logo.png
).
- Absolutna (Absolute Path): Pełna ścieżka od katalogu głównego systemu plików (np.
- Uprawnienia (Permissions): Określają, kto i jakie operacje może wykonywać na pliku lub katalogu (odczyt, zapis, wykonanie). W systemach uniksowych są to typowo uprawnienia dla właściciela, grupy i innych.
Sprawdzanie Istnienia i Typów Plików/Katalogów
PHP oferuje wiele funkcji do sprawdzania statusu plików i katalogów:
file_exists(string $sciezka): bool
: Sprawdza, czy plik lub katalog o podanej ścieżce istnieje. Zwracatrue
, jeśli istnieje,false
w przeciwnym razie.is_file(string $sciezka): bool
: Sprawdza, czy podana ścieżka wskazuje na istniejący, zwykły plik.is_dir(string $sciezka): bool
: Sprawdza, czy podana ścieżka wskazuje na istniejący katalog.is_readable(string $sciezka): bool
: Sprawdza, czy plik lub katalog istnieje i jest odczytywalny dla bieżącego użytkownika PHP.is_writable(string $sciezka): bool
(lubis_writeable
- alias): Sprawdza, czy plik lub katalog istnieje i jest zapisywalny dla bieżącego użytkownika PHP.is_executable(string $sciezka): bool
: Sprawdza, czy plik istnieje i jest wykonywalny.filesize(string $sciezka): int|false
: Zwraca rozmiar pliku w bajtach lubfalse
w przypadku błędu (np. plik nie istnieje). Wyniki tej funkcji są cachowane, użyjclearstatcache()
, jeśli potrzebujesz aktualnych danych po modyfikacji pliku.filetype(string $sciezka): string|false
: Zwraca typ pliku. Możliwe wartości to np. "file", "dir", "link", "fifo", "char", "block", "unknown".filemtime(string $sciezka): int|false
: Zwraca czas ostatniej modyfikacji pliku jako znacznik czasu Unix (timestamp) lubfalse
w przypadku błędu.fileatime(string $sciezka): int|false
: Zwraca czas ostatniego dostępu do pliku.filectime(string $sciezka): int|false
: Zwraca czas ostatniej zmiany i-węzła pliku (zmiana metadanych, np. uprawnień).
<?php
$plikTestowy = "moj_plik.txt";
$katalogTestowy = "moj_katalog";
// Utworzenie pliku i katalogu do testów (jeśli nie istnieją)
if (!file_exists($plikTestowy)) {
file_put_contents($plikTestowy, "To jest testowy plik.\nLinia druga.");
}
if (!is_dir($katalogTestowy)) {
mkdir($katalogTestowy);
}
echo "<h3>Informacje o: $plikTestowy</h3>";
if (file_exists($plikTestowy)) {
echo "Plik istnieje.<br>";
if (is_file($plikTestowy)) {
echo "Jest to zwykły plik.<br>";
}
echo "Rozmiar pliku: " . filesize($plikTestowy) . " bajtów.<br>";
echo "Typ pliku: " . filetype($plikTestowy) . "<br>";
echo "Ostatnia modyfikacja: " . date("Y-m-d H:i:s", filemtime($plikTestowy)) . "<br>";
echo "Czy odczytywalny? " . (is_readable($plikTestowy) ? "Tak" : "Nie") . "<br>";
echo "Czy zapisywalny? " . (is_writable($plikTestowy) ? "Tak" : "Nie") . "<br>";
} else {
echo "Plik nie istnieje.<br>";
}
echo "<h3>Informacje o: $katalogTestowy</h3>";
if (file_exists($katalogTestowy)) {
echo "Katalog istnieje.<br>";
if (is_dir($katalogTestowy)) {
echo "Jest to katalog.<br>";
}
echo "Typ: " . filetype($katalogTestowy) . "<br>";
} else {
echo "Katalog nie istnieje.<br>";
}
// Czyszczenie po testach (opcjonalne)
// unlink($plikTestowy);
// rmdir($katalogTestowy);
?>
Odczyt Zawartości Plików
PHP oferuje kilka sposobów na odczytanie zawartości plików tekstowych.
1. file_get_contents(string $sciezka, bool $use_include_path = false, ?resource $context = null, int $offset = 0, ?int $maxlen = null): string|false
Jest to najprostszy i często preferowany sposób na wczytanie całej zawartości pliku do stringa. Funkcja ta jest bardzo wydajna dla mniejszych plików.
<?php
$plikDoOdczytu = "moj_plik.txt";
if (!file_exists($plikDoOdczytu)) file_put_contents($plikDoOdczytu, "Pierwsza linia pliku.\nDruga linia pliku.");
$zawartosc = file_get_contents($plikDoOdczytu);
if ($zawartosc !== false) {
echo "<h3>Zawartość pliku ($plikDoOdczytu) odczytana przez file_get_contents:</h3>";
echo "<pre>" . htmlspecialchars($zawartosc) . "</pre>";
} else {
echo "Nie udało się odczytać pliku $plikDoOdczytu.<br>";
}
// Odczyt fragmentu pliku (od PHP 5.1)
// $fragment = file_get_contents($plikDoOdczytu, false, null, 10, 5); // Od 10. bajtu, maksymalnie 5 bajtów
// if ($fragment !== false) {
// echo "Fragment pliku: " . htmlspecialchars($fragment) . "<br>";
// }
?>
2. file(string $sciezka, int $flags = 0, ?resource $context = null): array|false
Funkcja file()
wczytuje cały plik do tablicy, gdzie każdy element tablicy odpowiada jednej linii z pliku (wraz ze znakiem nowej linii na końcu). Jest to przydatne, gdy chcemy przetwarzać plik linia po linii.
Flagi (opcjonalne):
FILE_USE_INCLUDE_PATH
: Szuka pliku również w ścieżkach zinclude_path
.FILE_IGNORE_NEW_LINES
: Pomija znaki nowej linii na końcu każdej linii w tablicy.FILE_SKIP_EMPTY_LINES
: Pomija puste linie.
<?php
$plikDoOdczytuLiniami = "moj_plik.txt";
$linie = file($plikDoOdczytuLiniami);
if ($linie !== false) {
echo "<h3>Zawartość pliku ($plikDoOdczytuLiniami) odczytana przez file() jako tablica linii:</h3>";
echo "<pre>";
print_r(array_map('htmlspecialchars', $linie));
echo "</pre>";
echo "<h4>Przetwarzanie linii:</h4>";
foreach ($linie as $numerLinii => $linia) {
echo "Linia " . ($numerLinii + 1) . ": " . htmlspecialchars(trim($linia)) . "<br>";
}
} else {
echo "Nie udało się odczytać pliku $plikDoOdczytuLiniami.<br>";
}
// Z flagami
$linieBezEnterow = file($plikDoOdczytuLiniami, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
// echo "<h3>Linie bez enterów i pustych:</h3><pre>"; print_r(array_map('htmlspecialchars', $linieBezEnterow)); echo "</pre>";
?>
3. Niskopoziomowe operacje na plikach (fopen
, fread
, fgets
, fclose
)
Dla większej kontroli nad procesem odczytu (np. przy pracy z bardzo dużymi plikami, które nie mieszczą się w pamięci, lub przy odczycie binarnym) można używać funkcji niskopoziomowych:
fopen(string $nazwa_pliku, string $tryb, bool $use_include_path = false, ?resource $context = null): resource|false
: Otwiera plik lub URL. Zwraca wskaźnik do pliku (zasób) lubfalse
w przypadku błędu.$tryb
określa sposób otwarcia pliku (np. "r" - odczyt, "w" - zapis, "a" - dopisywanie).fread(resource $uchwyt, int $dlugosc): string|false
: Odczytuje do$dlugosc
bajtów z pliku wskazywanego przez$uchwyt
.fgets(resource $uchwyt, ?int $dlugosc = null): string|false
: Odczytuje jedną linię z pliku (do znaku nowej linii, końca pliku lub$dlugosc - 1
bajtów).fgetc(resource $uchwyt): string|false
: Odczytuje pojedynczy znak z pliku.feof(resource $uchwyt): bool
: Sprawdza, czy osiągnięto koniec pliku (End Of File).fclose(resource $uchwyt): bool
: Zamyka otwarty plik. Zawsze należy zamykać pliki po zakończeniu pracy z nimi, aby zwolnić zasoby.
<?php
$plikDoNiskopoziomowegoOdczytu = "moj_plik.txt";
$uchwyt = fopen($plikDoNiskopoziomowegoOdczytu, "r"); // Otwarcie do odczytu ("r")
if ($uchwyt) {
echo "<h3>Odczyt pliku ($plikDoNiskopoziomowegoOdczytu) za pomocą fgets():</h3>";
while (($linia = fgets($uchwyt)) !== false) {
echo htmlspecialchars($linia) . "<br>";
}
// Po odczytaniu do końca, trzeba przewinąć wskaźnik, aby czytać ponownie
rewind($uchwyt); // Przewija wskaźnik na początek pliku
echo "<h3>Odczyt pliku ($plikDoNiskopoziomowegoOdczytu) za pomocą fread():</h3>";
$calaZawartosc = fread($uchwyt, filesize($plikDoNiskopoziomowegoOdczytu));
echo "<pre>" . htmlspecialchars($calaZawartosc) . "</pre>";
if (!fclose($uchwyt)) {
echo "Nie udało się zamknąć pliku!<br>";
}
} else {
echo "Nie udało się otworzyć pliku $plikDoNiskopoziomowegoOdczytu do odczytu.<br>";
}
?>
Tryby otwarcia pliku (najważniejsze):
r
: Tylko do odczytu. Wskaźnik na początku pliku.r+
: Do odczytu i zapisu. Wskaźnik na początku pliku.w
: Tylko do zapisu. Otwiera i czyści zawartość pliku lub tworzy nowy plik, jeśli nie istnieje. Wskaźnik na początku pliku.w+
: Do odczytu i zapisu. Otwiera i czyści zawartość pliku lub tworzy nowy plik. Wskaźnik na początku pliku.a
: Tylko do dopisywania. Otwiera plik i ustawia wskaźnik na końcu. Jeśli plik nie istnieje, próbuje go utworzyć.a+
: Do odczytu i dopisywania. Otwiera plik i ustawia wskaźnik na końcu. Jeśli plik nie istnieje, próbuje go utworzyć.x
: Tylko do zapisu, tworzy nowy plik. Zwracafalse
i generujeE_WARNING
, jeśli plik już istnieje.x+
: Do odczytu i zapisu, tworzy nowy plik. Zwracafalse
i generujeE_WARNING
, jeśli plik już istnieje.- Dodanie
b
do trybu (np.rb
,wb
) oznacza tryb binarny (ważne w Windows).
Zapisywanie do Plików
1. file_put_contents(string $sciezka, mixed $dane, int $flags = 0, ?resource $context = null): int|false
Jest to najprostszy sposób na zapisanie danych (stringa, tablicy lub zasobu strumienia) do pliku. Domyślnie nadpisuje plik, jeśli istnieje, lub tworzy nowy.
Flagi (opcjonalne):
FILE_APPEND
: Dopisz dane na końcu pliku zamiast nadpisywać.LOCK_EX
: Uzyskaj wyłączną blokadę na pliku podczas zapisu (zapobiega jednoczesnemu zapisowi przez inne procesy).FILE_USE_INCLUDE_PATH
: Szuka pliku również w ścieżkach zinclude_path
.
<?php
$plikDoZapisu = "nowy_plik.txt";
$daneDoZapisu = "To są dane do zapisania w pliku.\nKolejna linia danych.";
// Zapis (nadpisanie lub utworzenie)
if (file_put_contents($plikDoZapisu, $daneDoZapisu) !== false) {
echo "Dane zostały pomyślnie zapisane do pliku $plikDoZapisu.<br>";
echo "<pre>" . htmlspecialchars(file_get_contents($plikDoZapisu)) . "</pre>";
} else {
echo "Nie udało się zapisać danych do pliku $plikDoZapisu.<br>";
}
// Dopisywanie do pliku
$daneDoDopisania = "To jest dopisana linia.\n";
if (file_put_contents($plikDoZapisu, $daneDoDopisania, FILE_APPEND | LOCK_EX) !== false) {
echo "Dane zostały pomyślnie dopisane do pliku $plikDoZapisu.<br>";
echo "<pre>" . htmlspecialchars(file_get_contents($plikDoZapisu)) . "</pre>";
} else {
echo "Nie udało się dopisać danych do pliku $plikDoZapisu.<br>";
}
// Zapis tablicy (każdy element jako nowa linia)
// $tablicaDanych = ["Linia 1", "Linia 2", "Linia 3"];
// file_put_contents("plik_z_tablicy.txt", implode("\n", $tablicaDanych));
?>
2. Niskopoziomowe operacje zapisu (fopen
, fwrite
, fputs
, fclose
)
Podobnie jak przy odczycie, można używać funkcji niskopoziomowych do zapisu, co daje większą kontrolę.
fwrite(resource $uchwyt, string $string, ?int $dlugosc = null): int|false
(fputs
jest aliasem): Zapisuje zawartość$string
do pliku wskazywanego przez$uchwyt
. Opcjonalnie można podać maksymalną długość zapisu. Zwraca liczbę zapisanych bajtów lubfalse
.
<?php
$plikDoNiskopoziomowegoZapisu = "zapis_niskopoziomowy.txt";
$dane1 = "Pierwsza porcja danych do zapisu.\n";
$dane2 = "Druga porcja danych.\n";
// Otwarcie do zapisu (tryb "w" - nadpisuje lub tworzy)
$uchwytZapisu = fopen($plikDoNiskopoziomowegoZapisu, "w");
if ($uchwytZapisu) {
fwrite($uchwytZapisu, $dane1);
fwrite($uchwytZapisu, $dane2);
fclose($uchwytZapisu);
echo "Dane zapisane niskopoziomowo do $plikDoNiskopoziomowegoZapisu:<br>";
echo "<pre>" . htmlspecialchars(file_get_contents($plikDoNiskopoziomowegoZapisu)) . "</pre>";
} else {
echo "Nie udało się otworzyć pliku $plikDoNiskopoziomowegoZapisu do zapisu.<br>";
}
// Otwarcie do dopisywania (tryb "a")
$daneDoDopisaniaNisko = "Dopisane dane niskopoziomowo.\n";
$uchwytDopisywania = fopen($plikDoNiskopoziomowegoZapisu, "a");
if ($uchwytDopisywania) {
fwrite($uchwytDopisywania, $daneDoDopisaniaNisko);
fclose($uchwytDopisywania);
echo "Dane dopisane niskopoziomowo do $plikDoNiskopoziomowegoZapisu:<br>";
echo "<pre>" . htmlspecialchars(file_get_contents($plikDoNiskopoziomowegoZapisu)) . "</pre>";
} else {
echo "Nie udało się otworzyć pliku $plikDoNiskopoziomowegoZapisu do dopisywania.<br>";
}
?>
Operacje na Plikach i Katalogach
copy(string $zrodlo, string $cel, ?resource $context = null): bool
: Kopiuje plik$zrodlo
do$cel
. Zwracatrue
w przypadku sukcesu,false
w przeciwnym razie. Jeśli plik docelowy istnieje, zostanie nadpisany.rename(string $stara_nazwa, string $nowa_nazwa, ?resource $context = null): bool
: Zmienia nazwę lub przenosi plik lub katalog.unlink(string $nazwa_pliku, ?resource $context = null): bool
: Usuwa plik.mkdir(string $sciezka_katalogu, int $uprawnienia = 0777, bool $rekursywnie = false, ?resource $context = null): bool
: Tworzy katalog.$uprawnienia
są ósemkowe (np.0755
) i mogą być modyfikowane przez umask. Jeśli$rekursywnie
jesttrue
, tworzy również nadrzędne katalogi, jeśli nie istnieją.rmdir(string $sciezka_katalogu, ?resource $context = null): bool
: Usuwa pusty katalog.chmod(string $nazwa_pliku, int $uprawnienia): bool
: Zmienia uprawnienia pliku lub katalogu. Uprawnienia są ósemkowe.chown(string $nazwa_pliku, string|int $uzytkownik): bool
: Zmienia właściciela pliku.chgrp(string $nazwa_pliku, string|int $grupa): bool
: Zmienia grupę pliku.
<?php
$oryginal = "oryginalny_plik.txt";
$kopia = "kopia_pliku.txt";
$nowaNazwa = "zmieniona_nazwa.txt";
$katalogDoStworzenia = "nowy_folder/podfolder";
if (!file_exists($oryginal)) file_put_contents($oryginal, "Zawartość oryginalnego pliku.");
// Kopiowanie
if (copy($oryginal, $kopia)) {
echo "Plik $oryginal skopiowany do $kopia.<br>";
} else {
echo "Nie udało się skopiować pliku.<br>";
}
// Zmiana nazwy
if (rename($kopia, $nowaNazwa)) {
echo "Plik $kopia zmieniono na $nowaNazwa.<br>";
} else {
echo "Nie udało się zmienić nazwy pliku.<br>";
}
// Usuwanie pliku
if (file_exists($nowaNazwa) && unlink($nowaNazwa)) {
echo "Plik $nowaNazwa usunięty.<br>";
}
if (file_exists($oryginal) && unlink($oryginal)) {
echo "Plik $oryginal usunięty.<br>";
}
// Tworzenie katalogu rekursywnie
if (!is_dir($katalogDoStworzenia) && mkdir($katalogDoStworzenia, 0755, true)) {
echo "Katalog $katalogDoStworzenia utworzony.<br>";
} else if (is_dir($katalogDoStworzenia)) {
echo "Katalog $katalogDoStworzenia już istnieje.<br>";
} else {
echo "Nie udało się utworzyć katalogu $katalogDoStworzenia.<br>";
}
// Usuwanie katalogu (musi być pusty, najpierw podfolder, potem nadrzędny)
if (is_dir($katalogDoStworzenia) && rmdir($katalogDoStworzenia)) {
echo "Katalog $katalogDoStworzenia usunięty.<br>";
// Usunięcie nadrzędnego, jeśli też jest pusty
if (rmdir(dirname($katalogDoStworzenia))) {
echo "Katalog " . dirname($katalogDoStworzenia) . " usunięty.<br>";
}
}
?>
Praca z Katalogami - Listowanie Zawartości
scandir(string $katalog, int $kolejnosc_sortowania = SCANDIR_SORT_ASCENDING, ?resource $context = null): array|false
: Zwraca tablicę plików i katalogów znajdujących się w danym$katalog
. Domyślnie zawiera.
(bieżący katalog) i..
(katalog nadrzędny).$kolejnosc_sortowania
może byćSCANDIR_SORT_DESCENDING
lubSCANDIR_SORT_NONE
.- Obiekt
DirectoryIterator
(SPL): Bardziej obiektowy sposób na iterację po zawartości katalogu. - Obiekt
RecursiveDirectoryIterator
iRecursiveIteratorIterator
(SPL): Do rekursywnego przeglądania katalogów. opendir()
,readdir()
,closedir()
: Niskopoziomowe funkcje do iteracji po katalogu (podobne do pracy z plikami).
<?php
$katalogDoListowania = "."; // Bieżący katalog
echo "<h3>Zawartość katalogu ".$katalogDoListowania." (scandir):</h3>";
$elementy = scandir($katalogDoListowania);
if ($elementy !== false) {
echo "<ul>";
foreach ($elementy as $element) {
if ($element != "." && $element != "..") { // Pomijamy . i ..
echo "<li>" . htmlspecialchars($element) . (is_dir($katalogDoListowania . DIRECTORY_SEPARATOR . $element) ? " (Katalog)" : " (Plik)") . "</li>";
}
}
echo "</ul>";
} else {
echo "Nie udało się odczytać zawartości katalogu.<br>";
}
// Użycie DirectoryIterator (bardziej obiektowo)
echo "<h3>Zawartość katalogu ".$katalogDoListowania." (DirectoryIterator):</h3>";
try {
$iterator = new DirectoryIterator($katalogDoListowania);
echo "<ul>";
foreach ($iterator as $fileinfo) {
if (!$fileinfo->isDot()) { // Pomijamy . i ..
echo "<li>" . htmlspecialchars($fileinfo->getFilename());
if ($fileinfo->isDir()) echo " (Katalog)";
if ($fileinfo->isFile()) echo " (Plik)";
echo "</li>";
}
}
echo "</ul>";
} catch (Exception $e) {
echo "Błąd iteratora: " . $e->getMessage() . "<br>";
}
// Stała DIRECTORY_SEPARATOR jest ważna dla kompatybilności między systemami ( / vs \ )
?>
Ścieżki i Informacje o Ścieżkach
basename(string $sciezka, string $przyrostek = ""): string
: Zwraca nazwę pliku ze ścieżki (np.plik.txt
z/sciezka/do/plik.txt
). Jeśli podano$przyrostek
i pasuje on do końca nazwy pliku, zostanie on usunięty.dirname(string $sciezka, int $poziomy = 1): string
: Zwraca nazwę katalogu nadrzędnego ze ścieżki.$poziomy
(od PHP 7.0) określa, ile poziomów w górę przejść.pathinfo(string $sciezka, int $opcje = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME): array|string
: Zwraca tablicę asocjacyjną z informacjami o ścieżce (dirname
,basename
,extension
,filename
) lub konkretny element, jeśli podano$opcje
.realpath(string $sciezka): string|false
: Zwraca kanoniczną, absolutną ścieżkę, rozwiązując wszystkie dowiązania symboliczne i odwołania./
oraz../
. Zwracafalse
, jeśli plik nie istnieje lub PHP nie ma uprawnień.getcwd(): string|false
: Zwraca bieżący katalog roboczy.chdir(string $katalog): bool
: Zmienia bieżący katalog roboczy.
<?php
$pelnaSciezka = "/var/www/html/projekt/index.php";
echo "<h3>Informacje o ścieżce: $pelnaSciezka</h3>";
echo "basename: " . basename($pelnaSciezka) . "<br>"; // index.php
echo "basename (.php): " . basename($pelnaSciezka, ".php") . "<br>"; // index
echo "dirname: " . dirname($pelnaSciezka) . "<br>"; // /var/www/html/projekt
$info = pathinfo($pelnaSciezka);
echo "pathinfo (tablica): <pre>"; print_r($info); echo "</pre>";
/*
Array
(
[dirname] => /var/www/html/projekt
[basename] => index.php
[extension] => php
[filename] => index
)
*/
echo "pathinfo (extension): " . pathinfo($pelnaSciezka, PATHINFO_EXTENSION) . "<br>"; // php
// Dla realpath, plik/katalog musi istnieć
// $sciezkaRelatywna = "../lessons_php/./01-wprowadzenie-do-php_content_draft.html"; // przykład
// if (file_exists($sciezkaRelatywna)) {
// echo "realpath: " . realpath($sciezkaRelatywna) . "<br>";
// } else {
// echo "Plik dla realpath nie istnieje: $sciezkaRelatywna<br>";
// }
echo "Bieżący katalog roboczy (getcwd): " . getcwd() . "<br>";
?>
Podsumowanie Lekcji
W tej lekcji nauczyliśmy się podstaw pracy z systemem plików w PHP. Poznaliśmy funkcje do sprawdzania istnienia i typów plików/katalogów, odczytywania zawartości plików na różne sposoby (file_get_contents
, file
, oraz niskopoziomowe fopen/fread/fgets
), zapisywania danych do plików (file_put_contents
, fopen/fwrite
), a także wykonywania operacji takich jak kopiowanie, zmiana nazwy, usuwanie plików i katalogów, oraz zmiana uprawnień. Omówiliśmy również sposoby listowania zawartości katalogów i uzyskiwania informacji o ścieżkach.
Umiejętność manipulowania plikami i katalogami jest niezbędna w wielu zastosowaniach webowych, od prostych skryptów po zaawansowane systemy zarządzania treścią. W następnej lekcji zajmiemy się obsługą formularzy HTML i przetwarzaniem danych przesyłanych przez użytkownika za pomocą metod GET i POST.
Zadanie praktyczne
Napisz skrypt PHP, który:
- Tworzy katalog o nazwie
dane_uzytkownika
(jeśli nie istnieje). - Wewnątrz tego katalogu tworzy plik o nazwie
profil.txt
. - Zapisuje do pliku
profil.txt
następujące informacje (każda w nowej linii): Imię: [Twoje Imię], Email: [Twój Email], Data rejestracji: [Aktualna data i czas]. - Odczytuje zawartość pliku
profil.txt
i wyświetla ją na stronie. - Wyświetla rozmiar pliku
profil.txt
w bajtach oraz datę jego ostatniej modyfikacji. - Tworzy kopię pliku
profil.txt
o nazwieprofil_backup.txt
w tym samym katalogu. - Listuje zawartość katalogu
dane_uzytkownika
(tylko nazwy plików).
Pokaż przykładowe rozwiązanie
<?php
$katalogUzytkownika = "dane_uzytkownika";
$plikProfilu = $katalogUzytkownika . DIRECTORY_SEPARATOR . "profil.txt";
$plikBackup = $katalogUzytkownika . DIRECTORY_SEPARATOR . "profil_backup.txt";
// 1. Tworzenie katalogu
if (!is_dir($katalogUzytkownika)) {
if (mkdir($katalogUzytkownika, 0755)) {
echo "Katalog ".$katalogUzytkownika." utworzony.<br>";
} else {
die("Nie udało się utworzyć katalogu ".$katalogUzytkownika.".<br>");
}
} else {
echo "Katalog ".$katalogUzytkownika." już istnieje.<br>";
}
// 2. i 3. Tworzenie i zapis do pliku profil.txt
$imie = "Jan"; // Zastąp swoimi danymi
$email = "jan.kowalski@example.com";
$dataRejestracji = date("Y-m-d H:i:s");
$daneProfilu = "Imię: $imie\n";
$daneProfilu .= "Email: $email\n";
$daneProfilu .= "Data rejestracji: $dataRejestracji\n";
if (file_put_contents($plikProfilu, $daneProfilu) !== false) {
echo "Dane zapisane do pliku $plikProfilu.<br>";
} else {
echo "Nie udało się zapisać danych do pliku $plikProfilu.<br>";
}
// 4. Odczyt i wyświetlenie zawartości
if (file_exists($plikProfilu)) {
$zawartoscProfilu = file_get_contents($plikProfilu);
echo "<h4>Zawartość pliku $plikProfilu:</h4>";
echo "<pre>" . htmlspecialchars($zawartoscProfilu) . "</pre>";
} else {
echo "Plik $plikProfilu nie istnieje.<br>";
}
// 5. Wyświetlenie rozmiaru i daty modyfikacji
if (file_exists($plikProfilu)) {
echo "Rozmiar pliku $plikProfilu: " . filesize($plikProfilu) . " bajtów.<br>";
echo "Data ostatniej modyfikacji $plikProfilu: " . date("Y-m-d H:i:s", filemtime($plikProfilu)) . "<br>";
}
// 6. Tworzenie kopii zapasowej
if (file_exists($plikProfilu)) {
if (copy($plikProfilu, $plikBackup)) {
echo "Utworzono kopię zapasową: $plikBackup.<br>";
} else {
echo "Nie udało się utworzyć kopii zapasowej.<br>";
}
}
// 7. Listowanie zawartości katalogu
echo "<h4>Zawartość katalogu $katalogUzytkownika:</h4>";
if (is_dir($katalogUzytkownika)) {
$elementyKatalogu = scandir($katalogUzytkownika);
if ($elementyKatalogu !== false) {
echo "<ul>";
foreach ($elementyKatalogu as $element) {
if ($element != "." && $element != "..") {
if (is_file($katalogUzytkownika . DIRECTORY_SEPARATOR . $element)) {
echo "<li>" . htmlspecialchars($element) . "</li>";
}
}
}
echo "</ul>";
} else {
echo "Nie udało się odczytać zawartości katalogu $katalogUzytkownika.<br>";
}
} else {
echo "Katalog $katalogUzytkownika nie istnieje.<br>";
}
// Opcjonalne czyszczenie po zadaniu:
// if (file_exists($plikProfilu)) unlink($plikProfilu);
// if (file_exists($plikBackup)) unlink($plikBackup);
// if (is_dir($katalogUzytkownika)) rmdir($katalogUzytkownika);
?>
Zadanie do samodzielnego wykonania
Napisz skrypt, który rekursywnie listuje wszystkie pliki i podkatalogi w danym katalogu (np. w bieżącym katalogu skryptu). Dla każdego elementu wyświetl jego pełną ścieżkę oraz informację, czy jest to plik czy katalog. Możesz do tego użyć funkcji rekurencyjnej lub obiektów RecursiveDirectoryIterator
i RecursiveIteratorIterator
.
FAQ - Praca z Plikami i Katalogami
Jaka jest różnica między ścieżką absolutną a względną?
Ścieżka absolutna zaczyna się od katalogu głównego systemu plików (np. /
w Linuksie, C:\
w Windows) i jednoznacznie identyfikuje lokalizację. Ścieżka względna jest określana w odniesieniu do bieżącego katalogu roboczego skryptu PHP.
Do czego służy stała DIRECTORY_SEPARATOR
?
Stała DIRECTORY_SEPARATOR
zawiera znak separatora katalogów specyficzny dla systemu operacyjnego, na którym działa PHP (/
dla systemów uniksowych, \
dla Windows). Używanie jej zamiast hardkodowania /
lub \
zapewnia większą przenośność kodu między platformami.
Jak sprawdzić uprawnienia do pliku przed próbą zapisu?
Można użyć funkcji is_writable("sciezka/do/pliku")
. Zwróci ona true
, jeśli plik istnieje i proces PHP ma uprawnienia do zapisu, a false
w przeciwnym razie. Podobnie is_readable()
dla odczytu.
Czy file_get_contents()
można używać do odczytu zdalnych plików (URL)?
Tak, jeśli opcja konfiguracyjna allow_url_fopen
jest włączona w php.ini
(domyślnie jest), file_get_contents()
może odczytywać zawartość z adresów URL (np. http://example.com/strona.html
). Należy jednak pamiętać o potencjalnych problemach z bezpieczeństwem i wydajnością.
Jak bezpiecznie obsługiwać przesyłane przez użytkownika pliki?
Obsługa przesyłanych plików (uploads) wymaga szczególnej ostrożności. Należy walidować typ pliku, rozmiar, sprawdzać rozszerzenie, używać funkcji move_uploaded_file()
zamiast rename()
/copy()
, oraz przechowywać przesyłane pliki poza głównym katalogiem dostępnym przez WWW, jeśli to możliwe. Temat ten zostanie szerzej omówiony w lekcji o przesyłaniu plików.
Co to jest "race condition" przy operacjach na plikach?
Race condition (wyścig) może wystąpić, gdy wiele procesów próbuje jednocześnie modyfikować ten sam plik. Na przykład, jeden proces sprawdza istnienie pliku (file_exists
), a zanim zdąży go utworzyć, inny proces robi to samo. Aby temu zapobiegać, można używać blokad plików (np. flaga LOCK_EX
w file_put_contents
lub funkcja flock()
).
Jak usunąć katalog, który nie jest pusty?
Funkcja rmdir()
usuwa tylko puste katalogi. Aby usunąć katalog z zawartością, należy najpierw rekursywnie usunąć wszystkie pliki i podkatalogi wewnątrz niego, a dopiero potem sam katalog. Można napisać własną funkcję rekurencyjną lub użyć gotowych rozwiązań z bibliotek.