PHP 8.2 - sprawdź co nowego!

Tablice, stringi, pliki - kolejny projekt > Lekcja 4

Wyświetlanie zadań z pliku

Przyszła pora na wyświetlenie zadań zapisanych w pliku. Zaczniemy od tego, dalej pojawi się dodawanie czy usuwanie.

Jakby tu zapisać zadania w pliku… Możliwości tak naprawdę jest wiele. Masz prawo teraz ich nie znać, ale ja bym widział przynajmniej kilka sposobów:

  • Najbardziej banalne wydaje się zrobienie w taki sposób, aby każde nowe zadanie było zapisywane w nowej linii.

  • Możemy też zapisywać wszystko jako jeden ciąg oddzielony jakimś znakiem (lub kilkoma), który następnie rozdzielany i łączony byłby przy użyciu funkcji jak explode/implode. Dajmy na to, że w pliku byłoby:

    Zadanie 1|Zadanie 2|Zadanie 3
    

    Po wywołaniu explode po znaku | na tym stringu uzyskalibyśmy tablicę, której kolejnymi elementami byłyby treści kolejnych zadań. Problem tu jest taki, że zawsze musimy jakieś znaki „wyrzucić” z normalnego używania - bo gdyby ktoś dodał zadanie o nazwie Testowe|zadanie to zostałoby to potraktowane jako dwa zadania, gdyż | jest naszym znakiem rozdzielającym.

  • Jeszcze inna opcja to zapisanie tego w jakimś formacie przechowywania danych. Przykłady to JSON, YAML czy XML. Ten ostatni jest już niezbyt powszechny do przechowywania danych. YAML z kolei używany jest, moim zdaniem, raczej głównie do przechowywania jakichś konfiguracji i podobnych. Gdybyśmy więc zdecydowali się na to rozwiązanie, próbowałbym z JSONem, który wydaje się obecnie najbardziej „trendy”. Każdy z tych formatów to oczywiście czysty tekst, posiadający odpowiedni zapis, zasady czy też znaczniki.

Wykonajmy sobie dziś opcję trzecią, czyli dane będziemy przechowywali w pliku jako format JSON. Do tak prostego projektu, przechowującego tylko jedną daną (treść zadania) wystarczyłoby nam co prawda spokojnie rozwiązanie pierwsze, ale w razie, gdybyś chciał bardziej rozbudować ten kod, będzie łatwiej. No i poznamy znów coś przydatnego i ciekawego. Nad samym JSONem nie będę się zbytnio rozwodził, doczytaj na przykład na Wikipedii. PHP posiada funkcje do operacji na tym formacie danych. Skorzystamy z json_encode() i json_decode().

Jak to wygląda w praktyce? A proszę bardzo, nic trudnego:

<?php

$tasks = ['Task 1', 'Task 2', 'Task 3'];
$json = json_encode($tasks);
var_dump($json);

var_dump(json_decode($json, true));
string(28) "["Task 1","Task 2","Task 3"]"
array(3) {
  [0]=>
  string(6) "Task 1"
  [1]=>
  string(6) "Task 2"
  [2]=>
  string(6) "Task 3"
}

Funkcja z _encode tworzy i zwraca JSON (jako ciąg tekstowy) z podanej w argumencie tablicy. json_decode(), jak nietrudno się domyślić, działa odwrotnie, czyli jako pierwszy argument oczekuje stringa z JSONem. Drugi argument, w moim przypadku true, mówi, że dane mają zostać zwrócone jako tablica. Jeśli go nie podamy, użyta zostanie wartość domyślna, którą jest false, i zwrócony zostanie obiekt (czego nie chcemy, bo: po pierwsze nie potrzebujemy tutaj obiektu, po drugie przypuszczalnie nie znasz jeszcze obiektów, gdyż nie pojawiły się w kursie).

Pozostaje mi jeszcze wyjaśnić odczyt danych z pliku. Skorzystamy z funkcji file_get_contents(). Wystarczy, że jako argument przekażemy nazwę pliku do odczytania (ewentualnie ze ścieżką) i jako zwracaną wartość otrzymamy jego zawartość.

Ciekawostka

Warto wiedzieć, że istnieją też inne sposoby operowania na plikach, na przykład funkcje fopen() czy file(). Ta druga zwraca tablicę zawierającą wszystkie linie w pliku - idealnie byłoby jej użyć, gdybyśmy zdecydowali się na pierwszy sposób przechowywania zadań w pliku.

Ok, napiszmy funkcję readFromFile() odpowiadającą za odczytanie danych (które będą w formacie JSON) z pliku i zwrócenie gotowej tablicy, która przyda nam się do dalszych operacji. Tak jak mówiłem, funkcje zapisywał będę w pliku functions.php, który lekcję wcześniej dołączyliśmy do index.php.

<?php
function readFromFile(): array
{
    $data = file_get_contents('data.txt');
    if ($data) {
        return json_decode($data, true);
    }
    return [];
}

Jeśli działasz z jakąś starszą wersją PHP, to nie zapomnij, że określenie typu zwracanego lub argumentu funkcji jest możliwe od php >=7. Większość już wcześniej omówiłem, więc mam nadzieję, że jest to zrozumiałe. Odczytujemy zawartość pliku do zmiennej $data, następnie, jeśli udało się coś odczytać, dekodujemy JSON na tablicę i zwracamy ją. Nazwa pliku jest właściwie dowolna. W sytuacji, gdyby warunek się nie spełnił, zwracamy na koniec pustą tablicę. elseif nie jest tu potrzebny, gdyż jak pewnie pamiętasz return kończy wykonywanie kodu wewnątrz danej funkcji. Przetestujmy, dodając wywołanie napisanej funkcji i var_dumpa w odpowiednim miejscu switcha w pliku index.php.

<?php
// switch ...
    case null:
        $tasks = readFromFile();
        var_dump($tasks);
        break;

Polecenie php index.php w konsoli i w efekcie błąd &quot;PHP Warning: file_get_contents(data.txt) failed to open stream: No such file or directory&quot;

Ups, coś chyba poszło nie tak. Komunikat o błędzie mówi: no such file or directory. Czyli nie ma pliku o podanej nazwie, stąd to ostrzeżenie. W zasadzie to moglibyśmy po prostu go stworzyć i problemu by nie było, ale i tak wypadałoby jakoś zabezpieczyć się przed przypadkowym brakiem pliku. Proponuję sprawdzać, czy plik istnieje przy użyciu funkcji file_exists() - jeśli tak wykonać odczyt i json_decode(), jeśli nie zwrócić pustą tablicę.

<?php
function readFromFile(): array
{
    if (file_exists(FILENAME)) {
        $data = file_get_contents(FILENAME);
        if ($data) {
            return json_decode($data, true);
        }
    }
    return [];
}

Dodałem też stałą FILENAME, w której przechowywana będzie nazwa pliku - skoro będziemy używali jej w kilku miejscach (na razie dwóch, ale już niedługo pojawi się zapis zadań do pliku) to zamiast powtarzać wszędzie to samo, lepiej podstawić zmienną czy stałą. Dzięki temu w razie konieczności poprawy zrobimy to raz, w jednym miejscu. Zadeklarowałem ją na samym początku pliku index.php.

Teraz już błędu nie będzie, ale jako że nie mamy jeszcze dodawania zadań nie za bardzo jak mamy przetestować działanie. Podaję więc poniżej przykładową zawartość pliku data.txt, możesz zrobić takowy u siebie, wkleić to i spróbować.

["Task 1","Task 2","Task 3"]

Jak to się mówi w IT: u mnie działa ;)

Wynik działania skryptu po wydaniu polecenia php index.php

Powszechny problem

Gdyby zdarzyło się, że otrzymasz błąd typu Permission denied oznacza to, że nie masz uprawnień do danego pliku (w przykładzie data.txt). Należy wtedy odpowiednio je zmienić, w systemach Linux na przykład korzystając z chmod, chown albo trybu graficznego.

Pozostaje jeszcze tylko dopisać jakieś normalne wyświetlanie zadań. Przy każdym potrzebujemy wyświetlić jego numer (żeby było łatwiej będzie to indeks z tablicy) oraz treść. Użyję do tego prostej pętli foreach. Na koniec tej lekcji plik index.php będzie prezentował się tak:

<?php

define('FILENAME', 'data.txt');
require_once 'functions.php';

$data = array_fill(0, 3, null);
$argv = array_replace($data, $argv);
[$filename, $command, $content] = $argv;

switch ($command) {
    case 'add':
        echo 'Add TODO';
        break;
    case 'remove':
        echo 'Remove TODO';
        break;
    case null:
        $tasks = readFromFile();
        foreach ($tasks as $number => $task) {
            echo ($number + 1) . ' | ' . $task . PHP_EOL;
        }
        echo '-----' . PHP_EOL . 'All tasks: ' . count($tasks);
        break;
    default:
        echo 'Sorry, invalid command!';
        break;
}
echo PHP_EOL;

Dodałem jeszcze informację o liczbie wszystkich zadań, którą można uzyskać dzięki count() (zliczenie elementów w tablicy). Zwiększanie wartości $number (czyli indeksu) o jeden robię, aby zadania na wyświetlaniu numerowane były od 1, a nie 0. Nie jest to rzecz jasna konieczne, taki dodatek. Te numerki posłużą nam później do usuwania zadań z listy, będziemy usuwać element o danym indeksie z tablicy.

Gotowe wyświetlanie listy zadań po wydaniu polecenia: php index.php

Poprzednia lekcja Następna lekcja

Udostępnij

  • Facebook
  • Twitter

Komentarze