JustPaste.it

System plików - Podstawy PHP

System plików

Przez wcześniejsze rozdziały często przewijały się nam różne funkcje odczytu danych z dysku twardego. Przyszedł czas na zebranie informacji o nich oraz ich usystematyzowanie. Pierwszą rzeczą, o której należy pamiętać, jest wydajność. Wszelkie odwołania do systemu plików są dosyć powolne i często stanowią nawet wąskie gardło w szybkości naszego kodu. Dlatego powinieneś starać się wykonywać ich tak mało, jak tylko się da i buforować wyniki działania niektórych z nich, aby późniejszy kod mógł odwoływać się do nich.

Odczyt danych

Zanim zaczniemy, utwórz sobie plik plik.txt z jakąś długą zawartością (najlepiej w kilku linijkach).

Zawartość pliku można odczytać w PHP na kilka sposobów. Oto pierwszy z nich, wywodzący się jeszcze z języka C:

<?php

$f = fopen('plik.txt', 'r');

while(!feof($f))
{
echo fread($f, 1024);
}

fclose($f);
?>

Każdy dostęp do pliku musi rozpocząć się od jego otwarcia. Zadaniem tym zajmuje się funkcja fopen(). Parametr r nakazuje otwarcie pliku do odczytu. Następnie w pętli pobieramy plik po kawałkach o wielkości jednego kilobajta. W ten sposób dane mogą być przetwarzane "równolegle" z odczytem. Funkcja feof() służy do sprawdzenia, czy osiągnęliśmy koniec pliku. Po zakończonej pracy nasze połączenie z plikiem trzeba zamknąć. Odpowiada za to fclose().

Uwaga! Uwaga!
Zawsze zamykaj swoje pliki. Inaczej możesz zablokować innych użytkowników korzystających ze strony!

Z powyższego kodu możemy wyrzucić pętlę i pobrać wszystko za jednym zamachem. Wystarczy tylko użyć funkcji filesize(), aby podała nam rozmiar pliku:

<?php
$f = fopen('plik.txt', 'r');

echo fread($f, filesize('plik.txt'));

fclose($f);
?>

Zwróćmy uwagę na jakość podanych przykładów. Zmień nazwę plików, do których się odwołujemy, na jakiś nieistniejący. Oba skrypty wtedy zgłupieją. Pierwszy zaleje nas niekończącą się falą ostrzeżeń, drugi zrobi ich "tylko" kilka (przyczyną jest brak pętli). Dlatego powinniśmy tak przygotować wszystko, abyśmy sami panowali nad komunikatami. Czas stworzyć prymitywną obsługę błędów. Wykorzystamy tutaj operator @, aby zagłuszyć funkcję fopen() i sprawdzić zwracany wynik. Powinna ona zwrócić nam połączenie z plikiem, tj. wartość typu Resource. Zobaczmy:

<?php

$f = @fopen('inny_plik.txt', 'r');

if(!is_resource($f))
{
die('Nie udało się otworzyć podanego pliku do odczytu!');
}

echo fread($f, filesize('inny_plik.txt'));

fclose($f);
?>
Uwaga! Uwaga!
Jeśli skrypt odczytujący zacznie się dziwnie zachowywać, pierwszym krokiem powinno być tymczasowe usunięcie operatora @ - inaczej nigdy nie dowiesz się, co jest przyczyną problemów!
Porada Porada
W celu uproszczenia twojego kodu możesz napisać sobie własny wariant funkcji fopen() posiadający twoją obsługę błędów. Uprości to zarządzanie kodem projektu.

Od PHP 4.3.0 nie trzeba już rozpisywać się, aby wczytać zawartość pojedynczego pliku. Cała czynność jest zautomatyzowana w funkcji file_get_contents(). Aby tu sprawdzić poprawność otwarcia, wystarczy porównać zwrócony wynik z wartością false, która jest zwracana w przypadku błędu:

<?php

$tresc = @file_get_contents('plik.txt');

if($tresc === false)
{
die('Nie udało się otworzyć podanego pliku do odczytu!');
}

echo $tresc;
?>

Pisząc księgę gości, poznaliśmy funkcję file(), która zwracaną zawartość rozbijała od razu na tablicę poszczególnych linijek. Dzięki tej właściwości wyświetlimy plik jako listę wypunktowaną HTML bez większych trudności:

<?php

$tresc = @file('plik.txt');

if($tresc === false)
{
die('Nie udało się otworzyć podanego pliku do odczytu!');
}

echo '<ul>';
foreach($tresc as $linia)
{
echo '<li>'.$linia.'</li>';
}
echo '</ul>';
?>

Pamiętaj, że file() nie gubi znaków końca linii - te są nadal zapisane w poszczególnych linijkach. Dlatego gdy będziesz chciał z powrotem połączyć wszystko w całość, powinieneś napisać

implode('', $tresc);

zamiast np.

implode("\n", $tresc);

Pod żadnym pozorem nie odczytuj plików w ten sposób:

implode('', file('plik.txt'));

Sens takiego kodu można streścić w prostym porównaniu: pakować się tylko po to, by się natychmiast rozpakować. Nie służy to niczemu, a konsumuje niezbędny czas. Aby przekonać się, jak mało wydajne jest takie rozwiązanie, spróbuj załadować tak plik tekstowy o wielkości megabajta, następnie powtórz to samo z wykorzystaniem file_get_contents() i porównaj wrażenia.

Porada Jeżeli pragniesz odczytywać pliki binarne, otwieraj je z parametrem rb zamiast r.

 

Zapis danych

Zapis danych wygląda analogicznie do odczytu. Różne jest tylko miejsce docelowe danych. Sposób pierwszy polega na otwarciu pliku funkcją fopen() i skorzystaniu z fwrite() do dodania nowej zawartości. Plik otwieramy z parametrem w (nadpisujemy starą zawartość) lub a (dopisujemy coś do pliku). W przypadku operowania danymi binarnymi, dodajemy jeszcze literę b.

<?php

$f = fopen('./plik.txt', 'w');
fwrite($f, 'To jest nowa zawartosc pliku');
fclose($f);

?>

Po uruchomieniu tego skryptu w plik.txt powinna pojawić nam się nowa zawartość. W przypadku pracy na systemie Linux/Unix sprawdź, czy PHP ma uprawnienia do edycji plików w twoim katalogu roboczym.

W PHP 5.0.0 pojawiła się funkcja file_put_contents(), która upraszcza całą sprawę. Zwraca ona liczbę zapisanych do pliku bajtów i możemy wykorzystać to do kontroli, czy operacja dopisywania faktycznie się udała. Funkcja pobiera dwa parametry: nazwę pliku oraz tekst do wpisania i "firmowo" nie zniekształca danych binarnych.

<?php

if(file_put_contents('./plik.txt', 'To jest nowa zawartosc pliku') != 0)
{
echo 'Udalo sie zapisac nowa zawartosc do pliku.';
}

?>

Zadajmy sobie pytanie, co jeśli musimy dopisać dodatkową treść. Naturalnie file_put_contents() także to potrafi. Trzeba tylko skorzystać z trzeciego parametru, w którym możemy ustawiać flagi. FILE_APPEND jest tym, czego potrzebujemy.

<?php

if(file_put_contents('./plik.txt', ' dopisana tresc', FILE_APPEND) != 0)
{
echo 'Udało się dodać zawartość do pliku.';
}

?>

Ten skrypt będzie już dopisywać dane do pliku, zamiast je nadpisywać.

Informacje o plikach

W wielu przypadkach przydaje się wiedza o tym, co w zasadzie w katalogach mamy. Możemy ją uzyskać, korzystając z rodziny funkcji udostępniających nam różne informacje o plikach. Wszystkie przyjmują za parametr nazwę pliku:

  • is_file() - zwraca true, jeśli obiekt jest plikiem.
  • is_dir() - zwraca true, jeśli obiekt jest katalogiem.
  • is_readable() - zwraca true, jeśli posiadamy prawa do odczytu zawartości obiektu.
  • is_writeable() - zwraca true, jeśli posiadamy prawa do zapisu do obiektu.
  • file_exists() - zwraca true, jeśli plik/katalog istnieje.
  • fowner() - zwraca ID właściciela pliku.
  • fgroup() - zwraca ID grupy, do której plik należy.
  • fperms() - zwraca uprawnienia pliku.
  • filesize() - zwraca wielkość pliku.
  • filemtile() - zwraca czas ostatniej modyfikacji pliku lub false, jeśli nie istnieje.

Przy korzystaniu z nich musimy pamiętać o wydajności. Odczyt wszelkich danych z dysku jest dość powolny, dlatego starajmy się jak najwięcej wycisnąć z pojedynczego wywołania funkcji. Oto przykład: załóżmy, że mamy plik A.txt i na jego podstawie generujemy B.txt zawsze, kiedy ulegnie on zmianie (taki kompilator). Musimy zatem napisać mechanizm sprawdzający, czy można uruchomić kompilację, czy też jest ona zbędna.

<?php

if(!file_exists('A.txt'))
{
die('Plik A.txt nie istnieje!');
}

if(file_exists('B.txt'))
{
if(filemtime('B.txt') != filemtime('A.txt'))
{
echo 'Plik A.txt wymaga kompilacji.';
}
else
{
echo 'Można czytać z pliku B.txt';
}
}
else
{
echo 'Plik A.txt wymaga kompilacji';
}

?>

Pozornie wszystko wygląda na poprawne - skrypt prawidłowo raportuje wszystkie sprawy. Jednak robi to zbyt wolno, gdyż przeciążyliśmy go dużą ilością odwołań do dysku twardego. Jeżeli uruchomimy go na witrynie z dużym ruchem, osiągnąłby gorsze wyniki wydajności, niż inne skrypty. Spróbujmy go nieco zmodyfikować. Czy naprawdę potrzebujemy funkcji file_exists()? Okazuje się, że nie. Przecież filemtime() zwróci nam false, jeżeli plik nie będzie istniał i możemy to wykorzystać. Oto poprawiony kod skryptu:

<?php

$czasA = @filemtime('A.txt');

if($czasA === false)
{
die('Plik A.txt nie istnieje!');
}
else
{
$czasB = @filemtime('B.txt');
}

if($czasB !== false)
{
if($czasB != $czasA)
{
echo 'Plik A.txt wymaga kompilacji.';
}
else
{
echo 'Można czytać z pliku B.txt';
}
}
else
{
echo 'Plik A.txt wymaga kompilacji';
}

?>

Zauważmy, w tym przypadku mamy tylko dwa odwołania do dysku, a jeśli plik A.txt nie będzie istnieć, to nawet jedno! Zamiast wykonywania za każdym razem setek nowych funkcji, wykorzystujemy maksymalnie te dane, które już mamy. To jest właściwa filozofia przy pracy z plikami.

 

Zakończenie

Plikom poświęciliśmy naprawdę bardzo duży rozdział. Jednak mało która aplikacja PHP wykorzystuje je jako główne źródło danych dla internauty. Znacznie poważniejszym i mającym większe możliwości narzędziem są bazy danych. Zagadnienie to jest omówione w następnej części podręcznika. Czy jednak pliki należy w takim razie wyrzucić? Nie, ze względu na wydajność. Wbrew pozorom, odczyt rekordów z bazy zazwyczaj jest wolniejszy, niż z pliku i w przypadku elementarnych ustawień aplikacji, które nie wymagają złożonego sortowania oraz stosowania rozbudowanych relacji (np. konfiguracja, dane systemowe), można pokusić się o zastąpienie ich plikami.

 

Treść pochodzi ze strony WikiBooks i jest udostępniana na licencji GNU FDL

 

Autor: WikiBooks